BERT的前世今生

Alt text

Description

BERT是一种预训练语言表示的方法,在大量文本语料(维基百科)上训练了一个通用的“语言理解”模型,然后用这个模型去执行想做的NLP任务。BERT比之前的方法表现更出色,因为它是第一个用在预训练NLP上的无监督的、深度双向系统。

无监督意味着BERT只需要用纯文本语料来训练,这点非常重要,因为海量的文本语料可以在各种语言的网络的公开得到。

Transformer

Transformer来自论文: All Attention Is You Need

别人的总结资源:

  1. 谷歌官方AI博客: Transformer: A Novel Neural Network Architecture for Language Understanding
  2. Attention机制详解(二)——Self-Attention与Transformer谷歌软件工程师
  3. 放弃幻想,全面拥抱Transformer:自然语言处理三大特征抽取器(CNN/RNN/TF)比较中科院软件所 · 自然语言处理 /搜索 10年工作经验的博士(阿里,微博);
  4. Calvo的博客:Dissecting BERT Part 1: The Encoder,尽管说是解析Bert,但是因为Bert的Encoder就是Transformer,所以其实它是在解析Transformer,里面举的例子很好;
  5. 再然后可以进阶一下,参考哈佛大学NLP研究组写的“The Annotated Transformer. ”,代码原理双管齐下,讲得也很清楚。
  6. 《Attention is All You Need》浅读(简介+代码)这个总结的角度也很棒。

A High-Level Look

可以将输入的语言序列转换成另外一种序列,比如下图的神经机器翻译:

Ze7v1H.png

Transformer模型由编码器-解码器组合组成,解码器负责对序列进行编码,提取时间和空间信息,解码器负责利用时间和空间特征信息进行上下文预测,下图是单个结构:

Ze7qAK.png

编码器和解码器堆栈的组合结构,在谷歌的实验结构中采用了6个编码器和6解码器相对应,使模型的编码能力和解码能力达到一个平衡状态(堆栈式结构):

Ze7LtO.png

编码器-解码器的内部结构,类似seq2seq模型:

Ze7H76.png

seq2seq模型:

ZeqWh8.png

Encoder: 由6个相同的层组成, 每层包含两个sub-layers.第一个sub-layer就是multi-head attention layer,然后是一个简单的全连接层。其中每个sub-layer都加了residual connection(残差连接)和normalisation(归一化)。

Decoder: 由6个相同的层组成,这里的layer包含三个sub-layers, 第一个sub-layer 是masked multi-head attention layer。这里有个特别点就是masked, 作用就是防止在训练的时候,使用未来的输出的单词。比如训练时,第一个单词是不能参考第二个单词的生成结果的。Masked是在点乘attention操作中加了一个mask的操作,这个操作是保证softmax操作之后不会将非法的values连到attention中,提高泛化性。

Ze7zjA.png

解码器的Attention:

  • decoder第一级自注意力的key, query, value均来自前一层decoder的输出,但加入了Mask操作,即我们只能attend到前面已经翻译过的输出的词语,因为翻译过程我们当前还并不知道下一个输出词语,这是我们之后才会推测到的。
  • decoder第二级注意力也被称作encoder-decoder attention layer,即它的query来自于之前一级的decoder层的输出,但其key和value来自于encoder的输出,这使得decoder的每一个位置都可以attend到输入序列的每一个位置。
  • 总结一下,k和v的来源总是相同的,q在encoder及decoder自注意力层中与k,v来源相同,在encoder-decoder attention layer中与k,v来源不同。

编码器与解码器的连接:

  • 编码器通过处理输入序列开启工作。顶端编码器的输出之后会变转化为一个包含向量K(键向量)和V(值向量)的注意力向量集 。这些向量将被每个解码器用于自身的“编码-解码注意力层”,而这些层可以帮助解码器关注输入序列哪些位置合适。

Self-Attention at a High Level

假设下面的句子就是我们需要翻译的输入句:

”The animal didn’t cross the street because it was too tired”

当模型处理单词的时候,self attention层可以通过当前单词去查看其输入序列中的其他单词,以此来寻找编码这个单词更好的线索。

