Attention机制介绍(原理+代码)

直接进入正题吧,在介绍Attention机制之前需要知道什么是seq2seq模型,也就是Encoder-Decoder模型,下面对seq2seq进行简单的介绍。

1、seq2seq模型

作为RNN模型的一种变体:N vs M,此结构又称为Encoder-Decoder模型,也就是我们常说的seq2seq模型。seq2seq模型的出现解决了许多应用的问题,比如解决了传统的序列等长的问题,在机器翻译等领域得到了很好的运用。
seq2seq模型先将输入数据编码成一个上下文向量c
Attention机制介绍(原理+代码)_第1张图片
这里的上下文向量c有多种方式,可以将最后一个隐状态赋值给c,也可以将最后一个隐状态做变换赋值给c,也可以将所有的状态做变换赋值给c
得到c之后,然后用另外一个RNN模型进行解码,也就是Decoder过程。你可以理解为将c作为新的h0作为输入到Decoder结构中,
Attention机制介绍(原理+代码)_第2张图片
Attention机制介绍(原理+代码)_第3张图片
输入的情况分为上面两种,这种结构不限制输入和输出的序列长度,所以在很多领域得到了应用,但其本身存在着有些问题,在Encoder-Decoder结构中,Encoder把所有的输入序列都编码成一个统一的语义特征c再解码,因此, c中必须包含原始序列中的所有信息,它的长度就成了限制模型性能的瓶颈。如机器翻译问题,当要翻译的句子较长时,一个c可能存不下那么多信息,就会造成翻译精度的下降,为了解决这个问题,采用了attention机制。

2、Attention机制

Attention机制的定义
1、给定一组向量集合values,以及一个向量query,attention机制是一种根据该query计算values的加权求和的机制。
2、attention的重点就是这个集合values中的每个value的“权值”的计算方法。
3、有时候也把这种attention的机制叫做query的输出关注了(或者说叫考虑到了)原文的不同部分。(Query attends to the values)
举例:刚才seq2seq中,哪个是query,哪个是values?
each decoder hidden state attends to the encoder hidden states (decoder的第t步的hidden state----st是query,encoder的hidden states是values)
从定义来看Attention的感性认识:

The weighted sum is a selective summary of the information contained in the values, where the query determines which values to focus on.
换句话说,attention机制就是一种根据某些规则或者某些额外信息(query)从向量表达集合(values)中抽取特定的向量进行加权组合(attention)的方法。简单来讲,只要我们从部分向量里面搞了加权求和,那就算用了attention。

  1. attention的计算变体
    首先从大的概念来讲,针对attention的变体主要有两种方式:
    1.一种是在attention 向量的加权求和计算方式上进行创新
    2.另一种是在attention score(匹配度或者叫权值)的计算方式上进行创新

当然还有一种就是把二者都有改变的结合性创新,或者是迁移性创新,比如借鉴CNN的Inception思想等等,后续会提到一点,详细的应该是在以后可能要讲的Tranformer里面会详细提到。
我们先针对第一种方法讲讲区别,其实虽然名字变来变去,他们的差异没有那么多。

3.1 针对attention向量计算方式变体
大概分成这么几种:

Soft attention、global attention、动态attention

Hard attention

“半软半硬”的attention (local attention)

静态attention

强制前向attention

Soft attention、global attention、动态attention
这三个其实就是Soft attention,也就是我们上面讲过的那种最常见的attention,是在求注意力分配概率分布的时候,对于输入句子X中任意一个单词都给出个概率,是个概率分布,把attention变量(context vecor)用ct c_tc
t

表示,attention得分在经过了softmax过后的权值用alpha表示

Attention机制介绍(原理+代码)_第4张图片
Hard attention
Soft是给每个单词都赋予一个单词match概率,那么如果不这样做,直接从输入句子里面找到某个特定的单词,然后把目标句子单词和这个单词对齐,而其它输入句子中的单词硬性地认为对齐概率为0,这就是Hard Attention Model的思想。
Attention机制介绍(原理+代码)_第5张图片
Hard attention的这个pt,我也没有详细研究,不过我觉得可能跟下面讲的local attention机制的找法差不多。据说Hard attention 一般用在图像里面,当图像区域被选中时,权重为1,剩下时候为0。
https://www.cnblogs.com/Determined22/p/6914926.html

