/ NLP  

NLP系列

文本表示进阶

1. 预训练在图像领域的应用

自从深度学习火起来后,预训练过程就是做图像或者视频领域的一种比较常规的做法,有比较长的历史了,而且这种做法很有效,能明显促进应用的效果。

图1

那么图像领域怎么做预训练呢,上图展示了这个过程,我们设计好网络结构以后,对于图像来说一般是CNN的多层叠加网络结构,可以先用某个训练集合比如训练集合A或者训练集合B对这个网络进行预先训练,在A任务上或者B任务上学会网络参数,然后存起来以备后用。假设我们面临第三个任务C,网络结构采取相同的网络结构,在比较浅的几层CNN结构,网络参数初始化的时候可以加载A任务或者B任务学习好的参数,其它CNN高层参数仍然随机初始化。之后我们用C任务的训练数据来训练网络,此时有两种做法,一种是浅层加载的参数在训练C任务过程中不动,这种方法被称为“Frozen”;另外一种是底层网络参数尽管被初始化了,在C任务训练过程中仍然随着训练的进程不断改变,这种一般叫“Fine-Tuning”,顾名思义,就是更好地把参数进行调整使得更适应当前的C任务。一般图像或者视频领域要做预训练一般都这么做。

这么做有几个好处,首先,如果手头任务C的训练集合数据量较少的话,现阶段的好用的CNN比如Resnet/Densenet/Inception等网络结构层数很深,几百万上千万参数量算起步价,上亿参数的也很常见,训练数据少很难很好地训练这么复杂的网络,但是如果其中大量参数通过大的训练集合比如ImageNet预先训练好直接拿来初始化大部分网络结构参数,然后再用C任务手头比较可怜的数据量上Fine-tuning过程去调整参数让它们更适合解决C任务,那事情就好办多了。这样原先训练不了的任务就能解决了,即使手头任务训练数据也不少,加个预训练过程也能极大加快任务训练的收敛速度,所以这种预训练方式是老少皆宜的解决方案,另外疗效又好,所以在做图像处理领域很快就流行开来。

那么新的问题来了,为什么这种预训练的思路是可行的?

目前我们已经知道,对于层级的CNN结构来说,不同层级的神经元学习到了不同类型的图像特征,由底向上特征形成层级结构,如上图所示,如果我们手头是个人脸识别任务,训练好网络后,把每层神经元学习到的特征可视化肉眼看一看每层学到了啥特征,你会看到最底层的神经元学到的是线段等特征,图示的第二个隐层学到的是人脸五官的轮廓,第三层学到的是人脸的轮廓,通过三步形成了特征的层级结构,越是底层的特征越是所有不论什么领域的图像都会具备的比如边角线弧线等底层基础特征,越往上抽取出的特征越与手头任务相关。正因为此,所以预训练好的网络参数,尤其是底层的网络参数抽取出特征跟具体任务越无关,越具备任务的通用性,所以这是为何一般用底层预训练好的参数初始化新任务网络参数的原因。而高层特征跟任务关联较大,实际可以不用使用,或者采用Fine-tuning用新数据集合清洗掉高层无关的特征抽取器。

一般我们喜欢用ImageNet来做网络的预训练,主要有两点,一方面ImageNet是图像领域里有超多事先标注好训练数据的数据集合,分量足是个很大的优势,量越大训练出的参数越靠谱;另外一方面因为ImageNet有1000类,类别多,算是通用的图像数据,跟领域没太大关系,所以通用性好,预训练完后哪哪都能用,是个万金油。分量足的万金油当然老少通吃,人人喜爱。

听完上述话,如果你是具备研究素质的人,也就是说具备好奇心,你一定会问下面这个问题:”既然图像领域预训练这么好用,那干嘛自然语言处理不做这个事情呢?是不是搞NLP的人比搞CV的傻啊?就算你傻,你看见人家这么做,有样学样不就行了吗?这不就是创新吗,也许能成,万一成了,你看,你的成功来得就是这么突然!”

1.2 图像与NLP的粗略对应关系

将图像和语言中的处理对象做一个不太严谨的对应。如下图所示,大体上像素类似于语言中的字母;图像中的对象类似于语言中的单词/概念;图像中对象组成的场景类似于语言中的句子表达的语义;视频则类似于语言中的篇章(文章)。

