BERT算法详解及其应用

一、背景

最近几年自然语言处理领域取得了飞速的发展,基本几项重要的成果都集中在了预训练上包括18年火爆一时的BERT,而后续发表的XLNet,近期发布的RoBERTa都是以更好的预训练为目的的。

什么是BERT?BERT是基于Transformer Encoder来构建的一种预训练模型,在BERT的论文中称之为Masked Lanauge Model(MLM),严格意义上而言BERT并不属于语言模型,因为它并未按照语言模型的方式来进行训练,其实就是随机的将一些单词通过MASK的标签来替换,然后去猜测被MASK的单词,这整个过程就是基于DAE(Denoising Autoencoder)框架做的。对于Transformer的介绍我之前的文章有介绍过。

BERT的本质上是通过在海量的语料的基础上运行自监督学习方法为单词学习一个好的特征表示,所谓自监督学习是指在没有人工标注的数据上运行的监督学习。在以后特定的NLP任务中,我们可以直接使用BERT的特征表示作为该任务的词嵌入特征。所以BERT提供的是一个供其它任务迁移学习的模型,该模型可以根据任务微调或者固定之后作为特征提取器。BERT的源码和模型10月31号已经在Github上开源,简体中文和多语言模型也于11月3号开源。BERT有两种主要训练好的模型,分别是BERT-Small和BERT-Large, 其中BERT-Large使用了12层的Encoder结构。 整个的模型具有非常多的参数。

在bert之前,将预训练的embedding应用到下游任务的方式大致可以分为2种,一种是feature-based,例如ELMo这种将经过预训练的embedding作为特征引入到下游任务的网络中;一种是fine-tuning,例如GPT这种将下游任务接到预训练模型上,然后一起训练。然而这2种方式都会面临同一个问题,就是无法直接学习到上下文信息,像ELMo只是分别学习上文和下文信息,然后concat起来表示上下文信息,抑或是GPT只能学习上文信息。因此,作者提出一种基于transformer encoder的预训练模型,可以直接学习到上下文信息,叫做bert。bert使用了12个transformer encoder block,在13G的数据上进行了预训练,可谓是nlp领域大力出奇迹的代表。

论文地址:BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding

二、BERT详解

2017年之前,语言模型都是通过rnn,lstm来建模,这样虽然可以学习上下文之间的关系,但是无法并行化,给模型的训练和推理带来了困难,因此论文提出了一种完全基于attention来对语言建模的模型,叫做transformer。transformer摆脱了nlp任务对于rnn,lstm的依赖,使用了self-attention的方式对上下文进行建模,提高了训练和推理的速度,transformer也是后续更强大的nlp预训练模型的基础,因此Bert网络结构也是基于Transformer结构发展而来的。

BERT的全称是Bidirectional Encoder Representation from Transformers,即双向Transformer的Encoder,因为decoder是不能获要预测的信息的。模型的主要创新点都在pre-train方法上,即用了Masked LM和Next Sentence Prediction两种方法分别捕捉词语和句子级别的representation。接下来我们先对BERT的核心结构Transformer进行介绍。

2.1 Transormer结构

