生成式对话seq2seq:从rnn到transformer

一、前言

最近因为做对话机器人的原因,看了一下seq2seq。不禁感慨,自由对话机器人的水好深呀。

查阅了一些市面上能看到资料,工业上的做法,普遍是 基础模板(例如 aiml)+IR闲聊库(例如 小黄鸡语料QA)+爬虫(百度、搜狗)+知识图谱(wiki百科)+对话生成模型。

aiml模板就不说了,网上有很多的资料,效果上来说,比较智障。人工编写模板的工作量也大,也不能覆盖很多的回答。

IR闲聊库的方法,理论上来说,面对单轮闲聊的话,能产生流畅的回复,通俗的说就是能说人话,毕竟是从正常的对话记录中去检索的。实现上的话,方式就很多了,词面上的编辑距离,tfidf,bm25等等。模型上就是Response Selection in Retrieval-Based Chatbots这类的。不过后面出现了多轮的Retrieval-Based Chatbots,诸如:SMN(Sequential Matching Network)、DAM(Deep Attention Matching Network)、IMN(Interactive Matching Network)和MRFN(Multi-Representation Fusion Network)这些多轮检索式对话。因为,不是本文所要讨论的话题,所以,具体的大家去看论文吧。

参考:https://zhuanlan.zhihu.com/p/65062025

https://zhuanlan.zhihu.com/p/46366940

https://www.jianshu.com/p/01a0ec0370c4

爬虫类的,就不说了,具体的大家可以看一下:QA-Snake(https://github.com/SnakeHacker/QA-Snake)

图谱的话,网上资料很多,我这里简单说一下我知道的一般的实现方案:

1)NER,获取需要的实体词,具体可以分为非监督的实现(模板,词典),监督的实现(lstm+crf,crf)。

2)将得到的实体词,送入图谱检索到可能三元组集合。

3)将三元组集合的属性和问句进行打分(其实就是做一个2分类),选出合适的三元组。

 

二、seq2seq

接下来,就是本文主要说的内容了seq2seq。seq2seq也就是encoder2decoder。

生成式对话seq2seq:从rnn到transformer_第1张图片

Seq2Seq(Sequence to Sequence), 就是一种能够根据给定的序列,通过特定的方法生成另一个序列的方法。它被提出于2014年,最早由两篇文章独立地阐述了它主要思想,分别是Google Brain团队的《Sequence to Sequence Learning with Neural Networks》(https://arxiv.org/pdf/1409.3215.pdf)和Yoshua Bengio团队的《Learning Phrase Representation using RNN Encoder-Decoder for Statistical Machine Translation》(https://arxiv.org/pdf/1406.1078.pdf)。

同年9月,随着Bahdanau的《Neural Machine Translation by Jointly Learning to Align and Translate》(https://arxiv.org/pdf/1409.0473.pdf)横空出世,也就是大名鼎鼎的NMT模型,随着attention被引入到seq2seq模型,从而解决原来rnn长序列的long-term问题,模型准确度随之得到提升。

下面,我们根据Pytorch的官方文档https://pytorch.org/tutorials/beginner/chatbot_tutorial.html,通过GRU+Attention的样例来熟悉一下seq2seq的模型吧。

 seq2seq模型的输入是一个变长的序列,而输出也是一个变长的序列。而且这两个序列的长度并不相同。一般我们使用RNN来处理变长的序列,一般来说两个RNN可以解决这类问题。

生成式对话seq2seq:从rnn到transformer_第2张图片

 

Encoder是一个RNN,它会遍历输入的每一个Token(词),每个时刻的输入是上一个时刻的隐状态和输入,然后会有一个输出和新的隐状态。这个新的隐状态会作为下一个时刻的输入隐状态。每个时刻都有一个输出,对于seq2seq模型来说,我们通常只保留最后一个时刻的隐状态,认为它编码了整个句子的语义。Encoder处理结束后会把最后一个时刻的隐状态作为Decoder的初始隐状态。

但是后面我们会用到Attention机制,它还会用到Encoder每个时刻的输出。

官方文档中采用的是双向Gated Recurrent Unit(GRU)作为Encoder。

这里需要说一下一般都会说的小知识点:

1)因为RNN机制实际中存在长程梯度消失的问题,seq2seq引入attention

2)使用拥有gate的lstm避免RNN的梯度消失问题

3)GRU因为使用更少的门,模型参数更小,因为比lstm效率好