Ze7xcd.png

Self-Attention in Detail

第一步是将输入的嵌入词向量通过三个不同的参数矩阵得到三个向量,分别是一个Query向量,一个Key向量和一个Value向量,参数矩阵分别为Wq,Wk,Wv,,如下图所示:

Ze7OhD.png

第二步是通过当前词的q向量与其他词的k向量计算当前词相对于其他词的得分,分数采用点积进行计算,如下图所示:

Ze7j9e.png

第三步和第四步是讲得到的分数除以k值维数的平方根(k值维数为64,可以使训练过程有更加稳定的梯度,这个归一化的值是经验所得),再通过softmax得到每个得分的标准化得分:

ZeHZcj.png

第五步是对当前词所得到的标准化值对所有value向量进行加权求和得到当前词的attention向量,这样就使不同单词的嵌入向量有了attention的参与,从而预测上下文句子的时候体现不同的重要的重要程度。

ZeHpnI.png

Matrix Calculation of Self-Attention

  • Attendtion向量计算的矩阵形式,通过全职矩阵进行词向量的计算大大加快了神经网络的速度

  • X矩阵中的每一行对应于输入句子中的一个单词。(图中的4个方框论文中为512个)和q / k / v向量(图中的3个方框论文中为64个)

ZeHiAf.png

公式中浓缩前面步骤2到5来计算self attention层的输出。

ZeH9Bt.png

The Beast With Many Heads

使用“Multi-headed”的机制来进一步完善self-attention层。“Multi-headed”主要通过两个方面改善了Attention层的性能,参数组成和子空间映射:

ZeHV3Q.png

Many Heads的优缺点:

  • 它拓展了模型关注不同位置的能力。Multi head 的每个参数矩阵都会记录单词的位置信息,使原来的单个位置信息变得更加复杂。

  • 它为attention层提供了多个“representation subspaces”。由下图可以看到,在self attention中,我们有多个个Query / Key / Value权重矩阵(Transformer使用8个attention heads),使特征的提取变得更加复杂,而不是作为一个整体的特征进行,每个单独的子空间都会进行上下文的信息融合

在8个不同的子空间进行self-attention的操作,每个单词生成独立的8个向量

ZeHCHP.png

将8个子空间生成的向量压缩成一个大向量,每个向量的子空间矩阵能够学习到更多细节,压缩过程采用一个更大的参数矩阵进行,对multi-head向量进行组合,生成最终的特征向量。

ZeHE9g.png

整体的框图来表示一下计算的过程:

ZeHFN8.png

Representing The Order of The Sequence Using Positional Encoding

其实上面介绍的网络里面并没有考虑序列的位置信息,在RNN中不同时刻的信息是通过递归网络的时间t来刻画的,有明显的时间刻度,所以引入了位置向量来解决时间刻度问题。

ZeHk4S.png

为了让模型捕捉到单词的顺序信息,添加位置编码向量信息(POSITIONAL ENCODING),位置编码向量不需要训练,它有一个规则的产生方式,生成与词嵌入向量有着相同的向量就可以。

ZeHKH0.png

通过构造函数sin、cos来对位置进行嵌入,pos为单词位置信息,而i用来表达dimension 这里为了好说明,如果2i= dmodel, PE 的函数就是sin(pos/10000)。这样的sin, cos的函数是可以通过线性关系互相表达的,通过两个函数对奇偶维度进行编码。位置编码的公式如下图所示:

个人认为选择正余弦函数主要是在-1和1之间是一个对称关系,两个相邻的维度编码相差比较大,在位置上有更好的区分性,1000是序列的长度,一般尽量将取值范围控制在四分一个周期里面,这样会使每一个序列的每一个维度都取唯一的值。

ZeHlNT.png

The Residuals

编码器和解码器里面的每一层都采用残差的思想进行训练,目的就是为了解决网络过深情况下的难训练问题,残差连接可以将目标值问题转化成零值问题,一定程度也可以减少网络的过拟合问题。

ZeHejs.png

使用残差连接的编码器内部结构:

ZeHuBq.png

使用残差连接的编码器-解码器内部结构:

ZeHQEV.png

The Decoder Side

通过自回归方式进行预测,解码器每一个时间步输入一个单词,然后输出一个单词,将预测的单词作为下一时刻的输入进行单词的预测,直到预测结束。

ZeHB4O.gif

The Final Linear and Softmax Layer

  • 线性层是一个简单的全连接神经网络,模型一次生成一个输出,我们可以假设模型从该概率分布中选择具有最高概率的单词并丢弃其余的单词。

  • 对于最终句子的生成有2个方法:一个是贪婪算法(greedy decoding),一个是波束搜索(beam search)。

ZeH14U.png

  • 线性变换:解码器最终会输出一个实数向量。解码器输出后的线性变换层是一个简单的全连接神经网络,它可以把解码组件产生的向量投射到一个比它大得多的(字典维度),被称作对数几率(logits)的向量里。不妨假设我们的模型从训练集中学习一万个不同的英语单词(我们模型的“输出词表”)。因此对数几率向量为一万个单元格长度的向量,其中每个单元格对应某一个单词的分数。
  • softmax层:Softmax 层便会把那些分数变成概率(都为正数、和为1)。概率最高的单元格被选中,并且它对应的单词被作为这个时间步的输出。

Bidirectional Encoder Representation from Transformers(BERT)

Word Embedding

  • 线性模型,主要是对高维空间进行映射,其实是对one-hot向量的空间转换。

  • 通过神经网络对输入的词进行映射,获取词向量,一般有cbow和skip-gram两种方法,此方法训练的词向量与上下文无关,并没有参考位置信息,只是对词的有无进行参考,采用的是负采样,预测的时候进行的是一个二分类器,模型认为只要在下文中找出正确的词就认为是完成了任务。

ZeH8CF.png

尚未解决一词多义等问题。比如多义词Bank,有两个常用含义,但是Word Embedding在对bank这个单词进行编码的时候,是区分不开这两个含义的,因为它们尽管上下文环境中出现的单词不同,但是在用语言模型训练的时候,不论什么上下文的句子经过word2vec,都是预测相同的单词bank,而同一个单词占的是同一行的参数空间,这导致两种不同的上下文信息都会编码到相同的word embedding空间里去。所以word embedding无法区分多义词的不同语义,这就是它的一个比较严重的问题。

ZeHG34.png

Embedding from Language Models(ELMO)

  • ElMO采用双向的LSTM做上下文相关的任务,从前到后和后到前分别做一遍LSTM的encoding操作,从而获得两个方向的token联系。

  • Word Embedding本质上是个静态的方式,所谓静态指的是训练好之后每个单词的表达就固定住了,以后使用的时候,不论新句子上下文单词是什么,这个单词的Word Embedding不会跟着上下文场景的变化而改变,所以对于比如Bank这个词,它事先学好的Word Embedding中混合了几种语义 ,在应用中来了个新句子,即使从上下文中(比如句子包含money等词)明显可以看出它代表的是“银行”的含义,但是对应的Word Embedding内容也不会变,它还是混合了多种语义。

ZeHYv9.png

ELMO的本质思想是:

事先用语言模型学好一个单词的Word Embedding,此时多义词无法区分,不过这没关系。在我实际使用Word Embedding的时候,单词已经具备了特定的上下文了,这个时候我可以根据上下文单词的语义去调整单词的Word Embedding表示,这样经过调整后的Word Embedding更能表达在这个上下文中的具体含义,自然也就解决了多义词的问题了。所以ELMO本身是个根据当前上下文对Word Embedding动态调整的思路。

ZeHJgJ.png

一样的,在具体进行下游任务的时候,采用神经网络参数微调的方法根据不同的词的上下文环境对词向量进行调整,从而得到同一词的不同向量表示。

缺点:

  • LSTM的抽取能力远远落后于Transformer,主要是并行计算能力

  • 拼接方式融合双向特征能力偏弱

Bidirectional Encoder Representation from Transformers Information

BERT的模型架构

mdArwQ.png

Google在论文中提到了两个不同模型规模的BERT:

  • BERT BASE –和OpenAI Transformer模型的规模差不多,方便与其进行性能比较
  • BERT LARGE – 一个达到目前多个benchmark的SOTA的巨大的模型