Transormer是一个encoder-decoder网络,由若干个编码器和解码器堆叠形成,如下图所示:
transformer_02.png
我们将encoder和decoder展开来看的更细致后,就如下所示:
01.png
接下来我们针对以上结构进行逐个解释:

  • <1> Inputs是经过padding的输入数据,大小是[batch size, max seq length]。
  • <2> 初始化embedding matrix,通过embedding lookup将Inputs映射成token embedding,大小是[batch size, max seq length, embedding size],然后乘以embedding size的开方。
  • <3> 通过sin和cos函数创建positional encoding,表示一个token的绝对位置信息,并加入到token embedding中,然后dropout。
  • <4> multi-head attention

    • <4.1> 输入token embedding,通过Dense生成Q,K,V,大小是[batch size, max seq length, embedding size],然后按第2维split成num heads份并按第0维concat,生成新的Q,K,V,大小是[num heads*batch size, max seq length, embedding size/num heads],完成multi-head的操作。
    • <4.2> 将K的第1维和第2维进行转置,然后Q和转置后的K的进行点积,结果的大小是[num heads*batch size, max seq length, max seq length]。
    • <4.3> 将<4.2>的结果除以hidden size的开方(在transformer中,hidden size=embedding size),完成scale的操作。
    • <4.4> 将<4.3>中padding的点积结果置成一个很小的数(-2^32+1),完成mask操作,后续softmax对padding的结果就可以忽略不计了。
    • <4.5> 将经过mask的结果进行softmax操作。
    • <4.6> 将softmax的结果和V进行点积,得到attention的结果,大小是[num heads*batch size, max seq length, hidden size/num heads]。
    • <4.7> 将attention的结果按第0维split成num heads份并按第2维concat,生成multi-head attention的结果,大小是[batch size, max seq length, hidden size]。Figure 2上concat之后还有一个linear的操作,但是代码里并没有。
  • <5> 将token embedding和multi-head attention的结果相加,并进行Layer Normalization。

  • <6> 将<5>的结果经过2层Dense,其中第1层的activation=relu,第2层activation=None。
  • <7> 功能和<5>一样。
  • <8> Outputs是经过padding的输出数据,与Inputs不同的是,Outputs的需要在序列前面加上一个起始符号”<s>”,用来表示序列生成的开始,而Inputs不需要。
  • <9> 功能和<2>一样。
  • <10> 功能和<3>一样。
  • <11> 功能和<4>类似,唯一不同的一点在于mask,<11>中的mask不仅将padding的点积结果置成一个很小的数,而且将当前token与之后的token的点积结果也置成一个很小的数。
  • <12> 功能和<5>一样。
  • <13> 功能和<4>类似,唯一不同的一点在于Q,K,V的输入,<13>的Q的输入来自于Outputs 的token embedding,<13>的K,V来自于<7>的结果。
  • <14> 功能和<5>一样。
  • <15> 功能和<6>一样。
  • <16> 功能和<7>一样,结果的大小是[batch size, max seq length, hidden size]。
  • <17> 将<16>的结果的后2维和embedding matrix的转置进行点积,生成的结果的大小是[batch size, max seq length, vocab size]。
  • <18> 将<17>的结果进行softmax操作,生成的结果就表示当前时刻预测的下一个token在vocab上的概率分布。
  • <19> 计算<18>得到的下一个token在vocab上的概率分布和真实的下一个token的one-hot形式的cross entropy,然后sum非padding的token的cross entropy当作loss,利用adam进行训练。

2.2 Transormer的技术细节

transformer中的self-attention是从普通的点积attention中演化出来的,但是对于上述结构我们还有很多细节可以探讨。

1.为什么<2>要乘以embedding size的开方?
论文并没有讲为什么这么做,看了代码,猜测是因为embedding matrix的初始化方式是xavier init,这种方式的方差是1/embedding size,因此乘以embedding size的开方使得embedding matrix的方差是1,在这个scale下可能更有利于embedding matrix的收敛。

2.为什么inputs embedding要加入positional encoding?
因为self-attention是位置无关的,无论句子的顺序是什么样的,通过self-attention计算的token的hidden embedding都是一样的,这显然不符合人类的思维。因此要有一个办法能够在模型中表达出一个token的位置信息,transformer使用了固定的positional encoding来表示token在句子中的绝对位置信息。positional encoding的公式如下:
$$
PE(pos,2i)=sin(\frac{pos}{10000^{\frac{2i}{d_{model}}}})\\
PE(pos,2i+1)=cos(\frac{pos}{10000^{\frac{2i}{d_{model}}}})
$$
至于positional encoding为什么能表示位置信息,可以看如何理解Transformer论文中的positional encoding,和三角函数有什么关系?

3.为什么<4.2>的结果要scale?
以数组为例,2个长度是len,均值是0,方差是1的数组点积会生成长度是len,均值是0,方差是len的数组。而方差变大会导致softmax的输入推向正无穷或负无穷,这时的梯度会无限趋近于0,不利于训练的收敛。因此除以len的开方,可以是数组的方差重新回归到1,有利于训练的收敛。

4.为什么<5>要将multi-head attention的输入和输出相加?
类似于resnet中的残差学习单元,有ensemble的思想在里面,解决网络退化问题。

5. 为什么attention需要multi-head,一个大head行不行?
multi-head相当于把一个大空间划分成多个互斥的小空间,然后在小空间内分别计算attention,虽然单个小空间的attention计算结果没有大空间计算得精确,但是多个小空间并行然后concat有助于网络捕捉到更丰富的信息,类比cnn网络中的channel。

6.为什么multi-head attention后面要加一个ffn?
类比cnn网络中,cnn block和fc交替连接,效果更好。相比于单独的multi-head attention,在后面加一个ffn,可以提高整个block的非线性变换的能力。

