pre-train

2019/08/10

从现在的大趋势来看,使用某种模型预训练一个语言模型看起来是一种比较靠谱的方法。从之前AI2的 ELMo,到 OpenAI的fine-tune transformer,再到Google的这个BERT,全都是对预训练的语言模型的应用。

BERT这个模型与其它两个不同的是:

1、BERT在训练双向语言模型时以减小的概率少量的词替成了Mask或者另一个随机的词。我个人感觉这个目的在于使模型被迫增加对上下文的记忆。至于这个概率,我猜是Jacob拍脑袋随便设的。 2、增加了一个预测下一句的loss。这个看起来就比较新奇了。

BERT模型具有以下两个特点:

第一,是这个模型非常的深,12层,并不宽(wide),中间层只有1024,而之前的Transformer模型中间层有2048。这似乎又印证了计算机图像处理的一个观点——深而窄浅而宽 的模型更好。

第二,MLM(Masked Language Model),同时利用左侧和右侧的词语,这个在ELMo上已经出现了,绝对不是原创。其次,对于Mask(遮挡)在语言模型上的应用,已经被Ziang Xie提出了:[1703.02573] Data Noising as Smoothing in Neural Network Language Models。

通常情况 transformer 模型有很多参数需要训练。譬如 BERT BASE 模型: L=12, H=768, A=12, 需要训练的模型参数总数是 12 * 768 * 12 = 110M。这么多参数需要训练,自然需要海量的训练语料。如果全部用人力标注的办法,来制作训练数据,人力成本太大。

预训练

图像领域做预训练:

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

目前我们已经知道,对于层级的CNN结构来说,不同层级的神经元学习到了不同类型的图像特征由底向上特征形成层级结构

如果我们手头是个人脸识别任务,训练好网络后,把每层神经元学习到的特征可视化肉眼看一看每层学到了啥特征:

最底层的神经元学到的是线段等特征,

第二个隐层学到的是人脸五官的轮廓

第三层学到的是人脸的轮廓

通过三步形成了特征的层级结构,越是底层的特征越是所有不论什么领域的图像都会具备的比如边角线弧线等底层基础特征越往上抽取出的特征越与手头任务相关

正因为此,所以预训练好的网络参数,尤其是底层的网络参数抽取出特征跟具体任务越无关越具备任务的通用性,所以这是为何一般用底层预训练好的参数初始化新任务网络参数的原因。而高层特征跟任务关联较大,实际可以不用使用,或者采用 Fine-tuning 用新数据集合清洗掉高层无关的特征抽取器。

一般NLP里面做预训练的选择是用语言模型任务来做。什么是语言模型?为了能够量化地衡量哪个句子更像一句人话。 核心函数P的思想:根据句子里面前面的一系列前导单词(context-before),预测后面跟哪个单词的概率大小(理论上除了上文(context-before)之外,也可以引入单词的下文(context-after)联合起来预测单词出现概率)。句子里面每个单词都有个根据上文预测自己的过程,把所有这些单词的产生概率乘起来,数值越大代表这越像一句人话。

从Word Embedding到ELMO

Word Embedding其实对于很多下游NLP任务是有帮助的,只是帮助没有大到闪瞎忘记戴墨镜的围观群众的双眼而已。

为什么这样训练及使用Word Embedding的效果没有期待中那么好呢?因为Word Embedding有问题。

Word Embedding存在什么问题?多义词问题。比如多义词Bank,有两个常用含义,但是Word Embedding在对bank这个单词进行编码的时候,是区分不开这两个含义的,因为它们尽管上下文环境中出现的单词不同,但是在用语言模型训练的时候,不论什么上下文的句子经过word2vec,都是预测相同的单词bank,而同一个单词占的是同一行的参数空间,这导致两种不同的上下文信息都会编码到相同的word embedding空间里去。所以word embedding无法区分多义词的不同语义,这就是它的一个比较严重的问题。ELMO提供了一种简洁优雅的解决方案。

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

  • 目前语言模型训练的任务目标是根据单词 的上下文去正确预测单词 之前的单词序列 Context-before 称为上文,之后的单词序列Context-after称为下文

  • 图中左端的前向双层LSTM代表正方向编码器,输入的是从左到右顺序的除了预测单词外 的上文Context-before;右端的逆向双层LSTM代表反方向编码器,输入的是从右到左的逆序的句子下文Context-after;

  • 每个编码器的深度都是两层LSTM叠加。这个网络结构其实在NLP中是很常用的。

  • 使用这个网络结构利用大量语料做语言模型任务就能预先训练好这个网络,如果训练好这个网络后,输入一个新句子,句子中每个单词都能得到对应的三个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个任务的覆盖范围比较广,包含句子语义关系判断,分类任务,阅读理解等多个领域,这说明其适用范围是非常广的,普适性强,这是一个非常好的优点。