BERT基本上就是一个训练好的Transformer编码器栈。关于Transformer的内容可以看看 图解Transformer这篇博文。

mdA6Fs.png

两种规模的BERT模型都有许多编码器层 (在论文中称为“Transformer块”) – BERT Base有12个这样的结构,BERT Large有24个。编码器中也有前馈网络 (BERT Base中的是768个隐层神经元,BERT Large中的是1024个隐层神经元), 以及注意力层中使用了比Transformer那篇论文中更多的“头” (BERT Base有12个“头”,BERT Large中有16个)。

BRET采用两阶段模型,首先是语言模型预训练;其次是使用Fine-Tuning模式解决下游任务。在预训练阶段采用了类似ELMO的双向语言模型,双向指的是对于预测单词的上文和下文是否参与,如果都参与预测那么就是双向,双向容易导致自己看自己的问题,后面提出mask来解决

ZeHNuR.png

经过预训练的BRET模型,其已经具备了丰富的词向量特征信息,然后将此词向量信息与下游任务进行组合进行NLP下游任务,例如文本生成,文本分类。

ZeHaHx.png

如何能够更好将BRET模型与下游任务进行改造是一个比较复杂的问题,再好的预训练语言模型都要与下游的任务模型相结合才有好的效果, BRET的优势在于可以自由根据预训练模型进行单词级别的任务和句子级的任务。

ZeHrCD.png

BRET模型的输入

mdELNj.png
输入序列的第一个token是一个特殊的符号[CLS],这里的CLS代表class。

就像Transformer的编码器一样,BERT以一串单词作为输入,这些单词不断地想编码器栈上层流动。每一层都要经过自注意力层和前馈网络,然后在将其交给下一个编码器。

mdEO4s.png
在体系结构方面,到目前为止,还是与Transformer是相同的(除了一些超参数之外)。接下来在输出端,我们会看到其和Transformer的不同之处。

BRET模型的输出

每个位置对应地输出一个维度为hidden_size(BERT Base中为768)的向量。对于之前提到的句子分类的例子,我们只关注第一个位置的输出(也就是被我们用[CLS]符号代替的位置)。
mdEjCn.png

输出的这个向量现在可以用作我们选择的分类器的输入。论文利用一个单层神经网络作为分类器,就能取得较好的分类效果。

mdEv3q.png
如果你有更多的标签(例如,如果你是一个电子邮件服务提供商,你需要将电子邮件标记为“垃圾邮件”、“非垃圾邮件”、“社交”和“促销”等等),你只需调整分类器这部分的网络,使其具有更多的输出神经元,然后通过softmax。

与卷积网络并行

对于有CV背景的人来说,这种向量传递应该让人想起像VGGNet这样的网络的卷积部分和网络结构最后的全连接层之间发生的事情。
mdExg0.png

BERT在下游任务中使用迁移学习

既然OpenAI Transformer已经经过了预训练,而且它的各个层也经过了调整,我们就可以开始在下游任务中使用它了。让我们先来看看句子分类(将邮件信息分为“垃圾邮件”或“非垃圾邮件”):
mdMYFO.png

OpenAI的论文列出了许多用于处理不同类型任务输入的输入变换。下图显示了模型的结构和执行不同任务时的输入变换。
mdMdld.png

BERT:从解码器到编码器(下游任务)

openAI Transformer为我们提供了一个基于Transformer的可微调的预训练的模型。但是把LSTM换成Transformer还是让有些东西丢失了。ELMo的语言模型是双向的,而openAI Transformer则只训练一个从左到右的语言模型。那么我们能否建立一个既能从左到右预测又能从右到左预测(同时受上、下文的制约)的基于Transformer的模型呢?

MLM语言模型

“我们将使用Transformer编码器”,BERT说。

“这太疯狂了”,有人说,“每个人都知道双向条件作用会让每个词在多层次的语境中间接地看到自己。”

“我们将使用掩码”,BERT自信地说。

