2019年3月,百度正式发布NLP模型ERNIE,其在中文任务中全面超越BERT一度引起业界普遍关注和探讨。通过短短几个月时间,百度ERNIE再升级,发布持续学习的语义理解框架ERNIE 2.0,及基于此框架的ERNIE 2.0预训练模型。继1.0后,ERNIE英文任务方面取得全新突破,在共计16个中英文任务上超越了BERT和XLNet, 取得了SOTA效果。git
本篇内容能够说是史上最强实操课程,由浅入深完整带你们试跑ERNIE,你们可前往AI Studio fork代码 (https://aistudio.baidu.com/aistudio/projectdetail/117030),运行便可获赠12小时GPU算力,天天都有哦~github
step1:下载ERNIE代码。舒适提示:若是下载慢,暂停重试json
!git clone --depth 1 https://github.com/PaddlePaddle/ERNIE.git
step2:下载并解压finetune数据数据结构
!wget --no-check-certificate https://ernie.bj.bcebos.com/task_data_zh.tgz !tar xf task_data_zh.tgz
step3:下载预训模型app
!wget --no-check-certificate https://ernie.bj.bcebos.com/ERNIE_1.0_max-len-512.tar.gz !mkdir -p ERNIE1.0 !tar zxf ERNIE_1.0_max-len-512.tar.gz -C ERNIE1.0
备用方案,若是下载慢的话,能够用咱们预先下载好的代码和数据框架
%cd ~ !cp -r work/ERNIE1.0 ERNIE1.0 !cp -r work/task_data task_data !cp -r work/lesson/ERNIE ERNIE
完成ERNIE代码部分的准备以后,让咱们一块儿以一个序列标注任务来举例。less
什么是序列标注任务?ide
下面这张图能够归纳性的让你们理解序列标注任务:函数
序列标注的任务能够用来作什么?学习
能够:信息抽取、数据结构化,帮助搜索引擎搜索的更精准
能够:…
序列标注任务: 一块儿来看看这个任务的数据长什么样子吧?
序列标注任务输入数据包含2部分:
1)标签映射文件:存储标签到ID的映射。
2)训练测试数据:2列,文本、标签(文本中每一个字之间使用隐藏字符\2分割,标签同理。)
# 标签映射文件 !cat task_data/msra_ner/label_map.json
{ "B-PER": 0, "I-PER": 1, "B-ORG": 2, "I-ORG": 3, "B-LOC": 4, "I-LOC": 5, "O": 6 }
# 测试数据 !head task_data/msra_ner/dev.tsv
B: Begin
I: Inside
O: Outside
ERNIE应用于序列化标注
step1:设置环境变量
%cd ERNIE !ln -s ../task_data !ln -s ../ERNIE1.0 %env TASK_DATA_PATH=task_data %env MODEL_PATH=ERNIE1.0 !echo "task_data_path: ${TASK_DATA_PATH}" !echo "model_path: ${MODEL_PATH}"
step2:运行finetune脚本
!sh script/zh_task/ernie_base/run_msra_ner.sh
在finetune过程当中,会自动保存对test集的预测结果,咱们能够查看预测结果是否符合预期。
因为Finetune须要一些时间,因此不等Finetune完了,直接查看咱们以前已经Finetune收敛后的模型与test集的预测结果
%cd ~ show_ner_prediction('work/lesson/test_result.5.final')
脚本进阶:模型太大,没法彻底放进显存的状况下,如何只使用前3层参数热启Finetune?
若是能只加载几层模型就行了!
方法:只须要修改一行配置文件ernie_config.json,就能自动的使用前3层参数热启Finetune。
提示:ernie_config.json在ERNIE1.0发布的预训练模型中
TODO 结合“终端”标签,运行一下吧
提示:您能够须要用到sed与pwd命令
step1:设置环境变量
%cd ~%cd ERNIE !ln -s ../task_data !ln -s ../ERNIE1.0 %env TASK_DATA_PATH=task_data %env MODEL_PATH=ERNIE1.0 !echo "task_data_path: ${TASK_DATA_PATH}" !echo "model_path: ${MODEL_PATH}" !pwd
!sh script/zh_task/ernie_base/run_msra_ner.sh
数据进阶:如何修改输入格式?
假设msra ner任务的输入数据格式变了,每条样本不是以行式保存,而是以列式保存。列式保存是指,每条样本由多行组成,每行包含一个字符和对应的label,不一样样本间以空行分割,具体样例以下:
text_a label 海 O 钓 O 比 O 赛 O 地 O 点 O 在 O 厦 B-LOC 门 I-LOC 与 O 金 B-LOC 门 I-LOC 之 O 间 O 的 O 海 O 域 O 。 O
当输入数据为列式时,咱们如何修改ERNIE的数据处理代码,以适应新的数据格式。
首先,咱们先大体了解一下ERNIE的数据处理流程:
step 1. 从文件中逐条读取样本,经过_read_tsv等方法,读取不一样格式的文件,并将读取的每条样本存入一个list
step 2. 逐一将读取的样本转化为Record。Record中包含了一条样本通过数据处理后,模型所须要的全部features。处理成Record的流程通常又分如下几步:
1. 将文本tokenize,超过最大长度时截断;
2. 加入'[CLS]'、'[SEP]'等标记符后,将文本ID化;
3. 生成每一个token对应的position和token_type信息。
step 3. 将多个Record组成batch,同一个batch内feature长度不一致时,padding至batch内最大的feature长度。
了解了ERNIE的数据处理流程之后,咱们发现当输入数据格式变了,咱们只须要修改第1步的代码,保持其余代码不变,就能适应新的数据格式。具体来讲,只须要在reader/task_reader.py的 SequenceLabelReader 类中,加入下面的 _read_tsv 函数(重写基类 BaseReader 的 _read_tsv)。
def _read_tsv(self, input_file, quotechar=None): with open(input_file, 'r', encoding='utf8') as f: reader = csv_reader(f) headers = next(reader) text_indices = [ index for index, h in enumerate(headers) if h != 'label' ] Example = namedtuple('Example', headers) examples = [] buf_t, buf_l = [], [] for line in reader: if len(line) != 2: assert len(buf_t) == len(buf_l) example = Example(u'^B'.join(buf_t), u'^B'.join(buf_l)) examples.append(example) buf_t, buf_l = [], [] continue if line[0].strip() == '': continue buf_t.append(line[0]) buf_l.append(line[1]) if len(buf_t) > 0: assert len(buf_t) == len(buf_l) example = Example(u'^B'.join(buf_t), u'^B'.join(buf_l)) examples.append(example) buf_t, buf_l = [], [] return examples
咱们将已经修改好的数据和代码,预先放在work/lesson/2目录中,能够替换掉ERNIE项目中对应的文件,而后尝试运行
%cd ~ !cp -r work/lesson/2/msra_ner_columnwise task_data/msra_ner_columnwise !cp -r work/lesson/2/task_reader.py ERNIE/reader/task_reader.py !cp -r work/lesson/2/run_msra_ner.sh ERNIE/script/zh_task/ernie_base/run_msra_ner_columnwise.sh %cd ERNIE !ln -s ../task_data !ln -s ../ERNIE1.0 %env TASK_DATA_PATH=task_data %env MODEL_PATH=ERNIE1.0 !sh script/zh_task/ernie_base/run_msra_ner_columnwise.sh
模型进阶:如何将序列标注任务的损失函数替换为CRF?
目前序列标注任务的finetune代码中,以 softmax ce 做为损失函数,该损失函数较为简单,没有考虑到序列中词与词之间的联系,如何替换一个更优秀的损失函数呢?
咱们只须要修改其中的create_model函数,将 softmax ce 损失函数部分,替换为 linear_chain_crf 便可,具体代码以下:
def create_model(args, pyreader_name, ernie_config, is_prediction=False): pyreader = fluid.layers.py_reader( capacity=50, shapes=[[-1, args.max_seq_len, 1], [-1, args.max_seq_len, 1], [-1, args.max_seq_len, 1], [-1, args.max_seq_len, 1], [-1, args.max_seq_len, 1], [-1, args.max_seq_len, 1], [-1, 1]], dtypes=[ 'int64', 'int64', 'int64', 'int64', 'float32', 'int64', 'int64' ], lod_levels=[0, 0, 0, 0, 0, 0, 0], name=pyreader_name, use_double_buffer=True) (src_ids, sent_ids, pos_ids, task_ids, input_mask, labels, seq_lens) = fluid.layers.read_file(pyreader) ernie = ErnieModel( src_ids=src_ids, position_ids=pos_ids, sentence_ids=sent_ids, task_ids=task_ids, input_mask=input_mask, config=ernie_config, use_fp16=args.use_fp16) enc_out = ernie.get_sequence_output() enc_out = fluid.layers.dropout( x=enc_out, dropout_prob=0.1, dropout_implementation="upscale_in_train") logits = fluid.layers.fc( input=enc_out, size=args.num_labels, num_flatten_dims=2, param_attr=fluid.ParamAttr( name="cls_seq_label_out_w", initializer=fluid.initializer.TruncatedNormal(scale=0.02)), bias_attr=fluid.ParamAttr( name="cls_seq_label_out_b", initializer=fluid.initializer.Constant(0.))) infers = fluid.layers.argmax(logits, axis=2) ret_infers = fluid.layers.reshape(x=infers, shape=[-1, 1]) lod_labels = fluid.layers.sequence_unpad(labels, seq_lens) lod_infers = fluid.layers.sequence_unpad(infers, seq_lens) lod_logits = fluid.layers.sequence_unpad(logits, seq_lens) (_, _, _, num_infer, num_label, num_correct) = fluid.layers.chunk_eval( input=lod_infers, label=lod_labels, chunk_scheme=args.chunk_scheme, num_chunk_types=((args.num_labels-1)//(len(args.chunk_scheme)-1))) probs = fluid.layers.softmax(logits) crf_loss = fluid.layers.linear_chain_crf( input=lod_logits, label=lod_labels, param_attr=fluid.ParamAttr( name='crf_w', initializer=fluid.initializer.TruncatedNormal(scale=0.02))) loss = fluid.layers.mean(x=crf_loss) graph_vars = { "inputs": src_ids, "loss": loss, "probs": probs, "seqlen": seq_lens, "num_infer": num_infer, "num_label": num_label, "num_correct": num_correct, } for k, v in graph_vars.items(): v.persistable = True return pyreader, graph_vars
咱们将已经修改好的数据和代码,预先放在work/lesson/3 目录中,能够替换掉ERNIE项目中对应的文件,而后尝试运行
%cd ~ !cp -r work/lesson/3/sequence_label.py ERNIE/finetune/sequence_label.py %cd ERNIE !ln -s ../task_data !ln -s ../ERNIE1.0 %env TASK_DATA_PATH=task_data %env MODEL_PATH=ERNIE1.0 !sh script/zh_task/ernie_base/run_msra_ner_columnwise.sh
修改后从新运行finetune脚本:
sh script/zh_task/ernie_base/run_msra_ner.sh等待运行完后,取最后一次评估结果,对好比下:
以上即是实战课程的所有操做,直接fork可点击下方连接:
https://aistudio.baidu.com/aistudio/projectdetail/117030
划重点!
查看ERNIE模型使用的完整内容和教程,请点击下方连接,建议Star收藏到我的主页,方便后续查看。
GitHub:https://github.com/PaddlePaddle/ERNIE
版本迭代、最新进展都会在GitHub第一时间发布,欢迎持续关注!
也邀请你们加入ERNIE官方技术交流**QQ群:760439550**,可在群内交流技术问题,会有ERNIE的研发同窗为你们及时答疑解惑。