ELMO有什么值得改进的缺点呢?

  1. 首先,一个非常明显的缺点在特征抽取器选择方面,ELMO使用了LSTM而不是新贵Transformer,Transformer是谷歌在17年做机器翻译任务的“Attention is all you need”的论文中提出的,引起了相当大的反响,很多研究已经证明了Transformer提取特征的能力是要远强于LSTM的。如果ELMO采取Transformer作为特征提取器,那么估计Bert的反响远不如现在的这种火爆场面。
  2. 另外一点,ELMO采取双向拼接这种融合特征的能力可能比Bert一体化的融合特征方式弱,但是,这只是一种从道理推断产生的怀疑,目前并没有具体实验说明这一点。

我们如果把ELMO这种预训练方法和图像领域的预训练方法对比,发现两者模式看上去还是有很大差异的。

除了以ELMO为代表的这种基于特征融合的预训练方法外,NLP里还有一种典型做法,这种做法和图像领域的方式就是看上去一致的了,一般将这种方法称为“基于Fine-tuning的模式”,而GPT就是这一模式的典型开创者。

从Word Embedding到GPT

GPT 是 “Generative Pre-Training” 的简称,从名字看其含义是指的生成式的预训练

GPT也采用两阶段过程:

  • 第一个阶段是利用语言模型进行预训练

  • 第二阶段通过 Fine-tuning 的模式解决下游任务。

上图展示了 GPT 的预训练过程,其实和 ELMO 是类似的,主要不同在于两点:

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

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

上图展示了GPT在第二阶段如何使用。

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

GPT论文给了一个改造施工图如上:

  • 对于分类问题,不用怎么动,加上一个起始和终结符号即可;
  • 对于句子关系判断问题,比如Entailment,两个句子中间再加个分隔符即可;
  • 对于文本相似性判断问题,把两个句子顺序颠倒下做出两个输入即可,这是为了告诉模型句子顺序不重要;
  • 对于多项选择问题,则多路输入,每一路把文章和答案选项拼接作为输入即可。

从上图可看出,这种改造还是很方便的,不同任务只需要在输入部分施工即可。

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

GPT有什么值得改进的地方呢?

其实最主要的就是那个单向语言模型,如果改造成双向的语言模型任务估计也没有Bert太多事了。当然,即使如此GPT也是非常非常好的一个工作,跟Bert比,其作者炒作能力亟待提升。

Bert的诞生

Bert — Bidirectional Encoder Representations from Transformers

Bert采用和GPT完全相同的两阶段模型:

  • 首先是语言模型预训练;

  • 其次是使用Fine-Tuning模式解决下游任务。

和GPT的最主要不同在于:

  • 在预训练阶段采用了类似ELMO的双向语言模型
  • 当然另外一点是语言模型的数据规模要比GPT大。

第二阶段,Fine-Tuning阶段,这个阶段的做法和GPT是一样的。当然,它也面临着下游任务网络结构改造的问题。

在改造任务方面Bert和GPT有些不同,下面简单介绍一下。在介绍Bert如何改造下游任务之前,先大致说下NLP的几类问题,说这个是为了强调Bert的普适性有多强。通常而言,绝大部分NLP问题可以归入上图所示的四类任务中:

  • 一类是序列标注,这是最典型的NLP任务,比如中文分词,词性标注,命名实体识别,语义角色标注等都可以归入这一类问题,它的特点是句子中每个单词要求模型根据上下文都要给出一个分类类别
  • 第二类是分类任务,比如我们常见的文本分类,情感计算等都可以归入这一类。它的特点是不管文章有多长,总体给出一个分类类别即可
  • 第三类是句子关系判断,比如Entailment,QA,语义改写,自然语言推理等任务都是这个模式,它的特点是给定两个句子,模型判断出两个句子是否具备某种语义关系
  • 第四类是生成式任务,比如机器翻译,文本摘要,写诗造句,看图说话等都属于这一类。它的特点是输入文本内容后,需要自主生成另外一段文字

