之前提到,seq2seq的一大缺点是单一的语义向量难以表达长序列的完整语义,而改善这一问题的一个有效方法就是结合注意力机制,在不同的时刻针对输出计算包含不同语义的语义向量:
所谓注意力机制,本质上就是在分析过程中引入权重,在本文,我主要介绍两种注意力计算框架:原始的计算框架和multi-head attention,从原始的框架中又进一步划分为:soft attention(key=value)、soft attention(key!=value)、self-attention(query=key=value)
原始的注意力计算框架是由谷歌提出的,通过这个框架,我们可以计算出权重(也就是分配的注意力),并最终计算出一个对应的attention value:
用公式表述就是:
A t t e n t i o n ( Q u e r y , S o u r c e ) = ∑ i = 1 L x S i m i l a r i t y ( Q u e r y , K e y i ) ∗ V a l u e i Attention(Query, Source) = \sum_{i=1}^{L_x} Similarity(Query, Key_i) * Value_i Attention(Query,Source)=i=1∑LxSimilarity(Query,Keyi)∗Valuei
其中source就是输入序列,所以其实计算注意力需要设定query、key、value的数值,也需要明确最后计算出来的attention value是什么,我们可以结合NEURAL MACHINE TRANSLATION BY JOINTLY LEARNING TO ALIGN AND TRANSLATE这篇论文,看看这个计算框架是如何和seq2seq结合在一起应用的:
对于输入序列,作者使用双向RNN作为encoder,此时两个方向的RNN对于每个输入x(i)都会得到一个隐层输出h(i),作者把这两个hi拼接在一起作为x(i)对应的h(i)。
然后作者选择了decoder的上一时刻的隐层S(t-1)作为query,h(i)同时作为key和value,计算当前时刻每个h(i)对应的权重a(i),而这个a(i)其实就是分配的注意力大小:
e i j = F ( S i − 1 , h j ) e_{ij} = F(S_{i-1}, h_j) eij=F(Si−1,hj)
a i j = e x p ( e i j ) ∑ k = 1 T x e x p ( e i k ) a_{ij} = \frac{exp(e_{ij})}{\sum_{k=1}^{T_x} exp(e_{ik})} aij=∑k=1Txexp(eik)exp(eij)
F是一个普通的神经网络,利用神经网络计算再进行归一化作为h(i)的权重,巧妙的地方就在于引入了神经网络计算权重,而不是单纯通过点积之类的公式计算,增强模型整体的拟合能力。
计算出当前时刻每个h(i)的权重之后,作者选择对h(i)加权平均得到当前时刻的语义向量c(i):
c i = ∑ j = 1 T x a i j h j c_i = \sum_{j=1}^{T_x} a_{ij} h_j ci=j=1∑Txaijhj
注意,c(i)就是注意力计算框架中的attention value,在这里,不同的时刻就会计算出不同的语义向量c(i),从感性的认识上来说,这是希望不同的语义向量能包含不同的信息,从而更有效地计算每轮的输出,改善了语义向量无法一次性包含句子所有信息的问题。之后只需要利用当前的语义向量c(i)、上一时刻隐层S(i-1)、上一时刻最终输出y(i-1)计算出当前时刻的隐层S(i):
S i = f ( S i − 1 , y i − 1 , c i ) S_i = f(S_{i-1}, y_{i-1}, c_i) Si=f(Si−1,yi−1,ci)
然后就可以计算当前时刻的最终输出:
Y i = f ( y i − 1 , S i , c i ) Y_i = f(y_{i-1}, S_i, c_i) Yi=f(yi−1,Si,ci)
对照着模型的结构图和注意力机制的计算框架图,其实两者本质上是一样的,只是落实到实际的模型时,需要明确query、key、value到底是什么,最后计算出来的attention value是什么,中间的计算步骤是什么。
以上的模型就是soft attention中key等于value的情况(key和value都是hi),除此之外还有key不等于value的情况。除此之外,还可以考虑query、key、value都相等的情况,也就是self-attention。
self-attention在transformer中第一次被提出,他的应用场景还是挺多的,我主要谈一个,就是对句子中的词向量进行特征提取。一般来说,在NLP中对词进行了embedding(比如word2vec),得到的词向量是包含了多个含义的(一词多义),需要结合上下文才能明确语义,同时词向量中没有包含了句子的顺序信息,为了解决诸如此类的问题,一般我们会通过BiLSTM对输入的词向量进行特征提取。但是毕竟BiLSTM有长期依赖的问题,对于长句子可能处理的效果没那么好,于是就提出了通过self-attention进行处理。
具体过程可以这样理解,句子中的每个词同时作为query、key、value,分析的时候,就变成每个词和句子中的所有词进行对比,比如说"how are you",就会分析"how"和"how"、“are”、“you"的关系,然后继续分析"are"和"how”、“are”、"you"的关系等等。一般也是计算两两词之间的关系,从而归一化得到权重,最后再用整个句子的所有单词的词向量的加权平均作为这个词的新的词向量,遍历一次从而完成句子中词向量的更新。可以看到这个过程中两个词之间的分析与距离无关,仅仅与词向量本身有关,所以不会存在长期依赖问题,
上面介绍的都是基于原始的注意力计算框架的注意力机制,除了原始的框架,谷歌还提出了一个新的框架multi-head attention,它主要改进的地方就在于对query、key、value进行了多次不同的线性变换,再计算注意力并把结果拼接在一起,大大提高了模型的拟合能力。
Q
i
=
Q
∗
W
i
Q
Q_i = Q * W_i^Q
Qi=Q∗WiQ
K i = K ∗ W i K K_i = K * W_i^K Ki=K∗WiK
V i = V ∗ W i V V_i = V * W_i^V Vi=V∗WiV
上图就清晰地反映了模型是如何对query、key、value进行多次线性变换并拼接的,值得注意的是,线性变换的参数矩阵也是可以被模型学习调整的,所以multi-head的模型可以对query、key、value进行多次调整,这个过程有点像用多个卷积核计算进行多通道的卷积运算,认为不同的卷积核提取不同的特征,这样就可以从多个角度进行分析,类似的,我认为multi-head中选择对QKV进行多次线性变换一方面也是希望能从多个角度进行分析,但更重要的我认为还是希望增加模型参数量,提高模型的拟合能力。
最后总结一下,本文介绍了两种注意力计算框架,原始的框架根据query、key、value不同可以划分为soft attention和self-attention,当然除此之外还有hard attention,但是这个不常用这里我就不说了,multi-head作为改进版的计算框架,在拟合能力上则更强大。