图解-基于Attention的Seq2Seq模型
这篇文章是im2latex系列的第一篇,希望能够讲清楚基于attention的Sequence-to-Sequence和Beam Search的一些基本概念。从公式图片直接到latex代码已经有好些解决方案了,比如这个网站,用户评价还不错:
如果你对Seq2Seq很熟悉了,想直接跳到tensorflow代码部分的话。
介绍
在上一篇命名实体识别中,博主介绍了一个相对简单的任务:如何去预测一个单词的属性。如果是翻译之类的任务就需要设计比较复杂的系统了。我们知道最近神经机器翻译已经取得了很大的突破---(几乎)可以达到人类的水平了(比如Google翻译已经被大量应用在生活中,具体参考文章Google's Multilingual Neural Machine Translation System: Enabling Zero-Shot Translation)。这些新的结构依赖于一种称为Encoder-Decoder的的结构,可以用于产生特定实体的目标序列。
阅读这篇文章前,假定你对深度学习的基础知识(卷积CNN,长短时记忆LSTM…)之类的基本概念已经很熟悉了。如果还对这些CV和NLP方面的知识不是很熟悉,建议可以先看看大名鼎鼎的斯坦福公开课CS231n和CS224n。
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})
当解码器遇到特殊停止符(往往是"<eos>")的时候,解码过程就会停止。
直觉上说,隐藏层向量就是还未被解码的信息。
上述方法旨在下一个单词在句子开头已知情况下的条件概率分布:
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的详细介绍请参阅文章Neural Machine Translation by Jointly Learning to Align and Translate。这里我们稍微修改一下上面的公式,在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}就是注意力(或称上下文context)向量。每一步解码的过程计算一个新的注意力向量。首先,使用函数 f(h_{t-1}, 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}}) \in R for all t_{old}
\overline{\alpha} = softmax(\alpha) ,
c_{t} = \sum_{t_{old}=0}^n\overline{\alpha}_{t_{old}}{e_{t_{old}}}
在这里,计算注意力向量 \alpha_{t_{old}}的函数那可就多啦,一般来说常用的也就下面几种:
,
这样子注意力权重 \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来预测每次下一步骤产生的输出 (comment``vas
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}[<eos>]
这不就是标准的交叉熵吗!我们实际上在最小化目标分布和我们模型(概率 p_{i} )预测的输出分布之间的交叉熵!
解码
上述讨论中主要的问题是对于同一个模型,我们可以有不同的行为。特别在加速训练的时候。
那在推理/测试阶段呢?是不是有另外的方式来解码(翻译)一个句子呢?
的确,在测试阶段有两种方法来解码(测试就是翻译一个我们还没有翻译过的句子)。一种就是我们文章开始提到的解码方式:greedy decoding。它包含了上次预测的值最可能进入下次的方式,也是最自然的解码方式。
但你不觉得这种方式也在累积误差吗?
即便训练好了模型,也可能恰巧模型出了点小毛病(可能首次预测 vas
的概率高于 comment
)。那整个的解码过程就彻底崩了!
有种更好的解码过程,叫Beam Search:相对于仅预测最高分数的输出,可以在预测的时候跟踪最有可能的K个候选(比如K=5,我们就使用5作为Beam Search的大小)。在每一个新的预测(解码)步骤,对每5个候选我们可以有V个新的输出。这一下子就有了5V个新的候选。然后再选择其中5个最靠谱的,这一过程继续下去.... 上公式,定义 \mathcal{H}_ t 为第t个步骤的解码候选集合。
\mathcal{H}_ t := \{ (w^1_1, \dots, w^1_t), \dots, (w^k_1, \dots, w^k_t) \}
再举个 ,如果k=2,这时 \mathcal{H}_ 2 可能就是:
\mathcal{H}_ 2 := \{ (\text{comment vas}), (\text{comment tu}) \}
现在通过增加新的字符,我们考虑所有从 \mathcal{H}_ t 产生的可能的候选 \mathcal{C}_ {t+1} ,: 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的几个概念。可以看到训练和解码部分不太一样。也介绍了两种解码方法:greedy和beam search。虽然beam search可以取得较好的结果,但是它依然存在exposure bias。训练阶段,模型不会exposed到错误。他也会有Loss-Evaluation mismatch。模型是通过输出字符级别的交叉熵来优化的,而我们更感兴趣的是整个句子结构的重建....
下面,让我们应用Seq2Seq来将公式图片自动生成LaTex公式吧! 上代码!
Seq2Seq经典论文:
- Sequence to Sequence Learning with Neural Networks
- Neural Machine Translation by Jointly Learning to Align and Translate
- Effective Approaches to Attention-based Neural Machine Translation
- Neural Machine Translation in Linear Time
- Convolutional Sequence to Sequence Learning
- Attention Is All You Need
试图解决一些一些局限性的文章:
- An Actor-Critic Algorithm for sequence prediction
- Sequence-to-Sequence Learning as Beam-Search Optimization
- Six Challenges for Neural Machine Translation
- Professor Forcing: A New Algorithm for Training Recurrent Networks
全文翻译自Guillaume Genthial的博客。