Encoder-Decoder框架==sequence to sequence 條件生成框架
attention 機制的最典型應用是統計機器翻譯。給定任務,輸入是“Echt”, “Dicke” and “Kiste”進 encoder,使用 rnn 表示文本為固定長度向量 h3。但問題就在於,當前 decoder 生成 y1 時僅僅依賴於最后一個隱層狀態h3,也就是 sentence_embedding。那么這個 h3 必須 encode 輸入句子中的全部信息才行。可實際上,傳統Encoder-Decoder模型並不能達到這個功能。那 LSTM [3]不就是用來解決長期依賴信息問題的嘛?但事實上,長短期記憶網絡仍然存在問題。我們說,RNN在長期信息訪問當前處理單元之前,需要按順序地通過所有之前的單元。這意味着它很容易遭遇梯度消失問題。然后引入 LSTM,使用門控某種程度上解決這個問題。的確,LSTM、GRU 和其變體能學習大量的長期信息,但它們最多只能記住相對長的信息,而不是更大更長。Encoder-Decoder框架,也被稱為 sequence to sequence 條件生成框架[1],是一種文本處理領域的研究模式。常規的 encoder-decoder方法,第一步,將輸入句子序列 X通過神經網絡編碼為固定長度的上下文向量C,也就是文本的語義表示;第二步,由另外一個神經網絡作為解碼器根據當前已經預測出來的詞記憶編碼后的上下文向量 C,來預測目標詞序列,過程中編碼器和解碼器的 RNN 是聯合訓練的,但是監督信息只出現在解碼器 RNN 一端,梯度隨着反向傳播到編碼器 RNN 一端。使用 LSTM 進行文本建模時當前流行的有效方法[2]。\alpha_{t,i}=\frac{\exp(score(u_i, u_t))}{\sum\limits_{j=1}^{T_x}\exp(score(u_j, u_t))}
\alpha_{t,i}=\frac{\exp(score(u_i, u_t))}{\sum\limits_{j=1}^{T_x}\exp(score(u_j, u_t))} |
使用 RNN 文本表示與生成
所以,我們來總結一下傳統 encoder-decoder的一般范式及其問題:任務是翻譯中文“我/愛/賽爾”到英文。傳統 encoder-decoder 先把整句話輸入進去,編碼最后一個詞“賽爾”結束之后,使用 RNN生成一個整句話的表示-向量 C,在條件生成時,當翻譯到第 2個詞“賽爾”的時候,需要退 1 步找到已經預測出來的h_1以及上下文表示 C, 然后 decode 輸出。
從注意力均等到注意力集中
在傳統Encoder-Decoder 框架下:由解碼器根據當前已經預測出來的詞記憶編碼后的上下文向量 C,來預測目標詞序列。也就是說,不論生成那個詞,我們使用的句子編碼表示 C 都是一樣的。換句話說,句子中任意單詞對生成某個目標單詞P_yi來說影響力都是相同的,也就是注意力均等。很顯然這不符合直覺。直覺應該:我翻譯哪個部分,哪個部分就應該把注意力集中於我的翻譯的原文,翻譯到第一個詞,就應該多關注原文中的第一個詞是什么意思。詳見偽代碼和下圖:
P_y1 = F(E<start>,C),
P_y2 = F((E<the>,C)
P_y3 = F((E<black>,C)
傳統 Encoder-Decoder 框架下的 RNN 進行文本翻譯,一直使用同一個 c
接下來觀察上下兩個圖的區別:相同的上下文表示C會替換成根據當前生成單詞而不斷變化的Ci。
融合 attention 機制的RNN 模型進行文本翻譯每個時刻生成不同的 c
文本翻譯過程變為:
P_y1 = F(E<start>,C_0),
P_y2 = F((E<the>,C_1)
P_y3 = F((E<black>,C_2)
Encoder-Decoder框架的代碼實現
1 class EncoderDecoder(nn.Module): 2 """ 3 A standard Encoder-Decoder architecture. Base for this and many 4 other models. 5 """ 6 def __init__(self, encoder, decoder, src_embed, tgt_embed, generator): 7 super(EncoderDecoder, self).__init__() 8 self.encoder = encoder 9 self.decoder = decoder 10 self.src_embed = src_embed 11 self.tgt_embed = tgt_embed 12 self.generator = generator 13 14 def forward(self, src, tgt, src_mask, tgt_mask): 15 "Take in and process masked src and target sequences." 16 return self.decode(self.encode(src, src_mask), src_mask, 17 tgt, tgt_mask) 18 19 def encode(self, src, src_mask): 20 return self.encoder(self.src_embed(src), src_mask) 21 22 def decode(self, memory, src_mask, tgt, tgt_mask): 23 return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)
考慮可解釋性
不含注意力模型的傳統encoder-decoder 可解釋差:對於編碼向量中究竟編碼了什么信息,如何利用這些信息以及解碼器特定行為的原因是什么我們並沒有明確的認識。包含注意力機制的結構提供了一張相對簡單的方式讓我們了解解碼器的推理過程以及模型究竟在學習什么內容,學到那些東西。盡管是一種弱可解釋性,但是已經 make sense 了。
直面 attention 的核心公式
在預測目標語言的第i個詞時,源語言第j個詞的權重為 , 權重的大小可i以j 看做是一種源語言和目標語言的軟對齊信息。
總結
使用 attention 方法實際上就在於預測一個目標詞 yi 時,自動獲取原句中不同位置的語義信息,並給每個位置信息的語義賦予的一個權重,也就是“軟”對齊信息,將這些信息整理起來計算對於當前詞 yi 的原句向量表示 c_i。
Attention 的 PyTorch應用實現
1 import torch 2 import torch.nn as nn 3 4 class BiLSTM_Attention(nn.Module): 5 def __init__(self): 6 super(BiLSTM_Attention, self).__init__() 7 8 self.embedding = nn.Embedding(vocab_size, embedding_dim) 9 self.lstm = nn.LSTM(embedding_dim, n_hidden, bidirectional=True) 10 self.out = nn.Linear(n_hidden * 2, num_classes) 11 12 # lstm_output : [batch_size, n_step, n_hidden * num_directions(=2)], F matrix 13 def attention_net(self, lstm_output, final_state): 14 hidden = final_state.view(-1, n_hidden * 2, 1) # hidden : [batch_size, n_hidden * num_directions(=2), 1(=n_layer)] 15 attn_weights = torch.bmm(lstm_output, hidden).squeeze(2) # attn_weights : [batch_size, n_step] 16 soft_attn_weights = F.softmax(attn_weights, 1) 17 # [batch_size, n_hidden * num_directions(=2), n_step] * [batch_size, n_step, 1] = [batch_size, n_hidden * num_directions(=2), 1] 18 context = torch.bmm(lstm_output.transpose(1, 2), soft_attn_weights.unsqueeze(2)).squeeze(2) 19 return context, soft_attn_weights.data.numpy() # context : [batch_size, n_hidden * num_directions(=2)] 20 21 def forward(self, X): 22 input = self.embedding(X) # input : [batch_size, len_seq, embedding_dim] 23 input = input.permute(1, 0, 2) # input : [len_seq, batch_size, embedding_dim] 24 25 hidden_state = Variable(torch.zeros(1*2, len(X), n_hidden)) # [num_layers(=1) * num_directions(=2), batch_size, n_hidden] 26 cell_state = Variable(torch.zeros(1*2, len(X), n_hidden)) # [num_layers(=1) * num_directions(=2), batch_size, n_hidden] 27 28 # final_hidden_state, final_cell_state : [num_layers(=1) * num_directions(=2), batch_size, n_hidden] 29 output, (final_hidden_state, final_cell_state) = self.lstm(input, (hidden_state, cell_state)) 30 output = output.permute(1, 0, 2) # output : [batch_size, len_seq, n_hidden] 31 attn_output, attention = self.attention_net(output, final_hidden_state) 32 return self.out(attn_output), attention # model : [batch_size, num_classes], attention : [batch_size, n_step]
注意力機制是一種在編碼器-解碼器結構中使用到的機制, 現在已經在多種任務中使用:
- 機器翻譯(Neural Machine Translation, NMT)
- 圖像描述(Image Captioning (translating an image to a sentence))
- 文本摘要(Summarization(translating to a more compact language))
而且也不再局限於編碼器-解碼器結構, 多種變體的注意力結構, 應用在各種任務中.
總的來說, 注意力機制應用在:
- 允許解碼器在序列中的多個向量中, 關注它所需要的信息, 是傳統的注意力機制的用法. 由於使用了編碼器多步輸出, 而不是使用對應步的單一定長向量, 因此保留了更多的信息.
- 作用於編碼器, 解決表征問題(例如Encoding Vector再作為其他模型的輸入), 一般使用自注意力(self-attention)
1. 編碼器-解碼器注意力機制
1.1 編碼器-解碼器結構
如上圖, 編碼器將輸入嵌入為一個向量, 解碼器根據這個向量得到輸出. 由於這種結構一般的應用場景(機器翻譯等), 其輸入輸出都是序列, 因此也被稱為序列到序列的模型Seq2Seq.
對於編碼器-解碼器結構的訓練, 由於這種結構處處可微, 因此模型的參數θθ可以通過訓練數據和最大似然估計得到最優解, 最大化對數似然函數以獲得最優模型的參數, 即:
這是一種端到端的訓練方法.
1.2 編碼器
原輸入通過一個網絡模型(CNN, RNN, DNN), 編碼為一個向量. 由於這里研究的是注意力, 就以雙向RNN作為示例模型.
對於每個時間步tt, 雙向RNN編碼得到的向量htht可以如下表示:
1.3 解碼器
這里的解碼器是單向RNN結構, 以便在每個時間點上產生輸出, 行程序列. 由於解碼器僅使用最后時間步TT對應的編碼器的隱藏向量hTxhTx, TxTx指的是當前樣本的時間步長度(對於NLP問題, 經常將所有樣本處理成等長的). 這就迫使編碼器將更多的信息整合到最后的隱藏向量hTxhTx中.
但由於hTxhTx是單個長度一定的向量, 這個單一向量的表征能力有限, 包含的信息量有限, 很多信息都會損失掉.
注意力機制允許解碼器在每一個時間步tt處考慮整個編碼器輸出的隱藏狀態序列(h1,h2,⋯,hTx)(h1,h2,⋯,hTx), 從而編碼器將更多的信息分散地保存在所有隱藏狀態向量中, 而解碼器在使用這些隱藏向量時, 就能決定對哪些向量更關心.
具體來說, 解碼器生產的目標序列(y1,⋯,yTx)(y1,⋯,yTx)中的每一個輸出(如單詞)ytyt, 都是基於如下的條件分布:
其中h~th~t是引入注意力的隱藏狀態向量(attentional hidden state), 如下得到:
htht為編碼器頂層的隱藏狀態, ctct是上下文向量, 是通過當前時間步上下文的隱藏向量計算得到的, 主要有全局和局部兩種計算方法, 下午中提到. WcWc和WsWs參數矩陣訓練得到. 為了式子的簡化沒有展示偏置項.
1.4 全局注意力
通過全局注意力計算上下文向量ctct時, 使用整個序列的隱藏向量htht, 通過加權和的方式獲得. 假設對於樣本xx的序列長度為TxTx, ctct如下計算得到:
其中長度為TxTx的校准向量alignment vector αtαt的作用是在tt時間步, 隱藏狀態序列中的所有向量的重要程度. 其中每個元素αt,iαt,i的使用softmax方法計算:
值的大小指明了序列中哪個時間步對預測當前時間步tt的作用大小.
score函數可以是任意的比對向量的函數, 一般常用:
-
點積: score(ht,hi)=hTthiscore(ht,hi)=htThi
這在使用全局注意力時有更好的效果
-
使用參數矩陣:
score(ht,hi)=hTtWαhiscore(ht,hi)=htTWαhi
這就相當於使用了一個全連接層, 這種方法在使用局部注意力時有更好的效果.
全局注意力的總結如下圖:
1.5 局部注意力
全局注意力需要在序列中所有的時間步上進行計算, 計算的代價是比較高的, 可以使用固定窗口大小的局部注意力機制, 窗口的大小為2D+12D+1. DD為超參數, 為窗口邊緣距離中心的單方向距離. 上下文向量ctct的計算方法如下:
可以看到, 只是考慮的時間步范圍的區別, 其他完全相同. ptpt作為窗口的中心, 可以直接使其等於當前時間步tt, 也可以設置為一個變量, 通過訓練獲得, 即:
其中σσ為sigmoid函數, vpvp和WpWp均為可訓練參數. 因此這樣計算得到的ptpt是一個浮點數, 但這並沒有影響, 因為計算校准權重向量αtαt時, 增加了一個均值為ptpt, 標准差為D2D2的正態分布項:
當然, 這里有pt∈R∩[0,Tx]pt∈R∩[0,Tx], i∈N∩[pt−D,pt+D]i∈N∩[pt−D,pt+D]. 由於正態項的存在, 此時的注意力機制認為窗口中心附近的時間步對應的向量更重要, 且與全局注意力相比, 除了正態項還增加了一個截斷, 即一個截斷的正態分布.
局部注意力機制總結如下圖:
2. 自注意力
2.1 與編碼器-解碼器注意力機制的不同
最大的區別是自注意力模型沒有解碼器. 因此有兩個最直接的區別:
- 上下文向量ctct在seq2seq模型中, ct=∑i=1Txαt,ihict=∑i=1Txαt,ihi, 用來組成解碼器的輸入
h~t=tanh(Wc[ct;ht])h~t=tanh(Wc[ct;ht]), 但由於自注意力機制沒有解碼器, 所以這里就直接是模型的輸出, 即為stst
- 在計算校准向量αtαt時, seq2seq模型使用的是各個位置的隱藏向量與當前隱藏向量的比較值. 在自注意力機制中, 校准向量中的每個元素由每個位置的隱藏向量與當前時間步tt的平均最優向量計算得到的, 而這個平均最優向量是通過訓練得到的
2.2 自注意力機制實現
首先將隱藏向量hihi輸入至全連接層(權重矩陣為WW), 得到uiui:
使用這個向量計算校正向量αtαt, 通過softmax歸一化得到:
這里的utut是當前時間步tt對應的平均最優向量, 每個時間步不同, 這個向量是通過訓練得到的.
最后計算最后的輸出:
一般來說, 在序列問題中, 只關心最后時間步的輸出, 前面時間步不進行輸出, 即最后的輸出為s=sTs=sT
2.3 層級注意力
如下圖, 對於一個NLP問題, 在整個架構中, 使用了兩個自注意力機制: 詞層面和句子層面. 符合文檔的自然層級結構:
詞->句子->文檔. 在每個句子中, 確定每個單詞的重要性, 在整片文檔中, 確定不同句子的重要性.
參考:
https://zhuanlan.zhihu.com/p/88376673
https://www.cnblogs.com/databingo/p/9769928.html
Attention 的本質是什么
Attention(注意力)機制如果淺層的理解,跟他的名字非常匹配。他的核心邏輯就是「從關注全部到關注重點」。
Attention 機制很像人類看圖片的邏輯,當我們看一張圖片的時候,我們並沒有看清圖片的全部內容,而是將注意力集中在了圖片的焦點上。大家看一下下面這張圖:
我們一定會看清「錦江飯店」4個字,如下圖:
但是我相信沒人會意識到「錦江飯店」上面還有一串「電話號碼」,也不會意識到「喜運來大酒家」,如下圖:
所以,當我們看一張圖片的時候,其實是這樣的:
上面所說的,我們的視覺系統就是一種 Attention機制,將有限的注意力集中在重點信息上,從而節省資源,快速獲得最有效的信息。
AI 領域的 Attention 機制
Attention 機制最早是在計算機視覺里應用的,隨后在 NLP 領域也開始應用了,真正發揚光大是在 NLP 領域,因為 2018 年 BERT 和 GPT 的效果出奇的好,進而走紅。而 Transformer 和 Attention 這些核心開始被大家重點關注。
如果用圖來表達 Attention 的位置大致是下面的樣子:
這里先讓大家對 Attention 有一個宏觀的概念,下文會對 Attention 機制做更詳細的講解。在這之前,我們先說說為什么要用 Attention。
Attention 的3大優點
之所以要引入 Attention 機制,主要是3個原因:
- 參數少
- 速度快
- 效果好
參數少
模型復雜度跟 CNN、RNN 相比,復雜度更小,參數也更少。所以對算力的要求也就更小。
速度快
Attention 解決了 RNN 不能並行計算的問題。Attention機制每一步計算不依賴於上一步的計算結果,因此可以和CNN一樣並行處理。
效果好
在 Attention 機制引入之前,有一個問題大家一直很苦惱:長距離的信息會被弱化,就好像記憶能力弱的人,記不住過去的事情是一樣的。
Attention 是挑重點,就算文本比較長,也能從中間抓住重點,不丟失重要的信息。下圖紅色的預期就是被挑出來的重點。
Attention 的原理
Attention 經常會和 Encoder–Decoder 一起說,之前的文章《一文看懂 NLP 里的模型框架 Encoder-Decoder 和 Seq2Seq》 也提到了 Attention。
下面的動圖演示了attention 引入 Encoder-Decoder 框架下,完成機器翻譯任務的大致流程。
但是,Attention 並不一定要在 Encoder-Decoder 框架下使用的,他是可以脫離 Encoder-Decoder 框架的。
下面的圖片則是脫離 Encoder-Decoder 框架后的原理圖解。
小故事講解
上面的圖看起來比較抽象,下面用一個例子來解釋 attention 的原理:
圖書管(source)里有很多書(value),為了方便查找,我們給書做了編號(key)。當我們想要了解漫威(query)的時候,我們就可以看看那些動漫、電影、甚至二戰(美國隊長)相關的書籍。
為了提高效率,並不是所有的書都會仔細看,針對漫威來說,動漫,電影相關的會看的仔細一些(權重高),但是二戰的就只需要簡單掃一下即可(權重低)。
當我們全部看完后就對漫威有一個全面的了解了。
Attention 原理的3步分解:
第一步: query 和 key 進行相似度計算,得到權值
第二步:將權值進行歸一化,得到直接可用的權重
第三步:將權重和 value 進行加權求和
從上面的建模,我們可以大致感受到 Attention 的思路簡單,四個字“帶權求和”就可以高度概括,大道至簡。做個不太恰當的類比,人類學習一門新語言基本經歷四個階段:死記硬背(通過閱讀背誦學習語法練習語感)->提綱挈領(簡單對話靠聽懂句子中的關鍵詞匯准確理解核心意思)->融會貫通(復雜對話懂得上下文指代、語言背后的聯系,具備了舉一反三的學習能力)->登峰造極(沉浸地大量練習)。
這也如同attention的發展脈絡,RNN 時代是死記硬背的時期,attention 的模型學會了提綱挈領,進化到 transformer,融匯貫通,具備優秀的表達學習能力,再到 GPT、BERT,通過多任務大規模學習積累實戰經驗,戰斗力爆棚。
要回答為什么 attention 這么優秀?是因為它讓模型開竅了,懂得了提綱挈領,學會了融會貫通。
——阿里技術
想要了解更多技術細節,可以看看下面的文章或者視頻:
「文章」深度學習中的注意力機制
「文章」探索 NLP 中的 Attention 注意力機制及 Transformer 詳解
Attention 的 N 種類型
Attention 有很多種不同的類型:Soft Attention、Hard Attention、靜態Attention、動態Attention、Self Attention 等等。下面就跟大家解釋一下這些不同的 Attention 都有哪些差別。
由於這篇文章《Attention用於NLP的一些小結》已經總結的很好的,下面就直接引用了:
本節從計算區域、所用信息、結構層次和模型等方面對Attention的形式進行歸類。
1. 計算區域
根據Attention的計算區域,可以分成以下幾種:
1)Soft Attention,這是比較常見的Attention方式,對所有key求權重概率,每個key都有一個對應的權重,是一種全局的計算方式(也可以叫Global Attention)。這種方式比較理性,參考了所有key的內容,再進行加權。但是計算量可能會比較大一些。
2)Hard Attention,這種方式是直接精准定位到某個key,其余key就都不管了,相當於這個key的概率是1,其余key的概率全部是0。因此這種對齊方式要求很高,要求一步到位,如果沒有正確對齊,會帶來很大的影響。另一方面,因為不可導,一般需要用強化學習的方法進行訓練。(或者使用gumbel softmax之類的)
3)Local Attention,這種方式其實是以上兩種方式的一個折中,對一個窗口區域進行計算。先用Hard方式定位到某個地方,以這個點為中心可以得到一個窗口區域,在這個小區域內用Soft方式來算Attention。
2. 所用信息
假設我們要對一段原文計算Attention,這里原文指的是我們要做attention的文本,那么所用信息包括內部信息和外部信息,內部信息指的是原文本身的信息,而外部信息指的是除原文以外的額外信息。
1)General Attention,這種方式利用到了外部信息,常用於需要構建兩段文本關系的任務,query一般包含了額外信息,根據外部query對原文進行對齊。
比如在閱讀理解任務中,需要構建問題和文章的關聯,假設現在baseline是,對問題計算出一個問題向量q,把這個q和所有的文章詞向量拼接起來,輸入到LSTM中進行建模。那么在這個模型中,文章所有詞向量共享同一個問題向量,現在我們想讓文章每一步的詞向量都有一個不同的問題向量,也就是,在每一步使用文章在該步下的詞向量對問題來算attention,這里問題屬於原文,文章詞向量就屬於外部信息。
2)Local Attention,這種方式只使用內部信息,key和value以及query只和輸入原文有關,在self attention中,key=value=query。既然沒有外部信息,那么在原文中的每個詞可以跟該句子中的所有詞進行Attention計算,相當於尋找原文內部的關系。
還是舉閱讀理解任務的例子,上面的baseline中提到,對問題計算出一個向量q,那么這里也可以用上attention,只用問題自身的信息去做attention,而不引入文章信息。
3. 結構層次
結構方面根據是否划分層次關系,分為單層attention,多層attention和多頭attention:
1)單層Attention,這是比較普遍的做法,用一個query對一段原文進行一次attention。
2)多層Attention,一般用於文本具有層次關系的模型,假設我們把一個document划分成多個句子,在第一層,我們分別對每個句子使用attention計算出一個句向量(也就是單層attention);在第二層,我們對所有句向量再做attention計算出一個文檔向量(也是一個單層attention),最后再用這個文檔向量去做任務。
3)多頭Attention,這是Attention is All You Need中提到的multi-head attention,用到了多個query對一段原文進行了多次attention,每個query都關注到原文的不同部分,相當於重復做多次單層attention:
最后再把這些結果拼接起來:
4. 模型方面
從模型上看,Attention一般用在CNN和LSTM上,也可以直接進行純Attention計算。
1)CNN+Attention
CNN的卷積操作可以提取重要特征,我覺得這也算是Attention的思想,但是CNN的卷積感受視野是局部的,需要通過疊加多層卷積區去擴大視野。另外,Max Pooling直接提取數值最大的特征,也像是hard attention的思想,直接選中某個特征。
CNN上加Attention可以加在這幾方面:
a. 在卷積操作前做attention,比如Attention-Based BCNN-1,這個任務是文本蘊含任務需要處理兩段文本,同時對兩段輸入的序列向量進行attention,計算出特征向量,再拼接到原始向量中,作為卷積層的輸入。
b. 在卷積操作后做attention,比如Attention-Based BCNN-2,對兩段文本的卷積層的輸出做attention,作為pooling層的輸入。
c. 在pooling層做attention,代替max pooling。比如Attention pooling,首先我們用LSTM學到一個比較好的句向量,作為query,然后用CNN先學習到一個特征矩陣作為key,再用query對key產生權重,進行attention,得到最后的句向量。
2)LSTM+Attention
LSTM內部有Gate機制,其中input gate選擇哪些當前信息進行輸入,forget gate選擇遺忘哪些過去信息,我覺得這算是一定程度的Attention了,而且號稱可以解決長期依賴問題,實際上LSTM需要一步一步去捕捉序列信息,在長文本上的表現是會隨着step增加而慢慢衰減,難以保留全部的有用信息。
LSTM通常需要得到一個向量,再去做任務,常用方式有:
a. 直接使用最后的hidden state(可能會損失一定的前文信息,難以表達全文)
b. 對所有step下的hidden state進行等權平均(對所有step一視同仁)。
c. Attention機制,對所有step的hidden state進行加權,把注意力集中到整段文本中比較重要的hidden state信息。性能比前面兩種要好一點,而方便可視化觀察哪些step是重要的,但是要小心過擬合,而且也增加了計算量。
3)純Attention
Attention is all you need,沒有用到CNN/RNN,乍一聽也是一股清流了,但是仔細一看,本質上還是一堆向量去計算attention。
5. 相似度計算方式
在做attention的時候,我們需要計算query和某個key的分數(相似度),常用方法有:
1)點乘:最簡單的方法,
2)矩陣相乘:
3)cos相似度:
4)串聯方式:把q和k拼接起來,
5)用多層感知機也可以:
參考:
https://zhuanlan.zhihu.com/p/91839581
【NLP】Attention原理和源碼解析
對attention一直停留在淺層的理解,看了幾篇介紹思想及原理的文章,也沒實踐過,今天立個Flag,一天深入原理和源碼!如果你也是處於attention model level one的狀態,那不妨好好看一下啦。
內容:
- 核心思想
- 原理解析(圖解+公式)
- 模型分類
- 優缺點
- TF源碼解析
P.S. 拒絕長篇大論,適合有基礎的同學快速深入attention,不明白的地方請留言咨詢~
1. 核心思想
Attention的思想理解起來比較容易,就是在decoding階段對input中的信息賦予不同權重。在nlp中就是針對sequence的每個time step input,在cv中就是針對每個pixel。
2. 原理解析
針對Seq2seq翻譯來說,rnn-based model差不多是圖1的樣子:

