本系列笔记从2018年3月开始编写,虽然题名为“神经翻译笔记”,但是历经2年3个月,虽然偶尔提到一些神经翻译使用的方法(例如subword),却仍并未真正涉及机器翻译本身,颇有点“博士买驴”的感觉。不过从本章开始,终于要进入正题,聊一聊神经机器翻译用到的核心技术了(然而要跟上时代,讲述Transformer,可能还需要过很久。本章预计会写10节,将是一个漫长的过程)
本章的主要内容是讲述基于RNN的序列到序列模型与注意力机制,本文参考如下课程讲义和教材:
Koehn的NMT综述,13.5节
Neubig的NMT和s2s教程,第7、8节(除CNN和树结构)(本文写完,Neubig的这篇文章就算看完了)
Stanford CS224n 2020 Winter第8讲幻灯片、官方笔记
其它相关的经典论文
机器翻译是自然语言处理领域出现比较早的一类任务,其在1950年开始就受到了广泛研究。该任务的目的是将源语言 S S S的句子 x x x翻译成目标语言 T T T的句子 y y y。早期的机器翻译是纯基于规则的,人们通过编写的双语词典逐词翻译。在20世纪90年代到2014年以前,机器翻译一直处于统计机器翻译时代。假设给定了一个法语句子 x x x,要找到其对应的最佳英语翻译 y y y,实际上就是要找
a r g max y P ( y ∣ x ) \mathop{\rm arg \max}_y P(y|x) argmaxyP(y∣x)
根据贝叶斯定律,有
a r g max y P ( y ∣ x ) = P ( x ∣ y ) P ( y ) \mathop{\rm arg \max}_y P(y|x) = P(x|y)P(y) argmaxyP(y∣x)=P(x∣y)P(y)
这样的分解说明要做好机器翻译,需要引入两个模型
由此带来的问题是,应该如何从平行语料中学习翻译模型 P ( x ∣ y ) P(x|y) P(x∣y)呢?做法是引入一个隐变量 a a a,来学习 P ( x , a ∣ y ) P(x,a|y) P(x,a∣y)。这里这个隐变量 a a a被称为对齐信息,即源句 x x x和目标句 y y y之间的对应关系。理想上,将源语言的每个词翻译成目标语言的对应词,就应该大功告成了,但是实际上事情远没有这么简单,其核心原因就是自然语言中不同语言之间对齐的复杂性,例如
为了更准确地描述这种复杂的对齐关系,成功的统计翻译系统通常都比较庞大,由许多独立的子系统构成。此外,还需要大量的特征工程工作,甚至要为不同的语言现象分别设计特征,人力成本很高
神经翻译的提出有力地改变了这样的状况,它的核心思路是只使用一个系统,也就是一个神经网络模型端到端地解决所有问题。通过大规模语料的训练,模型接收到一个简单处理过的源语言句子,就可以直接生成一个目标语言句子,不用经过其它模型人工提取特征。这种神经网络模型称为序列到序列(sequence-to-sequence, s2s)模型,因为输入是一个标识符序列,输出也是一个标识符序列。典型的序列到序列模型可以看做是由编码器和解码器两个部分构成
因此,序列到序列模型也通常被称为编码器-解码器模型(本文及后文会混用这两个名词,不对这两个概念做区分)。下面给出了一个最简单的序列到序列模型示意图(图自Neubig的turorial)
由于还没有涉及到CNN和Transformer,因此这里先假设编码器和解码器部分都是两个单向的RNN,编码器记作 R N N f ( ⋅ ) {\rm RNN}_f(\cdot) RNNf(⋅),解码器记作 R N N e ( ⋅ ) {\rm RNN}_e(\cdot) RNNe(⋅),则模型可以表示为
x f ( t ) = E f [ f ( t ) ] h f ( t ) = { R N N f ( x f ( t ) , h f ( t − 1 ) ) t ≥ 1 0 o t h e r w i s e x e ( t ) = E e [ e ( t − 1 ) ] h e ( t ) = { R N N e ( x e ( t ) , h e ( t − 1 ) ) t ≥ 1 h f ∣ F ∣ o t h e r w i s e p e ( t ) = s o f t m a x ( W h s h e ( t ) + b s ) \begin{aligned} \boldsymbol{x}_f^{(t)} &= \boldsymbol{E}_f[f^{(t)}] \\ \boldsymbol{h}_f^{(t)} &= \begin{cases}{\rm RNN}_f\left(\boldsymbol{x}_f^{(t)}, \boldsymbol{h}_f^{(t-1)}\right) & t \ge 1\\ \boldsymbol{0} & {\rm otherwise}\end{cases} \\ \boldsymbol{x}_e^{(t)} &= \boldsymbol{E}_e[e^{(t-1)}] \\ \boldsymbol{h}_e^{(t)} &= \begin{cases}{\rm RNN}_e\left(\boldsymbol{x}_e^{(t)}, \boldsymbol{h}_e^{(t-1)}\right) & t \ge 1\\ \boldsymbol{h}^{|F|}_f & {\rm otherwise}\end{cases} \\ \boldsymbol{p}_e^{(t)} &= {\rm softmax}\left(\boldsymbol{W}_{hs}\boldsymbol{h}_e^{(t)} + \boldsymbol{b}_s\right) \end{aligned} xf(t)hf(t)xe(t)he(t)pe(t)=Ef[f(t)]={RNNf(xf(t),hf(t−1))0t≥1otherwise=Ee[e(t−1)]={RNNe(xe(t),he(t−1))hf∣F∣t≥1otherwise=softmax(Whshe(t)+bs)
这里前两行对应编码器,意思是对源语言 f f f,在每一时刻(对应每个标识符)在源语言词嵌入矩阵 E f \boldsymbol{E}_f Ef中寻找该标识符对应的词向量,连同上一时刻隐藏状态一起计算该时刻句子的隐藏状态。编码器的初始隐藏状态是零向量。在经历了源句长度 ∣ F ∣ |F| ∣F∣个时刻以后,模型已经看过了源句的所有标识符,并且生成了源句的编码 h f ∣ F ∣ \boldsymbol{h}_f^{|F|} hf∣F∣,我们认为这个向量包含了源句的所有信息。后三行对应编码器,大致思路类似,除去解码器需要一个额外的softmax过程来输出单词(的id),两者最大的不同是解码器在每一刻看到的输入单词实际上是上一刻的单词。其中,第0时刻是一个特殊的、固定的标识符。示意图中将这个标识符算在了目标语言中,记作 e 0 e_0 e0,但是实践中通常使用的是源语言词表中的标识符,记为,表示输入的结束。该标识符在源语言词表和目标语言词表中都有重要作用:在源语言词表中,该标识符出现意味着源句的结束,可以开始目标句的输出;而在目标语言词表中,解码阶段如果产生了该标识符,意味着解码完毕。以将法语il a m’ entarté(这里m’看做是一个独立的标识符,与后面的词分开)翻译成he hit me with a pie的训练过程为例,模型看到的整个序列实际上是
il a m' entarté he hit me with a pie
,在entarté
这一步模型已经将源句编码完成,看到时就开始努力让这一步的输出匹配
he
(让he这个词的概率最大),在pie
这一步则是努力让输出匹配目标语言的。对于每组样本(一个句子对),模型的目标函数是解码器部分每一时刻输出分布与实际分布交叉熵的均值。下图给出了对应的示例(图自CS224n讲义)
需要注意的是,序列到序列模型的训练阶段和推断阶段有不同。在训练阶段,解码器可以看到目标句的真实内容,因此无论在第 t t t时刻模型的输出多么奇怪,在第 t + 1 t+1 t+1时刻它看到的输入单词仍然是第 t t t步那个正确的词。但是,在解码阶段,由于没有真实值的存在,解码器在第 t + 1 t+1 t+1时刻看到的输入单词实际上是其第 t t t时刻的输出。这隐含地为序列到序列模型带来了两个问题
对于前者,在该模型提出的早期,做神经翻译的一个常见小技巧是将输入反向,例如将il a m' entarté
转换成entarté m' a il
。这样编码器接受的源句开头离解码器更“近”,一定程度上可以抵消RNN因两个词距离太远造成的遗忘现象,使得解码器在开始阶段产生的结果更准确,降低出错几率。在注意力机制和Transformer提出以后,这种现象得到了进一步改善。对于后者,目前还没有太好的解决办法。近几年涌现的“非自回归翻译”(non auto-regressive translation, NAT)可以并行化输出,提高了计算效率,但是模型产生的翻译句子质量暂时还无法与传统的自回归翻译相媲美
前面图中给出的编码器-解码器结构是最简单的形式。为了达到更好的模型效果,人们对编码器部分做了若干改造,例如使用双向RNN、多层RNN和引入残差连接等等,但是更革命性的是引入注意力机制。另一方面,在解码阶段,人们也在最简单的贪婪算法(每一步选择概率最高的单词输出)基础上进行了发展,其中目前应用最广泛的称为集束搜索(beam search)。本章后面两节将分别介绍这两种重要技术
在未改进的搜索策略中,如果在某一步解码器生成的最大概率词并非正确词,那么后面有可能一错到底,无法回头。为了解决这样的问题,很自然的做法是扩大解码器的选择范围,每次不止看最优的结果。假设目标语言的词表大小为 ∣ V ∣ |V| ∣V∣,目标句的长度为 T T T,则在最理想的情况下,应该对全部序列(共 ( ∣ V ∣ ) T (|V|)^T (∣V∣)T个)进行打分,但是这样的搜索空间实在太大,实践中不可能实现
对这种全搜索的改进方法是使用集束搜索,其遵循如下步骤
最后,集束搜索对产生的所有结果综合打分评估。显而易见的是,如果只是计算 ∑ log P ( e i ∣ F , e 1 , … e i − 1 ) \sum \log P(e_i|F, e_1, \ldots e_{i-1}) ∑logP(ei∣F,e1,…ei−1),那么长句的得分肯定不如短句。通常的做法是对每个结果除以序列长度来做归一化,然后再作比较
下图给出了 b = 2 b=2 b=2的集束搜索过程的示意图(来自于CS224n讲义)
集束搜索是在序列到序列模型中使用最广泛的搜索方法,但是也存在若干问题。在本章的后续小节中,将对序列到序列模型其它一些常见的魔改搜索方法做进一步介绍
编码器解码器结构的出现为机器翻译领域开辟了一个新的纪元,但是人们很快发现,随着句子长度变长,模型效果急剧下降,仅靠一个定长向量编码源句的所有信息有些捉襟见肘,而且模型仍然无法很好地建模长距离依赖关系。[Bahdanau2014]为此提出了注意力(attention)机制,试图将源句表达为一个向量序列,在解码时自适应地选取这个序列的一个子集,进而有效地解决了长度变大时编码器解码器失效的问题
设源句长度为 N N N,记编码器每一步的隐藏状态为 h ( 1 ) , … , h ( N ) \boldsymbol{h}^{(1)}, \ldots, \boldsymbol{h}^{(N)} h(1),…,h(N),显然在这些隐藏状态中,每一个隐藏状态都对应了一个源句单词。一个比较自然的想法是,对这些隐藏状态做一个加权求和,而权重则由目标句的单词来决定。例如英文“Last week I went to the theatre”翻译成中文“上周 我 去 了 剧院”,这里“剧院”这个词肯定受“theatre”影响最大,“the”有一些,但“week”就应该没什么关系,因此“theatre”此时权重应该很高,而“week”的需要接近0——这其实就是注意力机制的基本思路。其具体做法为,在解码的每一步 t t t,对解码器在该步的隐藏状态 s ( t ) \boldsymbol{s}^{(t)} s(t),先不将其送进输出层,而是与编码器的各隐藏状态计算得分,得到一个对齐向量 a ( t ) \boldsymbol{a}^{(t)} a(t)
a ( t ) = [ s c o r e ( s ( t ) , h ( 1 ) ) , … , s c o r e ( s ( t ) , h ( N ) ) ] \boldsymbol{a}^{(t)} = \left[{\rm score}\left(\boldsymbol{s}^{(t)}, \boldsymbol{h}^{(1)}\right), \ldots, {\rm score}\left(\boldsymbol{s}^{(t)}, \boldsymbol{h}^{(N)}\right)\right] a(t)=[score(s(t),h(1)),…,score(s(t),h(N))]
这里 s c o r e \rm score score是一个打分函数,稍后会重提。在得到这个得分向量以后,通过softmax操作将其归一化,就可以得到满足前面要求的权重向量 α ( t ) \boldsymbol{\alpha}^{(t)} α(t)
α ( t ) = s o f t m a x ( a ( t ) ) \boldsymbol{\alpha}^{(t)} = {\rm softmax}\left(\boldsymbol{a}^{(t)}\right) α(t)=softmax(a(t))
自然地,可以使用这个权重向量对编码器的众隐藏状态加权求均值,得到上下文向量 c ( t ) \boldsymbol{c}^{(t)} c(t)
c ( t ) = ∑ i = 1 N α i ( t ) h ( i ) \boldsymbol{c}^{(t)} = \sum_{i=1}^N \alpha_i^{(t)}\boldsymbol{h}^{(i)} c(t)=i=1∑Nαi(t)h(i)
将这个上下文向量 c ( t ) \boldsymbol{c}^{(t)} c(t)与 s ( t ) \boldsymbol{s}^{(t)} s(t)连接起来,再送进输出层求softmax
p ( y ( t ) ∣ y ( < t ) , x ) = s o f t m a x ( W o [ c ( t ) : s ( t ) ] ) \boldsymbol{p}(y^{(t)}|y^{(
注意力机制的引入打破了前面提到的原始编码器解码器模型遇到的两个瓶颈。其一,解码器不只依赖于编码器产生的最终定长向量,而是会和编码器每一步的隐藏向量产生交互,获取更多信息;其二,由于解码器每一步和编码器各步都有了直接连接,因此反向传播时梯度有了很多直连通路,有效降低了多步传递带来的梯度消失/梯度爆炸等风险
更广泛地说,注意力机制可以抽象为给定查询 q q q(这里是解码器的隐藏状态),计算每个关键字 k k k对应值 v v v的加权平均值的过程(这里 k k k和 v v v都是编码器各步的隐藏状态)。这种加权平均可以看做是对所有关键字组成的信息产生有选择的摘要,而如何选择则是根据具体查询值决定
常见的打分函数有如下几种
自2014年首次提出以来,编码器解码器结构的序列到序列模型已经取得了相当大的成功,成为了当前解决神经翻译问题的标配。实际上,该模型的应用范围不仅局限于机器翻译这一个领域,还可以用来解决问答、文本摘要、聊天机器人等各种问题,是NLP领域里的一种重要模型。而注意力机制的出现进一步改善了编码器解码器模型在长句上表现不佳的状况,人们在之后也提出了各种衍生版本,而纯粹使用注意力机制的Transformer也令人耳熟能详。关于注意力机制的变种和Transformer,将在本系列笔记的后续中详细介绍
[Bahdanau2014]注意力机制与[Luong2015]注意力机制的区别
前文提到注意力机制最早出现于[Bahdanau2014],[Luong2015]发表时间稍晚,但是基本处于同一时段。这两篇都是注意力机制领域的奠基之作,[Luong2015]引用了[Bahdanau2014],不过有所区别(实际上,[Bahdanau2014]与前文介绍的思路也有差别,但是前面介绍时采用了CS224n讲义和Neubig tutorial的内容,两者基本一致)。具体区别为