在这种类比下看,NLP/IR在单词层面的处理要比CV中的图像识别简单得多,只需要做一下tokenization、lemmatization、stemming等(中文复杂一些需要额外做自动分词),就可以利用关键词匹配完成很多任务,例如信息检索、文本分类、拼写纠错、情感分析、关键词提取等等,实际上已经得到非常广泛的应用,如搜索引擎、拼音输入法、新闻分类、阅读推荐等。

而由于图像中对象的复杂性和多样性,仅在对象识别层面,甚至特定的人脸识别,还有很多技术挑战。只不过是近年来,由于深度学习对非结构数据的强大表示和学习能力,开始让对象识别走向了实用化。而进入到更高层面,例如面向图像的场景图构建,面向文本的句法语义分析,都需要对复杂语境(上下文)的精准而强大的建模能力。

1.3 NLP相对图像的特点

词作为NLP的基本要素,比像素的抽象程度更高,已经加入了人类数万年进化而来的抽象经验。
光是对词的表示已经耗费了科学家非常多的精力。所以之前的NLP预训练工作主要集中于对词的表示。

2. ELMO:基于上下文的word-embedding

ELMO是“Embedding from Language Models”的简称,其实这个名字并没有反应它的本质思想,提出ELMO的论文题目:“Deep contextualized word representation”更能体现其精髓,而精髓在哪里?在deep contextualized这个短语,一个是deep,一个是context,其中context更关键。在此之前的Word Embedding本质上是个静态的方式,所谓静态指的是训练好之后每个单词的表达就固定住了,以后使用的时候,不论新句子上下文单词是什么,这个单词的Word Embedding不会跟着上下文场景的变化而改变,所以对于比如Bank这个词,它事先学好的Word Embedding中混合了几种语义 ,在应用中来了个新句子,即使从上下文中(比如句子包含money等词)明显可以看出它代表的是“银行”的含义,但是对应的Word Embedding内容也不会变,它还是混合了多种语义。这是为何说它是静态的,这也是问题所在。ELMO的本质思想是:我事先用语言模型学好一个单词的Word Embedding,此时多义词无法区分,不过这没关系。在我实际使用Word Embedding的时候,单词已经具备了特定的上下文了,这个时候我可以根据上下文单词的语义去调整单词的Word Embedding表示,这样经过调整后的Word Embedding更能表达在这个上下文中的具体含义,自然也就解决了多义词的问题了。所以ELMO本身是个根据当前上下文对Word Embedding动态调整的思路。

ELMO采用了典型的两阶段过程,第一个阶段是利用语言模型进行预训练;第二个阶段是在做下游任务时,从预训练网络中提取对应单词的网络各层的Word Embedding作为新特征补充到下游任务中。上图展示的是其预训练过程,它的网络结构采用了双层双向LSTM,目前语言模型训练的任务目标是根据单词 W_i 的上下文去正确预测单词 W_i , W_i 之前的单词序列Context-before称为上文,之后的单词序列Context-after称为下文。图中左端的前向双层LSTM代表正方向编码器,输入的是从左到右顺序的除了预测单词外 W_i 的上文Context-before;右端的逆向双层LSTM代表反方向编码器,输入的是从右到左的逆序的句子下文Context-after;每个编码器的深度都是两层LSTM叠加。这个网络结构其实在NLP中是很常用的。使用这个网络结构利用大量语料做语言模型任务就能预先训练好这个网络,如果训练好这个网络后,输入一个新句子 Snew ,句子中每个单词都能得到对应的三个Embedding:最底层是单词的Word Embedding,往上走是第一层双向LSTM中对应单词位置的Embedding,这层编码单词的句法信息更多一些;再往上走是第二层LSTM中对应单词位置的Embedding,这层编码单词的语义信息更多一些。也就是说,ELMO的预训练过程不仅仅学会单词的Word Embedding,还学会了一个双层双向的LSTM网络结构,而这两者后面都有用。

上面介绍的是ELMO的第一阶段:预训练阶段。那么预训练好网络结构后,如何给下游任务使用呢?上图展示了下游任务的使用过程,比如我们的下游任务仍然是QA问题,此时对于问句X,我们可以先将句子X作为预训练好的ELMO网络的输入,这样句子X中每个单词在ELMO网络中都能获得对应的三个Embedding,之后给予这三个Embedding中的每一个Embedding一个权重a,这个权重可以学习得来,根据各自权重累加求和,将三个Embedding整合成一个。然后将整合后的这个Embedding作为X句在自己任务的那个网络结构中对应单词的输入,以此作为补充的新特征给下游任务使用。对于上图所示下游任务QA中的回答句子Y来说也是如此处理。因为ELMO给下游提供的是每个单词的特征形式,所以这一类预训练的方法被称为“Feature-based Pre-Training”。至于为何这么做能够达到区分多义词的效果,你可以想一想,其实比较容易想明白原因。