而比較基礎的加入attention與rnn結合的model是下面的樣子(也叫soft attention):


其中 是
對應的權重,算出所有權重后會進行softmax和加權,得到
。
可以看到Encoding和decoding階段仍然是rnn,但是decoding階使用attention的輸出結果 作為rnn的輸入。
那么重點來了, 權重 是怎么來的呢?常見有三種方法:
思想就是根據當前解碼“狀態”判斷輸入序列的權重分布。
如果把attention剝離出來去看的話,其實是以下的機制:

輸入是query(Q), key(K), value(V),輸出是attention value。如果與之前的模型對應起來的話,query就是 ,key就是
,value也是
。模型通過Q和K的匹配計算出權重,再結合V得到輸出:
再深入理解下去,這種機制其實做的是尋址(addressing),也就是模仿中央處理器與存儲交互的方式將存儲的內容讀出來,可以看一下李宏毅老師的課程。
3. 模型分類
3.1 Soft/Hard Attention
soft attention:傳統attention,可被嵌入到模型中去進行訓練並傳播梯度
hard attention:不計算所有輸出,依據概率對encoder的輸出采樣,在反向傳播時需采用蒙特卡洛進行梯度估計
3.2 Global/Local Attention
global attention:傳統attention,對所有encoder輸出進行計算
local attention:介於soft和hard之間,會預測一個位置並選取一個窗口進行計算
3.3 Self Attention
傳統attention是計算Q和K之間的依賴關系,而self attention則分別計算Q和K自身的依賴關系。具體的詳解會在下篇文章給出~
4. 優缺點
優點:
- 在輸出序列與輸入序列“順序”不同的情況下表現較好,如翻譯、閱讀理解
- 相比RNN可以編碼更長的序列信息
缺點:
- 對序列順序不敏感
- 通常和RNN結合使用,不能並行化
5. TF源碼解析
發現已經有人解析得很明白了,即使TF代碼有更新,原理應該還是差不多的,直接放上來吧:
顧秀森:Tensorflow源碼解讀(一):AttentionSeq2Seq模型Attention機制詳解(一)——Seq2Seq中的Attention
Attention模型在機器學習領域越來越得到廣泛的應用,准備寫一個關於Attention模型的專題,主要分為三個部分:(一)在Seq2Seq 問題中RNN與Attention的結合。 (二)拋除RNN的Self-Attention模型以及谷歌的Transformer架構。 (三)Attention及Transformer在自然語言處理及圖像處理等方面的應用。主要參考資料是Yoshua Bengio組的論文、谷歌研究組的論文及Tensor2Tensor的官方文檔、斯坦福自然語言處理相關部分講義等。
這一篇先來介紹早期的在Machine Translation(機器翻譯)中Attention機制與RNN的結合。
RNN結構的局限
機器翻譯解決的是輸入是一串在某種語言中的一句話,輸出是目標語言相對應的話的問題,如將德語中的一段話翻譯成合適的英語。之前的Neural Machine Translation(一下簡稱NMT)模型中,通常的配置是encoder-decoder結構,即encoder讀取輸入的句子將其轉換為定長的一個向量,然后decoder再將這個向量翻譯成對應的目標語言的文字。通常encoder及decoder均采用RNN結構如LSTM或GRU等(RNN基礎知識可參考循環神經網絡RNN——深度學習第十章),如下圖所示,我們利用encoder RNN將輸入語句信息總結到最后一個hidden vector中,並將其作為decoder初始的hidden vector,利用decoder解碼成對應的其他語言中的文字。

