文本摘要項目(五)——Transformer:Attention Is All You Need

2021-03-01 煉丹之路
我們前面幾節的模型都是基於seq2seq模型的,seq2seq模型固然很好,它有很多優點,比如可以學習到序列的順序特徵、通過LSTM或GRU單元,可以學習到長期依賴的特徵等。但是在實際應用中發現,seq2seq中的RNN機制,存在以下兩個問題:因此,Transformer應運而生,它拋棄了傳統的CNN和RNN結構,整個網絡結構完全是由self-attention和Feed Forward Neural Network組成。由於不再使用RNN,所以Transformer具備了並行計算的能力,符合現有的GPU框架;其次,它使用了self-attention機制,可以計算當前詞與其他詞的attention權重,因此可以學習到具有長期依賴的文本間信息。下面,我們就具體來看看Transformer的模型結構:我們以機器翻譯為例,剖析Transformer結構,在機器翻譯中,Transformer就是一個黑盒機制:

圖1: Transformer總體結構圖

而在Transformer內部,其整體結構還是一個Encoder-Decoder,我們可以將圖1中的Transformer部分放大如下圖2:

圖2: Transformer內部結構圖

我們繼續將Encoders部分和Decoders部分進行放大,如圖3所示,編碼器與解碼器都是由6個block組成。而且與seq2seq模型類似,編碼器的輸出會作為解碼器的輸入。

圖3: Encoder及Decoder部分拆分

我們繼續放大Encoder的block部分,如圖4,包含一個「self-attention」模塊和一個「Feed Forward Neural Network」模塊。

圖4: Encoder Block內部結構

在Encoder中,輸入的語料會先被表徵成詞嵌入,然後經過一個「self-attention」模塊,得到向量Z,經過Add&Normalize後,向量Z輸入到「Feed Forward Neural Network」模塊,輸出向量R,再經過一個Add&Normalize操作,最後將向量R傳遞到下一個block。

圖5: block2block

Self-Attention接下來,我們繼續解析Encoder的block部分,其中最核心的,就是Self-Attention。在前面的RNN模型中,我們已經介紹了一個Attention機制,RNN中的Attention機制是通過t-1時刻的hidden state與Encoder的output加權求和後,對結果進行一個正切加softmax運算,得到的權重,其主要計算的是Encoder的各個時刻對Decoder的當前時刻的注意力權重。而Self-Attention則與之不同,它計算的是Encoder中,不同詞之間的權重關係。

圖6: Self-Attention的可視化表示

那Self-Attention具體是怎麼實現的呢?首先,對於輸入語料中的一個單詞而言,它通過一個embedding層,得到一個512維的向量X,然後,用X乘以形狀為(512,64)的三個權重矩陣,得到三個64維的向量Q(Query向量)、K(Key向量)、V(Value向量)。

然後,每個單詞的q向量,依次乘以每個單詞的k向量,得到score,為了將數據的方差拉回到原來的水平,需要對score值除以,這裡是Q、K、V的維度,即為64。接下來再對score施以softmax激活函數,得到的值乘以對應的向量V,最後將每個向量v相加得到最終的輸出結果z。

這裡的向量Q,其實就是查詢向量的意思,它詢問每個單詞對當前單詞的影響權重,得到最終的輸出向量。當然在實際計算過程中,是以矩陣的方式進行計算:

圖9: Self-Attention矩陣計算

此外,自注意力還有一個多頭的概念,多頭就意味著多組Q、K、V矩陣,在原論文中,作者定義了8個頭,即8組Q、K、V矩陣,這樣就會得到8組Z向量。最後將8組Z向量進行一個拼接後再進行一個矩陣變換,重新將向量維度變為512維。這樣做的好處是可以從多個角度學習語料的特徵,這樣可以學習到語料的更多特徵。

圖10: Multi-Self-Attention
Input Embedding由於Transformer拋棄了RNN結構,為了使它能夠學習到每個詞的位置信息,所以Transformer的Input Embedding是由Token Embedding和Positional Embedding相加得到,其中,Token Embedding就是一個512維的詞嵌入,而Positional Embedding則是由正餘弦函數得到。