mdQEjA.png
找到合适的任务来训练Transformer的编码器栈是一个复杂的问题,BERT采用了早期文献(完形填空任务)中的“带掩码的语言模型”概念来解决这个问题。

除了屏蔽15%的输入,BERT还混入一些东西,以改进模型的微调方式。有时它会随机地将一个单词替换成另一个单词,并让模型预测该位置的正确单词。

两个句子的任务

如果你还记得OpenAI Transformer处理不同任务时所做的输入变换,你会注意到一些任务需要模型处理关于两个句子的信息(例如,一个句子是否是另一个句子的复述;再例如假设一个维基百科条目作为输入,一个关于这个条目的问题作为另一个输入,我们能回答这个问题吗?)

为了让BERT更好地处理多个句子之间的关系,预训练的过程还有一个额外的任务:给定两个句子(A和B), B可能是接在A后面出现的句子吗?

mdMNfe.png

用于特征提取的BERT

微调的方法并不是使用BERT的唯一方法。就像ELMo一样,你也可以使用预训练好的BERT来创建语境化的词嵌入。然后,您可以将这些嵌入表示喂给现有的模型——论文中也提到,在NER这类任务中,这种用法的最终效果也没有比用微调的方法的结果差很多。

mdMaSH.png
哪种向量作为语境化嵌入的效果最好?我认为这取决于具体任务。论文比较了6中选择(与微调后的96.4分模型相比):
mdMtYD.png

BRET模型的创新

就是论文中指出的Masked 语言模型和Next Sentence Prediction。而Masked语言模型上面讲了,本质思想其实是CBOW,但是细节方面有改进。

Masked 语言模型:

  • 而Masked语言模型上面讲了,本质思想其实是CBOW,但是细节方面有改进,掩盖的同时,要输出掩盖的词的位置,然后用真实词来预测。
  • Mask LM主要是为了增加模型的鲁棒性和实际性能,但是在训练时使用mask过多会影响实际任务的表现,所以做了一些处理:随机选择语料中15%的单词,把它抠掉,也就是用[Mask]掩码代替原始单词,然后要求模型去正确预测被抠掉的单词。但是这里有个问题:训练过程大量看到[mask]标记,但是真正后面用的时候是不会有这个标记的,这会引导模型认为输出是针对[mask]这个标记的,但是实际使用又见不到这个标记,这自然会有问题。为了避免这个问题, BRET改造了一下,15%的被选中要执行[mask]替身这项光荣任务的单词中,只有80%真正被替换成[mask]标记,10%被狸猫换太子随机替换成另外一个单词,10%情况这个单词还待在原地不做改动。这就是Masked双向语音模型的具体做法。

ZeHUD1.png

Next Sentence Prediction:

  • 指的是做语言模型预训练的时候,分两种情况选择两个句子,一种是选择语料中真正顺序相连的两个句子;另外一种是第二个句子从语料库中抛色子,随机选择一个拼到第一个句子后面。
  • 我们要求模型除了做上述的Masked语言模型任务外,附带再做个句子关系预测,判断第二个句子是不是真的是第一个句子的后续句子。之所以这么做,是考虑到很多NLP任务是句子关系判断任务,单词预测粒度的训练到不了句子关系这个层级,增加这个任务有助于下游句子关系判断任务。所以可以看到,它的预训练是个多任务过程。这也是BRET的一个创新,一般用于句级任务。

ZeH0UK.png

ZeHwE6.png

Transformer&BERT总结

  • 首先是两阶段模型,第一阶段双向语言模型预训练,这里注意要用双向而不是单向,第二阶段采用具体任务Fine-tuning或者做特征集成;

  • 第二是特征抽取要用Transformer作为特征提取器而不是RNN或者CNN;

  • 第三,双向语言模型可以采取CBOW的方法去做(当然我觉得这个是个细节问题,不算太关键,前两个因素比较关键)。 BRET最大的亮点在于效果好及普适性强,几乎所有NLP任务都可以套用BRET这种两阶段解决思路,而且效果应该会有明显提升。可以预见的是,未来一段时间在NLP应用领域,Transformer将占据主导地位,而且这种两阶段预训练方法也会主导各种应用。