但是這個結構有些問題,尤其是RNN機制實際中存在長程梯度消失的問題,對於較長的句子,我們很難寄希望於將輸入的序列轉化為定長的向量而保存所有的有效信息,所以隨着所需翻譯句子的長度的增加,這種結構的效果會顯著下降。
Attention機制的引入
為了解決這一由長序列到定長向量轉化而造成的信息損失的瓶頸,Attention注意力機制被引入了。Attention機制跟人類翻譯文章時候的思路有些類似,即將注意力關注於我們翻譯部分對應的上下文。同樣的,Attention模型中,當我們翻譯當前詞語時,我們會尋找源語句中相對應的幾個詞語,並結合之前的已經翻譯的部分作出相應的翻譯,如下圖所示,當我們翻譯“knowledge”時,只需將注意力放在源句中“知識”的部分,當翻譯“power”時,只需將注意力集中在"力量“。這樣,當我們decoder預測目標翻譯的時候就可以看到encoder的所有信息,而不僅局限於原來模型中定長的隱藏向量,並且不會喪失長程的信息。
以上是直觀理解,我們來詳細的解釋一下數學上對應哪些運算。

- 首先我們利用RNN結構得到encoder中的hidden state
,
- 假設當前decoder的hidden state 是
,我們可以計算每一個輸入位置j與當前輸出位置的關聯性,
,寫成相應的向量形式即為
,其中
是一種相關性的算符,例如常見的有點乘形式
,加權點乘
,加和
等等。
- 對於
進行softmax操作將其normalize得到attention的分布,
,展開形式為
- 利用
我們可以進行加權求和得到相應的context vector
- 由此,我們可以計算decoder的下一個hidden state
以及該位置的輸出
。
這里關鍵的操作是計算encoder與decoder state之間的關聯性的權重,得到Attention分布,從而對於當前輸出位置得到比較重要的輸入位置的權重,在預測輸出時相應的會占較大的比重。
通過Attention機制的引入,我們打破了只能利用encoder最終單一向量結果的限制,從而使模型可以集中在所有對於下一個目標單詞重要的輸入信息上,使模型效果得到極大的改善。還有一個優點是,我們通過觀察attention 權重矩陣的變化,可以更好地知道哪部分翻譯對應哪部分源文字,有助於更好的理解模型工作機制,如下圖所示。

