做好文本表示工作,可以简单理解成词向量训练,即做好**“数据-->信息”**的流程
文本表示是NLP特征工程的一部分,在文本预处理和特征提取之后
主要涉及内容包括语义粒度,文本长度,词向量的选择,目前还有语言模型的选择和使用
目前最大热的框架就是BERT了,可以理解为语言模型的迁移学习框架
在此之前需要理解Word2vec,ELMo,Glove,Fasttext等之前的框架。
NNLM、RNNLM
为了能够量化地衡量哪个句子更像一句人话,可以设计如上图所示函数,核心函数P的思想是根据句子里面前面的一系列前导单词预测后面跟哪个单词的概率大小(理论上除了上文之外,也可以引入单词的下文联合起来预测单词出现概率)。句子里面每个单词都有个根据上文预测自己的过程,把所有这些单词的产生概率乘起来,数值越大代表这越像一句人话。
- 基于one-hot、tf-idf、textrank等的bag-of-words;
存在维数灾难和语义鸿沟的问题
- 主题模型:LSA(SVD)、pLSA、LDA;
利用全局语料特征,但SVD求解计算复杂度大
- 基于词向量的固定表征:word2vec、fastText、glove
相比n-gram,解决分布式假设问题,分布式假设:相同上下文语境的词有似含义。
固定表征的词向量无法解决一词多义等问题
word2vec、fastText基于局部预料,glove基于全局语料库、并结合上下文语境构建词向量,结合了LSA和word2vec的优点
- 基于词向量的动态表征:elmo、GPT、bert
解决了固定表征无法解决一词多义的问题
ELMO:“Embedding from Language Models” 对应论文“Deep contextualized word representation”,特征提取器RNN,LSTM,双向
GPT是“Generative Pre-Training”的简称,两阶段,特征提取器Transformer,单向,只通过上文不通过下文,阅读理解更差
Bert,两阶段,双向,语言模型数据规模大于GPT
Bert借鉴了ELMO,GPT及CBOW,主要提出了Masked 语言模型及Next Sentence Prediction,但是这里Next Sentence Prediction基本不影响大局,而Masked LM明显借鉴了CBOW的思想。
分布式假设,用一句话可以表达:相同上下文语境的词有似含义。而由此引申出了word2vec、fastText,在此类词向量中,虽然其本质仍然是语言模型,但是它的目标并不是语言模型本身,而是词向量,其所作的一系列优化,都是为了更快更好的得到词向量。
word embedding其实就是NLP里的早期预训练技术
下游NLP任务在使用Word Embedding的时候也类似图像有两种做法,
一种是Frozen,就是Word Embedding那层网络参数固定不动;
另外一种是Fine-Tuning,就是Word Embedding这层参数使用新的训练集合训练也需要跟着训练过程更新掉。
文本是由每个单词构成的,而谈起词向量,one-hot是可认为是最为简单的词向量,但存在维度灾难和语义鸿沟等问题
Google 2013 word2vec 中的数学原理详解
word2Vec 有两种模型:CBOW 和 Skip-Gram:
- CBOW 在已知
context(w)
的情况下,预测w
; - Skip-Gram在已知
w
的情况下预测context(w)
;
一个“线性”语言模型,可以实现线性的语义运算,如”皇帝-皇后=男-女“
与NNLM相比,word2vec的主要目的是生成词向量而不是语言模型,在CBOW中,投射层将词向量直接相加而不是拼接起来,并舍弃了隐层,这些牺牲都是为了减少计算量,使训练更加
word2vec的一个精髓是把语言模型的那一套softmax加速方法也给顺便优化了,用一个看似开脑洞的“负采样”方法来代替传统的层级softmax和NCE做法
训练语言模型时,要预测的是当前位置是哪个词,那么这个类别数就等同于词典规模,算softmax函数很费力。此时将问题转化为训练得到语言模型的副产物-词向量,那么其实只需要用这里隐含的一个计算代价更小的“子任务”就好啦。
想一想,给你10000张写有数字的卡片,让你找出其中的最大值,是不是特别费力?但是如果把里面的最大值事先抽出来,跟都五张随机抽取的卡片混到一起,让你选出其中的最大值,是不是就容易多啦?
负采样就是这个思想,即不直接让模型从整个词表找最可能的词了,而是直接给定这个词(即正例)和几个随机采样的噪声词(即采样出来的负例),只要模型能从这里面找出正确的词就认为完成目标。对应的目标函数为:
这里 是正例, 是随机采样出来的负例(采样k个), 是sigmoid函数。然后即最大化正例的似然,最小化负例的似然。
GloVe的全称叫Global Vectors for Word Representation,它是一个基于全局词频统计(count-based & overall statistics)的词表征(word representation)工具
这边不做过多介绍,详细参看Glove详解
char-level的文本中也蕴含了一些word-level的文本所难以描述的模式,因此一方面出现了可以学习到char-level特征的词向量FastText
另一方面在有监督任务中开始通过浅层CNN、HIghwayNet、RNN等网络引入char-level文本的表示
不过,至此为止,词向量都是上下文无关的。也就是说,同一个词在不同的语境中总是相同的词向量,很明显这就导致词向量模型缺乏词义消歧(WSD)的能力。于是,人们为了让词向量变得上下文相关,开始在具体的下游任务中基于词向量sequence来做encoding。
最常见的encoding方法当然就是用RNN系的网络,除此之外还有成功的用深层CNN来encoding的工作(如文本分类,机器翻译,机器阅读理解),然!而!Google说了,CNN也太俗了,我们要用全连接网络!self-attention!于是就有了为NLP深度定制的Transformer模型,Transformer的提出是在机器翻译任务上,但是其在其他领域如检索式对话上也发挥了巨大的威力。
不过,既然发现在各个NLP任务中基本都有encoding的需要,那么为啥不在最开始就让词向量拥有上下文相关的能力呢?于是有了ELMo。
产生上下文相关的词向量的语言模型,是一个让你有充分理由放弃word2vec的模型
ELMo在模型层上就是一个stacked bi-lstm(严格来说是训练了两个单向的stacked lstm),所以当然有不错的encoding能力。同时其源码实现上也支持用Highway Net或者CNN来额外引入char-level encoding。训练它的话自然也是语言模型标准的最大化似然函数,即
不过这个ELMo的亮点当然不在于模型层,而是其通过实验间接说明了在多层的RNN中,不同层学到的特征其实是有差异的,因此ELMo提出在预训练完成并迁移到下游NLP任务中时,要为原始词向量层和每一层RNN的隐层都设置一个可训练参数,这些参数通过softmax层归一化后乘到其相应的层上并求和便起到了weighting的作用,然后对“加权和”得到的词向量再通过一个参数来进行词向量整体的scaling以更好的适应下游任务。
ps: 其实最后这个参数还是非常重要的,比如word2vec中,一般来说cbow和sg学出来的词向量方差差异比较大,这时那个方差跟适合下游任务后续层方差匹配的词向量就收敛更快,更容易有更好的表现
数学表达式如下
其中L=2就是ELMo论文中的设定,j=0时代表原始词向量层,j=1是lstm的第一隐层,j=2是第二隐层。 是参数被softmax归一化之后的结果(也就是说 )。
通过这样的迁移策略,那些对词义消歧有需求的任务就更容易通过训练给第二隐层一个很大的权重,而对词性、句法有明显需求的任务则可能对第一隐层的参数学习到比较大的值(实验结论)。总之,这样便得到了一份”可以被下游任务定制“的特征更为丰富的词向量,效果比word2vec好得多也就不足为奇了。
不过话说回来,ELMo的目标也仅仅是学习到上下文相关的、更强大的词向量,其目的依然是为下游任务提供一个扎实的根基,还没有想要弑君称王的意思。
而我们知道,仅仅是对文本进行充分而强大的encoding(即得到每个词位非常精准丰富的特征)是远不够覆盖所有NLP任务的。在QA、机器阅读理解(MRC)、自然语言推理(NLI)、对话等任务中,还有很多更复杂的模式需要捕捉,比如句间关系。为此,下游任务中的网络会加入各种花式attention(参考NLI、MRC、Chatbot中的SOTA们)。
而随着捕捉更多神奇模式的需要,研究者们为每个下游任务定制出各种各样的网络结构,导致同一个模型,稍微一换任务就挂掉了,甚至在同一个任务的情况下换另一种分布的数据集都会出现显著的性能损失,这显然不符合人类的语言行为呀~要知道人类的generalization能力是非常强的,这就说明,或许现在整个NLP的发展轨迹就是错的,尤其是在SQuAD的带领下,穷尽各种trick和花式结构去刷榜,真正之于NLP的意义多大呢?
好像扯远了,不过所幸,这条越走越偏的道路终于被一个模型shutdown了,那就是几天前Google发布的Bidirectional Encoder Representations from Transformers (BERT).
为每个NLP任务去深度定制泛化能力极差的复杂模型结构其实是非常不明智的
预训练模型的潜力远不止为下游任务提供一份精准的词向量
BERT作为一个general的龙骨级模型轻松的挑战了11个任务上的深度定制的模型
它里面已经充分的描述了字符级、词级、句子级甚至句间关系的特征,那么在不同的NLP任务中,只需要去为任务定制一个非常轻量级的输出层(比如一个单层MLP)就好了
深层双向的encoding
首先,它指出,对上下文相关的词向量的学习上,先前的预训练模型还不够!
虽然在下游有监督任务中,encoding的方式已经是花里胡哨非常充分了,深度双向encoding基本成了许多复杂下游任务的标配(比如MRC, dialogue)。但是在预训练模型上,先前的最先进模型也只是基于传统的语言模型来做,而传统的语言模型是单向的(数学上已经定义了),即
而且往往都很浅(想象一下LSTM堆三层就train不动了,就要上各种trick了),比如ELMo。
另外,虽然ELMo有用双向RNN来做encoding,但是这两个方向的RNN其实是分开训练的,只是在最后在loss层做了个简单相加。这样就导致对于每个方向上的单词来说,在被encoding的时候始终是看不到它另一侧的单词的。而显然句子中有的单词的语义会同时依赖于它左右两侧的某些词,仅仅从单方向做encoding是不能描述清楚的。
那么为什么不像下游监督任务中那样做真正的双向encoding呢?
原因一想就很清楚了,毕竟传统的语言模型是以预测下一个词为训练目标的,然而如果做了双向encoding的话,那不就表示要预测的词已经看到了嘛,这样的预测当然没有意义了。
所以,在BERT中,提出了使用一种新的任务来训练监督任务中的那种真正可以双向encoding的模型,这个任务称为Masked Language Model (Masked LM)。
Masked LM
顾名思义,Masked LM就是说,我们不是像传统LM那样给定已经出现过的词,去预测下一个词,而是直接把整个句子的一部分词(随机选择)盖住(make it masked),这样模型就可以放心的去做双向encoding了,然后就可以放心的让模型去预测这些盖住的词是啥。这个任务其实最开始叫做cloze test(大概翻译成“完形填空测验”)。
这样显然会导致一些小问题。这样虽然可以放心的双向encoding了,但是这样在encoding时把这些盖住的标记也给encoding进去了,而这些mask标记在下游任务中是不存在的。对此,为了尽可能的把模型调教的忽略这些标记的影响,作者通过如下方式来告诉模型“这些是噪声是噪声!靠不住的!忽略它们吧!”,对于一个被盖住的单词:
- 有80%的概率用“[mask]”标记来替换
- 有10%的概率用随机采样的一个单词来替换
- 有10%的概率不做替换(虽然不做替换,但是还是要预测哈)
Masked双向语言模型:随机选择语料中15%的单词,把它抠掉,也就是用[Mask]掩码代替原始单词,然后要求模型去正确预测被抠掉的单词。但是这里有个问题:训练过程大量看到[mask]标记,但是真正后面用的时候是不会有这个标记的,这会引导模型认为输出是针对[mask]这个标记的,但是实际使用又见不到这个标记,这自然会有问题。为了避免这个问题,Bert改造了一下,15%的被上天选中要执行[mask]替身这项光荣任务的单词中,只有80%真正被替换成[mask]标记,10%被狸猫换太子随机替换成另外一个单词,10%情况这个单词还待在原地不做改动。这就是Masked双向语音模型的具体做法。
Encoder
在encoder的选择上,作者并没有用烂大街的bi-lstm,而是使用了可以做的更深、具有更好并行性的Transformer encoder来做。这样每个词位的词都可以无视方向和距离的直接把句子中的每个词都有机会encoding进来。
另一方面我主观的感觉Transformer相比lstm更容易免受mask标记的影响,毕竟self-attention的过程完全可以把mask标记针对性的削弱匹配权重,但是lstm中的输入门是如何看待mask标记的那就不得而知了。
在位置信息的解决上,作者很简单粗暴的直接去训练了一个position embedding,比如把句子截断到50的长度,那么就有50个位置,所以就有50个表征位置的单词,即从位置0一直到位置49,然后给每个位置词一个随机初始化的词向量,再随他们训练。另外,position embedding和word embedding的结合方式上,BERT里选择了直接相加。
最后,在深度方面,最终BERT完全版的encoder丧心病狂的叠加了24层的multi-head attention block(要知道对话里的SOTA模型DAM也才用了5层…)。。。而且每个block包含16抽头、1024隐单元,此处打出标语:money is all you need (划掉)
学习句子与句对关系表示
像之前说的,在很多任务中,仅仅靠encoding是不足以完成任务的(这个只是学到了一堆token级的特征),还需要捕捉一些句子级的模式,来完成SLI、QA、dialogue等需要句子表示、句间交互与匹配的任务。对此,BERT又引入了另一个极其重要却又极其轻量级的任务,来试图把这种模式也学习到。
句子级负采样
BERT学习sentence-level representation时把word2vec的负采样的过程给generalize到sentence-level
BERT这里跟word2vec做法类似,不过构造的是一个句子级的分类任务。即首先给定的一个句子(相当于word2vec中给定context),它下一个句子即为正例(相当于word2vec中的正确词),随机采样一个句子作为负例(相当于word2vec中随机采样的词),然后在该sentence-level上来做二分类(即判断句子是当前句子的下一句还是噪声)。
句子级表示
BERT这里并没有像下游监督任务中的普遍做法一样,在encoding的基础上再搞个全局池化之类的,它首先在每个sequence(对于句子对任务来说是两个拼起来的句子,对于其他任务来说是一个句子)前面加了一个特殊的token,记为[CLS],如图
ps:这里的[sep]是句子之间的分隔符,BERT同时支持学习句对的表示,这里是[SEP]便是为了区分句对的切割点。
然后让encoder对[CLS]进行深度encoding,深度encoding的最高隐层即为整个句子/句对的表示。
这个做法乍一看有点费解,不过别忘了,Transformer是可以无视空间和距离的把全局信息encoding进每个位置的,而[CLS]作为句子/句对的表示是直接跟分类器的输出层连接的,因此其作为梯度反传路径上的“关卡”,当然会想办法学习到分类相关的上层特征啦。
另外,为了让模型能够区分里面的每个词是属于“左句子”还是“右句子”,作者这里引入了“segment embedding”的概念来区分句子。对于句对来说,就用embedding A和embedding B来分别代表左句子和右句子;而对于句子来说,就只有embedding A啦。这个embedding A和B也是随模型训练出来的。
Next Sentence Prediction
做语言模型预训练的时候,分两种情况选择两个句子,一种是选择语料中真正顺序相连的两个句子;另外一种是第二个句子从语料库中抛色子,随机选择一个拼到第一个句子后面。我们要求模型除了做上述的Masked语言模型任务外,附带再做个句子关系预测,判断第二个句子是不是真的是第一个句子的后续句子。之所以这么做,是考虑到很多NLP任务是句子关系判断任务,单词预测粒度的训练到不了句子关系这个层级,增加这个任务有助于下游句子关系判断任务。所以可以看到,它的预训练是个多任务过程。这也是Bert的一个创新。
输入部分
输入部分是个线性序列,两个句子通过分隔符分割,最前面和最后增加两个标识符号。每个单词有三个embedding:位置信息embedding,这是因为NLP中单词顺序是很重要的特征,需要在这里对位置信息进行编码;单词embedding,这个就是我们之前一直提到的单词embedding;第三个是句子embedding,因为前面提到训练数据都是由两个句子构成的,那么每个句子有个句子整体的embedding项对应给每个单词。把单词对应的三个embedding叠加,就形成了Bert的输入。
所以最终BERT每个token的表示由token原始的词向量token embedding、前文提到的position embedding和这里的segment embedding三部分相加而成,如图:
输出部分
简洁到过分的下游任务接口
真正体现出BERT这个模型是龙骨级模型而不再是词向量的,就是其到各个下游任务的接口设计了,或者换个更洋气的词叫迁移策略。 首先,既然句子和句子对的上层表示都得到了,那么当然对于文本分类任务和文本匹配任务(文本匹配其实也是一种文本分类任务,只不过输入是文本对)来说,只需要用得到的表示(即encoder在[CLS]词位的顶层输出)加上一层MLP就好了呀~
既然文本都被深度双向encoding了,那么做序列标注任务就只需要加softmax输出层就好了呀,连CRF都不用了呀~
在span抽取式任务如SQuAD上,把深度encoding和深度attention这俩大礼包省掉就算了,甚至都敢直接把输出层的pointer net给丢掉了?直接像DrQA那样傲娇的用两个线性分类器分别输出span的起点和终点?不多说了,已跪m(_ _)m
最后来看一下实验效果
BERT总结
从模型或者方法角度看,Bert借鉴了ELMO,GPT及CBOW,主要提出了Masked 语言模型及Next Sentence Prediction,但是这里Next Sentence Prediction基本不影响大局,而Masked LM明显借鉴了CBOW的思想。所以说Bert的模型没什么大的创新,更像最近几年NLP重要进展的集大成者。
如果归纳一下这些进展就是:
首先是两阶段模型,第一阶段双向语言模型预训练,这里注意要用双向而不是单向,第二阶段采用具体任务Fine-tuning或者做特征集成;
第二是特征抽取要用Transformer作为特征提取器而不是RNN或者CNN;
第三,双向语言模型可以采取CBOW的方法去做(当然我觉得这个是个细节问题,不算太关键,前两个因素比较关键)。
Bert最大的亮点在于效果好及普适性强,几乎所有NLP任务都可以套用Bert这种两阶段解决思路,而且效果应该会有明显提升。可以预见的是,未来一段时间在NLP应用领域,Transformer将占据主导地位,而且这种两阶段预训练方法也会主导各种应用。
本质上预训练是通过设计好一个网络结构来做语言模型任务,然后把大量甚至是无穷尽的无标注的自然语言文本利用起来,预训练任务把大量语言学知识抽取出来编码到网络结构中,当手头任务带有标注信息的数据有限时,这些先验的语言学特征当然会对手头任务有极大的特征补充作用,因为当数据有限的时候,很多语言学现象是覆盖不到的,泛化能力就弱,集成尽量通用的语言学知识自然会加强模型的泛化能力。
如何引入先验的语言学知识其实一直是NLP尤其是深度学习场景下的NLP的主要目标之一,不过一直没有太好的解决办法,而ELMO/GPT/Bert的这种两阶段模式看起来无疑是解决这个问题自然又简洁的方法,这也是这些方法的主要价值所在。
对于当前NLP的发展方向,我个人觉得有两点非常重要,
一个是需要更强的特征抽取器,目前看Transformer会逐渐担当大任,但是肯定还是不够强的,需要发展更强的特征抽取器;
第二个就是如何优雅地引入大量无监督数据中包含的语言学知识,注意我这里强调地是优雅,而不是引入,此前相当多的工作试图做各种语言学知识的嫁接或者引入,但是很多方法看着让人牙疼,就是我说的不优雅。目前看预训练这种两阶段方法还是很有效的,也非常简洁,当然后面肯定还会有更好的模型出现。