【Hello NLP】CS224n笔记[7]:整理了12小时,只为让你20分钟搞懂Seq2seq
相比于计算机视觉,NLP可能看起来没有那么有趣,这里没有酷炫的图像识别、AI作画、自动驾驶,我们要面对的,几乎都是枯燥的文本、语言、文字。但是,对于人工智能的征途来说,NLP才是皇冠上的那颗珍珠,它美丽却又难以摘取,当NLP的问题解决了,机器才真正具备了理解、思考的能力,我们才敢说实现了真正的“智能”。
SimpleAI 【HelloNLP】系列笔记,主要参考各知名网课(Stanford CS224n、DeepLearning.ai、李宏毅机器学习等等),并配合NLP的经典论文和研究成果、我的个人项目实践经验总结而成。希望能和各位NLP爱好者一起探索这颗AI皇冠的明珠!
CS224n笔记[7]:机器翻译和seq2seq
作者:郭必扬
今天主要介绍机器翻译的简单发展历史和方法,由此引入seq2seq框架,我们会一起深入讨论seq2seq框架中的各种细节,并配合精美的结构图、流程图辅助大家理解。
本文约5000字,阅读约20分钟
目录:
机器翻译 传统机器翻译,SMT 神经机器翻译,NMT Seq2seq Seq2seq结构详解 为什么训练和预测时的Decoder不一样? Seq2seq的损失函数 Decoding和Beam Search 总结 NMT的优缺点 机器翻译的评价指标
机器翻译
传统机器翻译
早期的(1950s)机器翻译的思路十分简单,通过设置大量的翻译规则,构建一个大型的双语对照表,来将源语言翻译成目标语言。这个固然简单,也自然效果很一般。因此我们不展开描述。
后来(1990s-2010s)我们有了更为先进复杂的机器翻译技术——「统计机器翻译」(Statistical Machine Translation, SMT)。
SMT的主要思想就是从大量的数据中学习一个概率模型,其中x是源语言(source language),y是目标语言(target language),即x翻译成y的概率有多大。在翻译时,我们只需要通过求就行了,即找到概率最大的那个y,就是x的最佳翻译了。
这么一想,是不是感觉跟之前介绍过的语言模型(LM)挺像的?LM是根据一句话的前面几个词,给出下一个词的概率。这里的SMT则是根据source文本给出target文本的概率。但不要搞混淆,这里的x和y不是单个词,而是一句话。我们对通过贝叶斯公式进行转换,可以得到:
则
这里由于我们是要找出使得上式最大的y,因此只跟x相关的分母可以被省去。跟上面这个公式的变换,我们发现SMT真的跟语言模型是有关的:
就是求y这个句子的概率,这就是一个「语言模型」(LM)。而后者则被称为「翻译模型」(TM)。LM可以通过目标语言的语料进行训练,TM则需要通过「平行语料」(parallel corpus,即源和目标两种语言的互相对照的语料,比如“罗塞塔石碑”,上面刻有一份古埃及的诏书的三个语言的版本)进行训练。
有人(我)就奇怪了,原本的不就是一个翻译模型吗?经过这么一分解,不仅还有一个翻译模型,还多了一个语言模型!这不是越搞越复杂吗?其实不是这样的,翻译模型和语言模型,各自有所偏重。翻译模型通过大量的平行语料,学习到的主要是两种语言之间的对应关系,而语言模型则侧重于学习一种语言内部的语法结构,不同词汇是怎么流畅地组合成句子的。原本的公式只有一个翻译模型,会导致我们训练出来的模型在翻译结果的语言通畅性方面很差。因此,我们经过公式变换,将一个TM任务转化成TM+LM两种任务,可以模型学习的结果更好。
对于TM的学习,一般我们会进一步进行分解,考虑两种语言之间的各种对齐方式(alignment),即在原有的翻译模型上,引入一个隐变量a,得到,可以理解为给定句子y,按照对齐方式a翻译成x的概率。
具体什么是对齐方式alignment呢?它的意思就是在两种语言A和B之间,A的词是跟B的词怎么对应的。很明显,这种对应关系可以是一对一、一对多、多对一、多对多的。比方下图:
这里例子展示了一个法语句子和英语句子词语对齐关系,其中法语词entarte,英文翻译是“hit me with a pie”,英文中根本没有一个词可以直接表示这个含义。中英文中这样的例子更加常见了,有些英文单词可以用一个汉字对应,但也有很多单词需要两个甚至多个汉字对应。另外,同一个词,在不同的语境下,对齐的词和数量都有可能不同,比如“牛”可以对应“cow”也可以对应“awesome”,“cool”可以对应“酷”也可以对应“凉快”。因此,对齐,alignment,是一个十分复杂的东西,学习也是很麻烦,这里我也不太了解,就不细讲了。
在学习了LM和TM这两个模型之后,是不是就完事儿了呢?当然没有,别忘了公式里还有一个argmax,我们要找出最佳的翻译是什么。根据LM和TM寻找最佳y的过程,就称为“decoding”,即解码。
一个最直接的方法就是,遍历所有可能的y,选择概率最大的那个,当然就是最佳的翻译。明显,这种方式带来的开销是我们无法忍受的。如果学习过CRF或者HMM,我们应该知道对于这种解码的过程,我们一般使用动态规划、启发式搜索的方法来处理。在SMT中具体怎么解码,我们这里也暂时不做深入的研究。
统计机器翻译——SMT,在深度学习时代之前,风光无限,一直是机器翻译的巅峰技术。但是,SMT的门槛也是很高的,那些表现优异的SMT模型,通常都是极其复杂的,里面涉及到大量的特征工程,海量的专家知识,无数的资源积累,繁多的功能模块,还需要庞大的人力去维护。这也是我根本不想去深入了解这个技术的原因。
幸好,在深度学习时代,我们有了更好的方法:神经机器翻译(Neural Machine Translation,NMT)。
神经机器翻译(NMT)
深度学习的“可恨之处”在于,它把那些需要大量人力的工作都吃掉了,导致行业专家和搬砖工人门纷纷下岗。NMT就是这样,企图就是用一个简洁的神经网络结构,就把机器翻译这么大的一个工程给包下来。我画了一个形象生动的图来示意SMT和NMT的区别:
NMT使用的神经网络结构,是一种被称为sequence-to-sequence的结构,即seq2seq。它还有另外一个常见的名字:Encoder-Decoder结构。这种结构一般都是由两个RNN组成。下面我画了一个抽象的示意图:
从这个抽象示意图上看,seq2seq的结构的Encoder部分读取输入文本,在机器翻译中即源语言文本,通过Encoder编码成一个表示向量,即context vector,然后交给Decoder来进行解码,翻译成目标语言。在训练和预测时,我们都可以使用这样的结构,没有其他的花里胡哨的东西,因此总体上看起来比SMT要简洁明了得多。
上面这张图还是太抽象了,下面让我们深入seq2seq结构的内部,看看我们如何使用这个结构来进行训练和预测。
seq2seq
这里说个题外话,seq2seq我之前一直读/sek-tu:-sek/
,后来听了网课,发现正确的读法应该是/si:k-tu:-si:k/
,毕竟sequence的读音是/si:kwəns/
。读正确的读音,也让我们讨论技术的时候也更有底气不是?
seq2seq结构详解
我们把前面那张抽象图展开,可以看到内部的结构是这样的:
❝
One hour later(辛苦画图中)......
❞
这张图,展示了在「训练时」,seq2seq内部的详细结构。
在Encoder端,我们将source文本的词序列先经过embedding层转化成向量,然后输入到一个RNN结构(可以是普通RNN,LSTM,GRU等等)中。另外,这里的RNN也可以是多层、双向的。经过了RNN的一系列计算,最终隐层的输入,就作为源文本整体的一个表示向量,称为「context vector」。
Decoder端的操作就稍微复杂一些了。首先,Decoder的输入是什么呢?Decoder的输入,训练和测试时是不一样的!「在训练时,我们使用真实的目标文本,即“标准答案”作为输入」(注意第一步使用一个特殊的<start>
字符,表示句子的开头)。每一步根据当前正确的输出词、上一步的隐状态来预测下一步的输出词。
下图则展示了在「预测时」,seq2seq的内部结构:
预测时,Encoder端没什么变化,在Decoder端,由于此时没有所谓的“真实输出”或“标准答案”了,所以只能「自产自销:每一步的预测结果,都送给下一步作为输入」,直至输出<end>
就结束。如果你对我之前写的笔记很熟悉的话,会发现,「这时的Decoder就是一个语言模型」。由于这个语言模型是根据context vector来进行文本的生成的,因此这种类型的语言模型,被称为“条件语言模型”:Conditional LM。正因为如此,在训练过程中,我们可以使用一些预训练好的语言模型来对Decoder的参数进行初始化,从而加快迭代过程。
为什么训练和预测时的Decoder不一样?
很多人可能跟我一样,对此感到疑惑:为什么在训练的时候,不能直接使用这种语言模型的模式,使用上一步的预测来作为下一步的输入呢?
我们称这两种模式,根据标准答案来decode的方式为「teacher forcing」,而根据上一步的输出作为下一步输入的decode方式为「free running」。
其实,free running的模式真的不能在训练时使用吗?——当然是可以的!从理论上没有任何的问题,又不是不能跑。但是,在实践中人们发现,这样训练太南了。因为没有任何的引导,一开始会完全是瞎预测,正所谓“一步错,步步错”,而且越错越离谱,这样会导致训练时的累积损失太大(「误差爆炸」问题,exposure bias),训练起来就很费劲。这个时候,如果我们能够在每一步的预测时,让老师来指导一下,即提示一下上一个词的正确答案,decoder就可以快速步入正轨,训练过程也可以更快收敛。因此大家把这种方法称为teacher forcing。所以,这种操作的目的就是为了使得训练过程更容易。
这就好比我们考驾照时,很多教练为了让我们快速通关,会给我们在场地上画上各种标记,告诉我们你看到某个标记就执行某个动作(说白了就是作弊手段)。这种方法很有效,我们在练车的时候,死记住这些作弊技巧,很容易在训练场顺利倒车、侧方停车。但这种方法在我们上考场的时候就会暴露出问题了——考场上可没人给你做标记!因此很多人明明在下面自己练车的时候很顺,以上考场就挂了。这也是teacher forcing方法的一个弊端:预测时我们没有老师给你做标记了!纯靠自己很可能挂掉。
所以,更好的办法,更常用的办法,是老师只给适量的引导,学生也积极学习。即我们设置一个概率p,每一步,以概率p靠自己上一步的输入来预测,以概率1-p根据老师的提示来预测,这种方法称为「计划采样」(scheduled sampling):
这是种什么感觉呢?就拿我们来刷LeetCode来说吧,完全不看答案的话,对于我来说的话就太难了。。。做题的进度会灰常慢,如果我完全看答案写,那也没啥意义,过几天就忘了,所以最好的方式就是自己也思考,遇到太难的时候就看看答案,这样我们又能保证进度,又能有学习效果。
另外有一个小细节:在seq2seq的训练过程中,decoder即使遇到了<end>
标识也不会结束,因为训练的时候并不是一个生成的过程 ,我们需要等到“标准答案”都输入完才结束。
seq2seq的损失函数
前面我们详细介绍了seq2seq的内部的结构,明白了内部结构,想知道是怎么训练的就很容易了。
在上面的图中,我们看到decoder的每一步产生隐状态后,会通过一个projection层映射到对应的词。那怎么去计算每一步的损失呢?实际上,这个projection层,通常是一个softmax神经网络层,假设词汇量是V,则会输出一个V维度的向量,每一维代表是某个词的概率。映射的过程就是把最大概率的那个词找出来作为预测出的词。
在计算损失的时候,我们使用交叉熵作为损失函数,所以我们要找出这个V维向量中,正确预测对应的词的那一维的概率大小,则这一步的损失就是它的负导数,将每一步的损失求和,即得到总体的损失函数:
其中T代表Decoder有多少步,[EOS]代表'end ofsentence’这个特殊标记,本来想打<end>
跟前面保持一致的,因为LaTeX里面显示的问题,我替换了一下。
Decoding和Beam search
前面画的几个图展示的预测过程,其实就是最简单的decoding方式——「Greedy Decoding」,即每一步,都预测出概率最大的那个词,然后输入给下一步。
这种Greedy的方式,简单快速,但是既然叫“贪心”,肯定会有问题,那就是「每一步最优,不一定全局最优」,这种方式很可能“捡了芝麻,丢了西瓜”。
改进的方法,就是使用「Beam Search」方法:每一步,多选几个作为候选,最后综合考虑,选出最优的组合。
下面我们来具体看看Beam Search的操作步骤:
首先,我们需要设定一个候选集的大小beam size=k; 每一步的开始,我们从每个当前输入对应的所有可能输出,计算每一条路的“序列得分”; 保留“序列得分”最大的k个作为下一步的输入; 不断重复上述过程,直至结束,选择“序列得分”最大的那个序列作为最终结果。
这里的重点就在于这个“序列得分”的计算。
我们使用如下的score函数来定义「序列得分」:
这个score代表了当前到第t步的输出序列的一个综合得分,越高越好。其中类似于前面我们写的第t步的交叉熵损失的负数。所以这个score越到,就意味着到当前这一步为止,输出序列的累积损失越小。
再多描述不如一张图直观,我用下图描绘一个极简的案例(只有3个词的语料,k=2):
本来想贴CS224N上的图,发现上面省去了一些细节容易造成误解。在每一步,我们都会去对所有的可能输出,计算一次score,假设beam size为k,词汇量为V,那么每一步就需要分出k×V个分支并逐一计算score。所以在图中我们可以看到除了第一步,后面每一步都是分出来2×3=6支。然后综合这k×V个score的结果,只选择其中最大的k个保留。
最后还有一个问题:由于会有多个分支,所以很有可能我们会遇到多个<end>
标识,由于分支较多,如果等每一个分支都遇到<end>
才停的话,可能耗时太久,因此一般我们会设定一些规则,比如已经走了T步,或者已经积累了N条已完成的句子,就终止beam search过程。
在search结束之后,我们需要对已完成的N个序列做一个抉择,挑选出最好的那个,那不就是通过前面定义的score函数来比较吗?确实可以,但是如果直接使用score来挑选的话,会导致那些很短的句子更容易被选出。因为score函数的每一项都是负的,序列越长,score往往就越小。因此我们可以使用长度来对score函数进行细微的调整:对每个序列的得分,除以序列的长度。根据调整后的结果来选择best one。
Beam Search的使用,往往可以得到比Greedy Search更好的结果,道理很容易理解,高手下棋想三步,深思熟虑才能走得远。
NMT的优缺点、评价方式
上面我们花了大量时间基本介绍清楚了神经机器翻译以及seq2seq的结构细节。最后我们对NMT稍作总结,并补充一些小细节。
NMT的优缺点
NMT相比于SMT,最大的优点当然就如前面所说的——简洁。我们不需要什么人工的特征工程,不需要各种复杂的前后组件,就是一个端到端的神经网络,整个结构一起进行优化。
另外,由于使用了深度学习的方法,我们可以引入很多语义特征,比如利用文本的相似度,利用文本内隐含的多层次特征,这些都是统计学方法没有的。
但是,没有什么东西是绝对好或绝对差的,NMT也有其不足。它的不足也是跟深度学习的黑箱本质息息相关。NMT的解释性差,难以调试,难以控制,我们谁也不敢保证遇到一个新的文本它会翻译出什么奇怪的玩意儿,所以NMT在重要场合使用是有明显风险的。
NMT的评价
机器翻译的效果如何评价呢?——「BLEU」指标。
BLEU,全称是Bilingual Evaluation Understudy,它的主要思想是基于N-gram等特征来比较人工翻译和机器翻译结果的相似程度。详情我不赘述,毕竟写这篇文章时我也还没有自己动手去做一个NMT。等之后做这一块的时候我再详细讨论吧。
好了,再不结束,不光你们要烦了,我也要烦了(·́へ·́╬)。关于seq2seq的基本知识点,我应该已经讲得很清楚了,你们觉得呢?