機器翻譯注意力機制及其PyTorch實現


前面闡述注意力理論知識,后面簡單描述PyTorch利用注意力實現機器翻譯

Effective Approaches to Attention-based Neural Machine Translation

簡介

Attention介紹

在翻譯的時候,選擇性的選擇一些重要信息。詳情看這篇文章 。

本着簡單和有效的原則,本論文提出了兩種注意力機制

Global

每次翻譯時,都選擇關注所有的單詞。和Bahdanau的方式 有點相似,但是更簡單些。簡單原理介紹

Local

每次翻譯時,只選擇關注一部分的單詞。介於soft和hard注意力之間。(soft和hard見別的論文)。

優點有下面幾個

  • 比Global和Soft更好計算
  • 局部注意力 隨處可見、可微,更好實現和訓練。

應用范圍

在訓練神經網絡的時候,注意力機制應用十分廣泛。讓模型在不同的形式之間,學習對齊等等。有下面一些領域:

  • 機器翻譯
  • 語音識別
  • 圖片描述
  • between image objects and agent actions in the dynamic control problem (不懂,以后再說吧)

神經機器翻譯

思想

輸入句子x=(x1,x2,,xn)x=(x1,x2,⋯,xn) ,輸出目標句子y=(y1,y2,,ym)y=(y1,y2,⋯,ym) 。

神經機器翻譯(Neural machine translation, NMT),利用神經網絡,直接對p(yx)p(y∣x) 進行建模。一般由Encoder和Decoder構成。Encoder-Decoder介紹文章鏈接 。

Encoder把輸入句子xx 編碼成一個語義向量ss (c表示也可以),然后由Decoder 一個一個產生目標單詞 yiyilogp(yx)=mj=1logp(yjy<j,s)=mj=1logp(yjy1,,yj1,s)log⁡p(y∣x)=∑j=1mlog⁡p(yj∣y<j,s)=∑j=1mlog⁡p(yj∣y1,⋯,yj−1,s)但是怎么選擇Encoder和Decoder(RNN, CNN, GRU, LSTM),怎么去生成語義ss卻有很多選擇。

概率計算

結合Decoder上一時刻的隱狀態hj1hj−1和encoder給的語義向量ss,通過函數ff ,就可以計算出當前的隱狀態hjhj :hj=f(hj1,s)hj=f(hj−1,s)通過函數gg對當前隱狀態hjhj進行轉換,再softmax,就可以得到翻譯的目標單詞yiyi了(選概率最大的那個)。

gg一般是線性變換,維數變化是[1,h][1,vocab_size][1,h]→[1,vocab_size]。p(yjy<j,s)=softmaxg(hj)p(yj∣y<j,s)=softmaxg(hj)語義向量ss​ 會貫穿整個翻譯的過程,每一步翻譯都會使用到語義向量的內容,這就是注意力機制

本論文的模型

本論文采用stack LSTM的構建NMT系統。如下所示:

訓練目標是Jt=(x,y)logp(yx)Jt=∑(x,y)−log⁡p(y∣x)

注意力模型

注意力模型廣義上分為globallocal。Global的attention來自於整個序列,而local的只來自於序列的一部分。

解碼總體流程

Decoder時,在時刻tt,要翻譯出單詞ytyt ,如下步驟:

  • 最頂層LSTM的隱狀態 htht
  • 計算帶有原句子信息語義向量ctct。Global和Local的區別在於ctct的計算方式不同
  • 串聯ht,ctht,ct,計算得到帶有注意力的隱狀態 ^ht=tanh(Wc[ct;ht])h^t=tanh⁡(Wc[ct;ht])
  • 通過注意力隱狀態得到預測概率 p(yty<t,x)=softmax(Ws^ht)p(yt∣y<t,x)=softmax(Wsh^t)

Global Attention

總體思路

在計算ctct 的時候,會考慮整個encoder的隱狀態。Decoder當前隱狀態htht, Encoder時刻s的隱狀態¯hsh¯s。

對齊向量αtαt代表時刻tt 輸入序列中的單詞對當前單詞ytyt 的對齊概率,長度是TxTx, 隨着輸入句子的長度而改變 。xsxs與ytyt 的對齊概率如下:αt(s)=align(ht,¯hs)=score(ht,¯hs)Txi=1score(ht,¯hi),softmaxαt(s)=align(ht,h¯s)=score(ht,h¯s)∑i=1Txscore(ht,h¯i),實際上softmax結合上面的解碼總體流程,有下面的流程all(¯hs),htαtct.ct,ht^ht.^htytall(h¯s),ht→αt→ct.ct,ht→h^t.h^t→yt簡單來說是htαtct^htytht→αt→ct→h^t→yt 。

