论文地址:BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
官方代码地址:https://github.com/google-research/bert
课程推荐:李宏毅机器学习--self-supervised:BERT
参考:BERT 论文逐段精读【论文精读】 - 哔哩哔哩
目录
1.前言
2.摘要
3 导论
4 结论
5 相关工作
5.1 基于特征的无监督方法
5.2 无监督的微调方法
5.3 监督数据中的迁移学习
6 BERT模型结构和输入输出
6.1BERT框架两个步骤:预训练和微调
6.2模型结构
6.3可学习参数的大小
6.4模型输入输出
input sequence
WordPiece
特殊记号
3种embedding
7 BERT预训练(MLM+NSP)
7.1 Task 1:Masked LM
7.2 Task 2:Next Sentence Prediction(NSP)
7.3 预训练数据和参数
8 BERT微调
微调参数
9 实验
9.1 GLUE数据集(分类)
9.2 SQuAD v1.1(问答)
9.3 SQuAD v2.0(问答)
9.4 SWAG(句子对任务)
10消融试验
10.1 模型各部分的影响
10.2 模型配置的影响
10.3 将BERT作为特征提取器
11 总结
在计算机视觉里面很早就能够在一个大的数据集(比如说ImageNet)上训练出一个CNN模型,用这个模型可以用来处理一大片的机器视觉任务,来提升他们的性能;但是在自然语言处理里面,在BERT出来之前,在NLP领域没有一个这样的深度神经网络模型,还是对每个任务构造自己的神经网络,自己训练。BERT的出现使得我们能够在一个大的数据集上面训练好一个比较深的神经网络,然后应用在很多的NLP任务上面,这样既简化了NLP任务的训练,又提升了它的性能,所以BERT和它之后的一系列工作使得自然语言处理在过去三年中有了质的飞跃。
BERT的主要贡献,是将预训练模式推广到深层双向体系结构,而具体的实现,是通过MLM任务来完成。通过完形填空的自监督训练机制,不需要使用监督语料,而是预测句子中被masked的词是什么,从而抽取文本特征。
由此BERT极大的拓展了Transformers的应用,使其可以在更大的无标签的数据集上训练,其效果比有标签、更小数据集上训练的模型效果还要好。
我们介绍一种新的语言表示模型叫BERT,它是Bidirectional Encoder Representations fromTransformers的缩写(双向Transformer编码器)。
BERT和ELMo、GPT的区别:
从创新的角度来看,bert其实并没有过多的结构方面的创新点。本文将ELMo双向的想法和GPT的transformer架构结合起来就成为了BERT。BERT的主要特征是,对于不同的任务都有一个统一的模型结构,是一个泛化能力较强的预训练模型。
语言模型预训练可以改善许多NLP任务,这些任务包括:
在使用预训练模型做特征表示的时候,一般有两类策略
这两种方法在预训练的过程中使用的是同一个目标函数,即单向的语言模型(给定一些词去预测下一个词是什么东西,说一句话然后预测这句话下面的词是什么东西),限制了预训练表示的能力。比如,在GPT中使用的是一个从左到右的架构(在看句子的时候只能从左看到右),这样的坏处在于如果要做句子层面的分析的话,比如说要判断一个句子层面的情绪是不是对的话,从左看到右和从右看到左都是合法的,另外,就算是词元层面上的一些任务,比如QA的时候也是看完整个句子再去选答案,而不是一个一个往下走。如果将两个方向的信息都放进去的话,应该是能够提升这些任务的性能的。
针对上面提出的问题,作者提出了BERT,使用的是带掩码的语言模型(MLM,masked language model)预训练目标,减轻了先前提到的单向语言模型限制 。
本文的贡献如下:
最近的实验表明,丰富的、无监督的预训练使得即使是资源少(样本少)的任务也可以享受深度神经网络。我们的主要贡献是把前人的工作扩展到深层双向体系结构,使得同样的预训练模型能够成功解决各种NLP任务。
主要是讲词嵌入、ELMo和之后的一些工作,跳过
代表作是GPT
计算机视觉研究证明了从大型预训练模型进行迁移学习的重要性,方法是对通过ImageNet预训练的模型进行微调。在NLP中,已经有标号而且比较大的数据(包括自然语言的推理和机器翻译这两块中都有比较大的数据集),在这些有标号的数据集上训练好了模型,然后在别的任务上使用,效果是非常好的。但是在NLP领域,迁移学习的应用不是特别理想,一方面可能是因为这两个任务跟别的任务差别还是挺大的,另一方面可能是因为数据量还是远远不够的。BERT和他后面的一系列工作证明了在NLP上面使用没有标号的大量数据集训练成的模型效果比在有标号的相对来说小一点的数据集上训练的模型效果更好,同样的想法现在也在慢慢地被计算机视觉采用,即在大量无标签的图片上训练的模型,可能比在ImageNet上训练的模型效果更好。
BERT的模型架构是多层Transformer的Encoder。仅使用了Transformer的EncoderLayer,然后堆叠多层,就是BERT,Transformer基于原始的论文和原始的代码,没有做改动。可以参考诸如The Annotated Transformer之类优秀的指南。
三个参数
两个模型
BERT中的模型复杂度和层数是一个线性关系,和宽度是一个平方的关系
BERT模型可学习参数来自词嵌入层和Transformer块
这两部分加起来就是一个transformer块中的参数,还要乘以L(transformer块的个数),所以总参数的个数就是30K⋅H+L⋅12H2,带入base的参数大概就是1.1亿。
BERT预训练的输入统一称为 input sequence。下游任务有些是处理一个句子,有些任务是处理两个句子,所以为了使BERT模型能够处理所有的任务,input sequence既可以是一个句子,也可以是一个句子对(这里的“句子”指一段连续的文字,不一定是真正的语义上的一段句子)。这和之前文章里的transformer是不一样的:transformer在训练的时候,他的输入是一个序列对,因为它的编码器和解码器分别会输入一个序列,但是BERT只有一个编码器,输入是一个序列,所以为了使它能够处理两个句子,就需要把两个句子变成一个序列
使用的切词的方法是WordPiece,核心思想是:
序列的第一个词永远是一个特殊的记号[CLS](表示classification),CLS对应的最终输出可以代表整个序列的信息,如果是单句子分类,表示输入句子的类别;如果是句子对分类,表示两个句子是相关的/不相关的、相似意思/相反意思。因为自注意力层中每一个词都会去看输出入中所有词,就算是词放在第一的位置,它也是有办法能够看到之后的所有词,所以CLS放在句首是没问题的,不一定非要在句尾。
句子对是合在一起输入的,但是为了做句子级别的任务,所以需要区分这两个句子,综合使用两个办法:
下图中粉色方框表示输入的序列,每一个token进入BERT得到这个token的embedding表示,最后transformer块的输出就表示这个词元的BERT表示,最后再添加额外的输出层来得到想要的结果。
缩写含义如下:
对于给定的token,它的输入表征是由token,segment,和 position embeddings相加构成的。得到的向量序列会进入transformer块。嵌入层结构的可视化效果如图所示。
BERT采用了双向并行输入的方式,即将句子整个输入到模型中,而不是将单词一个接着一个地输入,这样可以充分利用GPU的性能,大大提升模型的运行效率。与此同时由于并行输入会带来单词在文本中的位置信息的丢失,因此BERT模型额外需要增加了一个位置编码输入,确保位置信息不被丢失。Transformer中位置信息是通过位置编码(cos函数)来得到的,而这里的位置信息position embedding和句子信息segment embedding都是通过embedding学出来的。3个embedding的size都是(batch_size, seq_length, hidden_size),最后将3个embeddings按元素值相加,即表示BERT编码层的输入
BERT采用两个无监督任务进行参数预训练。
随机屏蔽每个token序列的15%,将其替换为[MASK](CLS和SEP不做替换),让模型去预测这些空原先的单词。 将mask部分的相应输出向量通过一个Linear transform(矩阵相乘),并做Softmax得到一个分布,使其和MASK字符one-hot vector之间的交叉熵损失最小。本质上是在解决一个分类问题,BERT要做的是预测什么被盖住。
这带来的一个问题是,微调时数据中是没有[MASK]的,于是预训练和微调时看到的数据有点不一样。为了缓解这种情况,我们并不总是用实际的[mask]标记替换“masked”词。
Although this allows us to obtain a bidirectional pre-trained model, a downside is that we are creating a mismatch between pre-training and fine-tuning, since the [MASK] token does not appear during fine-tuning.
解决方法:如果某个token被选中masked,那么有80%的概率是真的将它替换成[MASK],还有10%的概率将它替换成一个随机的词元(噪音),最后有10%的概率维持原单词不变(仍然要做预测)。这个概率是实验跑出来的,效果不错。附录中有例子。
那么为啥要以一定的概率使用随机词呢?这是因为transformer要保持对每个输入token分布式的表征,否则Transformer很可能会记住这个[MASK]就是"hairy"。至于使用随机词带来的负面影响,文章中说了,所有其他的token(即非"hairy"的token)共享15%*10% = 1.5%的概率,其影响是可以忽略不计的。
选择的句子对A和B,B有50%的概率是A的下一个句子(标记为is next),50%的概率是语料库中随机挑选句子(标记为not next),这就意味着有50%的样本是正例,50%的样本是负例。目的是让模型理解到两个句子之间的关系,从而能够应用于问答QA和自然语言推理NLI等下游任务中。
只看CLS的输出,CLS的输出经过和masking input一样的操作,目的是预测第二句是否是第一句的后续句。这是一个二分类问题,有两个可能的输出:是或不是。
尽管NSP很简单,但我们后面可以看到,加入这个目标函数能够极大地提升在QA和自然语言推理的效果(附录中有例子)
# 示例
Input = [CLS] the man went to [MASK] store [SEP]
he bought a gallon [MASK] milk [SEP]
Label = IsNext
Input = [CLS] the man [MASK] to the store [SEP]
penguin [MASK] are flight ##less birds [SEP]
Label = NotNext
在原文中 flightless 是一个词,但是由于这个词出现的概率不高,所以在WordPiece中把它砍成了两个词 flight 和 less ,他们都是比较常见的词,##表示在原文中后面的词跟在前面那个词后面
李宏毅认为:这个方法对于预训练的效果并不是很大,没有学到什么太有用的东西,原因之一可能是,Next Sentence Prediction 太简单了,通常,当我们随机选择一个句子时,它看起来与前一个句子有很大不同,因此对于BERT来说,预测两个句子是否相连并不是太难。有另外一招叫做Sentence order prediction,SOP,预测两个句子谁在前谁在后。也许因为这个任务更难,它似乎更有效
预训练过程很大程度上遵循现存文献的语言模型预训练,我们使用BooksCorpus(800M个单词)和English Wikipedia(25亿个单词)。应该使用文本层面的数据集,即数据集里面是一篇一篇的文章而不是一些随机打乱的句子,因为transformer确实能够处理比较长的序列,所以对于整个文本序列作为数据集效果会更好一些
所有数据和调试参数:
由于Transformer采用的是自注意力机制,使得BERT模型可以应用于各种各样的下游任务。对于句子对任务,常规的做法是在计算交叉注意力之前,先单独对句子对进行编码。而BERT是将这两步合二为一,即使用自注意力机制来对句子进行编码。
微调的过程中,对于下游任务的训练,仍然需要少量的标记数据。对于每个下游任务,只需将特定于任务的输入和输出连接到BERT中,然后端到端微调所有参数。
对于输入是两个句子(A和B)的任务,A和B可以是意思相同的两种表述,可以是假设-前提对,可以是问答系统中的问题-回答对,也可以是文本分类或序列标注的文本对。而在输出侧,token的输出向量用于分词级的分类任务,例如序列标注或问答系统;而[CLS]的输出向量用于分类任务,如蕴涵分析或情感分析等。 不管怎么样都是在最后加一个输出层,然后用一个softnax得到想要的标号。
语句分类
词性标注
跟预训练比微调相对来说比较便宜,所有的结果都可以使用一个TPU跑一个小时就可以了,使用GPU的话多跑几个小时也行
在fine-tune阶段,大部分模型参数与预训练阶段是一样的,只有batch_size, lr, epochs需要调整,推荐参数如下:
在这部分,我们将介绍BERT在前面提到的11项NLP任务中的结果。
GLUE基准是一个有着各种NLU任务的集合,是一个句子层面的任务。BERT把特殊词元[CLS]的输出向量C拿出来,放进学习到的输出层w,之后用softmax就能得到标号,这就变成了一个很正常的多分类问题了。即使用CLS最终输出向量来分类
average表示在所有数据集上的平均值,它表示精度,越高越好。可以发现BERT就算是在base跟GPT可学习参数差不多的情况下,也还是能够有比较大的提升
The Stanford Question Answering Dataset(SQuAD v1.1) 是斯坦福一个包含10w crowd-sourced句子对的数据集。给定一段话,然后问一个问题,答案在给定的那段话中,预测答案在段落中所在的位置(这个片段的开头和结尾),类似于阅读理解。就是对每个词元进行判断,看是不是答案的开头或者答案的结尾
具体来说就是学习开始向量S和结尾向量E,分别对应这个词元是答案开始的概率和答案最后的概率。对第二句话中每个词元,计算Ti和Si的点积,然后对段落中所有词的点积都进行softmax之后,得到这个段中每一个词元是答案开头的概率。结尾概率的计算同理。
微调使用的epoch值是3,学习率是5e-5,batch size是32。这句话对后面误导很大,因为BERT微调时很不稳定,同样的参数训练,方差非常大。所以需要多训练几个epoch。另外作者使用的优化器是不完全版的Adam,训练长的时候是没问题,训练短的话有问题,要改回正常版。
不再细讲
SWAG数据集包括了113K个句子对,给定一个句子,任务是从备选的四个选项中选择最合理的下一句。在SWAG数据集上微调的时候,我们构造四个输入,每一个序列都是由给定的句子(sentenceA)和可能的下一句(sentence B)组成。唯一要引入的特定任务参数是一个向量,这个向量与[CLS] 的输出C进行点积表示为每一个选择的分数,然后用softmax得到概率(和上面差不多)。微调的参数为epoch=3,learning rate=2e-5,batch size=16。试验结果就不贴了。
对这些不同的数据集,BERT基本上只要把这些数据集表示成所要的句子对的形式,最后拿到一个对应的输出然后再加一个输出层就可以了,所以BERT对整个NLP领域的贡献还是非常大的,大量的任务可以用一个相对来说比较简单的架构,不需要改太多的东西就能够完成了
在这一部分,我们对 BERT 的许多方面进行了消融实验,以便更好地理解介绍了BERT中每一块最后对结果的贡献。
从结果来看,去掉任何一部分,结果都会打折扣
探讨模型大小对微调任务准确度的影响。使用不同的层数,隐藏单元,注意力头数训练了一些BERT模型,而其他超参数和训练过程与之前描述的一样。
BERT base中有1亿的可学习参数。BERT large中有3亿可学习的参数。相对于之前的transformer,可学习参数数量的提升还是比较大的。能够看出在所有的数据集上,越大的模型准确率越高。人们都知道,增加模型的大小将带来如机器翻译和语言模型上的持续提升,然而,这是第一个展示将模型变得特别大的时候对语言模型有较大提升的工作(只需要进行微调少量参数)。
BERT由此引发了模型大战,看谁的模型更大。GPT-3都有一千个亿的参数,现在的模型都有往万亿的路上走。
Feature-based方法是从预训练模型中提取一些特定的特征,在一定程度上对模型训练是有性能提升的。首先并不是所有下游任务都可以用Transformer编码器结构来表示的,往往需要根据特定任务增加额外的结构。其次,从计算量的角度,先进行预计算,从训练数据上得到复杂庞大的表示,在此基础上再运行多次小模型试验,其花费是更廉价的。
为了比较feature-based和fine-tune两种方法的效果,从模型的一层或多层中提取出特征,如Embeddings, Last Hidden, Weighted Sum Last Four Hidden等,并输入到一个随机初始化的两层768维度的BiLSTM模型中,最后通过一个分类层输出。结果显示,如果BERT(base)只提取最后4个隐含层的输出并进行拼接,其表现只比BERT(base)进行fine-tune方法低了0.3 F1。由此可以看出,采用feature-based和fine-tune对BERT模型的训练都是有效的。
李沐:将BERT作为特征提取器,而不是进行微调,效果相比之下会差一些。所以使用BERT应该微调。
这篇文章认为本文的最大贡献就是双向性。BERT使用Transformer的编码器而不是解码器,好处是可以训练双向语言模型,在语言理解类的任务上表现比GPT更好,但是也有坏处,做生成类的任务不方便,比如机器翻译、摘要生成等等。只不过NLP领域,分类之类的语言理解任务多一些,所以大家更喜欢用BERT。
BERT所提供的是一个完整的解决问题的思路,符合了大家对于深度学习模型的期望:训练一个3亿参数的模型,在几百GB的数据集上预训练好,通过微调可以提升一大批NLP的下游任务,即使这些任务的数据集都很小
GPT和BERT是同期提出的模型,两者都是预训练模型微调,很多思路是一样的,即使后者效果好一点,但是早晚也会被后来者超越,那为啥BERT更出圈?因为BERT的利用率是GPT的10倍,影响力自然就大了10倍不止。