前面我们提到静态Word Embedding无法解决多义词的问题,那么ELMO引入上下文动态调整单词的embedding后多义词问题解决了吗?解决了,而且比我们期待的解决得还要好。上图给了个例子,对于Glove训练出的Word Embedding来说,多义词比如play,根据它的embedding找出的最接近的其它单词大多数集中在体育领域,这很明显是因为训练数据中包含play的句子中体育领域的数量明显占优导致;而使用ELMO,根据上下文动态调整后的embedding不仅能够找出对应的“演出”的相同语义的句子,而且还可以保证找出的句子中的play对应的词性也是相同的,这是超出期待之处。之所以会这样,是因为我们上面提到过,第一层LSTM编码了很多句法信息,这在这里起到了重要作用。

ELMO经过这般操作,效果如何呢?实验效果见上图,6个NLP任务中性能都有幅度不同的提升,最高的提升达到25%左右,而且这6个任务的覆盖范围比较广,包含句子语义关系判断,分类任务,阅读理解等多个领域,这说明其适用范围是非常广的,普适性强,这是一个非常好的优点。

3. GPT: Transformer建模句子信息

3.1. 从词向量到句子向量

  • 无监督句子表示:将句子表示成定长向量
  • 基线模型:word2vec
  • 现有模型:AE,LM,Skip-Thoughts
    • 本身的信息
    • 上下文的信息
    • 任务的信息

3.1.1. Skip-Thoughts

  • 类似skip-gram,关注句子与上下文句子的共现关系

3.2 Transformer/self-attention介绍

Transformer是个叠加的“自注意力机制(Self Attention)”构成的深度网络。其关键是自注意力机制(Self Attention)。所以我们主要介绍self-attention。

当我们想对句子“The animal didn’t cross the street because it was too tired”中“it”这个词编码时,注意力机制的基本思想是认为这个句话中每个词对it的语义均会有贡献。那怎么综合这些贡献呢,就是直接将每个词的embedding向量加权求和
所以关键的问题是如何得到每个词各自的权重,关系更近的词的权重更大。比如这句话中”The Animal”的权重就应该更大,它们的信息应该更多地编码到“it”中。
自注意力机制得到权重的方法非常简单,就是两个词向量的内积。最终通过一个softmax将各个权重归一化。

在上图中,颜色的粗细代表该词的权重大小,权重由该词与“it”的内积得到,最终通过一个softmax将各个权重归一化。

自注意力机制其实是最原始意义的卷积的思想的推广,因为卷积本身就是一种“加权求和”。

3.3 GPT

GPT是“Generative Pre-Training”的简称,从名字看其含义是指的生成式的预训练。GPT也采用两阶段过程,第一个阶段是利用语言模型进行预训练,第二阶段通过Fine-tuning的模式解决下游任务。上图展示了GPT的预训练过程,其实和ELMO是类似的,主要不同在于两点:首先,特征抽取器不是用的RNN,而是用的Transformer,上面提到过它的特征抽取能力要强于RNN,这个选择很明显是很明智的;其次,GPT的预训练虽然仍然是以语言模型作为目标任务,但是采用的是单向的语言模型,所谓“单向”的含义是指:语言模型训练的任务目标是根据 W_i 单词的上下文去正确预测单词 W_i , W_i 之前的单词序列Context-before称为上文,之后的单词序列Context-after称为下文。ELMO在做语言模型预训练的时候,预测单词 W_i 同时使用了上文和下文,而GPT则只采用Context-before这个单词的上文来进行预测,而抛开了下文。这个选择现在看不是个太好的选择,原因很简单,它没有把单词的下文融合进来,这限制了其在更多应用场景的效果,比如阅读理解这种任务,在做任务的时候是可以允许同时看到上文和下文一起做决策的。如果预训练时候不把单词的下文嵌入到Word Embedding中,是很吃亏的,白白丢掉了很多信息。

上面讲的是GPT如何进行第一阶段的预训练,那么假设预训练好了网络模型,后面下游任务怎么用?它有自己的个性,和ELMO的方式大有不同。