Decoder也是一个RNN,它每个时刻输出一个词。每个时刻的输入是上一个时刻的隐状态和上一个时刻的输出。一开始的隐状态是Encoder最后时刻的隐状态,输入是特殊的。然后使用RNN计算新的隐状态和输出第一个词,接着用新的隐状态和第一个词计算第二个词,...,直到遇到EOS,结束输出。

 

三、attention

因为上面小知识点第一个所说的问题,是当输入句子很长的时候,encoder的效果会很差,利用隐状态(context向量)来编码输入句子的语义实际上是很困难的。为了解决这个问题,Bahdanau等人提出了注意力机制(attention mechanism)。于是在Decoder进行t时刻计算的时候,除了t-1时刻的隐状态,当前时刻的输入,注意力机制还可以参考Encoder所有时刻的输入。

attention是一个值得聊一下的话题,下面来仔细说一下。

Attention函数的本质可以被描述为一个查询(query)到一系列(键key-值value)对的映射,如下图。

生成式对话seq2seq:从rnn到transformer_第3张图片

在计算attention时主要分为三步,第一步是将query和每个key进行相似度计算得到权重,常用的相似度函数有点积,拼接,感知机等;然后第二步一般是使用一个softmax函数对这些权重进行归一化;最后将权重和相应的键值value进行加权求和得到最后的attention。

上面所说的score函数中,dot就是简单的hidden*encoder outputs,注意这里是用的dot点乘,就是对应位置元素相乘再求和,所以两个向量点乘的结果是一个数。general将encoder output用一个全连接编码后再和hidden点乘。concat是将hidden和outputs连接后再点乘一个随机生成的向量。

生成式对话seq2seq:从rnn到transformer_第4张图片

 

简而言之,attention实际上就是一种取权重求和的过程。结果是使得 跟 当前词 有关的 前文的 部分词 具有较强的权重。

attention有很多种形态,大家通常会说5种:hard attention,soft attention,Global Attention,local attention,self attention。

hard attention

Hard Attention不会选择整个encoder的隐层输出做为其输入,它在t时刻会依多元伯努利分布来采样输入端的一部分隐状态来进行计算,而不是整个encoder的隐状态。在 hard attention 里面,每个时刻 t 模型的序列 [ St1,…,StL ] 只有一个取 1,其余全部为 0,是 one-hot 形式。也就是说每次只focus 一个位置。

soft attention

对于hard attention,soft attention 每次会照顾到全部的位置,只是不同位置的权重不同罢了。

global attention

global attention 顾名思义,forcus 全部的 position。它 和 local attention 的主要区别在于 attention 所 forcus 的 source positions 数目的不同。

Local Attention:

Local Attention只 focus 一部分 position,则为 local attention

global attention 的缺点在于每次都要扫描全部的 source hidden state,计算开销较大,为了提升效率,提出 local attention,每次只 focus 一小部分的 source position。

self attention

因为transformer现在已经在nlp领域大放异彩,所以,我这里打算详细的说一下transformer的核心组件self-attention,虽然网上关于self-attention的描述已经很多了。

说起self-attention的提出,是因为rnn存在非法并行计算的问题,而cnn存在无法捕获长距离特征的问题,因为既要又要的需求,当看到attention的巨大优势,《Attention is all you need》的作者决定用attention机制代替rnn搭建整个模型,于是Multi-headed attention,横空出世。

Self-attention的意思就是自己和自己做 attention,即K=V=Q,例如输入一个句子,那么里面的每个词都要和该句子中的所有词进行attention计算。目的是学习句子内部的词依赖关系,捕获句子的内部结构。

而Multi-head Attention其实就是多个Self-Attention结构的结合,每个head学习到在不同表示空间中的特征。

 

生成式对话seq2seq:从rnn到transformer_第5张图片

multi-head attention 由多个 scaled dot-product attention 这样的基础单元经过 stack 而成。那什么是scaled dot-product attention,字面意思:放缩点积注意力。使用点积进行相似度计算的attention,只是多除了一个(为K的维度)起到调节作用,使得内积不至于太大。scaled dot-product attention跟使用点积进行相似度计算的attention差不多,只是多除了一个(为K的维度)起到调节作用,使得内积不至于太大,有利于训练的收敛。

 

