简介
BERT模型来自谷歌团队的paper——BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding,它在11个NLP任务中刷新了成绩,效果非常好,非常惊人。但是,这项工作不是很好复现,如果没有足够的资源就不要想了 。之前,利用预训练的语言表征来完成下游NLP任务的研究,可以概括为两类:feature-based和fine-tuning。feature-based类别比如ELMo,fine-tuning类别比如OpenAI GPT、ULMFiT,ELMo是把表征作为feature提供给下游任务,OpenAI GPT、ULMFiT是fine tune(微调)预训练的参数。这些方法没有很好的利用上下文(context)信息,然而这些context信息对于SQuAD类似的任务非常重要。
上图表示BERT和OpenAI GPT、ELMo模型的区别。
- BERT使用双向Transformer;
- OpenAI GPT,虽然它一样使用了transformer,但是只利用了一个方向的注意力机制,即从左到右的Transformer(如第二个q和第一个k没有算点积,即注意力矩阵为上三角形,那是否说明这并不是严格上的Transformer),本质上是unidirectional(单项)的语言模型;
- ELMo使用经过独立训练的从左到右(正向)和从右到左(反向)LSTM的串联来生成下游任务的特征,本质上仍然是两个unidirectional模型的叠加。
三个模型中,只有BERT表示在所有层中共同依赖于左右上下文。除了体系结构差异之外,BERT和OpenAI GPT是fine-tuning方法,而ELMo是基于feature的方法。
BERT到底是什么呢?BERT,Bidirectional Encoder Representations from Transformers
,简单来说是Transformer的Encoder部分,即当前输入的特征表达(特征编码,特征表示,即词向量表示)。和word2vec一样,不需要用标签训练,有预料就能训练。如何去做?请见【预训练】部分。
原文摘要
本文介绍了一种新的语言表征模型 BERT(Bidirectional Encoder Representations from Transformers),它用Transformer的双向编码器表示。与最近的其他语言表示模型不同,BERT旨在通过联合调节所有层中的上下文来预先训练深度双向表示。因此,预训练的BERT表示可以通过一个额外的输出层进行微调,适用于广泛任务的最先进模型的构建,比如问答任务和语言推理,无需针对具体任务做大幅架构修改。
总体来说,BERT吊打Semi-supervised Sequence Learning, Generative Pre-Training, ELMo, ULMFit,在多个语言任务上(SQuAD, MultiNLI, and MRPC)基于BERT的模型都取得了state of the art(STOA)的效果。
作者提出的贡献(创新)
- 我们证明了双向预训练对于语言表达的重要性。不像Radford et al. (2018)使用单向语言模型进行预训练,BERT使用屏蔽语言模型实现预训练的深度双向表示。这也与 Peters et al. (2018a)的研究结果相反,它使用了从左到右和从右到左的独立训练LMs的浅串联。
- 我们表明,预先训练的表示减少了对许多精心设计的特定任务体系结构的需求。BERT是第一个基于精细调整的表示模型,它在一组大型句子级和令牌级任务上实现了最新的性能,其性能优于许多特定于任务的体系结构。
- BERT advances the state of the art for elevenNLP tasks. The code and pre-trained models are available at https://github.com/google-research/bert.
模型
论文使用了两种模型:
- BERT_BASE: L=12, H=768, A=12, 总参数=110M
- BERT_LARGE: L=24, H=1024, A=16, 总参数=340M
其中,L是layers层数(即 Transformer 块数),H是hidden vector size, A是self-attention的“头(head)数”。在所有实验中,将前馈/滤波器尺寸设置为 4H,即 H=768 时为 3072,H=1024 时为 4096。
为了进行比较,paper中选择BERT_BASE 的模型尺寸与OpenAI GPT具有相同的模型大小。然而BERT Transformer 使用双向self-attention,而GPT Transformer 使用受限制的self-attention,其中每个token只能关注到其左侧的上下文。
在NLP领域,10层以上的layer还是比较“惊人”的,Attention is All You Need第一次提出transformer的时候,在MT任务中用到了6层。当然从结构上来讲,transformers之间用的是residual connection,并且有batch normarlization这种“常规”操作,多层不是什么问题。有意思的是在于这么多层的结构究竟学到了什么?NLP不能和CV作简单的类比,网络层数并不是“多多益善”;有论点认为低层偏向于语法特征学习,高层偏向于语义特征学习。
注意需要的是,在paper中,双向 Transformer 通常称为「Transformer 编码器」,而只关注左侧语境的版本则因能用于文本生成而被称为「Transformer 解码器」
预训练
在BERT中, 主要是以两种预训练的方式来建立语言模型。
MASKED LM
Masked LM(Masked language Model)构建了语言模型, 这是BERT的预训练中任务之一, 简单来说, 就是随机遮盖或替换一句话里面任意字或词, 然后让模型通过上下文的理解预测那一个被遮盖或替换的部分, 之后做的时候只计算被遮盖部分的, 其实是一个很容易理解的任务, 实际操作方式如下:
1、随机把一句话中15%的替换成以下内容:
- 这些有80%的几率被替换成[];
- 有10%的几率被替换成任意一个其他的;
- 有10%的几率原封不动.
2、之后让模型预测和还原被遮盖掉或替换掉的部分
- 模型最终输出的隐藏层的计算结果的维度是:
_ℎ: [ℎ_, _, _]
- 我们初始化一个映射层的权重_:
_:[_, _]
- 用_完成隐藏维度到字向量数量的映射,
只要求ℎ和的矩阵乘(点积):
ℎ:[ℎ_, _, _]
- 之后把上面的计算结果在_(最后一个)维度做归一化,每个字对应的_的和为1,我们就可以通过_里概率最大的字来得到模型的预测结果,就可以和我们准备好的做损失()并反传梯度了.
注意做损失的时候, 只计算在第1步里当句中随机遮盖或替换的部分, 其余部分不做损失, 对于其他部分, 模型输出什么东西, 我们不在意.
Next Sentence Prediction
- 首先我们拿到属于上下文的一对句子, 也就是两个句子, 之后我们要在这两段连续的句子里面加一些特殊 t o k e n token token:
[ c l s ] [cls] [cls]上一句话, [ s e p ] [sep] [sep]下一句话. [ s e p ] [sep] [sep]
也就是在句子开头加一个 [ c l s ] [cls] [cls], 在两句话之中和句末加 [ s e p ] [sep] [sep], 具体地就像下图一样:
- 我们看到上图中两句话是 [ c l s ] [cls] [cls] my dog is cute [ s e p ] [sep] [sep] he likes playing [ s e p ] [sep] [sep], [ c l s ] [cls] [cls]我的狗很可爱 [ s e p ] [sep] [sep]他喜欢玩耍 [ s e p ] [sep] [sep], 除此之外, 我们还要准备同样格式的两句话, 但他们不属于上下文关系的情况;
[ c l s ] [cls] [cls]我的狗很可爱 [ s e p ] [sep] [sep]企鹅不擅长飞行 [ s e p ] [sep] [sep], 可见这属于上下句不属于上下文关系的情况;
在实际的训练中, 我们让上面两种情况出现的比例为 1 : 1 1:1 1:1, 也就是一半的时间输出的文本属于上下文关系, 一半时间不是.
- 我们进行完上述步骤之后, 还要随机初始化一个可训练的 s e g m e n t e m b e d d i n g s segment \ embeddings segment embeddings, 见上图中, 作用就是用 e m b e d d i n g s embeddings embeddings的信息让模型分开上下句, 我们一把给上句全 0 0 0的 t o k e n token token, 下句啊全 1 1 1的 t o k e n token token, 让模型得以判断上下句的起止位置, 例如:
[ c l s ] [cls] [cls]我的狗很可爱 [ s e p ] [sep] [sep]企鹅不擅长飞行 [ s e p ] [sep] [sep]
0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 \quad \ 0 \ \ 0 \ \ 0 \ \ 0 \ \ 0 \ \ 0 \ \ 0 \ \ \ 1 \ \ 1 \ \ 1 \ \ 1 \ \ 1 \ \ 1 \ \ 1 \ \ 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
上面 0 0 0和 1 1 1就是 s e g m e n t e m b e d d i n g s segment \ embeddings segment embeddings.
- 还记得我们上节课说过的, 注意力机制就是, 让每句话中的每一个字对应的那一条向量里, 都融入这句话所有字的信息, 那么我们在最终隐藏层的计算结果里, 只要取出 [ c l s ] t o k e n [cls]token [cls]token所对应的一条向量, 里面就含有整个句子的信息, 因为我们期望这个句子里面所有信息都会往 [ c l s ] t o k e n [cls]token [cls]token所对应的一条向量里汇总:
模型最终输出的隐藏层的计算结果的维度是:
我们 X h i d d e n : [ b a t c h _ s i z e , s e q _ l e n , e m b e d d i n g _ d i m ] X_{hidden}: [batch\_size, \ seq\_len, \ embedding\_dim] Xhidden:[batch_size, seq_len, embedding_dim]
我们要取出 [ c l s ] t o k e n [cls]token [cls]token所对应的一条向量, [ c l s ] [cls] [cls]对应着 s e q _ l e n \ seq\_len seq_len维度的第 0 0 0条:
c l s _ v e c t o r = X h i d d e n [ : , 0 , : ] cls\_vector = X_{hidden}[:, \ 0, \ :] cls_vector=Xhidden[:, 0, :]
c l s _ v e c t o r ∈ R b a t c h _ s i z e , e m b e d d i n g _ d i m cls\_vector \in \mathbb{R}^{batch\_size, \ embedding\_dim} cls_vector∈Rbatch_size, embedding_dim
之后我们再初始化一个权重, 完成从 e m b e d d i n g _ d i m embedding\_dim embedding_dim维度到 1 1 1的映射, 也就是逻辑回归, 之后用 s i g m o i d sigmoid sigmoid函数激活, 就得到了而分类问题的推断.
我们用 y ^ \hat{y} y^来表示模型的输出的推断, 他的值介于 ( 0 , 1 ) (0, \ 1) (0, 1)之间:
y ^ = s i g m o i d ( L i n e a r ( c l s _ v e c t o r ) ) y ^ ∈ ( 0 , 1 ) \hat{y} = sigmoid(Linear(cls\_vector)) \quad \hat{y} \in (0, \ 1) y^=sigmoid(Linear(cls_vector))y^∈(0, 1)
至此 B E R T BERT BERT的训练方法就讲完了, 是不是很简单, 下面我们来为 B E R T BERT BERT的预训练准备数据.
上面两种预训练方式总结为:
- 采取新的预训练的目标函数:the “masked language model” (MLM)随机mask输入中的一些tokens,然后在预训练中对它们进行预测。这样做的好处是学习到的表征能够融合两个方向上的context。这个做法我觉得非常像skip-gram。过去的同类算法在这里有所欠缺,比如上文提到的ELMo,它用的是两个单向的LSTM然后把结果拼接起来;还有OpenAI GPT,虽然它一样使用了Transformer,但是只利用了一个方向的注意力机制,本质上也一样是单项的语言模型。
- 增加句子级别的任务:“next sentence prediction”作者认为很多NLP任务比如QA和NLI都需要对两个句子之间关系的理解,而语言模型不能很好的直接产生这种理解。为了理解句子关系,作者同时pre-train了一个“next sentence prediction”任务。具体做法是随机替换一些句子,然后利用上一句进行IsNext/NotNext的预测。
参考:
BERT介绍
BERT模型介绍
如何评价 BERT 模型?
推荐阅读:
从Word Embedding到Bert模型—自然语言处理中的预训练技术发展史