當然,一個自然的疑問是,Attention機制如此有效,那么我們可不可以去掉模型中的RNN部分,僅僅利用Attention呢?下一篇會詳細解釋谷歌在Attention is All you need中提出的self-attention機制及Transformer
Attention機制詳解(二)——Self-Attention與Transformer
上一篇Attention機制詳解(一)——Seq2Seq中的Attention回顧了早期Attention機制與RNN結合在機器翻譯中的效果,RNN由於其順序結構訓練速度常常受到限制,既然Attention模型本身可以看到全局的信息, 那么一個自然的疑問是我們能不能去掉RNN結構,僅僅依賴於Attention模型呢,這樣我們可以使訓練並行化,同時擁有全局信息?
這一篇就主要根據谷歌的這篇Attention is All you need論文來回顧一下僅依賴於Attention機制的Transformer架構,並結合Tensor2Tensor源代碼進行解釋。
直觀理解與模型整體結構
先來看一個翻譯的例子“I arrived at the bank after crossing the river” 這里面的bank指的是銀行還是河岸呢,這就需要我們聯系上下文,當我們看到river之后就應該知道這里bank很大概率指的是河岸。在RNN中我們就需要一步步的順序處理從bank到river的所有詞語,而當它們相距較遠時RNN的效果常常較差,且由於其順序性處理效率也較低。Self-Attention則利用了Attention機制,計算每個單詞與其他所有單詞之間的關聯,在這句話里,當翻譯bank一詞時,river一詞就有較高的Attention score。利用這些Attention score就可以得到一個加權的表示,然后再放到一個前饋神經網絡中得到新的表示,這一表示很好的考慮到上下文的信息。如下圖所示,encoder讀入輸入數據,利用層層疊加的Self-Attention機制對每一個詞得到新的考慮了上下文信息的表征。Decoder也利用類似的Self-Attention機制,但它不僅僅看之前產生的輸出的文字,而且還要attend encoder的輸出。以上步驟如下動圖所示:
Transformer模型的整體結構如下圖所示