Query,Key,Value 做embedding,获得相应的word embedding,然后输入到放缩点积attention,注意这里要做h次,其实也就是所谓的多头,每一次算一个头。而且每次Q,K,V进行线性变换的参数W是不一样的。然后将h次的放缩点积attention结果进行拼接,再进行一次线性变换得到的值作为多头attention的结果。可以看到,google提出来的多头attention的不同之处在于进行了h次计算而不仅仅算一次,论文中说到这样的好处是可以允许模型在不同的表示子空间里学习到相关的信息。论文里有attention可视化的图,可以去看看。

生成式对话seq2seq:从rnn到transformer_第6张图片

self-attention的特点在于无视词之间的距离直接计算依赖关系,能够学习一个句子的内部结构,实现也较为简单并行可以并行计算。

如果你还想了解更多的attention,请查看:https://zhuanlan.zhihu.com/p/73357761

 

四、transformer

讲到transformer,我就想到张俊林老师《放弃幻想,全面拥抱Transformer》(https://zhuanlan.zhihu.com/p/54743941)的一句话:“Transformer考上了北京大学;CNN进了中等技术学校,希望有一天能够考研考进北京大学;RNN在百货公司当售货员:我们都有看似光明的前途。”

生成式对话seq2seq:从rnn到transformer_第7张图片

 

网上关于transformer的解读已经很多了,我就不详细说了,具体的细节推荐大家看《NLP预训练模型:从transformer到albert》(https://zhuanlan.zhihu.com/p/85221503),我就说一说一些需要注意的点吧。

1)结构上transformer由encoder和decoder组成,decoder比encoder多了一个Multi-headed attention层。同时,这个Multi-headed attention的k,v来自encoder的输出,而q来自于前一级的decoder层的输出。

2)Self-Attention则利用了Attention机制,计算每个单词与其他所有单词之间的关联。但是没有recurrence或convolution操作,所以没有明确的关于单词在源句子中位置的相对或绝对的信息,为了更好的让模型学习位置信息,所以添加了position encoding并将其叠加在word embedding上。

3)Add代表了Residual Connection,是为了解决多层神经网络训练困难的问题,通过将前一层的信息无差的传递到下一层,可以有效的仅关注差异部分。解决网络退化问题。

4)Norm则代表了Layer Normalization,通过对层的激活值的归一化,可以加速模型的训练过程,使其更快的收敛

5)在multi-head attention后面加一个ffn,可以提高整个block的非线性变换的能力。

比单独使用multi-head attention的效果更好。

 

 

五、bert和gpt之后的seq2seq

5.1 bert

2018年10月底,google的bert公布,bert一举打破11项nlp任务,并且大幅提升效果,成为nlp的highlight时刻,并被人称为nlp界的Restnet。于是nlp界开始关注语言模型预训练+少量数据下游任务调优的开发模式。

关于bert的解读和后续发展,世面上已经有很多的解读了,这里就不浪费大家时间重复描述了,如果你有兴趣可以阅读:https://zhuanlan.zhihu.com/p/68295881,https://zhuanlan.zhihu.com/p/68362016。我这里就简单说一下可能需要关注的点吧。

bert的优点和缺点

1)bert使用的是transformer的encoder部分。

2)bert的Embeding由三种Embedding求和而成,其中Position Embeddings和之前文章中的Transformer不一样,不是三角函数而是学习出来的,而是一个大小为 (512, 768)的lookup表。

3)bert比transformer多了Segment Embeddings,因为预训练不光做LM还要做以两个句子为输入的分类任务

4)预训练使用mask LM,就是在训练过程中作者随机mask 15%的token,最终的损失函数只计算被mask掉那个token。

5)Next Sentence Prediction,不过后来被证明效果不行,所以就不说了。

在bert大放异彩之后,后来出现了很多模型。如xlnet,spanbert,roberta,T5,除了带来了更巧妙的tricks还有就是继续着大力出奇迹的故事。

 

5.2 bert的压缩提速

虽然bert之后,bert的后继者还在续写着SOTA,但是bert们的大计算量,显存消耗,训练消耗却让工业界望而却步,无法及时的享受bert们的红利。于是乎,bert模型的压缩和提速被安排上了日程。

针对bert的压缩主要有3个方向,Pruning,Distill,Quantization。而我这里,主要说一下DistilBERT,ALBERT,TinyBERT3个模型。

首先需要说的是:DistilBERT

DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter

论文:https://arxiv.org/abs/1910.01108