圖11: Transformer Embedding
Add&Normalize下面我們進入Encoder Block的另一部分,Add&Normalize部分,我們先說一下Normalize,Transformer中的Normalize就是一個歸一化處理,不過這裡的歸一化是在層上的歸一化(Layer Normalization),而CV中的歸一化一般是Batch Normalization。為什麼要在層上做歸一化處理呢,這是因為不同的輸入長度是不一樣的,長度較短的輸入會用Padding填充,為了不把Padding計入分布,所以使用Layer Normalization。而Add屬於殘差結構,為了防止梯度消失。Feed ForwordFeed Forward層比較簡單,假設傳入的數據是y,那麼Feed Forward的操作公式如下:

由於Self Attention部分使用的基本上都是相乘和相加運算,所以為了增加整個模型的非線性能力,就加入了Feed Forward層。
介紹完Encoder部分,我們再來看看Decoder部分,其結構與Encoder部分大體相似,使用正確的生成語料作為輸入,有Self Attention和Feed Forward。不過Decoder部分多出了一塊——Encoder-Decoder Attention,與Self Attention不同的是,Encoder-Decoder Attention中的Q向量來自於Decoder部分的輸入,但是K向量和V向量則是來自於Encoder的output部分,也就是說,Encoder-Decoder Attention加入了Encoder部分的注意力權重。此外,在Decoder部分的最後,加入了一個全連接層和Softmax層,為的是將向量映射到Vocabulary中,輸出正確的詞。在摘要生成中,可以將最後的輸出和真實的標籤做一個交叉熵,然後進行訓練就可以了。

Transformer & PGN接下來,我們把Transformer與我們之前講到的PGN模型進行融合(出自論文 "Transformers and Pointer-Generator Networks for Abstractive Summarization")。我們先回顧一下之前p_gen係數的計算公式:

在Transformer中,dec_input就是Decoder的輸入經過Embedding層之後的值,hidden_state就是Decoder的hidden state,這兩個值都是已知的,而剩下的context vector,則是需要計算的,同樣和之前一樣,context vector等於attention weight與encoder output乘積,在Transformer中,encoder output用到的就是Encoder層最後的輸出,而attention weight用的則是Encoder-Decoder Attention,這樣就可以得到context vector,計算p_gen。

代碼部分說了這麼多,還是迷迷糊糊,不如直接看代碼來得清楚,ok,我們進入代碼部分。我們先來看一下Self-Attention部分,如下:
def scaled_dot_product_attention(q, k, v, mask):    matmul_qk = tf.matmul(q, k, transpose_b=True)         dk = tf.cast(tf.shape(k)[-1], tf.float32)    scaled_attention_logits = matmul_qk/tf.math.sqrt(dk)        if mask is not None:        scaled_attention_logits += (mask*-1e9)    attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1)    output = tf.matmul(attention_weights, v)    return output, attention_weights

這其中用到了一個mask,這是因為由於輸入的語料長度不一致,對於較長的語料,會進行截斷,而對於較短的語料,則會用"Padding"字符進行填充,但是在計算Self-Attention的時候,這些填充字符沒有實際意義,所以需要掩蓋掉。這裡用到的方法,是構造一個mask矩陣,對於填充部分,用-1e9表示(代表無窮小),這樣再計算Softmax的時候,這些無窮小的地方就會變為0,所以成功掩蓋掉填充欄位。再來看一下Embedding部分,這裡用Token Embedding和Position Embedding組合成Embedding部分,
class Embedding(tf.keras.layers.Layer):    def __init__(self, vocab_size, d_model):        super(Embedding, self).__init__()        self.vocab_size = vocab_size        self.d_model = d_model        # token embedding        self.embedding = tf.keras.layers.Embedding(vocab_size, d_model)        self.pos_encoding = positional_encoding(vocab_size, d_model)
def call(self, x): embed_x = self.embedding(x) embed_x *= tf.math.sqrt(tf.cast(self.d_model, tf.float32)) # 根據語料長度截取 embed_x += self.pos_encoding[:, :tf.shape(x)[1], :] return embed_x

def point_wise_feed_forward_network(d_model, dff):    return tf.keras.Sequential([        tf.keras.layers.Dense(dff, activation='relu'),        tf.keras.layers.Dense(d_model)    ])

