十四、十五、自然语言处理 NLP
BERT
NLP 里的迁移学习
在计算机视觉中比较流行,将 ImageNet 或者更大的数据集上预训练好的模型应用到其他任务中,比如小数据的预测、图片分类或者是目标检测
1、使用预训练好的模型(例如 word2vec 或语言模型)来抽取词、句子的特征
2、做迁移学习的时候,一般不更新预训练好的模型
3、在更换任务之后,还是需要构建新的网络来抓取新任务需要的信息
- 使用预训练好的模型来抽取特征的时候,一般得到的是一些比较底层的特征,很多时候只是当成一个 embedding 层来使用,还是需要设计一个比较复杂的模型
- word2vec 忽略了时序信息
- 语言模型只看一个方向,而且训练的模型不是很大(RNN 处理不了很长的序列,因为它只能看到很短的一部分)
BERT
BERT 结合了 ELMo 对上下文进行双向编码以及 GPT 任务无关这两方面的优点,对上下文进行双向编码,并且对于大多数的自然语言处理任务只需要最少的架构改变
- 通过将整个序列作为输入,ELMo 是为输入序列中的每一个单词分配一个表示的函数(ELMo 将来自预训练的双向长短期记忆网络的所有中间层表示组合为输出表示,ELMo 的表示将作为附加特征添加到下游任务的现有监督模型中)
- 在加入 ELMo 表示之后,冻结了预训练的双向 LSTM 模型中的所有权重;现有的监督模型是专门为给定的任务定制的(为每一个自然语言处理任务设计一个特定的架构实际上并不是一件容易的事情),利用不同任务的不同最佳模型,添加 ELMo 改进了六种自然语言处理任务的技术水平:情感分析、自然语言推断、语义角色标注、共指消解、命名实体识别和回答
- GPT (Generative Pre Training ,生成式预训练)模型为上下文的敏感表示设计了通用的任务无关模型,它在 Transformer 解码器的基础上,预训练了一个用于表示文本序列的语言模型,当将 GPT 应用于下游任务时,语言模型的输出被送到一个附加的线性输出层,以预测任务的标签
- 与 ELMo 冻结预训练模型的参数不同,GPT 在下游任务的监督学习过程中对预训练 Transformer 解码器中的所有参数进行微调,GPT 在自然语言推断、问答、句子相似性和分类等12项任务上进行了评估,并在对模型架构进行最小更改的情况下改善了其中9项任务的最新水平
- ELMo 对上下文进行双向编码,但使用特定于任务的架构;GPT 是任务无关的,但是从左到右编码上下文(由于语言模型的自回归特性,GPT 只能向前看(从左到右))
- 在下游任务的监督学习过程中,BERT 在两方面与GPT相似:BERT 表示将被输入到一个添加的输出层中,根据任务的性质对模型架构进行最小的更改(例如预测每个词元与预测整个序列);BERT 对预训练 Transformer 编码器的所有参数进行微调,而额外的输出层将从头开始训练
- BERT 进一步改进了 11 种自然语言处理任务的技术水平,这些任务分为以下几个大类:单一文本分类(如情感分析)、文本对分类(如自然语言推断)、问答、文本标记(如命名实体识别)
3、原始的 BERT 有两个版本,其中基本模型有 1.1 亿个参数,大模型有 3.4 亿个参数
4、最初的 BERT 模型是在两个庞大的图书馆语料库和英语维基百科的合集上预训练的
5、现成的预训练 BERT 模型可能不适合医学等特定领域的应用
6、在预训练 BERT 之后,可以用它来表示单个文本、文本对或其中的任何词元
7、BERT 表示是上下文敏感的,同一个词元在不同的上下文中具有不同的 BERT 表示
- 上下文敏感:同一个词可以根据上下文被赋予不同的表示(词的表征取决于它们的上下文)
BERT 的动机
1、基于微调的 NLP 模型
2、预训练的模型抽取了足够多的信息
3、新的任务只需要增加一个简单的输出层
做微调的时候,特征抽取的层是可以复用的(也可以应用到别的任务上面去),只需要修改分类器就可以了
预训练的模型抽取了足够多的信息,使得 feature 已经足够好能够抓住很多的信息,所以在做新的任务的时候,只需要增加一个输出层就可以了
BERT 架构
只有编码器的 Transformer
BERT 在原始的论文中提供了两个原始的版本(原始 BERT 模型中,输入序列最大长度是 512):
- Base:#blocks=12,hidden size=768,#heads=12,#parameters=110M
- Large:#blocks=24,hidden size=1024,#heads=16,#parameters=340M
在大规模数据上训练 > 3B 词
对输入的修改
1、每个样本是一个句子对
- 从源句子到目标句子
- 翻译的时候,源句子进的是编码器,目标句子进的是解码器,而现在只有一个编码器。因为 NLP 中很多情况下都是两个句子,比如说 Q&A 都是两个句子,一个句子进去,一个句子出来。在 BERT 中,将两个句子拼接起来,然后放进编码器,因此每个样本就是一个句子对
2、加入额外的片段嵌入
上图中的“this movie is great”和“i like it”两个句子是如何放进去的
- 首先在句首加了一个特殊的分类标签<cls>(class),作为句子对的开头( BERT 输入序列明确地表示单个文本和文本对)
- 然后在两个句子至今之间使用了一个特殊的分隔符<sep>(separate),将两个句子分开(第二个句子末尾也使用了一个分隔符)
- 也可以做得更长,将更多的句子连接起来,但是一般没有这种情况的使用场景,所以一般使用两个句子就够了
因为有两个句子,而且仅仅使用标签的话,对于 transformer 来讲并不是很好区分两个句子的先后顺序,所以额外地添加了一个 Segment Embedding 来进行区分
- 对于第一个句子中的所有词元添加 Segment Embedding 为 0 (包括句首的分类标签以及两个句子之间的分隔符)
- 对于第尔个句子中的所有词元添加 Segment Embedding 为 1(包括句末的分隔符)
3、位置编码可学习
- 在 Transformer 编码器中常见的是,位置嵌入被加入到输入序列的每个位置,而 BERT 中使用的是可学习的位置嵌入( BERT 输入序列的嵌入是词元嵌入、片段嵌入和位置嵌入的和)
- 不再使用 sin 和 cos 函数
预训练任务
BERT 是做一个通用的任务,因为他是一个预训练模型,做很常见的通用的任务,使得用这个任务训练出来的数据足够好,以至于做别的任务的时候都能做
在文本中,最通用的任务就是语言模型了,给定一个词,然后预测下一个词
- 但是 BERT 不能直接这么做,因为他里面的编码器是可以看到后面的东西的
- Transformer 中的编码器是双向的,既看前面又看后面,解码器才是单向的
- BERT 中的 B 是 bi-directional ,是双向的意思,所以它是看双向的信息,然后抽取比较好的特征,但是如果用来训练语言模型的话就会有问题
因此在 BERT 中做了一个修改,叫做带掩码的语言模型
- 给定一个句子,把中间的一些词遮起来,然后预测这些词,有点类似于完型填空
预训练任务 1:掩蔽语言模型(Masked Language Modeling)
1、Transformer 的编码器是双向的,标准语言模型要求单向
- 语言模型使用左侧的上下文预测词元
- 为了双向编码上下文以表示每个词元,BERT 随机掩蔽词元并使用来自双向上下文的词元以自监督的方式预测掩蔽词元
2、带掩码的语言模型每次随机(15%概率)将一些词元(作为预测的掩蔽词元)替换成 <mask>
- 任务就变成了预测被遮起来的那些词,模型就不是预测未来,而是变成了完型填空,因此看双向的信息是没有任何问题的
- 在每个预测位置,输入可以由特殊的“掩码”词元或随机词元替代,或者保持不变
3、虽然 BERT 在训练的时候加了很多的 <mask> ,但是在微调任务中不出现 <mask> 这种人造特殊词元,为了避免预训练和微调之间的这种不匹配,解决的办法是模型不要总是对 <mask> 遮掉的部分进行预测输出
- 80% 概率下,将选中的词元变成 <mask>
- 10% 概率下换成一个随机词元(这种偶然的噪声鼓励 BERT 在其双向上下文编码中不那么偏向于掩蔽词元,尤其是当标签词元保持不变时)
- 10% 概率下保持原有的词元
4、带掩码的语言虽然能够编码双向上下文来表示单词,但是它并不能显式地建模文本对之间的逻辑关系
预训练任务 2:下一句子预测(Next Sentence Prediction)
1、给定一个句子对,预测这个句子对中两个句子在原始的句子中是不是相邻,从而帮助理解两个文本序列之间的关系
2、在构造样本的时候,训练样本中:
- 50% 概率选择相邻句子对(在采样一个句子的时候,将该句子后面的一个句子也采样进去):<cls> this movie is great <sep> i like it <sep>
- 50% 概率选择随机句子对(在采样一个句子的时候,在其他地方再随机挑选一个句子采样进去):<cls> this movie is great <sep> hello world <sep>
3、将 <cls> 对应的输出放到一个全连接层来预测,判断两个句子是不是相邻的
总结
1、BERT 是针对 NLP 的微调设计,在大的文本上训练一个比较大的模型,在做别的任务的时候将输出层进行修改,最后的效果会比直接训练好一点( BERT 让微调在 NLP 中变成了主流)
2、BERT 其实就是一个基于 Transformer 的编码器,但是做了一点修改
- 模型更大,训练数据更多(一般是至少十亿个词,文本不像图片,文本不需要进行标记,所以文本可以无限大)
- 输入句子对,片段嵌入,可学习的位置编码
- 训练时使用两个任务:带掩码的语言模型和下一个句子预测
3、word2vec 和 GloVe 等词嵌入模型与上下文无关,它们将相同的预训练向量赋给同一个词,而不考虑词的上下文(如果有的话),因此很难处理好自然语言中的一词多义或复杂语义
4、对于上下文敏感的词表示,如 ELMo 和 GPT ,词的表示依赖于它们的上下文
- ELMo 对上下文进行双向编码,但使用特定于任务的架构(为每个自然语言处理任务设计一个特定的体系架构实际上并不容易)
- GPT 是任务无关的,但是从左到右编码上下文
5、BERT 结合了这两个方面的优点:对上下文进行双向编码,并且需要对大量自然语言处理任务进行最小的架构更改
6、BERT输入序列的嵌入是词元嵌入、片段嵌入和位置嵌入的和
7、BERT 预训练包括两个任务:掩蔽语言模型和下一句预测
- 掩蔽语言模型能够编码双向上下文来表示单词
- 下一句预测能够显式地建模文本对之间的逻辑关系
自然语言推断:微调BERT
对于自然语言处理应用可以设计不同的模型,比如基于循环神经网络、卷积神经网络,注意力和多层感知机,这些模型在有空间或者时间限制的情况下是有帮助的,但是为每个自然语言处理任务都设计一个特定的模型实际上是不可行的
BERT 预训练模型可以对广泛的自然语言处理任务进行最少的架构更改
BERT 模型的提出改进了各种语言处理任务的技术水平
原始 BERT 模型的两个版本分别带有 1.1 亿和 3.4 亿个参数,因此,当有足够的计算资源时可以考虑为下游自然语言处理应用微调 BERT
和图片分类的不同之处在于,图片分类训练好了 ResNet 之后,可以用来做图片分类,也是一个正常的任务;但是 BERT 本身的两个任务是没有意义的,很少会有做完形填空和预测两个句子是否相邻的情况,因此 BERT 主要用于做微调
自然语言处理应用可以分为序列级和词元级:序列级包括单文本分类任务和文本对分类(或回归)任务;词元级包括文本标注和回答
在微调期间,不同应用之间的 BERT 所需的“最小架构更改”是额外的全连接层
在下游应用的监督学习期间,额外的参数是从零开始学习的,而预训练 BERT 模型中的所有参数都是微调的
微调 BERT
1、在训练好 BERT 之后,句子(含分类标识符、句子和分隔符)进入 BERT ,BERT 会对每一个 token 返回一个长为 128 (BERT-Base:768;BERT-Large:1024)的特征向量(因为是 transformer ,所以可以认为这些特征已经包含了整个句子的信息的特征表示)
- BERT 对每一个词元返回抽取了上下文信息的特征向量
2、不同的任务使用不同的特征
例:句子分类
1、如何将文本输入的 BERT 表示转换为输出标签?将 <cls> 对应的向量输入到全连接层分类
2、单句子分类:单文本分类将单个文本序列作为输入,并输出其分类结果
- 特殊分类标记 <cls> 用于序列分类
- 特殊分类标记 <sep> 用于标记单个文本的结束或者分隔成对文本
- 将句子输入到 BERT 模型中,然后只将句子开始的 <cls> 标识符输出对应的特征向量,然后将这个特征向量输入到一个二分类(或者是 n 分类)输出层中做 softmax 进行分类
- 单文本分类应用中,特殊标记 <cls> 的 BERT 表示对整个输入文本序列的信息进行编码,作为单个文本的表示,它将被送入到由全连接(稠密)层组成的小多层感知机中,以输出所有离散标签值的分布
3、句子对分类:以一对文本作为输入但输出连续值
- 对于句子对也是一样的,将句子输入到 BERT 模型中,也是只将句子开始的 <cls> 标识符输出对应的特征向量,然后将这个特征向量输入到一个二分类(或者是 n 分类)输出层中做 softmax 进行分类
- 与单文本分类相比,文本对分类的 BERT 微调在输入表示上有所不同
2、为什么使用句子开始的标识符 <cls> ,而不是用别的东西?
- 在做预训练的时候,判断两个句子是不是相邻的时候使用的是 <cls> 这个标识符,所以也是在告诉 BERT 这个标识符是在做句子级别上分类所使用的向量,所以尽量将它对应输出的特征向量做得便于做分类
- 其实换成其他的也可以,反正最终需要进行微调,在训练的时候会更新 BERT 的权重,所以可以改变 BERT 的权重,使得最终输出的特征向量包含自己所想要的特征
例:命名实体识别
1、识别一个词元是不是命名实体,例如人名、机构、位置
2、BERT 如何表示输入并转换为输出标签?将非特殊词元放进全连接层分类
- 句子输入到 BERT 模型之后,将非特殊词元(丢弃掉 <cls> 、<sep> 等特殊词元,只留下真正的 token)放进全连接层进行分类,对每一个词进行词级别的分类判断(二分类或者是多分类)
- 与单文本分类的相比,唯一的区别在于,在命名实体识别中,输入文本的每个词元的 BERT 表示被送到相同的额外全连接层中,以输出词元的标签
例:问题回答
1、给定一个问题和它的描述文字,找出一个片段作为回答
- 给定一段话,然后提出一个问题,将描述中对应的词或者是句子截取出来作为回答
- 问答反映阅读理解能力
2、如何实现?BERT 如何表示输入并转换为输出标签?
- 首先将问题和描述性文字做成两句话(如果描述性文字是一段的话也不要紧,可以做成一个很长的序列,这也是为什么 BERT 长度通常可以做到 1024 ,因为它输入的数据可能确实比较长):第一句话是问题,问题不需要进行输出;第二句话是问题的描述文字,可能会比较长
- 分类器的作用:对于问题的描述性文字的每一个词,预测它是答案开始的那个词、结束的那个词还是什么都不是(这是一个三分类的问题),即对片段中的每个词元预测它是不是回答的开头或者结束(开始、结束或者其他)
总结
1、对于问题回答、句子分类或者词分类,虽然想要的东西不太一样,但是对于 BERT 来讲都是做成一个句子对输入到模型中,最后连接一个全连接层进行分类
- 全连接层所要看的 token 对应的特征向量可能不太一样,但是整体来讲,最后 BERT 模型所有的权重都是可以直接使用预训练好的模型
- 只有最后的输出层是真的需要从零开始训练的
2、即使 NLP 的任务各有不同,在使用 BERT 微调的时候都只需要增加输出层就可以了,而不需要在意怎样表示句子信息、段落信息、词信息,这些已经假设 BERT 已经提前做好了,只需要加一个简单的输出层就可以了
3、根据任务的不同,输入的表示和使用的 BERT 特征也会不一样,但是整体来讲,使用了 BERT 之后,使得整个微调相比于之前简单很多,而且 BERT 对于整个任务的提升也是非常显著的(比从零开始训练可能效果会好很多)
4、现在在 NLP 领域基本上也是转向微调的路线
5、对于序列级和词元级自然语言处理应用,BERT 只需要最小的架构改变(额外的全连接层)
6、在下游应用的监督学习期间,额外层的参数是从零开始学习的,而预训练 BERT 模型中的所有参数都是微调的
7、可以针对下游应用对预训练的 BERT 模型进行微调,在微调过程中,BERT 模型成为下游应用模型的一部分,仅与训练前损失相关的参数在微调期间不会更新