這里面Multi-head Attention其實就是多個Self-Attention結構的結合,每個head學習到在不同表示空間中的特征,如下圖所示,兩個head學習到的Attention側重點可能略有不同,這樣給了模型更大的容量。

Self-Attention詳解
了解了模型大致原理,我們可以詳細的看一下究竟Self-Attention結構是怎樣的。其基本結構如下

對於self-attention來講,Q(Query), K(Key), V(Value)三個矩陣均來自同一輸入,首先我們要計算Q與K之間的點乘,然后為了防止其結果過大,會除以一個尺度標度 ,其中
為一個query和key向量的維度。再利用Softmax操作將其結果歸一化為概率分布,然后再乘以矩陣V就得到權重求和的表示。該操作可以表示為
這里可能比較抽象,我們來看一個具體的例子(圖片來源於https://jalammar.github.io/illustrated-transformer/,該博客講解的極其清晰,強烈推薦),假如我們要翻譯一個詞組Thinking Machines,其中Thinking的輸入的embedding vector用 表示,Machines的embedding vector用
表示。

當我們處理Thinking這個詞時,我們需要計算句子中所有詞與它的Attention Score,這就像將當前詞作為搜索的query,去和句子中所有詞(包含該詞本身)的key去匹配,看看相關度有多高。我們用 代表Thinking對應的query vector,
及
分別代表Thinking以及Machines對應的key vector,則計算Thinking的attention score的時候我們需要計算
與
的點乘,同理,我們計算Machines的attention score的時候需要計算
與
的點乘。如上圖中所示我們分別得到了
與
的點乘積,然后我們進行尺度縮放與softmax歸一化,如下圖所示:

顯然,當前單詞與其自身的attention score一般最大,其他單詞根據與當前單詞重要程度有相應的score。然后我們在用這些attention score與value vector相乘,得到加權的向量。

如果將輸入的所有向量合並為矩陣形式,則所有query, key, value向量也可以合並為矩陣形式表示

其中 是我們模型訓練過程學習到的合適的參數。上述操作即可簡化為矩陣形式

而multihead就是我們可以有不同的Q,K,V表示,最后再將其結果結合起來,如下圖所示:

這就是基本的Multihead Attention單元,對於encoder來說就是利用這些基本單元疊加,其中key, query, value均來自前一層encoder的輸出,即encoder的每個位置都可以注意到之前一層encoder的所有位置。
對於decoder來講,我們注意到有兩個與encoder不同的地方,一個是第一級的Masked Multi-head,另一個是第二級的Multi-Head Attention不僅接受來自前一級的輸出,還要接收encoder的輸出,下面分別解釋一下是什么原理。

第一級decoder的key, query, value均來自前一層decoder的輸出,但加入了Mask操作,即我們只能attend到前面已經翻譯過的輸出的詞語,因為翻譯過程我們當前還並不知道下一個輸出詞語,這是我們之后才會推測到的。
而第二級decoder也被稱作encoder-decoder attention layer,即它的query來自於之前一級的decoder層的輸出,但其key和value來自於encoder的輸出,這使得decoder的每一個位置都可以attend到輸入序列的每一個位置。
總結一下,k和v的來源總是相同的,q在encoder及第一級decoder中與k,v來源相同,在encoder-decoder attention layer中與k,v來源不同。
論文其他細節解讀
我們再來看看論文其他方面的細節,一個使position encoding,這個目的是什么呢?注意由於該模型沒有recurrence或convolution操作,所以沒有明確的關於單詞在源句子中位置的相對或絕對的信息,為了更好的讓模型學習位置信息,所以添加了position encoding並將其疊加在word embedding上。該論文中選取了三角函數的encoding方式,其他方式也可以,該研究組最近還有relation-aware self-attention機制,可參考這篇論文[1803.02155] Self-Attention with Relative Position Representations。

再來看看模型中這些Add & Norm模塊的作用。

其中Add代表了Residual Connection,是為了解決多層神經網絡訓練困難的問題,通過將前一層的信息無差的傳遞到下一層,可以有效的僅關注差異部分,這一方法之前在圖像處理結構如ResNet等中常常用到。

而Norm則代表了Layer Normalization,通過對層的激活值的歸一化,可以加速模型的訓練過程,使其更快的收斂,可參考這篇論文Layer Normalization。
源碼解讀
我們可以通過閱讀源碼加深理解,主要的部分是common_attention library以及調用該library的Transformer模型。
我們先來看看common_attention library 中的multihead-attention method(為簡化省去很多argument及logic,集中於主要的logic,而且以單一head為例並加入了自己的一些comment,感興趣的可以詳細查閱源碼):
def multihead_attention(query_antecedent, memory_antecedent, ...): """Multihead scaled-dot-product attention with input/output transformations. Args: query_antecedent: a Tensor with shape [batch, length_q, channels] memory_antecedent: a Tensor with shape [batch, length_m, channels] or None ... Returns: The result of the attention transformation. The output shape is [batch_size, length_q, hidden_dim] """ #計算q, k, v矩陣 q, k, v = compute_qkv(query_antecedent, memory_antecedent, ...) #計算dot_product的attention x = dot_product_attention(q, k, v, ...) x = common_layers.dense(x, ...) return x
其中compute_qkv定義為
def compute_qkv(query_antecedent, memory_antecedent, ...): """Computes query, key and value. Args: query_antecedent: a Tensor with shape [batch, length_q, channels] memory_antecedent: a Tensor with shape [batch, length_m, channels] ... Returns: q, k, v : [batch, length, depth] tensors """ # 注意這里如果memory_antecedent是None,它就會設置成和query_antecedent一樣,encoder的 # self-attention調用時memory_antecedent 傳進去的就是None。 if memory_antecedent is None: memory_antecedent = query_antecedent q = compute_attention_component( query_antecedent, ...) # 注意這里k,v均來自於memory_antecedent。 k = compute_attention_component( memory_antecedent, ...) v = compute_attention_component( memory_antecedent, ...) return q, k, v def compute_attention_component(antecedent, ...): """Computes attention compoenent (query, key or value). Args: antecedent: a Tensor with shape [batch, length, channels] name: a string specifying scope name. ... Returns: c : [batch, length, depth] tensor """ return common_layers.dense(antecedent, ...)
其中dot_product_attention定義為
def dot_product_attention(q, k, v, ...): """Dot-product attention. Args: q: Tensor with shape [..., length_q, depth_k]. k: Tensor with shape [..., length_kv, depth_k]. Leading dimensions must match with q. v: Tensor with shape [..., length_kv, depth_v] Leading dimensions must match with q. Returns: Tensor with shape [..., length_q, depth_v]. """ # 計算Q, K的矩陣乘積。 logits = tf.matmul(q, k, transpose_b=True) # 利用softmax將結果歸一化。 weights = tf.nn.softmax(logits, name="attention_weights") # 與V相乘得到加權表示。 return tf.matmul(weights, v)
我們再來看看Transformer模型中是如何調用的,對於encoder
def transformer_encoder(encoder_input, hparams, ...): """A stack of transformer layers. Args: encoder_input: a Tensor hparams: hyperparameters for model ... Returns: y: a Tensors """ x = encoder_input with tf.variable_scope(name): for layer in range(hparams.num_encoder_layers or hparams.num_hidden_layers): with tf.variable_scope("layer_%d" % layer): with tf.variable_scope("self_attention"): # layer_preprocess及layer_postprocess包含了一些layer normalization # 及residual connection, dropout等操作。 y = common_attention.multihead_attention( common_layers.layer_preprocess(x, hparams), #這里注意encoder memory_antecedent設置為None None, ...) x = common_layers.layer_postprocess(x, y, hparams) with tf.variable_scope("ffn"): # 前饋神經網絡部分。 y = transformer_ffn_layer( common_layers.layer_preprocess(x, hparams), hparams, ...) x = common_layers.layer_postprocess(x, y, hparams) return common_layers.layer_preprocess(x, hparams)
對於decoder
def transformer_decoder(decoder_input, encoder_output, hparams, ...): """A stack of transformer layers. Args: decoder_input: a Tensor encoder_output: a Tensor hparams: hyperparameters for model ... Returns: y: a Tensors """ x = decoder_input with tf.variable_scope(name): for layer in range(hparams.num_decoder_layers or hparams.num_hidden_layers): layer_name = "layer_%d" % layer with tf.variable_scope(layer_name): with tf.variable_scope("self_attention"): # decoder一級memory_antecedent設置為None y = common_attention.multihead_attention( common_layers.layer_preprocess(x, hparams), None, ...) x = common_layers.layer_postprocess(x, y, hparams) if encoder_output is not None: with tf.variable_scope("encdec_attention"): # decoder二級memory_antecedent設置為encoder_output y = common_attention.multihead_attention( common_layers.layer_preprocess(x, hparams), encoder_output, ...) x = common_layers.layer_postprocess(x, y, hparams) with tf.variable_scope("ffn"): y = transformer_ffn_layer( common_layers.layer_preprocess(x, hparams), hparams, ...) x = common_layers.layer_postprocess(x, y, hparams) return common_layers.layer_preprocess(x, hparams)
這些代碼驗證了我們之前在self-attention詳解中的理解是正確的。
總結
完全的不依賴於RNN結構僅利用Attention機制的Transformer由於其並行性和對全局信息的有效處理使其獲得了較之前方法更好的翻譯結果,在這基礎上,Attention和Transformer架構逐步被應用在自然語言處理及圖像處理等領域,下一篇將會介紹這些方面的應用。
Attention機制詳解(三)——Attention模型的應用
上兩篇Attention機制詳解(一)——Seq2Seq中的Attention, Attention機制詳解(二)——Self-Attention與Transformer主要回顧了Attention與RNN結合在機器翻譯中的原理以及self-attention模型,這一篇准備分類整理一下Attention模型的各種應用場景,主要參考資料為谷歌研究組和Yoshua Bengio組的論文。
自然語言處理
之前已經見過Attention模型對於機器翻譯(Attention is All you need)有非常很好的效果,那么在自然語言處理方面Attention模型還有哪些其他應用呢?我們通過總結以下幾篇論文來了解以下:
這篇文章主要是結合了Transformer結構與RNN中循環歸納的優點,使得Transformer結構能夠適用更多自然語言理解的問題。其改進的結構如下

可以看到,通過引入Transition Function,我們對Attention可以進行多次循環。這一機制被有效的應用到諸如問答,根據主語推測謂語,根據上下填充缺失的單詞,數字字符串運算處理,簡易程序執行,機器翻譯等場景。
BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
這也是最近自然語言處理領域比較火的文章,打破了多項benchmark,主要是利用雙向Transformer進行預處理,得到包含有上下文信息的表示,這一表示可進一步用來fine-tune很多種自然語言處理任務。下圖是BERT模型(雙向Tansformer結構)與OpenAI GPT(單向Transformer結構)與ElMo(雙向獨立LSTM最終組合的結構)的對比。

BERT的表示進行fine-tuning后,對於GLUE Benchmark(主要包含MNLI,RTE:比較兩個句子的語義關系,QQP:判別Quora上兩個問題相似度,QNLI:問答,SST-2:情感分析,CoLA:語句合理性判別,STS-B, MRPC:句子相似度判別),SQuAD(問答),NER(命名實體識別)等都有極大的提高。將來,可能BERT pre-train在自然語言處理領域就會像VGG, ResNet, Inception等在圖像識別里的作用而成為預處理的標配。
Generating Wikipedia by Summarizing Long Sequences
文章生成:通過處理若干篇源文章,提取有效信息,再用Transformer Decoder合成一篇類似於Wikipedia風格的文章,這個模型以后加以改善可以極大的方便人們獲取有效信息。
圖像處理及合成
其實最早Attention模型是先在圖像處理領域得到應用,后來拓展到自然語言處理領域,例如這一篇
Show, Attend and Tell: Neural Image Caption Generation with Visual Attention
就是利用Attention機制進行Image Caption(將圖像翻譯為文字表述)

利用Attention機制進行圖像合成,如將局部圖像補全:

通過低分辨率的圖像還原高分辨率圖像

由於Image Transformer模型訓練的穩定性,很有可能代替GAN成為圖像生成任務的首選。
其他
這里再列出一些綜合或其他方面的應用
采用多適應性的模型架構希望一個模型可以處理多領域的問題。其模型結構如下所示

該論文中處理了語言文字、圖像、聲音和分類數據,雖然和state-of-the-art還不能相比,但也不失為一種泛機器學習的有意思的嘗試。
Neural Attentive Session-based Recommendation
利用Attention模型處理用戶session中的序列信息進行相關推薦,值得參考。
Generating Long-Term Structure in Songs and Stories
來自谷歌Magenta項目,利用Attention RNN創作樂曲,很有意思。
Attention模型由於其並行性,高效性及可解釋性,逐漸會用在越來越多的有意思的應用場景中,值得期待。
鏈接:https://zhuanlan.zhihu.com/p/47613793