distillbert采用传统的蒸馏方案,并将bert的block减少到6个。

先说结论:模型大小减小了40%(66M),推断速度提升了60%,但性能只降低了约3%。

具体做法:

1)给定原始的bert-base作为teacher网络。

2)在bert-base的基础上将网络层数减半。

3)利用teacher的软标签和teacher的隐层参数来训练student网络。

训练时的损失函数定义为三种损失函数的线性和,三种损失函数分别为:

1)Lce。这是teacher网络softmax层输出的概率分布和student网络softmax层输出的概率分布的交叉熵。

2)Lmlm。这是student网络softmax层输出的概率分布和真实的one-hot标签的交叉熵。

3)Lcos。这是student网络隐层输出和teacher网络隐层输出的余弦相似度值。

 

接着,我们来谈一下ALBERT

ALBERT: A Lite BERT for Self-supervised Learning of Language Representations

论文:https://arxiv.org/abs/1909.11942v2

代码:https://github.com/brightmart/albert_zh

albert从3个方面对bert进行了改造,但是很遗憾的是,没有提升模型的运算速度。

1、对Embedding因式分解(Factorized embedding parameterization)。将embedding维度从768下降到128。

2.跨层的参数共享(Cross-layer parameter sharing)。对全连接层与attention层都进行参数共享,即共享了encoder中所有的参数。效果显著,以base为例,BERT的参数是108M,而ALBERT仅有12M,效果仅降低2个点。

3、句间连贯(Inter-sentence coherence loss)。在BERT中,句子间关系的任务是next sentence predict(NSP)。作者认为NSP任务只学到了句子的主题,而主题分类是一个浅层语义的NLP任务,所以nsp没有什么效果。而albert 句子间关系的任务是sentence-order prediction(SOP),即句子间顺序预测。这里因为负例中的句子都来自于同一文档,所以通过句子的主题是无法区分正例和负例的,需要理解句子的深层语义才能区分。

 

最后是TinyBERT

TinyBERT: Distilling BERT for Natural Language Understanding

论文:https://arxiv.org/abs/1909.10351

TinyBERT提出了针对Transformer结构的知识蒸馏和针对pre-training和fine-tuning两阶段的知识蒸馏。

1.对transfomer做蒸馏,作者构造了四类损失,分别针对embedding layer,attention 权重矩阵,隐层输出,predict layer。

2.针对pre-training和fine-tuning两阶段的知识蒸馏,对pre-training蒸馏之外,在fine-tuning时也利用teacher的知识来训练模型可以取得在下游任务更好的效果。

本质上就是在pre-training蒸馏一个general TinyBERT,然后再在general TinyBERT的基础上利用task-bert上再蒸馏出fine-tuned TinyBERT。

3.albert的模型是6层block。

生成式对话seq2seq:从rnn到transformer_第8张图片

这里需要特别说一下的是TinyAlbert,训练和推理预测速度提升约10倍,精度基本保留,模型大小为bert的1/25;语义相似度数据集LCQMC测试集上达到85.4%,相比bert_base仅下降1.5个点。我已经试用过,效果确实很赞。代码地址:https://github.com/brightmart/albert_zh

 

5.3bert和GPT的seq2seq

上面说了一大堆bert相关的内容。下面回归我们的正题,因为,我们讨论的还是seq2seq模型。因为bert是使用transformer的Encoder模块作为block,所以bert不是一个seq2seq的结构。所以,就有一些bert+Decoder(使用lstm或者gru)的结构在做seq2seq。但是效果上很一般。那么有没有使用bert的优雅的方式呢?答案是有的。

 