上图展示了GPT在第二阶段如何使用。首先,对于不同的下游任务来说,本来你可以任意设计自己的网络结构,现在不行了,你要向GPT的网络结构看齐,把任务的网络结构改造成和GPT的网络结构是一样的。然后,在做下游任务的时候,利用第一步预训练好的参数初始化GPT的网络结构,这样通过预训练学到的语言学知识就被引入到你手头的任务里来了,这是个非常好的事情。再次,你可以用手头的任务去训练这个网络,对网络参数进行Fine-tuning,使得这个网络更适合解决手头的问题。就是这样。看到了么?这有没有让你想起最开始提到的图像领域如何做预训练的过程对,这跟那个模式是一模一样的。

这里引入了一个新问题:对于NLP各种花样的不同任务,怎么改造才能靠近GPT的网络结构呢?

GPT论文给了一个改造施工图如上,其实也很简单:对于分类问题,不用怎么动,加上一个起始和终结符号即可;对于句子关系判断问题,比如Entailment,两个句子中间再加个分隔符即可;对文本相似性判断问题,把两个句子顺序颠倒下做出两个输入即可,这是为了告诉模型句子顺序不重要;对于多项选择问题,则多路输入,每一路把文章和答案选项拼接作为输入即可。从上图可看出,这种改造还是很方便的,不同任务只需要在输入部分施工即可。

GPT的效果是非常令人惊艳的,在12个任务里,9个达到了最好的效果,有些任务性能提升非常明显。

4. BERT:预训练双向Transformer

BERT的全称是Bidirectional Encoder Representation from Transformers。

深层双向的encoding

首先,它指出,对上下文相关的词向量的学习上,先前的预训练模型还不够!虽然在下游有监督任务中,encoding的方式已经是花里胡哨非常充分了,深度双向encoding基本成了许多复杂下游任务的标配(比如MRC, dialogue)。但是在预训练模型上,先前的最先进模型也只是基于传统的语言模型来做,而传统的语言模型是单向的(数学上已经定义了),即

$$p(s)=p(w0)\cdot p(w1|w0)\cdot p(w2|w1,w0)\cdot p(w3|w2, w1,w0) …p(wn|context)$$

而且往往都很浅(想象一下LSTM堆三层就train不动了,就要上各种trick了),比如ELMo。

另外,虽然ELMo有用双向RNN来做encoding,但是这两个方向的RNN其实是分开训练的,只是在最后在loss层做了个简单相加。这样就导致对于每个方向上的单词来说,在被encoding的时候始终是看不到它另一侧的单词的。而显然句子中有的单词的语义会同时依赖于它左右两侧的某些词,仅仅从单方向做encoding是不能描述清楚的。

Masked LM

顾名思义,Masked LM就是说,我们不是像传统LM那样给定已经出现过的词,去预测下一个词,而是直接把整个句子的一部分词(随机选择)盖住(make it masked),这样模型不就可以放心的去做双向encoding了嘛,然后就可以放心的让模型去预测这些盖住的词是啥。这个任务其实最开始叫做cloze test(大概翻译成“完形填空测试”)。

这样显然会导致一些小问题。这样虽然可以放心的双向encoding了,但是这样在encoding时把这些盖住的标记也给encoding进去了╮( ̄▽ ̄””)╭而这些mask标记在下游任务中是不存在的呀。。。那怎么办呢?对此,为了尽可能的把模型调教的忽略这些标记的影响,作者通过如下方式来告诉模型“这些是噪声是噪声!靠不住的!忽略它们吧!”,对于一个被盖住的单词:

  • 有80%的概率用“[mask]”标记来替换
  • 有10%的概率用随机采样的一个单词来替换
  • 有10%的概率不做替换(虽然不做替换,但是还是要预测哈)

Encoder

在encoder的选择上,作者并没有用烂大街的bi-lstm,而是使用了可以做的更深、具有更好并行性的Transformer encoder来做。这样每个词位的词都可以无视方向和距离的直接把句子中的每个词都有机会encoding进来。另一方面我主观的感觉Transformer相比lstm更容易免受mask标记的影响,毕竟self-attention的过程完全可以把mask标记针对性的削弱匹配权重,但是lstm中的输入门是如何看待mask标记的那就不得而知了。

