论文:BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
参考:BERT论文逐段精读、李沐精读系列、李宏毅版BERT讲解
一、介绍
BERT(Bidirectional EncoderRepresentation Transformer,双向Transformer编码器)。与最近的语言表示模型(ELMo,GPT)不同,BERT的所有层旨在通过联合训练左右上下文的方式从未标注文本中预训练深层的双向表征。
BERT是基于EMLo和GPT(单向的Transformer编码器)的。但是又与它们俩不同,区别如下:
- BERT:设计用来训练深的双向表示,使用没有标号的数据,再联合左右的上下文信息。因为这样的设计导致训练好的BERT只用加一个额外的输出层,就可以在很多NLP的任务(比如问答、语言推理)上面得到一个不错的结果,而且不需要对任务做很多特别的架构上的改动
- GPT:单向Transformer编码器(用左边的上下文信息去预测未来),BERT同时使用了左侧和右侧的信息,它是双向的(Bidirectional)。
- ELMO:一个基于RNN的架构,BERT用的是transformer,所以ELMo在用到一些下游任务的时候需要对架构做一点点调整,但是BERT相对比较简单,和GPT一样只需要改最上层就可以了。
问:BERT实际上是做什么的呢?
其中,上图中的词是代表word,对于中文来说用字更为恰当。潮水是一个词,潮和水是字(character)。
二、导论
自然语言任务包括两类:
- 句子层面的任务(sentence-level):主要是用来建模句子之间的关系,比如说对句子的情绪识别或者两个句子之间的关系。
- 词元层面的任务(token-level):包括实体命名的识别(对每个词识别是不是实体命名,比如说人名、街道名),这些任务需要输出一些细粒度的词元层面上的输出。
在使用预训练模型做特征表示的时候,一般有两类策略:
- 一个策略是基于特征的(feature-based):代表作是ELMo,对每一个下游的任务构造一个跟这个任务相关的神经网络,它使用的RNN的架构,然后将预训练好的这些表示(比如说词嵌入也好,别的东西也好)作为一个额外的特征和输入一起输入进模型中,希望这些特征已经有了比较好的表示,所以导致模型训练起来相对来说比较容易,这也是NLP中使用预训练模型最常用的做法(把学到的特征和输入一起放进去作为一个很好的特征表达)。
- 另一个策略是基于微调的(fine-tuning):这里举的是GPT的例子,把预训练好的模型放在下游任务的时候不需要改变太多,只需要改动一点就可以了。这个模型预训练好的参数会在下游的数据上再进行微调(所有的权重再根据新的数据集进行微调)。
这两种方法在预训练的过程中都使用的是同一个目标函数,它们使用的都是单向的语言模型学习通用的语言表征,限制了预训练表示的能力,尤其是对于fine-tuning方法。 主要的限制是标准语言模型是单向的(语言模型是从前面的词预测后面的词),这样在选架构的时候有一些局限性。例如,在OpenAI GPT中,作者使用从左到右的体系结构,其中每个token只能参加Transformer的自注意层中的先前token。 这样的限制对于句子级任务不是最理想的,并且在将基于fine-tuning的方法应用于token级任务(例如问题回答)时可能非常不利,在这种情况下,双向整合上下文至关重要。
BERT通过使用“遮蔽语言模型”(MLM,Masked Language Model)预训练目标,减轻了先前提到的单向语言模型限制 。(MLM相当于完形填空,左右信息都要看)遮蔽语言模型从输入中随机屏遮蔽了某些token,目的是仅根据其上下文来预测被遮蔽的单词(原始词表对应的id)。 与从左到右的语言模型预训练不同,MLM使语言表征能够融合上下文,这使得我们能够预训练深层双向Transformer模型。 除了遮蔽语言模型外,我们还使用“下一个句子预测”(NSP,Next Sentence Prediction)任务来联合预训练文本对表示(NSP判断两个句子是否相邻,第二句是否是第一句的下一句)。
本文的贡献有三点:
- 证明了双向预训练对于语言表示的重要性。1)与GPT不同,它使用单向语言模型进行预训练,BERT使用遮蔽语言模型来实现预训练的深度双向表示。2)与ELMo不同,后者独立训练的从左到右和从右到左的网络,然后进行简单的浅层连接。(BERT在双向信息的应用上更好)
- 预训练好的语言表征,不需要再去针对特定的任务精心设计模型结构。BERT是第一个基于fine-tuning的表示模型,可在一系列sentence-level和token-level任务上实现最先进的性能,其性能优于许多任务特定的体系结构。
- BERT推动了11项NLP任务的发展。 可以在BERT上找到代码和经过预先训练的模型。
最主要贡献是将这些发现进一步推广到深层双向体系结构,使的同样的预训练模型能够成功解决各种NLP任务。
三、Related work
3.1 基于特征的无监督方法(EMLo)
阐述了讲词嵌入、ELMo等工作。这里我结合了李宏毅的视频稍微展开讲讲ELMo模型。
上图描述的就是单纯RNN的训练机制。但这是从左到右单向的RNN的。下图是双向的RNN。
但是这样就出现了一个问题,同一个词汇由于正向RNN和反向RNN的词嵌入不一样,那我应该选择哪一个词嵌入呢?EMLo全都要!
3.2 无监督的微调方法(GPT)
代表是GPT
3.3 监督数据中的迁移学习
自然语言推理(Conneau et al.,2017)和机器翻译(McCann et al.,2017)有很多大型的有监督数据集,在这些数据集上预训练好语言模型,再迁移到别的NLP任务上,效果是非常好的。计算机视觉研究还证明了从大型预训练模型进行迁移学习的重要性,其中有效的方法是对通过ImageNet预训练的模型进行微调。
四、BERT
4.1 框架
BERT的框架只要包含两个步骤:预训练和微调。
- 预训练是在无标签数据集上训练的
- 微调使用的也是BERT模型,其权重初始化为预训练权重。所有权重在微调时都会参与训练,微调时使用有标签的数据
- 每个下游任务都会创建一个新的BERT模型,来训练自己的任务。
其中,[CLS]是在每个输入示例前添加的特殊符号,[SEP]是特殊的分隔符标记(例如,分隔问题/答案)。
问:为什么[CLS]放在开头,不可以放在结尾嘛?
bert内部transformer的self-attention特色是天涯若比邻,两个相邻的word和两个距离很远的word对self-attention来说是没有差别的。如果不考虑positional encoding的影响,把一个token放在句子的开头或者句子的结尾对bert来说基本上没有影响。
4.1.1 模型结构
BERT的模型架构是多层双向Transformer编码器,基于Vaswani等人描述的原始实现。并且我们的Transformers实现几乎与原始实现相同。
在这项工作中,我们将层(即,Transformer块)的数量表示为L,将隐藏层的大小表示为H,并将自注意力头的数量表示为A。我们主要报告两种模型尺寸的结果:
其中,L:transformer块的个数;H:隐藏层的大小;A:自注意力机制中多头的头的个数 。
怎样把超参数换算成可学习参数的大小?
模型中可学习参数主要来自两块。
- 嵌入层:就是一个矩阵,输入是字典的大小(假设是30k),输出等于隐藏单元的个数(假设是H)
- Transformer块分为两部分:
- 自注意力子层:Q、K、V分别通过一个参数矩阵进行投影,参数矩阵维度是H×H(其实是多头,但是合起来还是原始维度大小。投影前后维度不变,所以矩阵大小必须是H×H)。投影之后输出的多头注意力还会经过一个矩阵将其映射回H维。所以自注意力层一共四个参数矩阵,可学习的参数量是
- MLP层:有两层,输入输出分别是[H,4H]和[4H,H],所以每一层参数是,两层就是
总参数应该是,带入base的参数大概就是1.1亿。
4.1.2 模型的输入输出
为了可以处理多种任务,BERT输入可以是一个句子,也可以是句子对。在整个工作中,“句子”可以是一段连续的文本,而不仅仅真正语义上的一句话。BERT的输入是一个标记序列,可以是一个或两个句子。
Transformer输入是一个句子对,编码器和解码器分别输入一个句子。而BERT只有编码器,要处理两个句子只能将其并为一个序列。
BERT如果使用空格切词,一个词是一个token。BERT的训练数据量是非常大的,那么词表大小也会特别大,比如百万级别。那么根据上面的算法,模型的可学习参数基本都在词嵌入部分。
WordPiece原理就是,一个词在数据集中出现的概率不大,那么就把它切开成子序列。如果子序列(很可能是词根)出现的概率很大的话,那么只保留这个子序列就行。这样就可以把比较长的词切成一些经常出现的片段。这样词表的大小相对来说比较小。
BERT使用3w个词表的WordPiece embeddings。每个输入序列的第一个标记始终是一个特殊的分类标记([CLS],代表classification),BERT希望CLS对应的最终输出可以代表整个序列的信息。(自注意力可以同时看到所有词,所以CLS放在句首是没问题的,不一定非要在句尾)。
句子对可以合在一起输入,但是为了做句子级别的任务,所以需要区分这两个句子,有两个办法:
- 句尾加上特殊词[SEP]来分隔(表示separate)
- 在词嵌入层用一个可学习的向量来表示每个token是属于句子A还是句子B。
- 在下图中,画出了大概示意。我们用E表示输入embedding,用来表示特殊token[CLS]的最终隐向量,用来表示第i个输入token的最终隐向量。
对于给定的token,它的输入表征是由token,segment,和 position embeddings相加构成的。这种结构的可视化效果如下。
Transformer中位置信息是通过位置编码(cos函数)来得到的,而这里的位置信息和句子信息都是通过embedding学出来的。
4.2 BERT预训练(MLM+NSP)
4.2.1 MLM,Masked Language Model
对于一个token序列,我们随机屏蔽了每个序列中15%的WordPiece token,将其替换为[MASK](CLS和SEP不做替换)。 这带来的一个问题是,微调时数据中是没有[MASK]的。预训练和微调时看到的数据有点不一样。为了缓解这种情况,我们并不总是用实际的[mask]标记替换“masked”词。
如果某个token被选中masked,那么有80%的概率这个token真的被替换为[MASK];10%的概率将其替换为一个随机token(噪音),另外10%保持不变(真实数据,但是这个位置也要预测)。
问:BERT怎么填回被替换为[MASK]的词呢?
4.2.2 NSP,Next Sentence Prediction
在QA和自然语言推理中都是句子对,如果让它学习一些句子层面的信息也不错,具体来说,一个输入序列里面有两个句子:a和b,有50的概率b在原文中间真的是在a的后面,还有50%的概率b就是随机从别的地方选取出来的句子,这就意味着有50%的样本是正例(两个句子是相邻的关系),50%的样本是负例(两个句子没有太大的关系),加入这个目标函数能够极大地提升在QA和自然语言推理的效果。
问:BERT怎么判断输入的两个句子是不是相接的关系?
根据Linear Binary Classifier来处理。
利用BERT的四个案例。
案例一:输入一个句子分类。
案例二:输入一个句子,给句子中的每个词进行分类。
案例三:根据输入的前提,判断假设是对的、错的还是无法判断。
案例四:给BERT读一篇文章,问它问题,希望它给出答案(问的问题一定可以在文章中找到)
如果没有s,只有e那么就是此题无解。
4.3 BERT微调
问:BERT和一些基于编码器解码器的架构有什么不同?
- transformer是编码器解码器架构
- 因为把整个句子对都放在一起放进去了,所以自注意力能够在两端之间相互能够看到,但是在编码器解码器这个架构中,编码器一般是看不到解码器的东西的,所以BERT在这一块会更好一点,但是实际上也付出了一定的代价(不能像transformer一样能够做机器翻译)
在做下游任务的时候会根据任务设计任务相关的输入和输出,好处是模型其实不需要做大的改动,主要是怎么样把输入改成所要的那个句子对。
- 如果真的有两个句子的话就是句子a和b
- 如果只有一个句子的话,比如说要做一个句子的分类,b就没有了
然后根据下游的任务要求,要么是拿到第一个词元对应的输出做分类或者是拿到对应的词元的输出做所想要的输出,不管怎么样都是在最后加一个输出层,然后用一个softnax得到想要的标号。
五、结论
BERT使用Transformer的编码器而不是解码器,好处是可以训练双向语言模型,在语言理解类的任务上表现比GPT更好,但缺点是,做生成类的任务不方便,比如机器翻译、摘要生成等等。只不过NLP领域,分类啊之类的语言理解任务多一些,所以大家更喜欢用BERT。(也就是BERT屠榜)