local attention (半软半硬attention)
Soft attention 每次对齐的时候都要考虑前面的encoder的所有hi,所以计算量会很大,因此一种朴素的思想是只考虑部分窗口内的encoder隐藏输出,其余部分为0,在窗口内使用softmax的方式转换为概率。这个local attention相反概念的是global attention,global attention其实就是softmax attention,这里不多赘述global attention了。
Attention机制介绍(原理+代码)_第6张图片
论文: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
静态attention:对输出句子共用一个St的attention就够了,一般用在Bilstm的首位hidden state输出拼接起来作为st(在图所示中为u)
Attention机制介绍(原理+代码)_第7张图片
论文: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变体。

3.2 针对Attention score的计算方式变体
讲完了在加权向量 α i \alpha_i αi(注意,此处这个 α i \alpha_i αi指的是得分softmax之后的向量的第i个分量)的计算上面的各种attention变体,我们现在再回到原始的soft attention公式上面来。在softmax 的Attention向量的计算总是依赖于权重求和,而权重往往是attention score的softmax。

Attention机制介绍(原理+代码)_第8张图片
我们可以看到,式子中的变量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的维数,属于超参数。

4.更加特殊的attention
4.1 self attention
思想:Self attention也叫做intra-attention在没有任何额外信息的情况下,我们仍然可以通过允许句子使用–self attention机制来处理自己,从句子中提取关注信息。
它在很多任务上都有十分出色的表现,比如阅读理解 (Cheng et al., 2016) 、文本继承 (textual entailment/Parikh et al., 2016) 、自动文本摘要 (Paulus et al., 2017) 。
其已经被证明是非常有效的,而且所以我们下面重点关注一下。

论文:
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,.

Self attention计算方式
以当前的隐藏状态去计算和前面的隐藏状态的得分,作为当前隐藏单元的attention score,例如:
Attention机制介绍(原理+代码)_第9张图片
2. 以当前状态本身去计算得分作为当前单元attention score,这种方式更常见,也更简单,例如:
Attention机制介绍(原理+代码)_第10张图片
其中va和Wa是参数,通过训练得到,Wa维数dxd,hi维数dx1,va维数dx1(第一个公式)
前面的w是dx1的向量,hi是dx1,b是(1,)(第二个公式),注意这里其实是简化过后的表示:原始论文 https://arxiv.org/pdf/1512.08756.pdf
Attention机制介绍(原理+代码)_第11张图片
上面的式子中,使用的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范式约束项来避免过拟合。

key-value attention

这种attention就是比较新的了,简单来说Key-value attention 是将hi拆分成了两部分[keyt key_tkeyt​	;valuet value_tvaluet​	],然后使用的时候只针对key部分计算attention权重,然后加权求和的时候只使用value部分进行加权求和。公式如下,attention权重计算如下:
Attention机制介绍(原理+代码)_第12张图片
论文: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一起参与下一步的运算

multi-head attention

最后简单讲一下google的attention is all you need 里面的attention,这一段基本上是摘抄的苏剑林的科学空间的博文了。
Google 在 attention is all you need 中发明了一种叫transformer的网络结构,其中用到了multi-head attention。Transformer在下一节课里面会讲,所以我们就简单介绍一下他里面用到的attention。
Attention机制介绍(原理+代码)_第13张图片
Attention机制介绍(原理+代码)_第14张图片
首先,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并联的感觉。

5. 代码示例

Attention机制介绍(原理+代码)_第15张图片

以在网上找到的一段keras实现的self attention的代码,其实现原理应该是这篇论文,结构如上图所示,是利用一个mlp来计算h(t)的attention得分,然后这个代码里面进行了进一步简化,让把mlp简化得只有一个共享的w向量,但是每个ht有一个不同的偏置。
在这里插入图片描述

#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

6. 总结

总的来说,attention的机制就是一个加权求和的机制,只要我们使用了加权求和,不管你是怎么花式加权,花式求和,只要你是根据了已有信息计算的隐藏状态的加权和求和,那么就是使用了attention,而所谓的self attention就是仅仅在句子内部做加权求和(区别与seq2seq里面的decoder对encoder的隐藏状态做的加权求和)。
self attention我个人认为作用范围更大一点,而key-value其实是对attention进行了一个更广泛的定义罢了,我们前面的attention都可以套上key-value attention,比如很多时候我们是把k和v都当成一样的算来,做self的时候还可能是quey=key=value。
最后,我把分享的ppt也放到文库了,免积分下载(设置未成功,默认要1积分…),有需要的人自取吧。

你可能感兴趣的:(深度学习)