自然语言处理NLP
本文旨在介绍前沿的自然语言处理技术-Bert。
介绍
2018年Google发布了BERT(来自Transformer的双向自编码器)预训练模型,旨在通过联合左侧和右侧的上下文,从未标记文本中预训练出一个深度双向表示模型。因此,BERT可以通过增加一个额外的输出层来进行微调,就可以达到为广泛的任务创建State-of-the-arts 模型的效果,比如QA、语言推理任务。
当时将预训练模应用于下游任务的策略通常有两种:基于特征的(feature-based)和基于微调(fine-tuning);前者比如ELMo[2],后者比如OpenAI GPT[3];
这两种策略在预训练期间具有相同的目标函数,在预训练期间,它们使用单向语言模型来学习一般的语言表示。但当前对预训练方法的限制(尤其是对基于微调的方法)是标准语言模型是单向(unidirectional)的,所以限制了在预训练阶段可选的模型结构体系。
比如GPT是从左到右的,每个token只能关注到前一个token的self-attention layers。这种局限对于句子级任务(sentence-level tasks)来说还不是很打紧,但是对于token-level tasks(比如QA)就很致命,所以结合两个方向的上下文信息至关重要。

BERT对比这两个算法的优点是,只有BERT表征会基于所有层中的左右两侧语境,而能做到这一点得益于Transformer中Attention机制将任意位置的两个单词的距离转换成了1。
那么BERT具体是如何实现的呢? 我们接着往下看
BERT框架及其详细实现
我们在本节中介绍BERT及其详细实现,训练框架主要由两个步骤构成:预训练和微调。
BERT,基于transformer的双向编码表示,它是一个预训练模型,模型训练时的两个任务是预测句子中被掩盖的词以及判断输入的两个句子是不是上下句。在预训练好的BERT模型后面根据特定任务加上相应的网络,可以完成NLP的下游任务,比如文本分类、机器翻译等。
虽然BERT是基于transformer的,但是它只使用了transformer的encoder部分,它的整体框架是由多层transformer的encoder堆叠而成的。每一层的encoder则是由一层muti-head-attention和一层feed-forword组成,大的模型有24层,每层16个attention,小的模型12层,每层12个attention。每个attention的主要作用是通过目标词与句子中的所有词汇的相关度,对目标词重新编码。所以每个attention的计算包括三个步骤:计算词之间的相关度,对相关度归一化,通过相关度和所有词的编码进行加权求和获取目标词的编码。
在通过attention计算词之间的相关度时,首先通过三个权重矩阵对输入的序列向量(512*768)做线性变换,分别生成query、key和value三个新的序列向量,用每个词的query向量分别和序列中的所有词的key向量做乘积,得到词与词之间的相关度,然后这个相关度再通过softmax进行归一化,归一化后的权重与value加权求和,得到每个词新的编码。
模型输入
在BERT中,输入的向量是由三种不同的embedding求和而成,分别是:
wordpiece embedding:单词本身的向量表示。WordPiece是指将单词划分成一组有限的公共子词单元,能在单词的有效性和字符的灵活性之间取得一个折中的平衡。
position embedding:将单词的位置信息编码成特征向量。因为我们的网络结构没有RNN 或者LSTM,因此我们无法得到序列的位置信息,所以需要构建一个position embedding。构建position embedding有两种方法:BERT是初始化一个position embedding,然后通过训练将其学出来;而Transformer是通过制定规则来构建一个position embedding
segment embedding:用于区分两个句子的向量表示。这个在问答等非对称句子中是用区别的。
BERT模型的输入就是wordpiece token embedding + segment embedding + position embedding,如图所示:
        
        对于每一种向量的具体表现形式,可以参考这篇文章,可视化的给出了BERT中各种embedding的表现:
网络结构
        BERT的主要结构是transformer(如图1所示),一个BERT预训练模型的基础结构是标准transformer结构的encoder部分,一个标准transformer结构如图2所示,其中左边的部分就是BERT中使用的encoder部分。
   
   
Bert网络结构
一个transformer的encoder单元由一个multi-head-Attention + Layer Normalization + feedforword + Layer Normalization 叠加产生,BERT的每一层由一个这样的encoder单元构成。在比较大的BERT模型中,有24层encoder,每层中有16个Attention,词向量的维度是1024。在比较小的BERT模型中,有12层encoder,每层有12个Attention,词向量维度是768。在所有情况下,将feed-forward/filter 的大小设置为 4H(H为词向量的维度),即H = 768时为3072,H = 1024时为4096。
        这种transformer的结构可以使用上下文来预测mask的token,从而捕捉双向关系。
       
  
  