7.为什么<11>要mask当前时刻的token与后续token的点积结果?
自然语言生成(例如机器翻译,文本摘要)是auto-regressive的,在推理的时候只能依据之前的token生成当前时刻的token,正因为生成当前时刻的token的时候并不知道后续的token长什么样,所以为了保持训练和推理的一致性,训练的时候也不能利用后续的token来生成当前时刻的token。这种方式也符合人类在自然语言生成中的思维方式。

如果想更多的了解Attention和Tranformer的知识,可以参考这篇《Attention机制和Transformer框架详解》博客。

三、BERT应用

由于模型的构成元素Transformer已经解析过,就不多说了,BERT模型的结构如下图最左:
02.png
BERT对比这两个算法的优点是只有BERT表征会基于所有层中的左右两侧语境。BERT能做到这一点得益于Transformer中Attention机制将任意位置两个单词的距离转换成了1。

对比OpenAI GPT(Generative pre-trained transformer),BERT是双向的Transformer block连接;就像单向rnn和双向rnn的区别,直觉上来讲效果会好一些。

对比ELMo,虽然都是“双向”,但目标函数其实是不同的。ELMo是分别以$P(w_{i}|w_{1},….,w_{i-1})$和$P(w_{i}|w_{i+1},….,w_{n})$作为目标函数,独立训练处两个representation然后拼接,而BERT则是以$P(w_{i}|w_{1},….,w_{i-1},w_{i+1},….,w_{n})$作为目标函数训练LM。

BERT提供了简单和复杂两个模型,对应的超参数分别如下:

  • $BERT_{BASE}$:L=12,H=768,A=12,参数总量110M
  • $BERT_{LARGE}$:L=24,H=1024,A=16,参数总量340M

在BERT的应用中主要分为预训练(Pre-training)和微调(Fine-Tuning)两部分。如下图所示
03.png
预训练能够很优雅的通过无监督的方式将数据中的语言知识引入进来,微调就是将预训练的模型结合自身业务和模型进行调整,调整后的模型与自身目标更相关。

3.1 BERT的Embedding

这里的Embedding由三种Embedding求和而成:
04.png

  • Token Embeddings是词向量,第一个单词是CLS标志,可以用于之后的分类任务。
  • Segment Embeddings用来区别两种句子,因为预训练不光做LM还要做以两个句子为输入的分类任务
  • Position Embeddings和之前文章中的Transformer不一样,不是三角函数而是学习出来的

上图中在输入部分是WordPiece,是指将单词划分成一组有限的公共子词单元,能在单词的有效性和字符的灵活性之间取得一个折中的平衡。例如上图中的示例中‘playing’被拆分成了‘play’和‘ing’。

3.2 BERT的预训练

BERT是一个多任务模型,它的任务是由两个自监督任务组成,即Masked LM和Next Sentence Prediction。

3.2.1 Task #1: Masked Language Model

Masked Language Model(MLM)和核心思想取自Wilson Taylor在1953年发表的一篇论文《cloze procedure: A new tool for measuring readability》。所谓MLM是指在训练的时候随即从输入预料上mask掉一些单词,然后通过的上下文预测该单词,该任务非常像我们在中学时期经常做的完形填空。正如传统的语言模型算法和RNN匹配那样,MLM的这个性质和Transformer的结构是非常匹配的。

在BERT的实验中,15%的WordPiece Token会被随机Mask掉。在训练模型时,一个句子会被多次喂到模型中用于参数学习,但是Google并没有在每次都mask掉这些单词,而是在确定要Mask掉的单词之后,80%的时候会直接替换为[Mask],10%的时候将其替换为其它任意单词,10%的时候会保留原始Token。

  • 80%:my dog is hairy -> my dog is [mask]
  • 10%:my dog is hairy -> my dog is apple
  • 10%:my dog is hairy -> my dog is hairy

这么做的原因是如果句子中的某个Token100%都会被mask掉,那么在fine-tuning的时候模型就会有一些没有见过的单词。加入随机Token的原因是因为Transformer要保持对每个输入token的分布式表征,否则模型就会记住这个[mask]是token ’hairy‘。至于单词带来的负面影响,因为一个单词被随机替换掉的概率只有15%*10% =1.5%,这个负面影响其实是可以忽略不计的。
另外文章指出每次只预测15%的单词,因此模型收敛的比较慢。

3.2.2 Task #2: Next Sentence Prediction

Next Sentence Prediction(NSP)的任务是判断句子B是否是句子A的下文。如果是的话输出’IsNext‘,否则输出’NotNext‘。训练数据的生成方式是从平行语料中随机抽取的连续两句话,其中50%保留抽取的两句话,它们符合IsNext关系,另外50%的第二句话是随机从预料中提取的,它们的关系是NotNext的。这个关系保存在图4中的[CLS]符号中。

