1. 程式人生 > >注意力機制 Attention Model

注意力機制 Attention Model

未接觸Attention Model之前,更多的疑惑是AM在影象當中的如何應用,怎樣在影象中計算影象區域性的注意力,計算的公式及引數的優化等。看了文章之後,發現介紹的幾乎是AM在NLP中以及文字中的應用。

一下文章轉載自https://blog.csdn.net/mpk_no1/article/details/72862348

深度學習裡的Attention model其實模擬的是人腦的注意力模型,舉個例子來說,當我們觀賞一幅畫時,雖然我們可以看到整幅畫的全貌,但是在我們深入仔細地觀察時,其實眼睛聚焦的就只有很小的一塊,這個時候人的大腦主要關注在這一小塊圖案上,也就是說這個時候人腦對整幅圖的關注並不是均衡的,是有一定的權重區分的。這就是深度學習裡的Attention Model的核心思想。

AM剛開始也確實是應用在影象領域裡的,AM在影象處理領域取得了非常好的效果!於是,就有人開始研究怎麼將AM模型引入到NLP領域。最有名的當屬“Neural machine translation by jointly learning to align and translate”這篇論文了,這篇論文最早提出了Soft Attention Model,並將其應用到了機器翻譯領域。後續NLP領域使用AM模型的文章一般都會引用這篇文章(目前引用量已經上千了!!!)

如下圖所示,機器翻譯主要使用的是Encoder-Decoder模型,在Encoder-Decoder模型的基礎上引入了AM,取得了不錯的效果:

Soft Attention Model:

這裡其實是上面圖的拆解,我們前面說過,“Neural machine translation by jointly learning to align and translate”這篇論文提出了soft Attention Model,並將其應用到了機器翻譯上面。其實,所謂Soft,意思是在求注意力分配概率分佈的時候,對於輸入句子X中任意一個單詞都給出個概率,是個概率分佈。

即上圖中的ci是對Encoder中每一個單詞都要計算一個注意力概率分佈,然後加權得到的。如下圖所示:

其實有Soft AM,對應也有一個Hard AM。既然Soft是給每個單詞都賦予一個單詞對齊概率,那麼如果不這樣做,直接從輸入句子裡面找到某個特定的單詞,然後把目標句子單詞和這個單詞對齊,而其它輸入句子中的單詞硬性地認為對齊概率為0,這就是Hard Attention Model的思想。Hard AM在影象裡證明有用,但是在文本里面用處不大,因為這種單詞一一對齊明顯要求太高,如果對不齊對後續處理負面影響很大。

但是,斯坦福大學的一篇paper“Effective Approaches to Attention-based Neural Machine Translation”提出了一個混合Soft AM 和Hard AM的模型,論文中,他們提出了兩種模型:Global Attention Model和Local Attention Model,Global Attention Model其實就是Soft Attention Model,Local Attention Model本質上是Soft AM和 Hard AM的一個混合。一般首先預估一個對齊位置Pt,然後在Pt左右大小為D的視窗範圍來取類似於Soft AM的概率分佈。

Global Attention Model和Local Attention Model

Global AM其實就是soft AM,Decoder的過程中,每一個時間步的Context vector需要計算Encoder中每一個單詞的注意力權重,然後加權得到。

 

Local AM則是首先找到一個對其位置,然後在對其位置左右一個視窗內來計算注意力權重,最終加權得到Context vector。這其實是Soft AM和Hard AM的一個混合折中。

 

靜態AM

其實還有一種AM叫做靜態AM。所謂靜態AM,其實是指對於一個文件或者句子,計算每個詞的注意力概率分佈,然後加權得到一個向量來代表這個文件或者句子的向量表示。跟soft AM的區別是,soft AM在Decoder的過程中每一次都需要重新對所有詞計算一遍注意力概率分佈,然後加權得到context vector,但是靜態AM只計算一次得到句子的向量表示即可。(這其實是針對於不同的任務而做出的改變)

 

強制前向AM

Soft AM在逐步生成目標句子單詞的時候,是由前向後逐步生成的,但是每個單詞在求輸入句子單詞對齊模型時,並沒有什麼特殊要求。強制前向AM則增加了約束條件:要求在生成目標句子單詞時,如果某個輸入句子單詞已經和輸出單詞對齊了,那麼後面基本不太考慮再用它了,因為輸入和輸出都是逐步往前走的,所以看上去類似於強制對齊規則在往前走。

 

看了這麼多AM模型以及變種,那麼我們來看一看AM模型具體怎麼實現,涉及的公式都是怎樣的。

我們知道,注意力機制是在序列到序列模型中用於注意編碼器狀態的最常用方法,它同時還可用於回顧序列模型的過去狀態。使用注意力機制,系統能基於隱藏狀態 s_1,...,s_m 而獲得環境向量(context vector)c_i,這些環境向量可以和當前的隱藏狀態 h_i 一起實現預測。環境向量 c_i 可以由前面狀態的加權平均數得出,其中狀態所加的權就是注意力權重 a_i:

注意力函式 f_att(h_i,s_j) 計算的是目前的隱藏狀態 h_i 和前面的隱藏狀態 s_j 之間的非歸一化分配值。