微软提出了一个通用预训练模型MASS(代码:论文:https://arxiv.org/abs/1905.02450,https://github.com/microsoft/MASS),采用了联合encoder-attention-decoder的训练框架。

Encoder: 输入为被随机mask掉连续部分token的句子,使用双向Transformer对其进行编码;这样处理的目的是可以使得encoder可以更好地捕获没有被mask掉词语信息用于后续decoder的预测;

Decoder: 输入为与encoder同样的句子,但是mask掉的正好和encoder相反(后面试验直接将masked tokens删去保留unmasked tokens position embedding),使用attention机制去训练,但只预测encoder端被mask掉的词。该操作可以迫使decoder预测的时候更依赖于source端的输入而不是前面预测出的token,同时减缓了传统seq2seq结构的exposure bias问题。

生成式对话seq2seq:从rnn到transformer_第9张图片

但是遗憾的是,MASS虽然在多个任务上拉高了SOTA,但是在对话生成任务上效果一般。

 

微软随后又推出了UNILM(论文:https://arxiv.org/abs/1905.03197,代码:https://github.com/microsoft/unilm),相比较而言,MASS的工作是将BERT整合到seq2seq框架上,而UNILM则是将seq2seq整合到BERT的框架上,利用调整mask矩阵的设置在同一个框架下训练不同任务,可同时用于NLU和NLG任务。NLU用BERT做,NLG则把BERT的 S1 [SEP] S2 当成 encoder-decoder。

生成式对话seq2seq:从rnn到transformer_第10张图片

具体的UNILM模型有以下3个优点:

(1)统一的预训练流程,使得仅仅使用一个Transformer语言模型即可。该Transformer模型在不同的LM(上述Table 2中的3个LM)上共享参数,这就无需在多个LM上分别训练和配置。

(2)多个LM之间的参数共享使得学习到的文本表征具有更强的泛化能力。在不同的语言模型目标上联合优化,使得上下文在不同方式中被使用,也减缓了在单一LM上的过拟合。

(3)除了可以应用到自然语言理解任务上,本文模型还能够作为一个sequence-to-sequence LM来处理自然语言生成任务,如摘要生成和问题生成。

 

下面来说说在文本生成上有着惊艳的表现的OpenAI 的GPT-2,其生成的文本在上下文连贯性和情感表达上都超过了人们对目前阶段语言模型的预期。我们前面说了BERT是通过transformer的encoder模块构建的,而GPT-2是使用transformer的Decoder构建的。需要指出的是,二者一个很关键的不同之处在于:GPT-2 就像传统的语言模型一样,一次只输出一个token (训练时是同时输入多个token)。

生成式对话seq2seq:从rnn到transformer_第11张图片

OpenAI 的 GPT-2 模型是只包含transfomer编码器(decoder-only)的模块,去除了第二个自注意力层。

生成式对话seq2seq:从rnn到transformer_第12张图片

GPT-2 使用的带掩模的自注意力模块,带掩模的自注意力模块不允许一个位置看到它右侧单词的信息。这种模型之所以效果好是因为在每个新单词产生后,该单词就被添加在之前生成的单词序列后面,这个序列会成为模型下一步的新输入。这种机制叫做自回归(auto-regression)。

对于GPT-2在对话生成上的应用,大家可以参考一下https://zhuanlan.zhihu.com/p/96755231。

当然还有微软的DialoGPT(论文:https://arxiv.org/abs/1911.00536,代码:https://github.com/microsoft/DialoGPT)。DialoGPT 模型基于 GPT-2 架构。它从 GPT-2 继承了带有层归一化的 12 到 24 层 transformer,采用Reddit 的对话语料(作者觉得从Reddit 数据中可以学习到的丰富信息)。

 

参考:

https://zhuanlan.zhihu.com/p/53796189

https://zhuanlan.zhihu.com/p/65062025

https://blog.csdn.net/qq_32241189/article/details/81591456

http://fancyerii.github.io/2019/02/14/chatbot/

https://www.cnblogs.com/robert-dlut/p/8638283.html

https://www.sohu.com/a/242214491_164987

https://www.cnblogs.com/hellojamest/p/11128799.html

https://zhuanlan.zhihu.com/p/85221503

https://zhuanlan.zhihu.com/p/77307258

https://blog.csdn.net/qq_38343151/article/details/102993202

https://zhuanlan.zhihu.com/p/47063917

https://zhuanlan.zhihu.com/p/47282410

https://www.cnblogs.com/jiangxinyang/p/11715678.html

https://zhuanlan.zhihu.com/p/86900556

https://blog.csdn.net/u012526436/article/details/101924049

https://zhuanlan.zhihu.com/p/65346812

https://www.zhihu.com/question/324019899/answer/709577373

https://kexue.fm/archives/6933

https://zhuanlan.zhihu.com/p/68327602

https://zhuanlan.zhihu.com/p/70663422

https://www.jiqizhixin.com/articles/2019-09-03-14

https://www.jiqizhixin.com/articles/2019-08-26-12

https://zhuanlan.zhihu.com/p/92842331

 

你可能感兴趣的:(nlp,自然语言处理,seq2seq,Bert,GPT,Transformer)