3.3 BERT的FineTuning

在海量单预料上训练完BERT之后,便可以将其应用到NLP的各个任务中了。对于NSP任务来说,其条件概率表示为$P=softmax(CW^{T})$,其中$C$是BERT输出中的[CLS]符号,$W$ 是可学习的权值矩阵。

对于其它任务来说,我们也可以根据BERT的输出信息作出对应的预测。下图展示了BERT在11个不同任务中的模型,它们只需要在BERT的基础上再添加一个输出层便可以完成对特定任务的微调。这些任务类似于我们做过的文科试卷,其中有选择题,简答题等等。图5中其中Tok表示不同的Token,$E$表示嵌入向量,$T_{i}$表示第$i$个Token在经过BERT处理之后得到的特征向量。
05.png

上图中(a)表示对句子分类任务:

  • MNLI:给定一个前提 (Premise) ,根据这个前提去推断假设 (Hypothesis) 与前提的关系。该任务的关系分为三种,蕴含关系 (Entailment)、矛盾关系 (Contradiction) 以及中立关系 (Neutral)。所以这个问题本质上是一个分类问题,我们需要做的是去发掘前提和假设这两个句子对之间的交互信息。
  • QQP:基于Quora,判断 Quora 上的两个问题句是否表示的是一样的意思。
  • QNLI:用于判断文本是否包含问题的答案,类似于我们做阅读理解定位问题所在的段落。
  • STS-B:预测两个句子的相似性,包括5个级别。
  • MRPC:也是判断两个句子是否是等价的。
  • RTE:类似于MNLI,但是只是对蕴含关系的二分类判断,而且数据集更小。
  • SWAG:从四个句子中选择为可能为前句下文的那个。

上图中(a)表示单个句子分类任务:

  • SST-2:电影评价的情感分析。
  • CoLA:句子语义判断,是否是可接受的(Acceptable)。

对于GLUE数据集的分类任务(MNLI,QQP,QNLI,SST-B,MRPC,RTE,SST-2,CoLA),BERT的微调方法是根据[CLS]标志生成一组特征向量$C$ ,并通过一层全连接进行微调。损失函数根据任务类型自行设计,例如多分类的softmax或者二分类的sigmoid。

SWAG的微调方法与GLUE数据集类似,只不过其输出是四个可能选项的softmax:
$$
P_{i}=\frac{e^{V C_{i}}}{\sum^{4}_{j=1}e^{V C_{i}}}
$$

(c)问答任务

  • SQuAD v1.1:给定一个句子(通常是一个问题)和一段描述文本,输出这个问题的答案,类似于做阅读理解的简答题。如上图(c)表示的,SQuAD的输入是问题和描述文本的句子对。输出是特征向量,通过在描述文本上接一层激活函数为softmax的全连接来获得输出文本的条件概率,全连接的输出节点个数是语料中Token的个数。
    $$
    P_{i}=\frac{e^{S T_{i}}}{\sum_{j}e^{S T_{i}}}
    $$

(d)命名实体识别

  • CoNLL-2003 NER:判断一个句子中的单词是不是Person,Organization,Location,Miscellaneous或者other(无命名实体)。微调CoNLL-2003 NER时将整个句子作为输入,在每个时间片输出一个概率,并通过softmax得到这个Token的实体类别。

四、总结

4.1 优缺点

  • 优点:BERT是截至2018年10月的最新state of the art模型,通过预训练和精调横扫了11项NLP任务,这首先就是最大的优点了。而且它还用的是Transformer,也就是相对rnn更加高效、能捕捉更长距离的依赖。对比起之前的预训练模型,它捕捉到的是真正意义上的bidirectional context信息。
  • 缺点:作者在文中主要提到的就是MLM预训练时的mask问题:
    • [MASK]标记在实际预测中不会出现,训练时用过多[MASK]影响模型表现
    • 每个batch只有15%的token被预测,所以BERT收敛得比left-to-right模型要慢(它们会预测每个token)

4.2 BERT的贡献

  • 使用Transformer的结构将已经走向瓶颈期的Word2Vec带向了一个新的方向,并再一次炒火了《Attention is All you Need》这篇论文;
  • 11个NLP任务的精度大幅提升足以震惊整个深度学习领域
  • 无私的开源了多种语言的源码和模型,具有非常高的商业价值
  • 迁移学习又一次胜利,而且这次是在NLP领域的大胜,狂胜

五、参考