score計算

score(ht,¯hs)score(ht,h¯s) 是一種基於內容content-based的函數,有3種實現方式score(ht,¯hs)=hTt¯hsdothTtWa¯hsgeneralvTatanh(Wa[ht;¯hs])concatscore(ht,h¯s)={htTh¯sdothtTWah¯sgeneralvaTtanh⁡(Wa[ht;h¯s])concat缺點

生成每個目標單詞的時候,都必須注意所有的原單詞, 這樣計算量很大,翻譯長序列可能很難,比如段落或者文章。

Local Attention

在生成目標單詞的時候,Local會選擇性地關注一小部分原單詞去計算αt,ctαt,ct,這樣就解決了Global的問題。如下圖

Soft和Hard注意

Soft 注意 :類似global注意,權值會放在圖片的所有patches中。計算復雜。

Hard 注意: 不同時刻,會選擇不同的patch。雖然計算少,但是non-differentiable,並且需要復雜的技術去訓練模型,比如方差減少和強化學習。

Local注意

類似於滑動窗口,計算一個對齊位置ptpt,根據經驗設置窗口大小DD,那么需要注意的源單詞序列是 :[ptD,pt+D][pt−D,pt+D]αtαt 的長度就是2D2D,只需要選擇這2D2D個單詞進行注意力計算,而不是Global的整個序列。

對齊位置選擇

對齊位置的選擇就很重要,主要有兩種辦法。

local-m (monotonic) 設置位置, 即以當前單詞位置作為對齊位置pt=tpt=tlocal-p (predictive) 預測位置

SS 是輸入句子的長度,預測對齊位置如下pt=Ssigmoid(vTptanh(Wpht)),pt[0,S]pt=S⋅sigmoid(vpTtanh⁡(Wpht)),pt∈[0,S]對齊向量計算

αtαt的長度就是2D2D,對於每一個s[ptD,pt+D]s∈[pt−D,pt+D], 為了更好地對齊,添加一個正態分布N(μ,σ2)N(μ,σ2),其中 μ=pt,σ=D2μ=pt,σ=D2。

計算對齊概率:αt(s)=align(ht,¯hs)exp((sμ)22σ2)=align(ht,¯hs)exp(2(spt)2D2)αt(s)=align(ht,h¯s)exp⁡(−(s−μ)22σ2)=align(ht,h¯s)exp⁡(−2(s−pt)2D2)

Input-feeding

前面的Global和Local兩種方式中,在每一步的時候,計算每一個attention (實際上是指 ^hth^t),都是獨立的,這樣只是次最優的。

在每一步的計算中,這些attention應該有所關聯,當前知道之前的attention才對。實際是應該有個coverage set去追蹤之前的信息。

我們會把當前的注意^hth^t 作為下一次的輸入,並且做一個串聯,來計算新的attention,如下圖所示

這樣有兩重意義:

  • 模型會知道之前的對齊選擇
  • 會建立一個水平和垂直都很深的網絡

PyTorch實現機器翻譯

機器翻譯github源代碼

計算輸入語義

比較簡單,使用GRU進行編碼,使用outputs作為哥哥句子的編碼語義。PyTorch RNN處理變長序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def forward(self, input_seqs, input_lengths, hidden=None):
''' 對輸入的多個句子經過GRU計算出語義信息
1. input_seqs > embeded
2. embeded - packed > GRU > outputs - pad -output
Args:
input_seqs: [s, b]
input_lengths: list[int],每個batch句子的真實長度
Returns:
outputs: [s, b, h]
hidden: [n_layer, b, h]
'''
# 一次運行,多個batch,多個序列
embedded = self.embedding(input_seqs)
packed = nn.utils.rnn.pack_padded_sequence(embedded, input_lengths)
outputs, hidden = self.gru(packed, hidden)
outputs, output_length = nn.utils.rnn.pad_packed_sequence(outputs)
# 雙向,兩個outputs求和
if self.bidir is True:
outputs = outputs[:, :, :self.hidden_size] + outputs[:, :, self.hidden_size:]
return outputs, hidden

計算對齊向量

實際上就是attn_weights, 也就是輸入序列對當前要預測的單詞的一個注意力分配

輸入輸出定義

Encoder的輸出,所有語義cc,encoder_outputs, [is, b, h]。 is=input_seq_len是輸入句子的長度

當前時刻Decoder的htht, decoder_rnn_output, [ts, b, h] 。實際上ts=1, 因為每次解碼一個單詞

1
2
3
4
5
6
7
8
def forward(self, rnn_outputs, encoder_outputs):
'''ts個時刻,計算ts個與is的對齊向量,也是注意力權值
Args:
rnn_outputs -- Decoder中GRU的輸出[ts, b, h]
encoder_outputs -- Encoder的最后的輸出, [is, b, h]
Returns:
attn_weights -- Yt與所有Xs的注意力權值,[b, ts, is]
'''

計算得分

使用gerneral的方式,先過神經網絡(線性層),再乘法計算得分

1
2
3
4
# 過Linear層 (b, h, is)
encoder_outputs = self.attn(encoder_outputs).transpose(1, 2)
# [b,ts,is] < [b,ts,h] * [b,h,is]
attn_energies = rnn_outputs.bmm(encoder_outputs)

softmax計算對齊向量

每一行都是原語義對於某個單詞的注意力分配權值向量。對齊向量實際例子

1
2
3
# [b,ts,is]
attn_weights = my_log_softmax(attn_energies)
return attn_weights

計算新的語義

新的語義也就是,對於翻譯單詞wtwt所需要的帶注意力的語義。

輸入輸出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def forward(self, input_seqs, last_hidden, encoder_outputs):
'''
一次輸入(ts, b),b個句子, ts=target_seq_len
1. input > embedded
2. embedded, last_hidden --GRU-- rnn_output, hidden
3. rnn_output, encoder_outpus --Attn-- attn_weights
4. attn_weights, encoder_outputs --相乘-- context
5. rnn_output, context --變換,tanh,變換-- output
Args:
input_seqs: [ts, b] batch個上一時刻的輸出的單詞,id表示。每個batch1個單詞
last_hidden: [n_layers, b, h]
encoder_outputs: [is, b, h]
Returns:
output: 最終的輸出,[ts, b, o]
hidden: GRU的隱狀態,[nl, b, h]
attn_weights: 對齊向量,[b, ts, is]
'''

當前時刻Decoder的隱狀態

輸入上一時刻的單詞和隱狀態,通過GRU,計算當前的隱狀態。實際上ts=1

1
2
# (ts, b, h), (nl, b, h)
rnn_output, hidden = self.gru(embedded, last_hidden)

計算對齊向量

當前時刻的隱狀態 rnn_output 和源句子的語義encoder_outputs,計算對齊向量。對齊向量

每一行都是原句子對當前單詞(只有一行)的注意力分配。

1
2
3
4
# 對齊向量 [b,ts,is]
attn_weights = self.attn(rnn_output, encoder_outputs)
# 如
[0.1, 0.2, 0.7]

計算新的語義

原語義和原語義對當前單詞分配的注意力,計算當前需要的新語義。

1
2
3
# 新的語義 
# [b,ts,h] < [b,ts,is] * [b,is,h]
context = attn_weights.bmm(encoder_outputs.transpose(0, 1))

預測當前單詞

結合新語義和當前隱狀態預測新單詞

1
2
3
4
5
6
7
8
9
# 語義和當前隱狀態結合 [ts, b, 2h] < [ts, b, h], [ts, b, h]
output_context = torch.cat((rnn_output, context), 2)
# [ts, b, h] 線性層2h-h
output_context = self.concat(output_context)
concat_output = F.tanh(output_context)
# [ts, b, o] 線性層h-o
output = self.out(concat_output)
output = my_log_softmax(output)
return output

總結

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 對齊向量
# 過Linear層 (b, h, is)
encoder_outputs = self.attn(encoder_outputs).transpose(1, 2)

# 關聯矩陣 [b,ts,is] < [b,ts,h] * [b,h,is]
attn_energies = rnn_outputs.bmm(encoder_outputs)
# 每一行求softmax [b,ts,is]
'''每一行都是原語義對當前單詞的注意力分配向量'''
attn_weights = my_log_softmax(attn_energies)

# 2. 新語義
# 新的語義 [b,ts,h] < [b,ts,is] * [b,is,h]
context = attn_weights.bmm(encoder_outputs.transpose(0, 1))

# 3. 新語義和當前隱狀態結合,輸出
# 語義和輸出 [ts, b, 2h] < [ts, b, h], [ts, b, h]
output_context = torch.cat((rnn_output, context), 2)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM