Bert模型详解

Bert

Bert模型核心

利用大规模无标注语料训练、获得文本的包含丰富语义信息的Representation,即:文本的语义表示,然后将文本的语义表示在特定NLP任务中作微调,最终应用于该NLP任务。

模型的输入为一个随机的向量或预训练好的Word2vec向量,通过Bert的预训练过程(Deep Bidirectional Transformer)转化为可以上下文语义的向量表示,进而在特定的NLP任务中对参数进行微调(fine-tuning),达到较好的效果

模型架构

模型的预训练包括两部分内容:

  • MLM,Masked Language Model
  • next sentence prediction

MLM:在Transformer训练过程中对原文本随机遮蔽其中的15%,预测被遮蔽的字词,为了防止mismatch的问题,在某个词被选中遮蔽的情况下,有80%的可能替换为[mask],10%的可能被替换成任意一个词,10%的可能不做任何改变,这样模型就不知道到底哪个词被替换,迫使模型更能准确的结合上下文语义。

next sentenct prediction:从原语料库中随机抽取20万对句子,其中50%为不是连续的上下文,50%是连续的上下文,让模型去根据前一个句子判断下一个句子是不是正确的上下文。

模型输入

Word Vector + Segment Vector + Position Vector

  • Word Vector:词向量,使用Word Piece表示
  • Segment Vector:标识Word属于哪个句子,当输入为2个句子时,为(0,1),输入为一个句子时,忽略
  • Position vector:记录Word的位置信息

特殊字符:

[CLS] 表示句子的开始位置,用于分类任务中
[SEP] 表示两个句子的分割位置

Transformer

Transformer中最重要的部分是Self-attention机制

Self Attention

Attention机制将目标字和上下文各个字的语义向量表示作为输入,首先通过线性变换获得目标字的Query向量表示、上下文各个字的Key向量表示以及目标字与上下文各个字的原始Value表示,然后计算Query向量与各个Key向量的相似度作为权重,加权融合目标字的Value向量和各个上下文字的Value向量,作为Attention的输出,即:目标字的增强语义向量表示。

在 Bert 中,不是采用单个目标词,而是对输入文本中的每个Word都做了Attention的变换,称为Self-Attention机制

假设有权重$W_Q,W_K,W_V$,分别为Query、Key、Value矩阵,则最终输出的变化过程为:

$$
Q = XW_Q\
V = XW_V \\
K = XW_K \
OUTPUT = softmax(\frac{QK^T}{\sqrt {d_K}})V
$$

Multi-head Self-Attention

并且在Bert中采用了Multi-head Self-Attention架构,为了增强Attention的多样性,进一步利用不同的Self-Attention模块获得文本中每个字在不同语义空间下的增强语义向量,并将每个字的多个增强语义向量进行线性组合,从而获得一个最终的与原始字向量长度相同的增强语义向量,如下图所示。

Encoder

Berth模型中不包含Decoder模块

Transformer Encoder 是在Self-Attention之上又增加了三个关键操作:

  • 残差连接:将模块的输入与输出直接相加,作为最后的输出。这种操作背后的一个基本考虑是:修改输入比重构整个输出更容易(“锦上添花”比“雪中送炭”容易多了!)。这样一来,可以使网络更容易训练。
  • Layer Normalization:对某一层神经网络节点作0均值1方差的标准化。
  • 线性转换:对每个字的增强语义向量再做两次线性变换,以增强整个模型的表达能力。这里,变换后的向量与原向量保持长度相同。

Bert model

组装好TransformerEncoder之后,再把多个Transformer Encoder一层一层地堆叠起来,BERT模型就大功告成了!

Fine tuning

分类任务

[CLS]符号对应的输出向量作为整篇文本的语义表示,用于文本分类,该向量作为一个K个神经元的分类层的输入,通过softmax转化为概率进行分类

认为[CLS]符号对应的向量可以表达整个句子的意思,是这个句子的整体描述

实战

分类任务

参考:https://www.jiqizhixin.com/articles/2018-11-23-15

在开源的代码中,预训练的入口是在run_pretraining.py而fine-tune的入口针对不同的任务分别在run_classifier.pyrun_squad.py

其中run_classifier.py适用的任务为分类任务。如CoLA、MRPC、MultiNLI这些数据集。而run_squad.py适用的是阅读理解(MRC)任务,如squad2.0和squad1.1。

其他语言的预训练模型:

https://github.com/google-research/bert/blob/master/multilingual.md

文件架构:

  • bert_model.ckpt开头的文件是负责模型变量载入的
  • vocab.txt是训练时中文文本采用的字典
  • bert_config.json是BERT在训练时,可选调整的一些参数。

修改Processor

自定义DataProcessor

重载获取labelget_labels和获取单个输入的get_train_examples,get_dev_examplesget_test_examples函数。

其分别会在main函数的FLAGS.do_trainFLAGS.do_evalFLAGS.do_predict阶段被调用。

  • get_train_examples为例,函数需要返回一个由InputExample类组成的list。
  • InputExample类是一个很简单的类,只有初始化函数
  • 需要传入的参数中guid是用来区分每个example的,可以按照train-%d'%(i)的方式进行定义。
  • text_a是一串字符串,text_b则是另一串字符串。在进行后续输入处理后(BERT代码中已包含,不需要自己完成) text_atext_b将组合成[CLS] text_a [SEP] text_b [SEP]的形式传入模型。
  • 最后一个参数label也是字符串的形式,label的内容需要保证出现在get_labels函数返回的list里。

修改Processor字典

修改完成processor后,需要在在原本main函数的processor字典里,加入修改后的processor类,即可在运行参数里指定调用该processor。

运行Fine-tune

一个完整的参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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/ #模型输出路径