而實際上,注意力函式也有很多種變體。接下來我們將討論四種注意力變體:加性注意力(additive attention)、乘法(點積)注意力(multiplicative attention)、自注意力(self-attention)和關鍵值注意力(key-value attention)。

加性注意力(additive attention)

加性注意力是最經典的注意力機制 (Bahdanau et al., 2015) [15],它使用了有一個隱藏層的前饋網路(全連線)來計算注意力的分配:

也就是:

 

乘法(點積)注意力(multiplicative attention)

乘法注意力(Multiplicative attention)(Luong et al., 2015) [16] 通過計算以下函式而簡化了注意力操作:

加性注意力和乘法注意力在複雜度上是相似的,但是乘法注意力在實踐中往往要更快速、具有更高效的儲存,因為它可以使用矩陣操作更高效地實現。兩個變體在低維度 d_h 解碼器狀態中效能相似,但加性注意力機制在更高的維度上效能更優。

 

自注意力(self-attention)

注意力機制不僅能用來處理編碼器或前面的隱藏層,它同樣還能用來獲得其他特徵的分佈,例如閱讀理解任務中作為文字的詞嵌入 (Kadlec et al., 2017) [37]。然而,注意力機制並不直接適用於分類任務,因為這些任務並不需要情感分析(sentiment analysis)等額外的資訊。在這些模型中,通常我們使用 LSTM 的最終隱藏狀態或像最大池化和平均池化那樣的聚合函式來表徵句子。

自注意力機制(Self-attention)通常也不會使用其他額外的資訊,但是它能使用自注意力關注本身進而從句子中抽取相關資訊 (Lin et al., 2017) [18]。自注意力又稱作內部注意力,它在很多工上都有十分出色的表現,比如閱讀理解 (Cheng et al., 2016) [38]、文字繼承 (textual entailment/Parikh et al., 2016) [39]、自動文字摘要 (Paulus et al., 2017) [40]。

 

關鍵值注意力(key-value attention)

關鍵值注意力 (Daniluk et al., 2017) [19] 是最近出現的注意力變體機制,它將形式和函式分開,從而為注意力計算保持分離的向量。它同樣在多種文字建模任務 (Liu & Lapata, 2017) [41] 中發揮了很大的作用。具體來說,關鍵值注意力將每一個隱藏向量 h_i 分離為一個鍵值 k_i 和一個向量 v_i:[k_i;v_i]=h_i。鍵值使用加性注意力來計算注意力分佈 a_i:

其中 L 為注意力窗體的長度,I 為所有單元為 1 的向量。然後使用注意力分佈值可以求得環境表徵 c_i:

其中環境向量 c_i 將聯合現階段的狀態值 v_i 進行預測。

 

最後的最後,再加一個自己用keras實現的簡單的靜態AM(自注意力)層的程式碼吧:

 

from keras import backend as K
from keras.engine.topology import Layer
from keras import initializers, regularizers, constraints
 
class Attention_layer(Layer):
    """
        Attention operation, with a context/query vector, for temporal data.
        Supports Masking.
        Follows the work of Yang et al. [https://www.cs.cmu.edu/~diyiy/docs/naacl16.pdf]
        "Hierarchical Attention Networks for Document Classification"
        by using a context vector to assist the attention
        # Input shape
            3D tensor with shape: `(samples, steps, features)`.
        # Output shape
            2D tensor with shape: `(samples, features)`.
        :param kwargs:
        Just put it on top of an RNN Layer (GRU/LSTM/SimpleRNN) with return_sequences=True.
        The dimensions are inferred based on the output shape of the RNN.
        Example:
            model.add(LSTM(64, return_sequences=True))
            model.add(AttentionWithContext())
        """
 
    def __init__(self,
                 W_regularizer=None, b_regularizer=None,
                 W_constraint=None, b_constraint=None,
                 bias=True, **kwargs):
 
        self.supports_masking = True
        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
        super(Attention_layer, self).__init__(**kwargs)
 
    def build(self, input_shape):
        assert len(input_shape) == 3
 
        self.W = self.add_weight((input_shape[-1], input_shape[-1],),
                                 initializer=self.init,
                                 name='{}_W'.format(self.name),
                                 regularizer=self.W_regularizer,
                                 constraint=self.W_constraint)
        if self.bias:
            self.b = self.add_weight((input_shape[-1],),
                                     initializer='zero',
                                     name='{}_b'.format(self.name),
                                     regularizer=self.b_regularizer,
                                     constraint=self.b_constraint)
 
        super(Attention_layer, self).build(input_shape)
 
    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):
        uit = K.dot(x, self.W)
 
        if self.bias:
            uit += self.b
 
        uit = K.tanh(uit)
 
        a = K.exp(uit)
 
        # 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())
 
        # in some cases especially in the early stages of training the sum may be almost zero
        # and this results in NaN's. A workaround is to add a very small positive number to the sum.
        # a /= K.cast(K.sum(a, axis=1, keepdims=True), K.floatx())
        a /= K.cast(K.sum(a, axis=1, keepdims=True) + K.epsilon(), K.floatx())
        print a
        # a = K.expand_dims(a)
        print x
        weighted_input = x * a
        print weighted_input
        return K.sum(weighted_input, axis=1)
 
    def compute_output_shape(self, input_shape):
        return (input_shape[0], input_shape[-1])