在面试的过程中被问到了attention,原来虽然其实已经实际用过attention了,也知道个大概原理是加权求和,但是对于加权的具体方法以及权值得分的计算并不是很清晰,面试答的一般,正好最近实习的地方要讲attention机制,所以就跟着多学习了一下,在此做一个总结。
因为这篇文章基本上是参考了很多前人的总结性文章,所以我先把参考的文章放到前面,觉得写得不是很清楚的地方的可以直接去看我参考的文章来看,
首先我们快速过一下seq2seq中的attention机制,要了解seq2seq模型的话,不妨去参考上面的雷锋网那篇文章,这里我就快速过一下。下面的图都是seq2seq的示意图,先放的图,然后再放的图片描述:
按照Stanford大学课件上的描述,attention的通用定义如下:
举例:刚才seq2seq中,哪个是query,哪个是values?
each decoder hidden state attends to the encoder hidden states (decoder的第t步的hidden state----st是query,encoder的hidden states是values)
从定义来看Attention的感性认识:
首先从大的概念来讲,针对attention的变体主要有两种方式:
1.一种是在attention 向量的加权求和计算方式上进行创新
2.另一种是在attention score(匹配度或者叫权值)的计算方式上进行创新
当然还有一种就是把二者都有改变的结合性创新,或者是迁移性创新,比如借鉴CNN的Inception思想等等,后续会提到一点,详细的应该是在以后可能要讲的Tranformer里面会详细提到。
我们先针对第一种方法讲讲区别,其实虽然名字变来变去,他们的差异没有那么多。
大概分成这么几种:
Soft attention、global attention、动态attention
Hard attention
“半软半硬”的attention (local attention)
静态attention
强制前向attention
这三个其实就是Soft attention,也就是我们上面讲过的那种最常见的attention,是在求注意力分配概率分布的时候,对于输入句子X中任意一个单词都给出个概率,是个概率分布,把attention变量(context vecor)用表示,attention得分在经过了softmax过后的权值用表示
Soft是给每个单词都赋予一个单词match概率,那么如果不这样做,直接从输入句子里面找到某个特定的单词,然后把目标句子单词和这个单词对齐,而其它输入句子中的单词硬性地认为对齐概率为0,这就是Hard Attention Model的思想。
Soft attention 每次对齐的时候都要考虑前面的encoder的所有hi,所以计算量会很大,因此一种朴素的思想是只考虑部分窗口内的encoder隐藏输出,其余部分为0,在窗口内使用softmax的方式转换为概率。这个local attention相反概念的是global attention,global attention其实就是softmax attention,这里不多赘述global attention了。
论文:Effective Approaches to Attention-based Neural Machine Translation
在这个模型中,对于是时刻t的每一个目标词汇,模型首先产生一个对齐的位置 pt(aligned position),context vector 由编码器中一个集合的隐藏层状态计算得到,编码器中的隐藏层包含在窗口[pt-D,pt+D]中,D的大小通过经验选择。
上式之中,大S指的是源句子的长度,Wp和vp是指的模型的参数,通过训练得到,为了支持pt附近的对齐点,设置一个围绕pt的高斯分布,其中小s是在以pt为中心的窗口中的整数,pt是一个在[0,S]之间的实数。小Sigma σ 一般取窗口大小的一半。
静态attention:对输出句子共用一个St的attention就够了,一般用在Bilstm的首位hidden state输出拼接起来作为st(在图所示中为u)
上面这个图是我从论文摘过来的静态attention的示意图,有同学可能会注意到的是:这个前面的每个hidden state 不应该都和这里的 u 算一次attention score吗,怎么这里只有一个r和u进行了交互?
其实他这里的r表示的是加权平均的self attention,这个权就是attention ct向量,这个图里面把attention ct的计算过程省略了。直接跳到了ct和st计算真正的s’t的部分。他这个里面用的实际的attention score的计算并不是用点积,是additive attention,什么是additive attention呢?这个下面就会讲根据按照attention score计算的不同的attention model变体。
讲完了在加权向量(注意,此处这个指的是得分softmax之后的向量的第i个分量)的计算上面的各种attention变体,我们现在再回到原始的soft attention公式上面来。在softmax 的Attention向量的计算总是依赖于权重求和,而权重往往是attention score的softmax。
其已经被证明是非常有效的,而且所以我们下面重点关注一下。
论文:
Cheng, J., Dong, L., & Lapata, M. (2016). Long Short-Term Memory-Networks for Machine Reading
Parikh, A. P., Täckström, O., Das, D., & Uszkoreit, J. (2016). A Decomposable Attention Model for Natural Language Inference. In Proceedings of the 2016 Conference on Empirical Methods in Natural Language Processing.
Paulus, R., Xiong, C., & Socher, R. (2017). A Deep Reinforced Model for Abstractive Summarization. In arXiv preprint arXiv:1705.04304,
其中va和Wa是参数,通过训练得到,Wa维数dxd,hi维数dx1,va维数dx1(第一个公式)
前面的w是dx1的向量,hi是dx1,b是(1,)(第二个公式),注意这里其实是简化过后的表示:原始论文 https://arxiv.org/pdf/1512.08756.pdf
上面的式子中,使用的va向量是一个通用的向量,每个隐藏状态并没有区分,如果我们对不同状态计算的时候学习不同的向量va,,也就是一个Va矩阵,得到的就是一个attention矩阵。
论文:
https://arxiv.org/pdf/1703.03130.pdf
上面的式子中,H是nx2u(双向lstm的结果拼接,每个单向LSTM的hidden units是u),Wa是dx2u,Va是rxd,得到的attention 矩阵 A是rxn。
C = rx2u,按论文意思来看就是把一句话编码成了一个rx2u的矩阵(sentence embedding)
在实际操作的过程中,我们也可能在学习时候给A一个F2范式约束项来避免过拟合。
这种attention就是比较新的了,简单来说Key-value attention 是将hi拆分成了两部分[;],然后使用的时候只针对key部分计算attention权重,然后加权求和的时候只使用value部分进行加权求和。公式如下,attention权重计算如下:
其中呢,这个L是attention窗口长度
WY=kxk,ki=kx1,Wh = kxk,1^T是一个1xL的向量
w 是一个kx1的向量
Wr = kxk,
Wx = kxk
最后得到kx1的ht*与ht一起参与下一步的运算
最后简单讲一下google的attention is all you need 里面的attention,这一段基本上是摘抄的苏剑林的科学空间的博文了。
Google 在 attention is all you need 中发明了一种叫transformer的网络结构,其中用到了multi-head attention。Transformer在下一节课里面会讲,所以我们就简单介绍一下他里面用到的attention。
以在网上找到的一段keras实现的self attention的代码,其实现原理应该是这篇论文,结构如上图所示,是利用一个mlp来计算h(t)的attention得分,然后这个代码里面进行了进一步简化,让把mlp简化得只有一个共享的w向量,但是每个ht有一个不同的偏置。
类似于的感觉。
#coding=utf-8'''Single model may achieve LB scores at around 0.043Don't need to be an expert of feature engineeringAll you need is a GPU!!!!!!!The code is tested on Keras 2.0.0 using Theano backend, and Python 3.5referrence Code:https://www.kaggle.com/lystdo/lstm-with-word2vec-embeddings'''########################################## import packages########################################import osimport reimport csvimport codecsimport numpy as npimport pandas as pd########################################## set directories and parameters########################################from keras import backend as Kfrom keras.engine.topology import Layer# from keras import initializationsfrom keras import initializers, regularizers, constraintsnp.random.seed(2018)class Attention(Layer): def __init__(self, W_regularizer=None, b_regularizer=None, W_constraint=None, b_constraint=None, bias=True, **kwargs): """ Keras Layer that implements an Attention mechanism for temporal data. Supports Masking. Follows the work of Raffel et al. [https://arxiv.org/abs/1512.08756] # Input shape 3D tensor with shape: `(samples, steps, features)`. # Output shape 2D tensor with shape: `(samples, features)`. :param kwargs: """ self.supports_masking = True # self.init = initializations.get('glorot_uniform') self.init = initializers.get('glorot_uniform') self.W_regularizer = regularizers.get(W_regularizer) self.b_regularizer = regularizers.get(b_regularizer) self.W_constraint = constraints.get(W_constraint) self.b_constraint = constraints.get(b_constraint) self.bias = bias self.features_dim = 0 super(Attention, self).__init__(**kwargs) def build(self, input_shape): self.step_dim = input_shape[1] assert len(input_shape) == 3 # batch ,timestep , num_features print(input_shape) self.W = self.add_weight((input_shape[-1],), #num_features initializer=self.init, name='{}_W'.format(self.name), regularizer=self.W_regularizer, constraint=self.W_constraint) self.features_dim = input_shape[-1] if self.bias: self.b = self.add_weight((input_shape[1],),#timesteps initializer='zero', name='{}_b'.format(self.name), regularizer=self.b_regularizer, constraint=self.b_constraint) else: self.b = None self.built = True def compute_mask(self, input, input_mask=None): # do not pass the mask to the next layers return None def call(self, x, mask=None): features_dim = self.features_dim step_dim = self.step_dim print(K.reshape(x, (-1, features_dim)))# n, d print(K.reshape(self.W, (features_dim, 1)))# w= dx1 print(K.dot(K.reshape(x, (-1, features_dim)), K.reshape(self.W, (features_dim, 1))))#nx1 eij = K.reshape(K.dot(K.reshape(x, (-1, features_dim)), K.reshape(self.W, (features_dim, 1))), (-1, step_dim))#batch,step print(eij) if self.bias: eij += self.b eij = K.tanh(eij) a = K.exp(eij) # apply mask after the exp. will be re-normalized next if mask is not None: # Cast the mask to floatX to avoid float64 upcasting in theano a *= K.cast(mask, K.floatx()) a /= K.cast(K.sum(a, axis=1, keepdims=True) + K.epsilon(), K.floatx()) print(a) a = K.expand_dims(a) print("expand_dims:") print(a) print("x:") print(x) weighted_input = x * a print(weighted_input.shape) return K.sum(weighted_input, axis=1) def compute_output_shape(self, input_shape): # return input_shape[0], input_shape[-1] return input_shape[0], self.features_dim
联系客服