注意力机制(Attention)
这节课我们学习注意力机制(Attention),它可以大幅度提升机器翻译的效果。
Revisiting Seq2Seq Model(复习Seq2seq模型)
我们首先来复习一下上节课的内容。
seq2seq模型由一个编码器Encoder和一个解码器Decoder组成。 Encoder输出最后一个状态向量
h
m
h_m
hm作为对之前全部输入的总结。Decoder RNN的初始状态
s
0
=
h
m
s_0=h_m
s0=hm,通过
h
m
h_m
hm,Decoder就知道了这句话的信息。然后Decoder就像一个文本生成器一样,逐字进行翻译。
可惜Seq2seq模型有一个明显的缺陷就是当输入句子很长时,Encoder会记不住完整的句子,Encoder最后一个状态可能会漏掉一些信息。
如果拿Seq2seq模型做机器翻译,会得到这样的结果。
横轴是句子长度,纵轴是BLUE score是评价机器翻译好坏的标准,值越高说明越准确。
从上图可以看到,如果不用Attention,大概20个词后,BLUE score就会往下降。用Attention后,即使句子很长也不会明显下降。
Attention for Seq2Seq Model(在Seq2seq模型上应用Attention)
Attention是2015的ICLR上的这篇论文提出的,使用Attention后,Decoder每次在更新状态时都会再看一眼Encoder的所有状态,这样就不会遗忘。Attention还可以告诉Decoder应该关注Encoder的哪个状态,这也是Attention名字的由来。Attention可以大幅度提高准确率,但缺点是计算量很大。
下面简要介绍Attention的原理。
在Encoder结束工作之后,Attention和Decoder同时开始工作。
Decoder的初始状态是Encoder的最后一个状态
s
0
=
h
m
s_0=h_m
s0=hm,我们同时保留Encoder的所有状态,然后计算
s
0
s_0
s0和每一个状态的相关性。我们用
α i = a l i g n ( h i , s 0 ) \alpha_i = align(h_i, s_0) αi=align(hi,s0)
来表示Encoder第i个状态和Decoder第0个状态的相关性。将结果记为
α
i
\alpha_i
αi,被称为权重Weight。
Encoder有m个状态,所以一共算出m个 α \alpha α,它们都是介于0到1之间的实数并且加和为1。下面我么来看一下具体怎么计算 α \alpha α。
有很多方法可以用来计算 h i h_i hi和 s 0 s_0 s0的相关性。第一种方法如下:
我们首先把
h
i
h_i
hi和
s
0
s_0
s0做concatenation得到一个更大的向量。然后求矩阵W和这个向量的乘积,得到一个向量。再把双曲正切函数应用到向量的每一个元素上,把每一个元素值都压缩到-1到+1之间。双曲正切函数的输出还是一个向量。最后计算向量v和刚算出来的向量的内积。两个向量的内积是个实数,记为
α
~
i
\widetilde{\alpha}_i
α
i。
这里的矩阵W和向量v都是参数,需要从训练数据里学习。
算出这些 α ~ 1 , α ~ 2 , ⋯ , α ~ m \widetilde{\alpha}_1, \widetilde{\alpha}_2, \cdots, \widetilde{\alpha}_m α 1,α 2,⋯,α m之后,对他们进行Softmax变换。把输出结果记为 α 1 , ⋯ , α m \alpha_1, \cdots, \alpha_m α1,⋯,αm。这种计算方法是Attention第一篇论文中所提出的。在这之后,有其他很多论文提出了很多计算Attention的方法。
下面介绍一个更流行的方法。
输入还是
h
i
h_i
hi与
s
0
s_0
s0向量,第一步是分别用两个参数矩阵
W
K
W_K
WK和
W
Q
W_Q
WQ对两个输入做线性变换。得到
k
i
k_i
ki与
q
0
q_0
q0两个向量。这两个参数矩阵要从训练数据中学习。
第二步是计算这两个向量的内积,把结果记为 α ~ i \widetilde{\alpha}_i α i。得到m个结果。
第三步是对得到的结果做Softmax变换,把输出结果记为 α 1 , ⋯ , α m \alpha_1, \cdots, \alpha_m α1,⋯,αm。
这种方法被Transformer模型采用。
以上讲了两种计算 h i h_i hi与 s 0 s_0 s0的相关性的方法,随便使用哪种方法都会得到m个 α \alpha α值,这些 α \alpha α被称为权重,每个 α \alpha α对应一个 h h h。利用这些权重 α \alpha α,我们可以对这些状态向量求加权平均,结果称为context vector记为 c 0 c_0 c0。每一个Context vector c c c都会对应一个状态 s s s。这里 c 0 c_0 c0对应 s 0 s_0 s0。
Decoder读入向量
x
′
x^{\prime}
x′,然后需要把状态更新为
s
1
s_1
s1。那么具体该如何计算呢?我们先来回顾一下,假如不用Attention,那么Simple RNN是这样更新状态的。
新的状态
s
1
s_1
s1是旧的状态
s
0
s_0
s0和输入
x
1
′
x^{\prime}_1
x1′的函数。公式如图所示,把
s
0
s_0
s0和
x
1
′
x^{\prime}_1
x1′做concatenation,然后乘到参数矩阵
A
′
A^{\prime}
A′上,加上偏置Intercept,然后做双曲正切变换后得到状态
s
1
s_1
s1。Simple RNN在更新状态时只需要知道旧的状态
s
0
s_0
s0和输入
x
1
′
x^{\prime}_1
x1′,而并不会去看Encoder的状态。
而使用Attention后,则会用到Context vector
c
0
c_0
c0。
在计算过程中需要把
s
0
s_0
s0、
x
1
′
x^{\prime}_1
x1′和
c
0
c_0
c0做concatenation。之后计算得到状态
s
1
s_1
s1。而
c
0
c_0
c0是知道Encoder的所有状态的,这样一来就解决了长句子遗忘的问题。
下一步我们跟之前一样的计算方式来计算 c 1 c_1 c1,
注意:这里的
α
\alpha
α是需要重新计算得到的。而不能用上一轮的值。
有了 α \alpha α之后就可以计算 c 1 c_1 c1,也是用同样的加权平均。
之后计算
s
2
s_2
s2。
以此类推,全部计算。
在计算过程中,我们需要计算很多的
α
\alpha
α,思考一下,为了完成计算所有的c,一共计算了多少
α
\alpha
α呢?
想要计算一个Context vector c c c,需要把Decoder的当前状态输出 s s s和Encoder所有m个状态做对比来计算出m个权重 α 1 , ⋯ , α m \alpha_1, \cdots, \alpha_m α1,⋯,αm。而Decoder每一步都会计算m个 α \alpha α,所以假设Decoder一共运行了t步后,共计算出了 m ∗ t m*t m∗t个权重。所以Attention的时间复杂度是 m ∗ t m*t m∗t,也就是Encoder与Decoder状态数量的乘积。这个时间复杂度是很高的。
Attention避免遗忘,大幅度提高了准确率。但是代价是巨大的计算。下面举个例子来说明权重 α \alpha α的实际意义。
上图下面是Encoder,输入为英语。上面是Decoder,输出为法语。Attention会把Encoder每个状态和Decoder每个状态作对比。得到两者相关性,也就是权重 α \alpha α。在图中,用线连接Encoder和Decoder的每个状态,每条线给与一个权重 α \alpha α,粗线表示 α \alpha α很大,细的表示很小。
例如,图中法语“zone”和英语“area”有很粗的线相连,这条线有很直观的解释。法语里的“zone”就是英语的“area”,在翻译时,Decoder都会看一遍所有的Encoder的状态,而Attention则告诉Decoder重点关注哪些部分。这也是Attention名字的由来。
Summary(总结)
标准的Seq2seq在面对长句子时会忘记之前的状态。而使用Attention之后,每次Decoder都会再看一遍Encoder的所有信息,并不会遗忘。
除了解决遗忘问题,Attention还可以告诉Encoder重点关注哪个单词。
Attention可以大幅度提高 翻译准确度。但缺点是时间复杂度太高了。