1. 什么是注意力机制

1.1 注意力机制的思想

关于什么是注意力机制,粗略的描述就是“你正在做什么,你就将注意力集中在那一点上”。这种机制就和人脑在思考问题时一样。例如我们在思考将句子“我是一个学生”翻译成英文的时候,脑子中首先想到的就是“我” ⟷ \longleftrightarrow ⟷ "I"的这样一种映射(而不是先想到其它部分),这就是注意力机制在人脑中的表现。即,我们在按词组依次翻译这句话的过程中,当我们翻译到某个部分时,我们的大脑就只会联想到(将注意力集中到)最与其**相关(相似)**的英文映射。因此,我们同样可以将这一思维过程运行到神经网络中来。

1.2 注意力机制的运用

自注意力机制被提出以来,在各个领域中都有着广泛的应用,例如机器翻译,看图说话(image caption)。而笔者也是通过谷歌开源的​​NMT翻译模型​​​而了解并学习到这一思想的。在​​之前的一篇文章中​​笔者已经介绍了注意力机制的相关计算过程,但现在想想并不算太详细。下面就再次通过Seq2Seq这一翻译模型来谈谈注意力机制。

浅谈Attention注意力机制及其实现_attention

如图p0109所示,英文句子“I am a student”被输入到一个两个的LSTM编码网络(蓝色部分),经过编码后输入到另外一个两层的LSTM解码网络(棕色部分)。当网络在按时刻进行翻译(解码)的时候,第一个时刻输出的就是图中的 h t h_t ht。在前面我们说到,我们希望网络也能同我们人脑的思考过程一样,在依次翻译每个时刻时,网络所“联想”到的都是与当前时刻最**相关(相似)**的映射。换句话说,在神经网络将"I am a student"翻译成中文的过程中,当解码到第一个时刻时,我们希望网络仅仅只是将注意力集中到单词"I"上,而尽可能忽略其它单词的影响。可这说起来容易,具体该怎么做,怎么体现呢?

我们知道 h t h_t ht​是第一个解码时刻的隐含状态,同时以上帝视角来看,最与 h t h_t ht​相关的部分应该是"I"对应的编码状态 h ‾ 1 \overline{h}_1 h1​。因此,只要网络在解码第一个时刻时,将注意力主要集中于 h ‾ 1 \overline{h}_1 h1​,也就算达成目的了。但我们怎么才能让解码部分的网络也能知道这一事实呢?好在此时的 h t h_t ht​与编码部分的隐含状态都处于同一个Embedding space,所以我们可以通过相似度对比来告诉解码网络:哪个编码时刻的隐含状态与当前解码时刻的隐含状态最为相似。这样,在解码当前时刻时,网络就能将“注意力”尽可能多的集中于对应编码时刻的隐含状态。

α t s = exp ⁡ ( s c o r e ( h t , h ˉ s ) ) ∑ s ′ = 1 S exp ⁡ ( s c o r e ( h t , h ˉ s ′ ) )                    [Attention weights] \begin{aligned} {\alpha _{ts}} = \frac{{\exp (score({h_t},{{\bar h}_s}))}}{{\sum\nolimits_{s' = 1}^S {\exp (score({h_t},{{\bar h}_{s'}}))}}}\;\;\;\;\;\;\;\;\;\text{[Attention weights]} \end{aligned} αts​=∑s′=1S​exp(score(ht​,hˉs′​))exp(score(ht​,hˉs​))​[Attention weights]​

其中score就是相似度计算函数(同时进行了softmax归一化),并且通常有两种计算方式:

s c o r e ( h t , h ‾ s ) = { h t T W h ‾ s , [Luong’s multiplicative style] v T t a n h ( w 1 h t + w 2 h ‾ s ) , [Bahdanau’s additive style] score(h_t,\overline{h}_s)= \begin{cases} h^T_tW\overline{h}_s, & \text{[Luong's multiplicative style]}\\ v^Ttanh(w_1h_t+w_2\overline{h}_s),&\text{[Bahdanau's additive style]} \end{cases} score(ht​,hs​)={htT​Whs​,vTtanh(w1​ht​+w2​hs​),​[Luong’s multiplicative style][Bahdanau’s additive style]​

当网络分别得到当前解码时刻与所有编码时刻对应的相似度系数之后(图中的attention weights),再以加权就和的形式将所有的编码状态累加起来得到context vector,最终与 h t h_t ht​ 组合得到当前解码时刻的输出。之所以要以加权求和的形式进行是因为,虽然此时的 h t h_t ht​仅仅只与 h ‾ 1 \overline{h}_1 h1​最为相关,但同样也受其它编码状态的影响(例如到句型复杂的时候)。但是,若是换了应用场景,只进行对应权重乘以对应隐含状态,不进行累加也是可以的。

c t = ∑ s α t s h ‾ s                    [Context vector] c_t=\sum_s\alpha_{ts}\overline{h}_s\;\;\;\;\;\;\;\;\;\text{[Context vector]} ct​=s∑​αts​hs​[Context vector]

α t = f ( c t , h t ) = t a n h ( W c [ c t ; h t ] )                    [Attention vector] \alpha_t=f(c_t,h_t)=tanh(W_c[c_t;h_t])\;\;\;\;\;\;\;\;\;\text{[Attention vector]} αt=f(ct,ht)=tanh(Wc[ct;ht])[Attention vector]

通过以上分析我们可以发现,要运用注意力机制最为重要的一点就是能找到一个相似度对比的过程,也就是图中 h t h_t ht与所有隐含状态对比的例子。可要是在其它场景中,没有解码时刻的 h t h_t ht,又该怎么应用注意力机制呢?

2.注意力机制的应用场景

2.1 最显而易见的编码-解码模型

由于注意力机制的首次提出(个人所知)就是在基于Seq2Seq的神经网络翻译模型中,因此若是一个模型同样是基于这样编码-解码的网络结构,那运用注意力机制就很容易了。因此,在很长一段时间内,笔者都天真的以为要想运用注意力机制就必须得是这种”编码-解码“的网络模型。以至于,第一次看到有人将注意力机制用于LSTM的文本分类模型的时候还纳闷儿,这个”注意力“是怎么给套上去的。经过和师兄的一番讨论才算是理解了广义上的注意力机制,即任给多个向量,都能将注意力给运用(套上)去。

2.2 广义上的注意力机制

浅谈Attention注意力机制及其实现_注意力机制_02

如图p0125所示为一个两层的LSTM分类网络,当网络进行文本分类时,从上帝视角可以看出决定这句话类别取向的最决定性因素在于"不知所谓"所对应的隐含状态 h ‾ 6 \overline{h}_6 h6,其次是"简直"。但是,我们怎样才能将这一事实告诉网络呢?即,如何通过注意力机制来解决这一问题。从上面的例子可知,要想运用注意力机制就得有一个相似度的比较过程,并且也正是通过这一过程的结果来告诉网络应该将注意力放在哪个隐含状态。但这个场景下的" h t h_t ht"在哪儿呢?

既然没有条件,那就创造条件,没有 h t h_t ht那就随机初始化一个 h t h_t ht,然后随着网络训练即可。

浅谈Attention注意力机制及其实现_相似度_03

如图p0126所示,这样我们就得到了context vector。至于接下来该怎么做,可就是仁者见仁智者见智了,可以同上面说到的一样,将 c t , h t c_t,h_t ct​,ht​结合作为输出,也可以直接将 c t c_t ct​作为输出。

3.怎么实现

第一种标准Seq2Seq模型的注意力机制在Tensorflow中已经有现实了,具体的用法可以参见上一篇文章​​带注意力机制的Seq2Seq翻译模型​

第二种广义上的注意力机制实现如下(以Luong相似度计算为例):

import tensorflow as tf

def attention(H):
# H: [batch_size,time_step,dimension]
H_shape = H.shape.as_list()
time_step,hidden_size = H_shape[1],H_shape[2]

h_t = tf.Variable(tf.truncated_normal(shape=[hidden_size, 1], stddev=0.5, dtype=tf.float32))
# h_t: [dimension,1] 没有h_t,创造h_t

W = tf.Variable(tf.truncated_normal(shape=[hidden_size, hidden_size], stddev=0.5, dtype=tf.float32))
# W: [dimension,dimension]
score = tf.matmul(tf.matmul(tf.reshape(H,[-1,hidden_size]), W), h_t) # score: [batch_size*time_step,1]
score = tf.reshape(score,[-1,time_step,1])# score: [batch_size,time_step,1]
alpha = tf.nn.softmax(score) # alpha: [batch_size,time_step,1]
c_t = tf.matmul(tf.transpose(H, [0, 2, 1]), alpha)#Tensor("MatMul_2:0", shape=(32, 128, 1), dtype=float32)

# c_t = H*alpha# Tensor("mul:0", shape=(32, 8, 128), dtype=float32)
return tf.tanh(c_t)


H = tf.Variable(tf.truncated_normal(shape=[32,8,128], stddev=0.5, dtype=tf.float32))
att = attention(H)
print(att)


###
Tensor("Tanh:0", shape=(32, 128, 1), dtype=float32)

需要注意的是:1)如果只进行权重相乘不累加,将第16行换成18行即可;2)此处返回的只是 c t c_t ct非线性变换后的结果,可根据需要调整;

错误更正:

经后续排查,发现上述第15行代码​​tf.nn.softmax(score)​​​有误,应该为​​tf.nn.softmax(score,axis = 1)​​​.原因在于,alpha衡量的是不同时刻H的重要性,而​​score​​​中,第1个维度(即​​axis=1​​​)才表示​​time_step​​​;默认的​​tf.nn.softmax​​计算的是最后一个维度的归一化,所以这里要指明是第1个维度。

浅谈Attention注意力机制及其实现_相似度_04