循环神经网络RNN 2—— attention注意力机制(附代码)

attention方法是一种注意力机制,很明显,是为了模仿人的观察和思维方式,将注意力集中到关键信息上,虽然还没有像人一样,完全忽略到不重要的信息,但是其效果毋庸置疑,本篇我们来总结注意力机制的不同方法。
循环神经网络RNN 1—— 基本模型及其变体
循环神经网络RNN 2—— attention注意力机制(附代码)
循环神经网络RNN 3——LSTM及其变体


1,attention的定义

我们先给出定义,后面给出attention的多种实现方法,带着问题来学习和实践。

1)给定一组向量集合values,以及一个向量query,那么attention机制就是一种根据该query计算values的加权求和的机制。这里query看做是decode的h,values看做是encode的h。
2)attention的重点就是这个集合values中的每个value的“权值”的计算方法。
3)有时候也把这种attention的机制叫做query的输出关注了(或者说叫考虑到了)原文的不同部分(Query attends to the values),因为这其中有对encode加权部分。

2,基础的attention

在Encoder-Decoder结构中,Encoder把所有的输入序列都编码成一个统一的语义特征c再解码,因此, c中必须包含原始序列中的所有信息,它的长度就成了限制模型性能的瓶颈。如机器翻译问题,当要翻译的句子较长时,一个c可能存不下那么多信息,就会造成翻译精度的下降。

Attention机制通过在每个时间输入不同的c来解决这个问题,下图是带有Attention机制的Decoder(注意,是解码部分):
在这里插入图片描述
每一个c会自动去选取与当前所要输出的y最合适的上下文信息。具体来说,我们用 a i j a_{ij} aij 衡量Encoder中第j阶段的 h j h_j hj和解码时第i阶段的相关性,最终Decoder中第i阶段的输入的上下文信息 c i c_i ci 就来自于所有hi对 a i j a_{ij} aij的加权和。
以机器翻译为例(将中文翻译成英文):
在这里插入图片描述
输入的序列是“我爱中国”,因此,Encoder中的h1、h2、h3、h4就可以分别看做是“我”、“爱”、“中”、“国”所代表的信息。在翻译成英语时,第一个上下文c1应该和“我”这个字最相关,因此对应的 a11 就比较大,而相应的 a12、a13、a14 就比较小。c2应该和“爱”最相关,因此对应的 a22 就比较大。最后的c3和h3、h4最相关,因此 a33 、 a34的值就比较大。

至此,关于Attention模型,我们就只剩最后一个问题了,那就是:这些权重 a i j a_{ij} aij 是怎么来的

事实上, a i j a_{ij} aij同样是从模型中学出的,它实际和Decoder的第i-1阶段的隐状态、Encoder第j个阶段的隐状态有关。

同样还是拿上面的机器翻译举例, a 1 j a_{1j} a1j a 1 j a_{1j} a1j 的计算(此时箭头就表示对h’和 hj 同时做变换):
在这里插入图片描述
这个过程如何计算呢? 其实思路很简单:

我们在encoder的过程中保留每个RNN单元的隐藏状态(hidden state)得到 ( h 1 … … h N ) (h_1……h_N) h1hN
此时假设decode的time-step为t,那么将decode前一个隐藏状态 h t − 1 ′ h_{t-1}' ht1,与 h 1 … … h N h_1……h_N h1hN相乘,得到attention score,成为相似度或者“影响度”,或者“匹配得分”.
在这里插入图片描述
然后利用softmax,将attention scores转化为概率分布,按照刚才的概率分布,计算encoder的hidden states的加权求和。
a t = ∑ i N α i h i a_t = \sum_i^N\alpha_ih_i at=iNαihi
attention计算完成, a t a_t at就是decoder的第t时刻的注意力向量。然后将 a t a_t at与decode时刻的hidden state并联起来,做后续处理,比如分类等。

3, attention变体

根据以上分析,attention主要分为两部分,score值的计算,权值的计算,剩下的就是加权,所以attention的变体也是围绕着这两个方面来的。一种是在attention 向量的加权求和计算方式上进行创新;一种是在attention score(匹配度或者叫权值)的计算方式上进行创新。

