BERT 全称 Bidirectional Encoder Representations from Transformers,是一种较新的语言模型。自Google在 2018 年 10 月底公布 BERT 在 11 项 NLP 任务中的卓越表现后,BERT 引起了社区极大的关注与讨论,被认为是 NLP 领域的极大突破。
BERT 简介
BERT 是一种预训练语言表示的方法:在大量文本语料上预训练一个通用的”语言理解“模型(pre-training),然后用这个模型去执行各类下游子任务(fine-tuning)。它是第一个用在预训练 NLP 上的无监督、深度双向系统。
Google已开放源码,并提供了预训练模型的下载地址,这些模型已经在大规模数据集上进行了预训练。
git 地址: google-research/bert
BERT 原理
BERT 的具体原理不是本文的主要内容,以后有空再更新吧。
BERT 代码
BERT 本质上是一个两段式的 NLP 模型。第一阶段:Pre-training,利用无标记的语料训练一个语言模型;第二阶段:Fine-tuning,利用预训练好的语言模型,完成具体的NLP下游任务。
fine-tuning 代码
首先使用 git
克隆开源的 BERT 代码(fine-tune 代码):
1 | git clone https://github.com/google-research/bert |
pre-training 代码
BERT 还提供了下列预训练模型,需要提前下载预训练模型并用于之后的 fine-tune 过程:
BERT-Large, Uncased (Whole Word Masking)
: 24-layer, 1024-hidden, 16-heads, 340M parametersBERT-Large, Cased (Whole Word Masking)
: 24-layer, 1024-hidden, 16-heads, 340M parametersBERT-Base, Uncased
: 12-layer, 768-hidden, 12-heads, 110M parametersBERT-Large, Uncased
: 24-layer, 1024-hidden, 16-heads, 340M parametersBERT-Base, Cased
: 12-layer, 768-hidden, 12-heads , 110M parametersBERT-Large, Cased
: 24-layer, 1024-hidden, 16-heads, 340M parametersBERT-Base, Multilingual Cased (New, recommended)
: 104 languages, 12-layer, 768-hidden, 12-heads, 110M parametersBERT-Base, Multilingual Uncased (Orig, not recommended)
(Not recommended, useMultilingual Cased
instead): 102 languages, 12-layer, 768-hidden, 12-heads, 110M parametersBERT-Base, Chinese
: Chinese Simplified and Traditional, 12-layer, 768-hidden, 12-heads, 110M parameters
其中,每个模型文件夹包含以下三种文件:
- 配置文件(
bert_config.json
):用于指定模型的超参数 - 词典文件(
vocab.txt
):用于 WordPiece 到 Word id 的映射 - Tensorflow checkpoint(
bert_model.ckpt
):包含了预训练模型的权重(实际包含三个文件)
BERT 文本分类实例
接下来,以中文文本相似度任务为例,运行一次 BERT 的 fine-tuning 过程。为了完成文本分类这类任务,显然我们需要调整 run_classifier.py
中的代码。
自定义数据类
run_classifier.py
文件中包含一些名为 ****Processor
的类,它们都继承于同一个父类 DataProcessor(object)
,该父类提供了如下 4 个抽象方法:
1 | class DataProcessor(object): |
代码中继承该父类的这些 ****Processor(DataProcessor)
类,各自代表一个内置的示例数据集,且各自都实现了上述四个方法:前三个方法用于获取 训练、验证、测试 的输入数据,并将每条数据封装为 InputExample
类;第四个方法则用于获取 标签。
InputExample
类的定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 class InputExample(object):
"""A single training/test example for simple sequence classification."""
def __init__(self, guid, text_a, text_b=None, label=None):
"""Constructs a InputExample.
Args:
guid: Unique id for the example.
text_a: string. The untokenized text of the first sequence. For single
sequence tasks, only this sequence must be specified.
text_b: (Optional) string. The untokenized text of the second sequence.
Only must be specified for sequence pair tasks.
label: (Optional) string. The label of the example. This should be
specified for train and dev examples, but not for test examples.
"""
self.guid = guid
self.text_a = text_a
self.text_b = text_b
self.label = label每个
InputExample
类就是一条输入的样本,其中:
- 属性
guid
为该样本的唯一 ID;- 属性
text_a
为该样本的文本;- 属性
text_b
只在部分文本匹配/相似度分析等任务中会被用到,为该样本对中的另一条文本;- 属性
label
为该样本的标签;
如果我们需要处理自己的数据,就需要自定义新的数据类,并实现这四个方法,从而实现数据的获取过程。
假设我们的输入数据已经划分为 train.csv
,dev.csv
和 test.csv
三个文件,且每个文件的格式如下:
text | label | |
---|---|---|
1 | 今天天气真不错 | 感叹 |
2 | 中国是一个国家 | 陈述 |
3 | 你也想起舞吗? | 疑问 |
参考代码内置的那些示例数据集的写法,为我们的输入数据实现一个名为 TextClassifierProcessor
的类:(以训练集为例)
1 | # 文本分类数据集 |
对于测试集和验证集的处理方式与相同。
为了方便,可以构建一个字典类型的变量,存放数字类别和文本标签中间的对应关系。当然也可以直接使用文本标签,想用哪种用哪种。
调整 main 函数
定义完 TextClassifierProcessor
类之后,还需要将其加入到 main
函数中的 processors
变量。
找到 main()
函数,增加我们新定义的数据类(给他取一个 task_mask
名为 classifier
),如下所示:
1 | def main(_): |
修改输出
在 run_classifier.py
文件中,预测部分的会输出两个文件,分别是 predict.tf_record
和 test_results.tsv
。其中 test_results.tsv
中存放的是每个测试数据得到的属于所有类别的概率值,维度为 [n*num_labels]
。
但这个结果并不能直接反应得到的预测结果,因此修改处理代码,直接获取得到的预测类别。
原始的输出代码为:
1 | if FLAGS.do_predict: |
将上述代码中最后注释掉的那部分修改为如下代码:
1 | real_label = [] # 测试数据的真实标签 |
该代码额外将模型预测得到的类别输出到 test_labels_out.txt
文件中。
训练过程
准备好数据集,修改完数据类后,接下来就是如何 fine-tuning 模型。 查看 run_classifier.py
文件的入口部分,包含了 fine-tuning 模型所需的必要参数,如下:
1 | if __name__ == "__main__": |
必要参数的解释如下:
data_dir
:数据存放路径task_mask
:数据集的任务名称,对于该文本分类任务,则为classifier
vocab_file
:字典文件的地址bert_config_file
:配置文件output_dir
:模型输出地址
由于需要设置的参数较多,因此我们将其统一放置到 shell 脚本中,在run_classifier.py
的相同路径下创建名为 fine-tuning_classifier.sh
的脚本,如下所示:
1 | !/usr/bin/env bashexport BERT_BASE_DIR=../chinese_L-12_H-768_A-12 #全局变量 下载的预训练bert地址export MY_DATASET=../data #全局变量 数据集所在地址python run_classifier.py --task_name=classifier --do_train=true --do_eval=true --do_predict=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=32 --train_batch_size=64 --learning_rate=5e-5 --num_train_epochs=10.0 --output_dir=../fine_tuning_out/text_classifier_64_epoch10_5e5 |
在命令行使用如下命令执行该脚本:
1 | sh ./fine-tuning_classifier.sh |
在 output_dir
中就能得到 fine-tune 训练好的模型,以及在测试集上的预测结果等。