大家好,又见面了,我是你们的朋友全栈君。
在面试的过程中被问到了attention,原来虽然其实已经实际用过attention了,也知道个大概原理是加权求和,但是对于加权的具体方法以及权值得分的计算并不是很清晰,面试答的一般,正好最近实习的地方要讲attention机制,所以就跟着多学习了一下,在此做一个总结。
因为这篇文章基本上是参考了很多前人的总结性文章,所以我先把参考的文章放到前面,觉得写得不是很清楚的地方的可以直接去看我参考的文章来看,
首先我们快速过一下seq2seq中的attention机制,要了解seq2seq模型的话,不妨去参考上面的雷锋网那篇文章,这里我就快速过一下。下面的图都是seq2seq的示意图,先放的图,然后再放的图片描述:
α t = s o f t m a x ( e t ) { {\alpha }^{t}}=softmax ({ {e}^{t}}) αt=softmax(et) 利用softmax函数将attention scores转化为概率分布。
a t = ∑ i N α i h i { {\mathrm{a}}_{\mathrm{t}}}=\sum\limits_{i}^{N}{ { {\alpha }_{i}}}{ {\mathrm{h}}_{\mathrm{i}}} at=i∑Nαihi 按照刚才的概率分布,计算encoder的hidden states的加权求和。到这里其实attention的计算就结束了,得到的这个at就已经是decoder的第t时刻的注意力向量了(在后面的文章中,这个 a t \mathrm{a}_{t} at也称作是上下文向量,context vector,符号表示也可能是用 c t c_{t} ct来表示的)。
最后将注意力向量at,以及decoder的t时刻的hidden state st,并联起来,然后做后续的步骤(比如加个dense全连接层做标签预测之类的。 到这里,seq2seq的最简单最基础的attention机制,就讲完了,如果还有疑问的同学,可以去看原始论文
按照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,也就是我们上面讲过的那种最常见的attention,是在求注意力分配概率分布的时候,对于输入句子X中任意一个单词都给出个概率,是个概率分布,把attention变量(context vecor)用 c t c_t ct表示,attention得分在经过了softmax过后的权值用 a l p h a alpha alpha表示
论文:Neural machine translation by jointly learning to align and translate
Soft是给每个单词都赋予一个单词match概率,那么如果不这样做,直接从输入句子里面找到某个特定的单词,然后把目标句子单词和这个单词对齐,而其它输入句子中的单词硬性地认为对齐概率为0,这就是Hard Attention Model的思想。
Hard attention的这个pt,我也没有详细研究,不过我觉得可能跟下面讲的local attention机制的找法差不多。据说Hard attention 一般用在图像里面,当图像区域被选中时,权重为1,剩下时候为0。 https://www.cnblogs.com/Determined22/p/6914926.html
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)
论文:Teaching Machines to Read and Comprehend 以及 Supervised Sequence Labelling with Recurrent Neural Networks
上面这个图是我从论文摘过来的静态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变体。
讲完了在加权向量 α i \alpha_i αi(注意,此处这个 α i \alpha_i αi指的是得分softmax之后的向量的第i个分量)的计算上面的各种attention变体,我们现在再回到原始的soft attention公式上面来。在softmax 的Attention向量的计算总是依赖于权重求和,而权重往往是attention score的softmax。
我们可以看到,式子中的变量e代表的就是attention score,alpha是attention的权重,a就是context vector 根据attention score的计算方式不同,我们可以将attention进一步细分分类。attention score的计算主要有以下几种:
注意点积attention score这里有个假设,就是s和h的维数要一样才能进行点积,很好理解。 第二个W矩阵是训练得到的参数,维度是d2 x d1,d2是s的hidden state输出维数,d1是hi的hidden state维数 最后就是上面提到的additive attention,是对两种hidden state 分别再训练矩阵然后激活过后再乘以一个参数向量变成一个得分。 其中,W1 = d3xd1,W2 = d3*d2,v = d3x1 ,d1,d2,d3分别为h和s还有v的维数,属于超参数。
其已经被证明是非常有效的,而且所以我们下面重点关注一下。
论文: 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,.
2. 以当前状态本身去计算得分作为当前单元attention score,这种方式更常见,也更简单,例如:
其中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拆分成了两部分[ k e y t key_t keyt; v a l u e t value_t valuet],然后使用的时候只针对key部分计算attention权重,然后加权求和的时候只使用value部分进行加权求和。公式如下,attention权重计算如下:
论文:Daniluk, M., Rockt, T., Welbl, J., & Riedel, S. (2017). Frustratingly Short Attention Spans in Neural Language Modeling. In ICLR 2017.
其中呢,这个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。
首先,google先定义了一下attention的计算,也是定义出key,value,query三个元素(在seq2seq里面,query是st,key和value都是hi)在self 里面,query 是当前要计算的hi,k和v仍然一样,是其他单元的hidden state。在key value attention里面key和value则是分开了的。 然后除以了一下根号dk,为了让内积不至于太大(太大的话softmax后就非0即1了,不够“soft”了) 这里我们不妨假设,Q是nxdk,K是mxdk,v是mxdv,忽略归一化和softmax的话就是三个矩阵相乘,得到的是n*dv的矩阵。我们可以说,通过这么一个attention层,就将一个nxdk的序列Q,提取信息编码成nxdv的序列了。 Wi用来先在算attention对三个矩阵做不同的矩阵变换映射一下,变成nxdk’,mxdk’,mxdv’维度。 最后做并联,有点类似于inception 里面多个卷积核的feature map并联的感觉。
CV里面呢,有一种新的attention机制流行了起来,最出名的文章是Squeeze-and-excitation networks,里面就是这个模块就是先将一个whc的图像,做globalpooling之后压缩再还原作为权重乘上原来的feature map。 Hu J, Shen L, Sun G. Squeeze-and-excitation networks[C]//Proceedings of the IEEE conference on computer vision and pattern recognition
那么,如果我们想要在NLP里面中,用上channel attention,应该怎么用呢? 笔者认为,可以这样做一个简单的实现并迁移:
### keras 代码
time_step = 512
emb_size = 128
input = Input(shape=(time_step,emb_size)) #
g_pooling = Lambda(lambda x : K.mean(x,axis=-1))(input) #bs,time_step
squeeze = Dense(time_step // 4, activation='relu')(g_pooling) #bs,512//4
scale = Dense(time_step,activation='sigmoid')(squeeze) #bs,time_step
scale = Lambda(lambda x : x[...,tf.newaxis])(scale) #bs,time_step,1
attention_feature = multiply([input,scale])
这个思路就是我们将一句话的中的每个词的词向量(1emb_size)当做cnn里面的wh,然后每个timestep当做channel,这样做了把每个词的average pooling之后当做这个词的表征来算一下attention,这种设计笔者个人认为在一些很长的句子的时候,可能会有一点用(比如举例的timestep是512个词)
另一种实现思路是同样把句子真的过几个textCNN变成多channel的,这样的话,每个channel就代表了整个句子的表征,哪种好的话可以尝试一下,我自己是没试过的:
time_step = 512
emb_size = 128
input = Input(shape=(time_step,emb_size)) #
conv = Conv1D(32 ,3,padding='same')(input) #bs,time_step,filter_size ##这里也可以多来几个,concat
g_pooling = Lambda(lambda x : K.mean(x,axis=1,keepdims=True))(conv) #bs,1,filter_size
filter_num = K.int_shape(g_pooling)[-1]
squeeze = Dense(filter_num// 4, activation='relu')(g_pooling) #bs,1,filter_size//4
scale = Dense(filter_num,activation='sigmoid')(squeeze) #bs,1,filter_size
attention_feature = multiply([conv,scale])
以在网上找到的一段keras实现的self attention的代码,其实现原理应该是这篇论文,结构如上图所示,是利用一个mlp来计算h(t)的attention得分,然后这个代码里面进行了进一步简化,让把mlp简化得只有一个共享的w向量,但是每个ht有一个不同的偏置。 类似于 α ( h t ) = t a n h ( w T h t + b t ) \alpha(h_t)=tanh(w^T{h_t}+b_t) α(ht)=tanh(wTht+bt)的感觉。
#coding=utf-8
'''
Single model may achieve LB scores at around 0.043
Don't need to be an expert of feature engineering
All you need is a GPU!!!!!!!
The code is tested on Keras 2.0.0 using Theano backend, and Python 3.5
referrence Code:https://www.kaggle.com/lystdo/lstm-with-word2vec-embeddings
'''
########################################
## import packages
########################################
import os
import re
import csv
import codecs
import numpy as np
import pandas as pd
########################################
## set directories and parameters
########################################
from keras import backend as K
from keras.engine.topology import Layer
# from keras import initializations
from keras import initializers, regularizers, constraints
np.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
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/161475.html原文链接:https://javaforall.cn