基于Attention的Seq2Seq与Beam搜索

2018-08-07 安兴乐-siler 更多博文 » 博客 » GitHub »

计算机

原文链接 http://www.lelovepan.cn/2018/08/07/%E5%9F%BA%E4%BA%8EAttention%E7%9A%84Seq2Seq%E4%B8%8Ebeam%E6%90%9C%E7%B4%A2.html
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。


这篇文章是im2latex系列的第一篇,希望能够讲清楚基于attention的Sequence-to-Sequence和Beam Search的一些基本概念。

如果你对Seq2Seq和熟悉了,想直接跳到tensorflow代码部分的话。

                                                                          [直接看代码](https://anxingle.github.io)

介绍

上一篇命名实体识别中,我介绍了一个相对简单的任务:如何去预测一个单词的属性。如果是翻译之类的任务就需要设计比较复杂的系统了。我们知道最近神经机器翻译已经取得了很大的突破---(几乎)可以达到人类的水平了(像Google翻译已经被大量应用在生活中,具体参与文章)。这些新的结构依赖于一种称为Encoder-decoder的的结构,可以用于产生特定实体的目标序列。

阅读这篇文章前,假定你对深度学习的基础知识(卷积CNN,长短时记忆LSTM…)之类的基本概念已经很熟悉了。如果你还对这些CV和NLP方面的知识不是很熟悉,建议先看看大名鼎鼎的斯坦福公开课CS231nCS224n

Sequence to Sequence 基本概念

先介绍我们使用的模型中最重要的Sequence to Sequence框架。先从翻译任务中最简单的版本说起。

举个🌰,翻译”how are you“成中文“干哈呢”.(法语:comment vas tu)

普通Seq2Seq

Seq2Seq模型依赖于encoder-decoder结构。Encoder端对输入序列编码,然后Decoder端产生解码后的目标序列。

编码器

上个例子中我们的输入序列是“how are U”。每个输入序列的单词被转化为了一个向量$w\in R^{d}$(这一过程通过速查表实现)。上个例子中有3个单词,这样输入将会被转化为$[W_{0}, W_{1}, W_{2}\in R^{d\times3}]$.之后,输入向量序列进入LSTM,存储下LSTM输出的最后一层隐藏层状态:这就是编码器的编码结果e了。写下这里的隐藏层状态: [$e_{0}, e_{1}, e_{2}$] (这里的$e=e_{2}$)。

解码器

现在有了向量 $e$ 捕获了输入序列的信息,下面使用它来一个单词一个单词的产生目标单词序列。隐层状态 $e$ 和特殊的开始标记 $W_{sos}$ 向量作为输入来送入LSTM单元。LSTM计算了下一个隐藏层状态$h_{0}\in R^{h}$。然后应用函数$ g : R^{h}\Rightarrow R^{V}$ ,这样$s_{0} := g(h_{0})$ 是词汇表中同样大小的向量。

$$ h_{0} = LSTM(e, w_{sos}) $$

,

$$ s_{0} = g(h{_0}) $$

,

$$ p_{0} = softmax(s_{0}) $$

,

$$ i_{0} = argmax(p_{0}) $$

之后,$s_{0}$经过softmax来正则化后进入概率矩阵$p_{0}\in R^{V}$ 。每个实体的 $p_{0}$ 用来衡量它和单词表中的单词的相似度。可以说“干”具有最高的概率(也就说$i_{0} = argmax(p_{0})$ 对应“干”的索引)。得到对应的向量$W_{i_{0}} = W_{干}$,然后重复这个过程:继续将隐藏层状态$h_{0}$ 和 $W_{干}$输入LSTM,LSTM再输出对应第二个词的概率矩阵$p_{1}$...为:

$$ h_{1} = LSTM(h_{0}, w_{i_{0}}) $$

,

$$ s_{1} = g(h{_1}) $$

,

$$ p_{1} = softmax(s_{1}) $$

,

$$ i_{1} = argmax(p_{1}) $$

,

当解码器遇到特殊停止符(往往是"")的时候,解码过程就会停止。

直觉上,隐藏层向量还未被解码的信息。

上述方法旨在下一个单词在句子开头已知情况下的条件概率分布: $$ P[y_{t+1}|y_{1},...,y_{t},x_{0},...,x_{n}] $$ 简写为: $$ P[y_{t+1}|y_{t},h_{t},e] $$

加入attention的Seq2Seq

托attention的福,这两年上面的Seq2Seq模型已经大变样了。Attention是一种让模型在解码时学习集中关注输入序列特定部分的模型,而不仅依靠解码中LSTM隐藏层的部分。关于attention的详细介绍请参阅文章。这里我们稍微修改一下上面的公式,在LSTM的输入部分加入一个新的向量$C_{t}$ : $$ h_{t} = LSTM(h_{t-1}, [w_{i_{t-1}}, c_{t}]) $$

,

$$ s_{t} = g(h_{t}) $$

,

$$ p_{t} = softmax(s_{t}) $$

,

$$ i_{t} = argmax(p_{t}) $$

,

向量 $c_{t}$ 就是注意力(或称上下文)向量。每一步解码的过程计算一个新的注意力向量。首先,使用函数$f(h_{t-1}, e_{e_{t^{old}}})\Rightarrow \alpha_{t_{old}} \in R$ 计算编码器每一个隐藏层状态$e_{t_{old}}$ 的分数;之后使用softmax正则化$\alpha_{t_{old}}$,然后与$e_{t_{old}}$ 加权计算$c_{t}$: $$ \alpha_{t_{old}} = f(h_{t-1}, e_{t_{old}}) $$

,

$$ \overline{\alpha} = softmax(\alpha) $$

,

$$ c_{t} = \sum_{t_{old}=0}^n\overline{\alpha}{t{old}}{e_{t_{old}}} $$

,

在这里,计算注意力向量$\alpha_{t_{old}}$ 的函数$f$那可就多啦,一般也就下面几种:

$$ f(h_{t-1},e_{t_{old}})=\begin{cases}h^{T}{t-1}e{t_{old}}&\text{dot}\h^{T}{t-1}We{t_{old}}&\text{general}\v^{T}tanh(W[h_{t-1},e_{t_{old}}])&\text{concat}\end{cases} $$

,

这样子注意力权重$\overline{\alpha}$ 很容易解释了。当产生单词vas的时候(对应英语are),我们期望$\overline{\alpha_{are}}$ 接近1,而$\overline{\alpha_{how}}$和$\overline{\alpha_{you}}$接近0。直觉上注意力向量$c$大致接近are的隐含层向量,它有助于产生法语单词vas

通过将注意力权重送入矩阵(行=输入序列,列=输出序列),我们就可以“对齐”输入的英语单词和输出的法语单词了(参见文章第六页)。关于Seq2Seq模型还有好多可以探讨的地方,比如编码器可以双向处理输入序列,限于篇幅,这里不再展开了。

训练

如果第一次产生的序列并不太确定是comment还是vas(训练开始很大程度都可能遇到)的话,会怎么样呢?那整个输出序列就全乱了,模型也很难学到什么东西了....

如果我们使用训练中预测的输出来作为下一步的输入,错误就会一直累积下去,模型很难收敛到正确的输入分布上。这会使训练过程很慢甚至不可训练。为加快训练过程,可以将真实的输出序列 (<sos>comment vas tu)送入解码LSTM来预测每次下一步骤产生的输出 (commentvas tu <eos>)。

解码器在每一步输出概率矩阵$p_{i}\in R^{V}$。对于每一个给定的目标输出序列$y_{1},...y_{n}$,我们在每一步计算每个输出的概率: $$ P(y_{1},...y_{m}) = \prod\limits_{i=1}^m p_{i}[y_{i}] $$ 这里$p_{i}[y_{i}]$代表在第i个解码步骤中提取第$y_{i}$个实体的概率$p_{i}$。这样,我们可以计算真是的目标序列概率。一个完美的系统可以输出目标序列接近1的概率,所以我们训练网络来最大化输出目标序列的概率,也就是最小化这个目标: $$ -\log P(y_{1},...,y_{m}) = -\log \prod\limits_{i=1}^m p_{i}[y_{i}]\ = -\sum_{i=1}^{n}\log p_{i}[y_{i}] $$

,

在我们这个例子中,也就等于 $$ -\log p_{1}[comment]-\log p_{2}[vas]-\log p_{3}[tu]-\log p_{4}[] $$ 。这不就是标准的交叉熵吗!我们实际上在最小化目标分布和我们模型(概率$p_{i}$)预测的输出分布之间的交叉熵!

解码

上述讨论中主要的问题是对于同一个模型,我们可以有不同的行为。特别在加速训练的时候。

那在推理/测试阶段呢?是不是有另外的方式来解码(翻译)一个句子呢?

的确,在测试阶段有两种方法来解码(测试就是翻译一个我们还没有翻译过的句子)。一种就是我们文章开始提到的解码方式:greedy decoding。它包含了上次预测的值最可能进入下次的方式,也是最自然的解码方式。

但你不觉得这种方式在累积误差吗?

即便训练好了模型,也可能恰巧模型出了点小毛病(可能首次预测 vas 的概率高于 comment )。那整个的解码过程就彻底崩了!

有种更好的解码过程,叫Beam Search:相对于仅预测最高分数的输出,可以在预测的时候跟踪最有可能的K个候选(比如K=5,我们就使用5作为Beam Search的大小)。在每一个新的预测(解码)步骤,对每5个候选我们可以有V个新的输出。这一下子就有了5V个心得候选。然后再选择其中5个最靠谱的,这一过程继续下去.... 上公式,定义$H_{t}$为第t个步骤的解码候选集合。 $$ H_{t} := {(W^{1}{1},...W^{1}{t}),...(W^{k}{1},...W^{k}{t})} $$ 再举个🌰,如果k=2,这时可能的集合就是 $$ H_{2} := {(comment vas), (comment tu)} $$ 现在我们考虑所有可能的候选$C_{t+1}$,从$H_{t}$中产生新的输出: $$ C_{t+1} := \bigcup^{k}{i=1}{(w^{i}{1},...w^{i}{i},1),...(w^{i}{1},...w^{i}{i},V) } $$ 保持K个最高的得分序列。在我们的例子中: $$ C{3} = {(comment,vas,comment), (comment,vas,vas), (comment,vas,tu)}\bigcup {(comment,tu,comment)} $$

我们假设最高的两个是: $$ H_{3} := { (comment, vas, tu),(comment, tu, vas) } $$ 每次候选遇到 <eos> 标志,就返回最高得分的候选。

如果我们使用Beam Search,因为我们保持着最优候选集合,初始步骤的小错误会在下一步被修正。

结论

本文介绍了Seq2Seq的几个概念。可以看到训练和解码部分不太一样。也介绍了两种解码方法:greedybeam search。虽然beam search可以取得较好的结果,但是它依然存在exposure bias。训练阶段,模型也不会exposed到错误。他也会有Loss-Evaluation mismatch。模型是通过输出token级别的交叉熵来优化的,而我们更感兴趣的是整个句子结构的重建....

下面,让我们应用Seq2Seq到图像生成LaTex上吧!

                                 [看代码](https://anxingle.github.io)

Seq2Seq经典论文:

试图解决一些一些局限性的文章:

全文翻译自Guillaume Genthial的博客

article translated from Guillaume Genthial Blog