接下來看一下Encoder Block部分,基本是按照上面的結構進行組裝,不過這裡多了一點就是在Self-Attention層和Feed Forward層後面用到了dropout層去提高模型的泛化能力。
class EncoderLayer(tf.keras.layers.Layer):    def __init__(self, d_model, num_heads, dff, rate=0.1):        super(EncoderLayer, self).__init__()        self.mha = MultiHeadAttention(d_model, num_heads)        self.ffn = point_wise_feed_forward_network(d_model, dff)        self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)        self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)        self.dropout1 = tf.keras.layers.Dropout(rate)        self.dropout2 = tf.keras.layers.Dropout(rate)    def call(self, x, training, mask):        attn_output, _ = self.mha(x, x, x, mask)        attn_output = self.dropout1(attn_output, training=training)        out1 = self.layernorm1(x+attn_output)        ffn_output = self.ffn(out1)        ffn_output = self.dropout2(ffn_output, training=training)        out2 = self.layernorm2(out1+ffn_output)        return out2

Decoder Block部分與Encoder Block部分相似,也是按照上面講的結構,對各個模塊進行組裝。可以看到這個地方用到兩個Attention,第一個是Self-Attention,第二個是EncoderDecoder-Attention。此外,還有一點,就是在Decoder Block部分中的Self-Attention中用到的mask不是Encoder Block部分中的Padding Mask,而是Look Ahead Mask,它對應的是一個下三角矩陣,依次將Decoder部分語料中的每個詞逐個顯示。這樣做的目的是為了讓每個詞生成的時候,只會注意到前面的詞,而不會注意到後面的詞。
class DecoderLayer(tf.keras.layers.Layer):    def __init__(self, d_model, num_heads, dff, rate=0.1):        super().__init__()        self.mha1 = MultiHeadAttention(d_model, num_heads)        self.mha2 = MultiHeadAttention(d_model, num_heads)        self.ffn = point_wise_feed_forward_network(d_model, dff)        self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)        self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)        self.layernorm3 = tf.keras.layers.LayerNormalization(epsilon=1e-6)        self.dropout1 = tf.keras.layers.Dropout(rate)        self.dropout2 = tf.keras.layers.Dropout(rate)        self.dropout3 = tf.keras.layers.Dropout(rate)    def call(self, x, enc_output, training, look_ahead_mask, padding_mask):        attn1, attn_weights_block1 = self.mha1(x, x, x, look_ahead_mask)        attn1 = self.dropout1(attn1, training=training)        out1 = self.layernorm1(attn1 + x)        attn2, attn_weights_block2 = self.mha2(enc_output, enc_output, out1, padding_mask)        attn2 = self.dropout2(attn2, training=training)        out2 = self.layernorm2(out1 + attn2)        ffn_output = self.ffn(out2)        ffn_output = self.dropout3(ffn_output, training=training)        out3 = self.layernorm3(ffn_output+out2)

最後看一下整個模型部分,首先是組織num_layers個Encoder Block,同樣加入Dropout層提高泛化能力。
class Encoder(tf.keras.layers.Layer):    def __init__(self, num_layers, d_model, num_heads, dff, rate=0.1):        super().__init__()        self.d_model = d_model        self.num_layers = num_layers        self.enc_layers = [EncoderLayer(d_model, num_heads, dff, rate) for _ in range(num_layers)]        self.dropout = tf.keras.layers.Dropout(rate)    def call(self, x, training, mask):        x = self.dropout(x, training=training)        for i in range(self.num_layers):            x = self.enc_layers[i](x, training, mask)        return x