等下,小夕在之前的文章中也说过了,直接用Transformer encoder显然不就丢失位置信息了嘛?难道作者这里也像Transformer原论文中那样搞了个让人怕怕的sin、cos函数编码位置?并木有,作者这里很简单粗暴的直接去训练了一个position embedding ╮( ̄▽ ̄””)╭ 这里就是说,比如我把句子截断到50的长度,那么我们就有50个位置嘛,所以就有50个表征位置的单词,即从位置0一直到位置49。。。然后给每个位置词一个随机初始化的词向量,再随他们训练去吧(很想说这特喵的也能work?太简单粗暴了吧。。。)。另外,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又引入了另一个极其重要却又极其轻量级的任务,来试图把这种模式也学习到。

句子级负采样

还记得小夕在前面word2vec章节说过的,word2vec的一个精髓是引入了一个优雅的负采样任务来学习词向量(word-level representation)嘛。那么如果我们把这个负采样的过程给generalize到sentence-level呢?这便是BERT学习sentence-level representation的关键啦。

BERT这里跟word2vec做法类似,不过构造的是一个句子级的分类任务。即首先给定的一个句子(相当于word2vec中给定context),它下一个句子即为正例(相当于word2vec中的正确词),随机采样一个句子作为负例(相当于word2vec中随机采样的词),然后在该sentence-level上来做二分类(即判断句子是当前句子的下一句还是噪声)。通过这个简单的句子级负采样任务,BERT就可以像word2vec学习词表示那样轻松学到句子表示啦。

句子级表示

等等,前面说了这么半天,还没有说句子该怎么表示呢。。。

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也是随模型训练出来的。

ps: 这做法跟position embedding一样感觉简单粗暴,实在很费解为什么BERT用在“quora question pairs”这种理论上需要网络保持对称的任务上依然能work,心情复杂

所以最终BERT每个token的表示由token原始的词向量token embedding、前文提到的position embedding和这里的segment embedding三部分相加而成,如图。

简洁到过分的下游任务接口

真正体现出BERT这个模型是龙骨级模型而不再是词向量的,就是其到各个下游任务的接口设计了,或者换个更洋气的词叫迁移策略。

首先,既然句子和句子对的上层表示都得到了,那么当然对于文本分类任务和文本匹配任务(文本匹配其实也是一种文本分类任务,只不过输入是文本对)来说,只需要用得到的表示(即encoder在[CLS]词位的顶层输出)加上一层MLP就好了呀~

既然文本都被深度双向encoding了,那么做序列标注任务就只需要加softmax输出层就好了呀,连CRF都不用了呀~

让小夕更木有想到的是,在span抽取式任务如SQuAD上,把深度encoding和深度attention这俩大礼包省掉就算了,甚至都敢直接把输出层的pointer net给丢掉了?直接像DrQA那样傲娇的用两个线性分类器分别输出span的起点和终点?不多说了,已跪。

最后来看一下实验效果

嗯,这很Google。

此论文一出,小夕非常开心,因为很多之前的想法都不用去做实验验证了,因为已经被BERT摁死了(。 ́︿ ̀。)分类、标注和迁移任务都可以从头开始了,SQuAD的造楼计划也可以停了,感谢BERT没有跑生成任务,这给人带来了一点想象空间。嗯,手动微笑流泪。

最后,喜欢小夕的小哥哥小姐姐们欢迎通过下方打赏按钮或者点击下方小广告鼓励小夕哦,爱你们🌹🌹~

参考文献

[1] 2018 | BERT- Pre-training of Deep Bidirectional Transformers for Language Understanding

[2] 2018NAACL | Deep contextualized word representations

[3] 2018 ACL | Multi-Turn Response Selection for Chatbots with Deep Attention Matching Network

[4] 2018ICLR | Fast and Accurate Reading Comprehension by Combining Self-Attention and Convolution

[5] 2017TACL | Enriching Word Vectors with Subword Information

[6] 2017ACL | Deep Pyramid Convolutional Neural Networks for Text Categorization

[7] 2017 | Convolutional Sequence to Sequence Learning

[8] 2017 | Do Convolutional Networks need to be Deep for Text Classification ?

[9] 2016 | Convolutional Neural Networks for Text Categorization/ Shallow Word-level vs. Deep Character-level

[10] 2013NIPS | Distributed-representations-of-words-and-phrases-and-their-compositionality

5. 基于BERT进行fine-tuning

1
2


1
2


1
2