对于种类如此繁多而且各具特点的下游NLP任务,Bert如何改造输入输出部分使得大部分NLP任务都可以使用Bert预训练好的模型参数呢?

  • 对于句子关系类任务,很简单,和GPT类似,加上一个起始和终结符号,句子之间加个分隔符即可。对于输出来说,把第一个起始符号对应的Transformer最后一层位置上面串接一个softmax分类层即可。BERT直接取第一个[CLS] token 的 final hidden state , 加一层权重

  • 对于分类问题,与GPT一样,只需要增加起始和终结符号,输出部分和句子关系判断任务类似改造;

  • 对于序列标注问题,输入部分和单句分类是一样的,只需要输出部分Transformer最后一层每个单词对应位置都进行分类即可。

  • 从这里可以看出,上面列出的NLP四大任务里面,除了生成类任务外,Bert其它都覆盖到了,而且改造起来很简单直观。尽管Bert论文没有提,但是稍微动动脑子就可以想到,其实对于机器翻译或者文本摘要,聊天机器人这种生成式任务,同样可以稍作改造即可引入Bert的预训练成果。只需要附着在S2S结构上,encoder部分是个深度Transformer结构,decoder部分也是个深度Transformer结构。根据任务选择不同的预训练数据初始化encoder和decoder即可。这是相当直观的一种改造方法。当然,也可以更简单一点,比如直接在单个Transformer结构上加装隐层产生输出也是可以的。

不论如何,从这里可以看出,NLP四大类任务都可以比较方便地改造成Bert能够接受的方式。这其实是Bert的非常大的优点,这意味着它几乎可以做任何NLP的下游任务,具备普适性,这是很强的。

在11个各种类型的NLP任务中达到目前最好的效果,某些任务性能有极大的提升。

Bert其实和ELMO及GPT存在千丝万缕的关系:

  • 如果我们把GPT预训练阶段换成双向语言模型,那么就得到了Bert;
  • 如果我们把ELMO的特征抽取器换成Transformer,那么我们也会得到Bert。
  • Bert最关键两点
    • 第一点是特征抽取器采用Transformer
    • 第二点是预训练的时候采用双向语言模型

那么新问题来了:对于Transformer来说,怎么才能在这个结构上做双向语言模型任务呢?

CBOW的核心思想是:在做语言模型任务的时候,我把要预测的单词抠掉,然后根据它的上文Context-Before和下文Context-after去预测单词。

其实Bert怎么做的?Bert就是这么做的。

那么Bert本身在模型和方法角度有什么创新呢?

  • Masked LM(MLM),本质思想其实是CBOW,细节方面有改进
  • Next Sentence Prediction。

Masked双向语言模型这么做:

  • 随机选择语料中15%的单词,把它抠掉,也就是用[Mask]掩码代替原始单词,然后要求模型去正确预测被抠掉的单词。
  • 但是这里有个问题:训练过程大量看到[mask]标记,但是真正后面用的时候是不会有这个标记的,这会引导模型认为输出是针对[mask]这个标记的,但是实际使用又见不到这个标记,这自然会有问题。
    • 为了避免这个问题,Bert改造了一下,15%的被上天选中要执行[mask]替身这项光荣任务的单词中,只有80%真正被替换成[mask]标记,10%被狸猫换太子随机替换成另外一个单词,10%情况这个单词还待在原地不做改动。这就是Masked双向语音模型的具体做法。
  • 在训练过程中作者随机mask 15%的token,而不是把像cbow一样把每个词都预测一遍。最终的损失函数只计算被mask掉那个token。
  • 注意:Masked LM预训练阶段模型是不知道真正被mask的是哪个词,所以模型每个词都要关注。

因为序列长度太大(512)会影响训练速度,所以90%的steps都用seq_len=128训练,余下的10%步数训练512长度的输入。

标准条件语言模型只能从左到右或从右到左进行训练,因为双向条件作用将允许每个单词在多层上下文中间接地“see itself”。为了训练一个深度双向表示(deep bidirectional representation),研究团队采用了一种简单的方法,即随机屏蔽(masking)部分输入token,然后只预测那些被屏蔽的token。论文将这个过程称为“masked LM”(MLM),尽管在文献中它经常被称为Cloze任务(Taylor, 1953)。