其次是相對比較複雜的Decoder部分,這裡使用Encoder-Decoder-Attention與Encoder部分的output相乘得到context vector,然後再和decoder input和decoder state一起計算出p_gen係數。
    def call(self, x, enc_output, training, look_ahead_mask, padding_mask):        attention_weights = {}        out = self.dropout(x, training=training)        for i in range(self.num_layers):            out, block1, block2 = self.dec_layers[i](out, enc_output, training, look_ahead_mask, padding_mask)            attention_weights['decoder_layer{}_block1'.format(i+1)] = block1            attention_weights['decoder_layer{}_block2'.format(i+1)] = block2        enc_out_shape = tf.shape(enc_output)        context = tf.reshape(enc_output, (enc_out_shape[0], enc_out_shape[1], self.num_heads, self.depth))        context = tf.transpose(context, [0, 2, 1, 3])        context = tf.expand_dims(context, axis=2)         attn = tf.expand_dims(block2, axis=-1)         context=context*attn         context = tf.reduce_sum(context, axis=3)         context = tf.transpose(context, [0, 2, 1, 3])         context = tf.reshape(context, (tf.shape(context)[0], tf.shape(context)[1], self.d_model))                 a = self.Wx(x)        b = self.Ws(out)        c = self.Wh(context)        p_gens = tf.sigmoid(self.V(a + b + c))        return out, attention_weights, p_gens

最後是PGN_TRANSFORMER模型部分,這裡最終生成的distribution的計算方法calc_final_dist與之前PGN模型的計算方法沒有區別。
    def call(self, inp, extended_inp, max_oov_len, tar, training, enc_padding_mask, look_ahead_mask, dec_padding_mask):        embed_x = self.embedding(inp)        embed_dec = self.embedding(tar)        enc_output = self.encoder(embed_x, training, enc_padding_mask)        dec_output, attention_weights, p_gens = self.decoder(embed_dec,                                                             enc_output,                                                             training,                                                             look_ahead_mask,                                                             dec_padding_mask)        final_output = self.final_layer(dec_output)        final_output = tf.nn.softmax(final_output)        attn_dists = attention_weights['decoder_layer{}_block2'.format(self.params["num_blocks"])]        attn_dists = tf.reduce_sum(attn_dists, axis=1)/self.params["num_heads"]        final_dists = calc_final_dist(extended_inp,                                      tf.unstack(final_output, axis = 1),                                      tf.unstack(attn_dists, axis=1),                                      tf.unstack(p_gens, axis=1),                                      max_oov_len,                                      self.params["vocab_size"],                                      self.params["batch_size"])        outputs = dict(logits=tf.stack(final_dists, 1), attentions=attn_dists)        return outputs