3.1,针对attention向量计算方式的变体

3.1.1 Soft-attention

在这里插入图片描述
这是一种基本的attention结构,先通过点乘获得attention score,经过softmax得到alpha权值,然后加权求和得到attention变量(context vecor)
论文:Neural machine translation by jointly learning to align and translate

3.1.2 Hard attention

在这里插入图片描述
区别于soft attention,hard-attention寻找特定的h与之相应的timestep对齐。

3.1.3 Local attention

在这里插入图片描述

Local-attention介于soft-attention与hard-attention之间,取一个窗口,对窗口内的数据进行加权。

方式有两种:
一种是线性对齐,假设对齐位置pt=t,然后计算窗口内的softmax。
α i = e x p ( s c o r e ( h i ′ , s i ) ) ∑ i e x p ( s c o r e ( h i ′ , s i ) ) \alpha_i=\frac{exp(score(h'_i,s_i))}{\sum_i exp(score(h'_i,s_i))} αi=iexp(score(hi,si))exp(score(hi,si))
另一种是通过一个函数预测pt的位置.
p t = S ∗ s i g m o i d ( V p t ∗ t a n h ( W p ∗ s t ) ) α t = e x p ( s c o r e ( h i ′ , s i ) ) ∑ i e x p ( s c o r e ( h i ′ , s i ) ) ∗ e x p ( − f r a c ( s − p t ) 2 σ 2 ) \begin{aligned} & p_t=S*sigmoid(V_p^t*tanh(W_p*s_t)) \\ & \alpha_t = \frac{exp(score(h'_i,s_i))}{\sum_i exp(score(h'_i,s_i))}*exp(-frac{(s-p_t)}{2\sigma^2}) \end{aligned} pt=Ssigmoid(Vpttanh(Wpst))αt=iexp(score(hi,si))exp(score(hi,si))exp(frac(spt)2σ2)
S表示源句子长度大小, W p W_p Wp V p V_p Vp表示模型参数通过训练得到,为了得到 p t p_t pt附近的对齐点,设置一个围绕 p t p_t pt的高斯分布,s是在以 p t p_t pt为中心的窗口中的整数。 p t p_t pt是一个在[0,S]之间的实数. σ 一般取窗口大小的一半.

3.2, 针对attention score计算方式的变体

在计算权值向量之前,我们要计算出attention score.
α = s o f t m a x ( e ) a = ∑ i = 1 N α i h i \begin{aligned} & \alpha = softmax(e)\\ & a = \sum_{i=1}^{N}\alpha_ih_i \end{aligned} α=softmax(e)a=i=1Nαihi
attention score的计算方式有多种:
点积:
e i = s T ∗ h i e_i = s^T*h_i ei=sThi
这里有个假设,就是s和h的维数要一样才能进行点积
乘法
e i = s T ∗ W ∗ h i e_i = s^T*W*h_i ei=sTWhi
W矩阵是训练得到的参数,维度是d2 x d1,d2是s的hidden state输出维数,d1是hi的hidden state维数。
加法
e i = v T ∗ t a n h ( W 1 h i + W 2 s ) e_i = v^T*tanh(W_1h_i+W_2s) ei=vTtanh(W1hi+W2s)
additive attention,是对两种hidden state 分别再训练矩阵然后激活过后再乘以一个参数向量变成一个得分。其中,W1 = d3xd1,W2 = d3*d2,v = d3x1 ,d1,d2,d3分别为h和s还有v的维数,属于超参数。

3.3 self-attention

Self attention也叫做intra-attention在没有任何额外信息的情况下,我们仍然可以通过允许句子使用–self attention机制来处理自己,从句子中提取关注信息。

方法一:以当前的隐藏状态去计算和前面的隐藏状态的得分,作为当前隐藏单元的attention score:
e h i = h t T ∗ W ∗ h i e_{h_i}=h_t^T*W*h_i ehi=htTWhi

方法二:以当前状态本身去计算得分作为当前单元attention score,这种方式更常见,也更简单,例如:

e h i = v α T t a n h ( w α h i ) e h i = t a n h ( w T h i + b ) \begin{aligned} & e_{h_i}=v_{\alpha}^{T}tanh(w_{\alpha}h_i)\\ & e_{h_i} = tanh(w^Th_i+b) \end{aligned} ehi=vαTtanh(wαhi)ehi=tanh(wThi+b)

#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

3.3 multi-head attention

在这里插入图片描述在这里插入图片描述
Google 在 attention is all you need 中发明了一种叫transformer的网络结构,其中用到了multi-head attention。

首先,google先定义了一下attention的参数 key,value,query三个元素(在seq2seq里面,query是st,key和value都是hi)在self 里面,query 是当前要计算的hi,k和v仍然一样,是其他单元的hidden state。在key value attention里面key和value则是分开了的。)

整个过程仍然没有脱离attention的基本结构,计算attention_score,然后得到权值,加权求和,不过这里可以设置多个head1组合在一起。

class MultiHeadAttention(Layer):

    def __init__(self, n_heads, head_dim, dropout_rate=.1, masking=True, future=False, trainable=True, **kwargs):
        self._n_heads = n_heads
        self._head_dim = head_dim
        self._dropout_rate = dropout_rate
        self._masking = masking
        self._future = future
        self._trainable = trainable
        super(MultiHeadAttention, self).__init__(**kwargs)

    def build(self, input_shape):
        self._weights_queries = self.add_weight(
            shape=(input_shape[0][-1], self._n_heads * self._head_dim),
            initializer='glorot_uniform',
            trainable=self._trainable,
            name='weights_queries')
        self._weights_keys = self.add_weight(
            shape=(input_shape[1][-1], self._n_heads * self._head_dim),
            initializer='glorot_uniform',
            trainable=self._trainable,
            name='weights_keys')
        self._weights_values = self.add_weight(
            shape=(input_shape[2][-1], self._n_heads * self._head_dim),
            initializer='glorot_uniform',
            trainable=self._trainable,
            name='weights_values')
        super(MultiHeadAttention, self).build(input_shape)


    def call(self, inputs):
        if self._masking:
            assert len(inputs) == 4, "inputs should be set [queries, keys, values, masks]."
            queries, keys, values, masks = inputs
        else:
            assert len(inputs) == 3, "inputs should be set [queries, keys, values]."
            queries, keys, values = inputs
        
        queries_linear = K.dot(queries, self._weights_queries) 
        keys_linear = K.dot(keys, self._weights_keys)
        values_linear = K.dot(values, self._weights_values)

        queries_multi_heads = tf.concat(tf.split(queries_linear, self._n_heads, axis=2), axis=0)
        keys_multi_heads = tf.concat(tf.split(keys_linear, self._n_heads, axis=2), axis=0)
        values_multi_heads = tf.concat(tf.split(values_linear, self._n_heads, axis=2), axis=0)
        
        if self._masking:
            att_inputs = [queries_multi_heads, keys_multi_heads, values_multi_heads, masks]
        else:
            att_inputs = [queries_multi_heads, keys_multi_heads, values_multi_heads]
            
        attention = ScaledDotProductAttention(
            masking=self._masking, future=self._future, dropout_rate=self._dropout_rate)
        att_out = attention(att_inputs)

        outputs = tf.concat(tf.split(att_out, self._n_heads, axis=0), axis=2)
        
        return outputs

    def compute_output_shape(self, input_shape):
        return input_shape

4,总结
综合来看,attention的机制就是一个加权求和的机制,不管是什么变体目前都没有离开这个思路,只要根据已有信息计算的隐藏状态的加权和求和,那么就是使用了attention。self attention就是仅仅在句子内部做加权求和(区别与seq2seq里面的decoder对encoder的隐藏状态做的加权求和),key-value attention是将h分开,很多模型中都可以借鉴key-value方法,比如可以将key-value看作是一样的,multi-attention看成多核并联就好了。

参考资料:

https://www.leiphone.com/news/201709/8tDpwklrKubaecTa.html
https://blog.csdn.net/hahajinbu/article/details/81940355
https://zhuanlan.zhihu.com/p/67115572
https://zhuanlan.zhihu.com/p/118503318
https://zhuanlan.zhihu.com/p/116091338
https://blog.csdn.net/xiaosongshine/article/details/86595847

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值