以下是奇点机警技能团队对 BERT 在中文数据集上的 fine tune 终极实践教程。
在自己的数据集上运行 BERT
BERT 的代码同论文里描述的同等,紧张分为两个部分。一个是演习措辞模型(language model)的预演习(pretrain)部分。另一个是演习详细任务( task )的fine-tune 部分。在开源的代码中,预演习的入口是在 run_pretraining.py 而 fine-tune 的入口针对不同的任务分别在 run_classifier.py 和 run_squad.py。个中 run_classifier.py 适用的任务为分类任务。如 CoLA、MRPC、MultiNLI 这些数据集。而 run_squad.py 适用的是阅读理解( MRC )任务,如 squad2.0 和 squad1.1。
预演习是 BERT 很主要的一个部分,与此同时,预演习须要巨大的运算资源。按照论文里描述的参数,其 Base 的设定在消费级的显卡 Titan x 或 Titan 1080ti ( 12GB RAM )上,乃至须要近几个月的韶光进行预演习,同时还会面临显存不敷的问题。不过所幸的是谷歌知足了Issues#2里各国开拓者的要求,针对大部分措辞都公布了 BERT 的预演习模型。因此在我们可以比较方便地在自己的数据集上进行 fine-tune。
下载预演习模型
对付中文而言,google 公布了一个参数较小的 BERT 预演习模型。详细参数数值如下所示:
Chinese Simplified and Traditional, 12-layer, 768-hidden, 12-heads, 110M parameters
模型的下载链接可以在 github 上 google 的开源代码里找到。对下载的压缩文件进行解压,可以看到文件里有五个文件,个中 bert_model.ckpt 开头的文件是卖力模型变量载入的,而 vocab.txt 是演习时中文文本采取的字典,末了 bert_config.json 是 BERT 在演习时,可选调度的一些参数。
修正 processor
任何模型的演习、预测都是须要有一个明确的输入,而 BERT 代码中 processor 便是卖力对模型的输入进行处理。我们以分类任务的为例,先容如何修正processor来 运行自己数据集上的 fine-tune。在 run_classsifier.py 文件中我们可以看到,google 对付一些公开数据集已经写了一些 processor,如XnliProcessor, MnliProcessor, MrpcProcessor 和 ColaProcessor。这给我们供应了一个很好的示例,辅导我们如何针对自己的数据集来写 processor。
对付一个须要实行演习、交叉验证和测试完全过程的模型而言,自定义的processor 里须要继续 DataProcessor,并重载获取 label 的 get_labels 和获取单个输入的 get_train_examples, get_dev_examples 和 get_test_examples 函数。其分别会在 main 函数的 FLAGS.do_train、FLAGS.do_eval 和 FLAGS.do_predict 阶段被调用。这三个函数的内容是相差无几的,差异只在于须要指定各自读入文件的地址。
以 get_train_examples 为例,函数须要返回一个由 InputExample 类组成的 list。InputExample 类是一个很大略的类,只有初始化函数,须要传入的参数中 guid 是用来区分每个 example 的,可以按照 train-%d'%(i) 的办法进行定义。text_a 是一串字符串,text_b 则是另一串字符串。在进行后续输入处理后( BERT 代码中已包含,不须要自己完成) text_a 和 text_b 将组合成 [CLS] text_a [SEP] text_b [SEP] 的形式传入模型。末了一个参数 label 也是字符串的形式,label 的内容须要担保涌如今 get_labels 函数返回的 list 里。
举一个例子,假设我们想要处理一个能够判断句子相似度的模型,现在在 data_dir的路径下有一个名为 train.csv 的输入文件,如果我们现在输入文件的格式如下 csv 形式:
1,你好,您好
0,你好,你家住哪
那么我们可以写一个如下的 get_train_examples 的函数。当然对付 csv 的处理,可以利用诸如 csv.reader 的形式进行读入。
def get_train_examples(self, data_dir):file_path = os.path.join(data_dir, 'train.csv') with open(file_path, 'r') as f:reader = f.readlinesexamples = for index, line in enumerate(reader):guid = 'train-%d'%indexsplit_line = line.strip.split(',')text_a = tokenization.convert_to_unicode(split_line[1])text_b = tokenization.convert_to_unicode(split_line[2])label = split_line[0]examples.append(InputExample(guid=guid, text_a=text_a,text_b=text_b, label=label)) return examples
同时对应判断句子相似度这个二分类任务,get_labels 函数可以写成如下的形式:
def get_labels(self):return ['0','1']
在对 get_dev_examples 和 get_test_examples 函数做类似 get_train_examples 的操作后,便完成了对 processor 的修正。个中 get_test_examples 可以传入一个随意的 label 数值,由于在模型的预测(prediction)中 label 将不会参与打算。
修正 processor 字典
修正完成 processor 后,须要在在原来 main 函数的 processor 字典里,加入修正后的 processor 类,即可在运行参数里指定调用该 processor。
processors = {\"大众cola\公众: ColaProcessor,\公众mnli\"大众: MnliProcessor,\"大众mrpc\"大众: MrpcProcessor,\"大众xnli\"大众: XnliProcessor,\"大众selfsim\公众: SelfProcessor #添加自己的processor}
运行 fine-tune
之后就可以直接运行 run_classsifier.py 进行模型的演习。在运行时须要制订一些参数,一个较为完全的运行参数如下所示:
export BERT_BASE_DIR=/path/to/bert/chinese_L-12_H-768_A-12 #全局变量 下载的预演习 bert 地址 export MY_DATASET=/path/to/xnli # 全局变量 数据集所在地址 python run_classifier.py \--task_name=selfsim \ #自己添加 processor 在 processors 字典里的 key 名--do_train=true \--do_eval=true \--dopredict=true \--data_dir=$MY_DATASET \--vocab_file=$BERT_BASE_DIR/vocab.txt \--bert_config_file=$BERT_BASE_DIR/bert_config.json \--init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \--max_seq_length=128 \ #模型参数--train_batch_size=32 \--learning_rate=5e-5 \--num_train_epochs=2.0 \--output_dir=/tmp/selfsim_output/ #模型输出路径
BERT 源代码里还有什么
在开始演习我们自己 fine-tune 的 BERT 后,我们可以再来看看 BERT 代码里除了 processor 之外的一些部分。我们可以创造,process 在得到字符串形式的输入后,在file_based_convert_examples_to_features 里先是对字符串长度,加入 [CLS] 和 [SEP] 等一些处理后,将其写入成 TFrecord 的形式。这是为了能在 estimator 里有一个更为高效和大略单纯的读入。
我们还可以创造,在 create_model 的函数里,除了从 modeling.py 获取模型主干输出之外,还有进行 fine-tune 时候的 loss 打算。因此,如果对付 fine-tune 的构造有自定义的哀求,可以在这部分对代码进行修正。如进行 NER 任务的时候,可以按照 BERT 论文里的办法,不但读第一位的 logits,而是将每一位 logits 进行读取。
BERT 这次开源的代码,由于是考虑在 google 自己的 TPU 上高效地运行,因此采取的 estimator 是 tf.contrib.tpu.TPUEstimator, 虽然 TPU 的 estimator 同样可以在 gpu 和 cpu 上运行,但若想在 gpu 上更高效地做一些提升,可以考虑将其换成 tf.estimator.Estimator,于此同时 model_fn 里一些tf.contrib.tpu.TPUEstimatorSpec 也须要修正成 tf.estimator.EstimatorSpec 的形式,以及干系调用参数也须要做一些调度。在转换成较普通的 estimator 后便可以利用常用的办法对 estimator 进行处理,如天生用于支配的 .pb 文件等。
GitHub Issues 里一些有趣的内容
从 google 对 BERT 进行开源开始,Issues 里的谈论便非常生动,BERT 论文第一作者 Jacob Devlin 也积极地在 Issues里进行回应,在互换谈论中,产生了一些很有趣的内容。
在 GitHub Issues#95 中大家谈论了 BERT 模型在今年 AI-Challenger 比赛上的运用。我们也同样考试测验了 BERT 在 AI-Challenger 的机器阅读理解(mrc)赛道的表现。如果大略得地将 mrc 的文本连接成一个长字符串的形式,可以在 dev 集上得到 79.1% 的准确率。
如果参考 openAI 的 GPT 论文里 multi-choice 的形式对 BERT 的输入输出代码进行修正则可以将准确率提高到 79.3%。采取的参数都是 BERT 默认的参数,而单一模型成绩在赛道的 test a 排名中已经能超过榜单上的第一名。因此,在干系中文的任务中,bert 能有很大的想象空间。
在 GitHub Issues#123 中,@hanxiao 给出了一个采取 ZeroMQ 便捷支配 BERT 的 service,可以直接调用演习好的模型作为运用的接口。同时他将 BERT 改为一个大的 encode 模型,将文本通过 BERT 进行 encode,来实现句子级的 encode。此外,他比拟了多 GPU 上的性能,创造 bert 在多 GPU 并行上的出色表现。
总结
总的来说,google 这次开源的 BERT 和其预演习模型是非常有代价的,可探索和改进的内容大概多。干系数据集上已经涌现了对 BERT 进行修正后的复合模型,如 squad2.0 上哈工大( HIT )的 AoA + DA + BERT 以及西湖大学(DAMO)的SLQA + BERT。 在感谢 google 这份付出的同时,我们也可以借此站在巨人的肩膀上,考试测验将其利用在自然措辞处理领域的方方面面,让人工智能的梦想更近一步。
点击阅读原文,理解总结+paper分享 | 任务型对话中的跨领域&个性化&迁移学习