相關焦點

  • Attention is All You Need | 每周一起讀
    論文連結:https://arxiv.org/pdf/1706.03762.pdf#Chainer#https://github.com/soskek/attention_is_all_you_need
  • 《Attention is All You Need》文獻總結
    不需要RNN、也不需要CNN,用Attention就可以了,故題為「Attention is all you need」。      以往在seq2seq任務中,我們往往使用RNN進行訓練,但在RNN中,每一time step的計算都依賴於上一time step的輸出,這就使得所有的time step必須串行化計算。
  • TensorFlow 2.0官方Transformer教程 (Attention is All you Need)
    Transformer是文章《Attention is All you Need》提出的經典自動翻譯模型。本文介紹TensorFlow 2.0實現的Transformer,使用了TensorFlow 2.0的TFDS (Datasets)等特性,從預處理到模型構建等大大降低了實現的成本,也具有很高的可讀性和可調試性。
  • 【源頭活水】詳解Transformer (Attention Is All You Need)
    同self-attention一樣,multi-head attention也加入了short-cut機制。2.5 Encoder-Decoder Attention在解碼器中,Transformer block比編碼器中多了個encoder-cecoder attention。在encoder-decoder attention中,
  • 圖解Transformer:Attention Is All You Need
    我們的輸入是一個文本句子,但是計算機只理解數字。 因此,首先,我們對輸入句子進行標記化,然後將其轉換為標記序列。 http://jalammar.github.io/illustrated-transformer/
  • 資源| 谷歌全attention機器翻譯模型Transformer的TensorFlow實現...
    機器之心編譯參與:黃小天、Smith谷歌前不久在 arXiv 上發表論文《Attention Is All You Need》,提出一種完全基於 attention 的翻譯架構 Transformer,實現了機器翻譯的新突破;近日,Github 上的一個項目給出了 Transformer 模型的 TensorFlow 實現,在官方代碼公布之前共享了自己的代碼。
  • 從Seq2seq到Attention模型到Self Attention(二)
    這篇文章將重點擺在Google於2017年發表論文「Attention is all you need」中提出的 「」The transformer模型。」The transformer」模型中主要的概念有2項:1. Self attention 2. Multi-head,此外,模型更解決了傳統attention model中無法平行化的缺點,並帶來優異的成效。
  • What you need to pay attention to in learning spoken English
    First of all, we should be interested in learningEnglish and make great efforts to develop this interest.
  • Attention isn’t all you need!BERT的力量之源遠不止注意力
    我們假設,在 BERT 模型的自然語言理解中, 組合階段也起到了很重要的作用:你並不只需要注意力機制(不僅僅需要解析,還需要組合),Attention isn’t all you need!solutions [Hochreiter 1998]Dissecting BERT part 2 [Ingham 2019]原文連結:https://medium.com/synapse-dev/understanding-bert-transformer-attention-isnt-all-you-need
  • 讀 | All you need is Love
    All you need is Love The first thing you
  • All You Need, You Already Have
    This is a Zen saying that can be translated in a number of ways, all to do with contentment. But my favorite translation is:「All you need, you already have.」
  • Transformer生成論文摘要方法已出
    快樂的是可以將自己的研究公之於眾,痛苦的是有大段大段的文本內容需要完成。特別是摘要、引言、結論,需要不斷重複說明論文的主要研究、觀點和貢獻。現在,這樣的工作可以依賴 AI 完成了。Element AI 的研究者們提出了一種新的模型,使用 Transformer 架構,自動地生成論文的摘要。AI 攢論文的進程又往前走了一步。
  • 獨家 | Python利用深度學習進行文本摘要的綜合指南(附教程)
    我們使用深度學習完成的文本摘要結果如何呢?非常出色。因此,在本文中,我們將逐步介紹使用深度學習構建文本摘要器的過程,其中包含構建它所需的全部概念。然後將用Python實現我們的第一個文本摘要模型!注意:本文要求對一些深度學習概念有基本的了解。 我建議閱讀以下文章。
  • 遍地開花的 Attention,你真的懂嗎?
    奠基之作無疑是:2017年 NIPS《Attention is all you need》提出 transformer 的結構(涉及 self-attention,multi-head attention)。
  • 一周論文 | 文本摘要
    2016-11-05 機器之心引文本摘要是自然語言處理的一大經典任務,研究的歷史比較長。隨著目前網際網路生產出的文本數據越來越多,文本信息過載問題越來越嚴重,對各類文本進行一個「降維」處理顯得非常必要,文本摘要便是其中一個重要的手段。
  • 遍地開花的 Attention ,你真的懂嗎?
    2015年 ICML 《Show, Attend and Tell: Neural Image Caption Generation with Visual Attention》是 attention(提出hard/soft attention的概念)在 image caption 上的應用,故事圓滿,符合直覺,人類再次表示很服氣。
  • 【transformer】 你應該知道的 transformer
    All You Need提出的transformer 其實就是 seq2seq + self attention。 所以目前來說,我們可以直接先把transformer當成一個黑盒,就是transformer可以當成是一個序列轉碼的模型,只是它其中用了特殊的self-attention的機制。如下圖所示:
  • 贈書| 新手指南——如何通過HuggingFace Transformer整合表格數據
    在ViLBERT的例子中,作者還引入了一個co-attention transformer層,以明確定義不同模態之間的attention機制。 與ViLBert類似,MuIT中也在模態對中採用了co-attention機制。同時,MAG希望通過門控機制在某些transformer層中注入其他模態信息。
  • 圖解OpenAI的秘密武器GPT-2:可視化Transformer語言模型
    在隨後的許多研究工作中,這種架構要麼去掉了encoder,要麼去掉了decoder,只使用其中一種transformer堆棧,並儘可能高地堆疊它們,為它們提供大量的訓練文本,並投入大量的計算機設備,以訓練其中一部分語言模型,這一研究需要花費數十萬美元,就像在研究AlphaStar時也投入了數百萬美元的資金。
  • Things U Need Not Say Yes All the Time (Though u Think u Do)
    If you think this person deserves your attention, schedule one day of a month when you can devote your attention to them.2. You don’t need to say yes to people asking for your money.