标准的transformer结构(左边是encoder部分)
训练框架

BERT的总体预培训和微调程序。除了输出层,预训练和微调中都使用相同的体系结构。相同的预训练模型参数用于初始化不同下游任务的模型。在微调期间,所有参数都会微调。[CLS]是在每个输入示例前面添加的特殊符号,【SEP】是一个特殊的分隔符令牌(例如,分隔问题/答案)。
- Pre-training预训练:
 
在预训练阶段,BERT用大量的无监督文本通过自监督训练的方式(通过使用受完形填空任务启发的Masked Language Model[4]预训练目标)训练,把文本中包含的语言知识(包括:词法、语法、语义等特征)以参数的形式编码到Transformer-encoder layer中。预训练模型学习到的是文本的通用知识,不依托于某一项NLP任务;(2.4小节展开详述)
- Fine-Tuning微调:
 
NLP 问题被证明同图像一样,可以通过 finetune 在垂直领域取得效果的提升。Bert 模型本身极其依赖计算资源,从 0 训练对大多数开发者都是难以想象的事。在节省资源避免重头开始训练的同时,为更好的拟合垂直领域的语料,我们有了 finetune 的动机。
在微调阶段,BERT首先使用预训练的参数初始化模型,所有参数都使用下游任务的标签数据进行微调,每个不同的下游任务都有单独的微调模型(2.5小节展开详述)
模型架构
BERT的模型体系结构是基于Vaswani等人描述的原始实现的多层双向变压器编码器[5]
关于大名鼎鼎的底座模型Transformer这边就不展开赘述了,详情可参考优秀指南
http://nlp.seas.harvard.edu/2018/04/03/attention.htmlnlp.seas.harvard.edu/2018/04/03/attention.html
首先明确几个概念,在本工作中,我们命名表示层数为L(Transformer Blocks),隐藏层数为H,自注意力头数量为A。我们主要报告的型号尺寸为
- BERT_base(L=12,H=768,A=12; parameters=110M)
 - BERT_large(L=24,H=1024,A=16; parameters=340M)
 
后者的大小和OpenAI GPT是相同的,以便比较效果。
输入/输出表示

如上图所示,BERT模型有两个特殊的token:CLS (用于分类任务)、 SEP(用于断句),以及三个类型的embedding:
- Token embedding:输入的文本经过tokenization之后,将CLS插入tokenization结果的开头,SEP插入到tokenization结果的结尾。然后进行token embedding look up。shape为:[seq_length, embedding_dims]。流程如下图所示:
 

Segment embedding:在NSP任务中,用于区分第一句和第二句。segment embedding中只有 0 和 1两个值,第一句所有的token(包括cls和紧随第一句的sep)的segment embedding的值为0,第二句所有的token(包括紧随第二句的sep)的segment embdding的值为1。shape为:[seq_length, embedding_dims]。流程如下图所示:

Segment Embeddings 层只有两种向量表示。前一个向量是把0赋给第一个句子中的各个token, 后一个向量是把1赋给第二个句子中的各个token。如果输入仅仅只有一个句子,那么它的segment embedding就是全0position Embedding
Transformers无法编码输入的序列的顺序性,加入position embeddings会让BERT理解下面下面这种情况, I think, therefore I am,第一个 “I” 和第二个 “I”应该有着不同的向量表示
BERT能够处理最长512个token的输入序列。论文作者通过让BERT在各个位置上学习一个向量表示来讲序列顺序的信息编码进来。这意味着Position Embeddings layer 实际上就是一个大小为 (512, 768) 的lookup表,表的第一行是代表第一个序列的第一个位置,第二行代表序列的第二个位置,以此类推。因此,如果有这样两个句子“Hello world” 和“Hi there”, “Hello” 和“Hi”会由完全相同的position embeddings,因为他们都是句子的第一个词。同理,“world” 和“there”也会有相同的position embedding
我们已经介绍了长度为n的输入序列将获得的三种不同的向量表示,分别是:
Token Embeddings, (1, n, 768) ,词的向量表示
Segment Embeddings, (1, n, 768),辅助BERT区别句子对中的两个句子的向量表示
Position Embeddings ,(1, n, 768) ,让BERT学习到输入的顺序属性
这些表示会被按元素相加,得到一个大小为(1, n, 768)的合成表示。这一表示就是BERT编码层的输入了
因此,BERT的输入为:
token_embedding + segment_embedding + position_embedding
预训练任务
(1)masked language model
随机掩盖掉一些单词,然后通过上下文预测该单词。BERT中有15%的wordpiece token会被随机掩盖,这15%的token中80%用[MASK]这个token来代替,10%用随机的一个词来替换,10%保持这个词不变。这种设计使得模型具有捕捉上下文关系的能力,同时能够有利于token-level tasks例如序列标注。
Q:为什么选中的15%的wordpiece token不能全部用 [MASK]代替,而要用 10% 的 random token 和 10% 的原 token
[MASK] 是以一种显式的方式告诉模型『这个词我不告诉你,你自己从上下文里猜』,从而防止信息泄露。如果 [MASK] 以外的部分全部都用原 token,模型会学到『如果当前词是 [MASK],就根据其他词的信息推断这个词;如果当前词是一个正常的单词,就直接抄输入』。这样一来,在 finetune 阶段,所有词都是正常单词,模型就照抄所有词,不提取单词间的依赖关系了。
以一定的概率填入 random token,就是让模型时刻堤防着,在任意 token 的位置都需要把当前 token 的信息和上下文推断出的信息相结合。这样一来,在 finetune 阶段的正常句子上,模型也会同时提取这两方面的信息,因为它不知道它所看到的『正常单词』到底有没有被动过手脚的。
Q:最后怎么利用[MASK] token做的预测?
最终的损失函数只计算被mask掉的token的,每个句子里 [MASK] 的个数是不定的。实际代码实现是每个句子有一个 maximum number of predictions,取所有 [MASK] 的位置以及一些 PADDING 位置的向量拿出来做预测(总共凑成 maximum number of predictions 这么多个预测,是定长的),然后再用掩码把 PADDING 盖掉,只计算[MASK]部分的损失。
(2)next sentence prediction
语料中50%的句子,选择其相应的下一句一起形成上下句,作为正样本;其余50%的句子随机选择一句非下一句一起形成上下句,作为负样本。这种设定,有利于sentence-level tasks,例如问答。注意:作者特意说了语料的选取很关键,要选用document-level的而不是sentence-level的,这样可以具备抽象连续长序列特征的能力。
模型训练设置
- pre-train阶段
 
(1)256个句子作为一个batch,每个句子最多512个token。
(2)迭代100万步。
(3)总共训练样本超过33亿。
(4)迭代40个epochs。
(5)用adam学习率, 1 = 0.9, 2 = 0.999。
(6)学习率头一万步保持固定值,之后线性衰减。
(7)L2衰减,衰减参数为0.01。
(8)drop out设置为0.1。
(9)激活函数用GELU代替RELU。
(10)Bert base版本用了16个TPU,Bert large版本用了64个TPU,训练时间4天完成。
(论文定义了两个版本,一个是base版本,一个是large版本。Large版本(L=24, H=1024, A=16, Total Parameters=340M)。base版本( L=12, H=768, A=12, Total Pa- rameters=110M)。L代表网络层数,H代表隐藏层数,A代表self attention head的数量。)
因为序列长度太大(512)会影响训练速度,所以90%的steps都用seq_len=128训练,余下的10%步数训练512长度的输入。
fine-tune 阶段
微调阶段根据不同任务使用不同网络模型。在微调阶段,大部分模型的超参数跟预训练时差不多,除了batchsize,学习率,epochs。
微调参数建议:
Batch size: 16, 32
Learning rate (Adam): 5e-5, 3e-5, 2e-5
Number of epochs: 3, 4
总结
模型特点
(1)使用transformer作为算法的主要框架,transformer能更彻底的捕捉语句中的双向关系;
(2)使用了mask language model 和next sentence prediction的多任务训练目标,是一个自监督的过程,不需要数据的标注;
(3)使用tpu这种强大的机器训练了大规模的预料,是NLP的很多任务达到了全新的高度。
BERT本质上是在海量语料的基础上,通过自监督学习的方法为单词学习一个好的特征表示。该模型的优点是可以根据具体的人物进行微调,或者直接使用预训练的模型作为特征提取器。
可优化空间
(1)如何让模型有捕捉Token序列关系的能力,而不是简单依靠位置嵌入。
(2)模型太大,太耗机器(后续的Albert有做改进)











