一.阅读相关论文
BERT论文原文:链接
中文翻译版:BERT中文翻译PDF版.pdf
BERT模型解读:解读的文章有很多,AINLP公众号上有系列文章汇总导读
此外,BERT模型采用的attention机制也有必要了解一下,
attention论文:Attention Is All You Need
对attention机制的解读:深度学习中的注意力机制
BERT使用的transformer模型:The Illustrated Transformer
二.下载BERT源码和预训练模型
BERT源码:Git地址
预训练模型:下载地址
三.阅读源码
1.源码结构和功能
tokenization.py,分词
create_pretraining_data.py,生成预训练数据
run_pretraining.py,模型预训练
modeling.py,BERT主模型的实现
run_squad.py,在squad数据集上的问题回答任务,可用于微调
run_classifier.py,文本分类任务,可用于微调
2.分词
tokenization.py里定义了FullTokenizer类,其中self.vocab加载词典,self.basic_tokenizer和self.wordpiece_tokenizer分别实例化BasicTokenizer 和WordpieceTokenizer类进行两种方式的分词。
BasicTokenizer类:主要是进行unicode转换、标点符号分割、小写转换、中文字符分割、去除重音符号等操作,最后返回的是关于词的数组(中文是字的数组)。
WordpieceTokenizer类:主要是将合成词分解成词根(其中使用了贪婪算法),避免词过于生僻没有被收录进词典最后只能以[UNK]代替(英文中常用)。
3.生成预训练数据
create_pretraining_data.py定义了将普通文本转换成可用于预训练BERT模型的tfrecord文件的方法。
如果使用现有的预训练BERT模型在文本分类/问题回答等任务上进行微调,则无需使用create_pretraining_data.py。
首先tf.flags定义了一些从命令行传入的参数,包括:
input_file,输入的语料文件
output_file,处理后的输出文件
vocab_file,词表文件
do_lower_case,是否区分大小写
do_whole_word_mask,是全词进行MASK还是对词片(WordPiece)进行MASK
max_seq_length,默认值128,BERT论文里提到最大值是512
max_predictions_per_seq,一个句子里最多有多少个[MASK]标记,默认值是20,如果max_seq_length设成最大值512的话?这个值是否也要相应增加呢?有人问了这个问题但还没有答案。
random_seed,随便设个数都可以,用来生成随机数的,设定某个数值以保证模型训练的效果可以复现
dupe_factor,同一个句子中可以重复生成的MASK次数
masked_lm_prob,默认值15,即15%的token会被随机MASK
short_seq_prob,默认值0.1,按此比例在预训练过程中构造一些短句子的样本。
Class TrainingInstance(): 一个实例就是一个[CLS] A [SEP] B [SEP]形式的句子对。
def create_training_instances(): 将文章和每篇文章的每个句子加到[文章,句子]的二维列表,再将列表传入create_instances_from_document生成TrainingInstance类的训练实例instances
def create_instances_from_document():读入上面的二维列表,对每篇文章生成instances,其中的tokens调用create_masked_lm_predictions函数进行随机MASK
def write_instance_to_example_files(): 将处理好的数据保存为tfrecord文件
这部分更详细的代码注释请参考:
谷歌BERT预训练源码解析(一):训练数据生成
Bert系列(三)——源码解读之Pre-train
4.模型预训练
run_pretraining.py对BERT模型的参数进行训练,包括两个训练任务:masked language model和next sentence prediction。
def get_masked_lm_output(): 计算masked language model这一任务的loss
def get_next_sentence_output(): 计算next sentence prediction这一任务的loss
def model_fn_builder(): 构造模型
def main(): 实现训练过程
这部分更详细的代码注释请参考:
BERT源码分析PART III
5.BERT主模型的实现
BertConfig类定义了模型的一些默认参数,以及一些文件处理函数,可以从文件中读入这些参数。在我们下载的预训练模型(下载地址)中,其中有个bert_config.json文件,里面就有这些参数。
vocab_size:词表大小
hidden_size:隐藏层神经元数
num_hidden_layers:Transformer encoder中的隐藏层数
num_attention_heads:multi-head attention 的head数
intermediate_size:encoder的“中间”隐层神经元数(例如feed-forward layer)
hidden_act:隐藏层激活函数
hidden_dropout_prob:隐层dropout率
attention_probs_dropout_prob:注意力部分的dropout
max_position_embeddings:最大位置编码
type_vocab_size:token_type_ids的词典大小,默认值为2,即next sentence prediction任务中的segmentA和segmentB。
initializer_range:truncated_normal_initializer初始化方法的stdev
BertModel类:BERT模型最核心的部分
这部分详细的代码注释请参考:
一本读懂BERT(实践篇)
BERT源码分析PART I
6.BERT微调
run_squad.py和run_classifier.py可以用于微调,同样地,网上可以找到很多攻略:
BERT系列(一)——BERT源码分析及使用方法
干货 | BERT fine-tune 终极实践教程
四.实战——命名实体识别
1.安装BERT-BiLSTM-CRF-NER项目
项目来源:https://github.com/macanv/BERT-BiLSTM-CRF-NER
clone项目源码到D:\download\BERT文件夹
git clone https://github.com/macanv/BERT-BiLSTM-CRF-NER
cd BERT-BiLSTM-CRF-NER/
python run.py
运行失败
看项目的说明文档,有一些包需要安装或更新
# client-side requirements, pretty light-weight right?
# tensorflow >= 1.12.0
# tensorflow-gpu >= 1.12.0 # GPU version of TensorFlow.
GPUtil >= 1.3.0 # no need if you dont have GPU
pyzmq >= 17.1.0 # python zmq
flask # no need if you do not need http
flask_compress # no need if you do not need http
flask_json # no need if you do not need http
也有的包版本过高报错。。。
pip uninstall numpy
pip install numpy==1.16.4
备注:我安装tensorflow的环境python版本是3.5,tensorflow-gpu版本是1.12.0,满足需求,就不要更新了。各个包的版本匹配很重要,更新后反而会带来麻烦
解决了包的版本问题,再次运行run.py,仍然报错,错误信息是有些参数(如下载的中文预训练模型路径)不对。
干脆还是安装这个项目吧,然后运行帮助命令,成功
python setup.py install
bert-base-ner-train help
2. 训练模型
接下来使用现成的标注语料,输入各项参数,训练模型
bert-base-ner-train \
-data_dir {your dataset dir}\
-output_dir {training output dir}\
-init_checkpoint {Google BERT model dir}\
-bert_config_file {bert_config.json under the Google BERT model dir} \
-vocab_file {vocab.txt under the Google BERT model dir}
其中data_dir是数据所在的目录,训练数据,验证数据和测试数据命名格式为:train.txt, dev.txt,test.txt,训练数据的格式如下,每行的第一个是字,第二个是它的标签,使用空格分隔,句与句之间使用空行划分。
海 O 钓 O 比 O 赛 O 地 O 点 O 在 O 厦 B-LOC 门 I-LOC 与 O 金 B-LOC 门 I-LOC 之 O 间 O 的 O 海 O 域 O 。 O
我在另一个项目里下载到了训练数据:https://github.com/mattzheng/tensorflow_nlp
最后我输入的参数如下(记录在这里备忘):
bert-base-ner-train
-data_dir D:\download\BERT\BERT-BiLSTM-CRF-NER\data -output_dir D:\download\BERT\BERT-BiLSTM-CRF-NER\ner_model_test -init_checkpoint D:\download\BERT\chinese_L-12_H-768_A-12\bert_model.ckpt -bert_config_file D:\download\BERT\chinese_L-12_H-768_A-12\bert_config.json -vocab_file D:\download\BERT\chinese_L-12_H-768_A-12\vocab.txt -batch_size 32
batch_size默认值是64,但是出现了报错TensorFlow: Resource exhausted: OOM when allocating tensor with shape……,所以调小了batch_size
output_dir是训练模型输出的文件路径,这个路径在作为服务的时候,可以指定为ner_model_dir
模型训练效果如下:
3.用bert-as-service进行命名实体识别
BERT-BiLSTM-CRF-NER这个项目使用了bert as service of hanxiao进行服务部署
运行服务:
bert-base-serving-start -model_dir D:\download\BERT\BERT-BiLSTM-CRF-NER\model_pb_dir -bert_model_dir D:\download\BERT\chinese_L-12_H-768_A-12 -mode NER
服务运行成功,开始监听
然后打开jupyter notebook,输入以下代码就可以进行命名实体识别了
from bert_base.client import BertClient
bc = BertClient() #按照项目页面给出的代码指定各项参数,一直失败,最后直接用默认值不指定任何参数才可以
str1 = "东北地区的主要山脉有大兴安岭,小兴安岭和长白山"
str2="陈小白毕业于北京大学化学系"
str3="尼泊尔、印度、越南、泰国、马来西亚及非洲热带亦有分布。"
bc.encode([str1,str2,str3])
4.使用自己标注的语料训练模型
首先要将自己标注的语料转换成需要的格式
我的语料是用自己的方法标注的,见我另一篇文章:一款好用的文本标注工具
标注好的效果如下:
雄蕊 4枚,二强,着生于花冠筒 中部,内藏,花药 长卵圆形;
用下面的代码就可以将语句切分成小段,及相应的标注
string="<organ>雄蕊</organ>4枚,二强,着生于<organ>花冠筒</organ>中部,内藏,<organ>花药</organ>长卵圆形;"
pattern=r'<([a-z]*?)>(.*?)</\1>' #正则表达式中"/1"表示前面第一个被匹配到的分组
segments = re.split(pattern,string)
剩下的步骤就略过了,总之将标注语料按8:1:1分成了train,dev,test,并全部转换成了需要的格式
开始训练
训练结果如下:
然后才发现标注的格式写错了,应该是“B-LOG”,“I-LOG”,而不是“LOG-B”和“LOG-I”
整理标注格式之后,模型结果如下:
有一类实体F1值为零,估计和我自己使用的语料有关(并非专门用来做NER的,其中这类实体的划分存在很大主观上的因素)
5.在网页上部署BERT模型
下一步计划参考开源项目:BERT模型从训练到部署全流程
将训练好的模型部署在网页上,暂时记录到这里