在将单词序列输入给 BERT 之前,每个序列中有 15% 的单词被 [MASK] token 替换。 然后模型尝试基于序列中其他未被 mask 的单词的上下文来预测被掩盖的原单词。这样就需要:

  1. 在 encoder 的输出上添加一个分类层
  2. 用嵌入矩阵乘以输出向量,将其转换为词汇的维度
  3. 用 softmax 计算词汇表中每个单词的概率

“Next Sentence Prediction”(NSP),指的是做语言模型预训练的时候,分两种情况选择两个句子,一种是选择语料中真正顺序相连的两个句子;另外一种是第二个句子从语料库中抛色子,随机选择一个拼到第一个句子后面。我们要求模型除了做上述的Masked语言模型任务外,附带再做个句子关系预测,判断第二个句子是不是真的是第一个句子的后续句子。之所以这么做,是考虑到很多NLP任务是句子关系判断任务,单词预测粒度的训练到不了句子关系这个层级,增加这个任务有助于下游句子关系判断任务。所以可以看到,它的预训练是个多任务过程。这也是Bert的一个创新。

在 BERT 的训练过程中,模型接收成对的句子作为输入,并且预测其中第二个句子是否在原始文档中也是后续句子。 在训练期间,50% 的输入对在原始文档中是前后关系,另外 50% 中是从语料库中随机组成的,并且是与第一句断开的。

为了帮助模型区分开训练中的两个句子,输入在进入模型之前要按以下方式进行处理:

  1. 在第一个句子的开头插入 [CLS] 标记,在每个句子的末尾插入 [SEP] 标记。
  2. 将表示句子 A 或句子 B 的一个句子 embedding 添加到每个 token 上。
  3. 给每个 token 添加一个位置 embedding,来表示它在序列中的位置。

为了预测第二个句子是否是第一个句子的后续句子,用下面几个步骤来预测:

  1. 整个输入序列输入给 Transformer 模型
  2. 用一个简单的分类层将 [CLS] 标记的输出变换为 2×1 形状的向量
  3. 用 softmax 计算 IsNextSequence 的概率

在训练 BERT 模型时,Masked LM 和 Next Sentence Prediction 是一起训练的,目标就是要最小化两种策略的组合损失函数。

它的输入部分是个线性序列,两个句子通过分隔符分割,最前面和最后增加两个标识符号

每个单词有三个embedding:

  • 位置信息 embedding,这是因为NLP中单词顺序是很重要的特征,需要在这里对位置信息进行编码,不是三角函数而是学习出来的;
  • 单词 embedding,这个就是我们之前一直提到的单词 embedding;
  • 句子 embedding,因为前面提到训练数据都是由两个句子构成的,那么每个句子有个句子整体的embedding项对应给每个单词。

把单词对应的三个embedding叠加,就形成了Bert的输入。

我们说过Bert效果特别好,那么到底是什么因素起作用呢?

  • 跟GPT相比,双向语言模型起到了最主要的作用,对于那些需要看到下文的任务来说尤其如此。
  • 而预测下个句子来说对整体性能来说影响不算太大,跟具体任务关联度比较高。

Bert 缺点:作者在文中主要提到的就是MLM预训练时的mask问题:

  1. [MASK]标记在实际预测中不会出现,训练时用过多[MASK]影响模型表现
  2. 每个batch只有15%的token被预测,所以BERT收敛得比left-to-right模型要慢(它们会预测每个token)

预训练本质上是通过设计好一个网络结构来做语言模型任务,然后把大量甚至是无穷尽的无标注的自然语言文本利用起来,预训练任务把大量语言学知识抽取出来编码到网络结构中,当手头任务带有标注信息的数据有限时,这些先验的语言学特征当然会对手头任务有极大的特征补充作用,因为当数据有限的时候,很多语言学现象是覆盖不到的,泛化能力就弱,集成尽量通用的语言学知识自然会加强模型的泛化能力。

如何引入先验的语言学知识其实一直是NLP尤其是深度学习场景下的NLP的主要目标之一,不过一直没有太好的解决办法,而ELMO/GPT/Bert的这种两阶段模式看起来无疑是解决这个问题自然又简洁的方法,这也是这些方法的主要价值所在。

Reference

Post Directory