論文《Neural Machine Translation by Jointly Learning to Align Translate》

背景

這篇論文是第一個在NLP中使用attention機制的工作。翻譯任務(wù)是典型的seq2seq問題。那么,什么是seq2seq問題?簡單的說就是,根據(jù)輸入序列X,生成一個輸出序列Y,序列的長度不固定。當(dāng)輸入序列X和輸出序列Y是不同的語言時,就是機器翻譯;當(dāng)輸入序列X是問題,輸出序列Y是答案時,就是問答系統(tǒng)或者對話系統(tǒng)。根據(jù)輸入和輸出序列的特征,seq2seq主要應(yīng)用在機器翻譯、會話建模、文本摘要等。

解決seq2seq問題的基本框架是encoder-decoder模型,即編碼-解碼模型。編碼就是將輸入序列X轉(zhuǎn)化成一個固定長度的向量;解碼就是將之前生成的固定向量轉(zhuǎn)換成輸出序列Y。encoder和decoder可以看作兩個自動編碼器,使用常用的模型RNN,LSTM,GRU進行編碼。本文encoder使用BiRNN,隱藏層的輸出包含了輸入序列中的詞以及前后一些詞的信息,decoder輸入時加入attention。下面首先接受encoder-decoder模型,重點介紹如何在encoder-decoder模型上加入attention。

encoder-decoder模型

基本的seq2seq模型,包括三個部分,即Encoder、Decoder以及連接兩者的中間狀態(tài)向量State Vector,Encoder通過學(xué)習(xí)輸入,將其編碼成一個固定大小的狀態(tài)向量C(也叫上下文向量),繼而將C傳給Decoder,Decoder再通過對狀態(tài)向量C的學(xué)習(xí)來進行輸出。如下圖:


Encoder-Decoder

得到C的方式很多,最簡單的方法是把Encoder的最后一個隱狀態(tài)賦值給C,也可以對最后一個隱狀態(tài)做一個變換得到C,還可以對所有的隱狀態(tài)做變換,如下圖:


得到固定大小的狀態(tài)向量C

拿到c之后,就用另一個RNN網(wǎng)絡(luò)對其進行解碼,這部分RNN網(wǎng)絡(luò)被稱為Decoder。具體做法就是將c當(dāng)做之前的初始狀態(tài)h0輸入到Decoder中:


將C作為decoder的初始狀態(tài)

根據(jù)給定的語義向量C和之前已經(jīng)生成的輸出序列y_1,y_2,..,y_{t-1}來預(yù)測下一個輸出的單詞y_t:
y_t=argmaxP(y_t)=\prod_{t=0}^{T}p(y_t|\{y_1,y_2,..,y_{t-1}\},C)
也可以寫作:
y_t=g(\{y_1,y_2,..,y_{t-1} \},C)
在RNN中,上式可以簡化成:
y_t=g(y_{t-1},s_t,C)
其中s_t是RNN中的隱藏層,C是語義向量,y_{t-1}是上個時間段的輸出,反過來作為這個時間段的輸入。g則可以是一個非線性的多層的神經(jīng)網(wǎng)絡(luò),產(chǎn)生詞典中各個詞語屬于y_t的概率。

還有一種做法將c作為每一步的輸入:

將C作為每一步的輸入

encoder-decoder模型的優(yōu)點是:輸入和輸出序列可以長度不同。

encoder-decoder模型的局限性

最大的局限性就在于編碼和解碼之間的唯一聯(lián)系就是一個固定長度的語義向量C。也就是說,編碼器要將整個序列的信息壓縮進一個固定長度的向量中去。

這樣做有兩個弊端,一是語義向量無法完全表示整個序列的信息,還有就是先輸入的內(nèi)容攜帶的信息會被后輸入的信息稀釋掉,或者說,被覆蓋了。輸入序列越長,這個現(xiàn)象就越嚴(yán)重。這就使得在解碼的時候一開始就沒有獲得輸入序列足夠的信息, 那么解碼的準(zhǔn)確度自然也就要打個折扣了。

由于基礎(chǔ)Seq2Seq的種種缺陷,隨后引入了Attention的概念以及Bi-directional encoder layer等。

attention

為了解決這個問題,作者提出了Attention模型,或者說注意力模型。簡單的說,這種模型在產(chǎn)生輸出的時候,還會產(chǎn)生一個“注意力范圍”表示接下來輸出的時候要重點關(guān)注輸入序列中的哪些部分,然后根據(jù)關(guān)注的區(qū)域來產(chǎn)生下一個輸出,如此往復(fù)。模型的大概示意圖如下所示:


seq2seq_attention

相比于之前的encoder-decoder模型,attention模型最大的區(qū)別就在于它不在要求編碼器將所有輸入信息都編碼進一個固定長度的向量之中。相反,此時編碼器需要將輸入編碼成一個向量的序列,而在解碼的時候,每一步都會選擇性的從向量序列中挑選一個子集進行進一步處理。這樣,在產(chǎn)生每一個輸出的時候,都能夠做到充分利用輸入序列攜帶的信息。而且這種方法在翻譯任務(wù)中取得了非常不錯的成果。
在這篇文章中,作者提出了一個用于翻譯任務(wù)的結(jié)構(gòu)。解碼部分使用了attention模型,而在編碼部分,則使用了BiRNN(bidirectional RNN,雙向RNN)。

解碼

解碼部分使用了attention模型。我們可以將之前定義的條件概率寫作
p(y_i|y_1,y_2,..,y_{i-1},X)=g(y_{i-1},s_i, c_i)
其中s_i表示解碼器i時刻的隱藏狀態(tài)。計算公式:
s_i=f(s_{i-1},y_{i-1},c_i)
注意這里的條件概率與每個目標(biāo)輸出y_i相對應(yīng)的內(nèi)容向量c_i有關(guān)。而在傳統(tǒng)的方式中,只有一個內(nèi)容向量C。那么這里的內(nèi)容向量c_i又該怎么算呢?其實c_i是由編碼時的隱藏向量序列(h_1,…,h_{T_x})按權(quán)重相加得到的。
c_i=\sum_{j=1}^{T_x}\alpha_{ij}h_j
由于編碼是雙向RNN,因此可以認(rèn)為h_i中包含輸入序列中第i個詞以及 前后一些詞的信息。將隱藏權(quán)重向量按權(quán)重相加,表示在生成第i個內(nèi)容向量對第j個輸出的注意力分配是不同的。\alpha_{ij}越高表示第i個輸出在第j個輸入上分配的注意力越多,在生成第i個輸出的時候受第j個輸入的影響也就越大。新的問題是\alpha_{ij}如何得到?
\alpha_{ij}由第i-1個輸出隱藏狀態(tài)s_{i-1}和輸入中各個隱藏狀態(tài)(h_1,…,h_{T_x})共同決定的,公式如下:
\alpha_{ij}=\frac {exp(e_{ij})}{\sum_{k=1}^{T_x}exp(e_{ik})}
e_{ij}=a(s_{i-1},h_{j})
也就是說s_{i-1}跟每一個h分別計算一個數(shù)值,然后使用softmax得到i時刻的輸出在T_x個輸入隱藏狀態(tài)中的注意力分配向量。這個分配向量,也就是計算c_i的權(quán)重。
把公式按執(zhí)行順序匯總:
e_{ij}=a(s_{i-1},h_{j})
\alpha_{ij}=\frac {exp(e_{ij})}{\sum_{k=1}^{T_x}exp(e_{ik})}
c_i=\sum_{j=1}^{T_x}\alpha_{ij}h_j
s_i=f(s_{i-1},y_{i-1},c_i)
p(y_i|y_1,y_2,..,y_{i-1},X)=g(y_{i-1},s_i, c_i)
attention:

def get_att_score(dec_output, enc_output):  # enc_output [n_step, n_hidden]
    score = tf.squeeze(tf.matmul(enc_output, attn), 0)  # score : [n_hidden]
    dec_output = tf.squeeze(dec_output, [0, 1])  # dec_output : [n_hidden]
    return tf.tensordot(dec_output, score, 1)  # inner product make scalar value

def get_att_weight(dec_output, enc_outputs):
    attn_scores = []  # list of attention scalar : [n_step]
    enc_outputs = tf.transpose(enc_outputs, [1, 0, 2])  # enc_outputs : [n_step, batch_size, n_hidden]
    for i in range(n_step):
        attn_scores.append(get_att_score(dec_output, enc_outputs[i]))

    # Normalize scores to weights in range 0 to 1
    return tf.reshape(tf.nn.softmax(attn_scores), [1, 1, -1])  # [1, 1, n_step]

編碼器:

model = []
Attention = []
with tf.variable_scope('decode'):
    dec_cell = tf.nn.rnn_cell.BasicRNNCell(n_hidden)
    dec_cell = tf.nn.rnn_cell.DropoutWrapper(dec_cell, output_keep_prob=0.5)

    inputs = tf.transpose(dec_inputs, [1, 0, 2]) #decoder的輸入
    hidden = enc_hidden  #encoder每一層最后一個step的輸出,將encoder的最后一個隱狀態(tài)賦值給hidden
    for i in range(n_step):
        # time_major True mean inputs shape: [max_time, batch_size, ...]
        dec_output, hidden = tf.nn.dynamic_rnn(dec_cell, tf.expand_dims(inputs[i], 1),
                                               initial_state=hidden, dtype=tf.float32, time_major=True)
        #拿到hidden以后,將hidden當(dāng)作之前的初始狀態(tài)h0輸入到decoser中
        #dec_output 最后一層每個step的輸出
        #hidden 每一層最后一個step的輸出

        attn_weights = get_att_weight(dec_output, enc_outputs)  # attn_weights : [1, 1, n_step]
        Attention.append(tf.squeeze(attn_weights))

        # matrix-matrix product of matrices [1, 1, n_step] x [1, n_step, n_hidden] = [1, 1, n_hidden]
        context = tf.matmul(attn_weights, enc_outputs)
        dec_output = tf.squeeze(dec_output, 0)  # [1, n_step]
        context = tf.squeeze(context, 1)  # [1, n_hidden]

        model.append(tf.matmul(tf.concat((dec_output, context), 1), out))  # [n_step(i), batch_size(=1), n_class]

編碼

編碼比較普通,只是傳統(tǒng)的單向的RNN中,數(shù)據(jù)是按順序輸入的,因此第j個隱藏狀態(tài)\overrightarrow {h_j}只能攜帶第j個單詞本身以及之前的一些信息;而如果逆序輸入,則\overleftarrow {h_j}包含第j個單詞及之后的一些信息。如果把這兩個結(jié)合起來,h_j=[\overrightarrow {h_j},\overleftarrow {h_j}]就包含了第j個輸入和前后的信息。

with tf.variable_scope('encode'):
    enc_cell = tf.nn.rnn_cell.BasicRNNCell(n_hidden) #n_hidden 隱藏層神經(jīng)單元的個數(shù)
    enc_cell = tf.nn.rnn_cell.DropoutWrapper(enc_cell, output_keep_prob=0.5)
    # enc_outputs : [batch_size(=1), n_step(=decoder_step), n_hidden(=128)] 最后一層每個step的輸出
    # enc_hidden : [batch_size(=1), n_hidden(=128)] 每一層最后一個step的輸出
    enc_outputs, enc_hidden = tf.nn.dynamic_rnn(enc_cell, enc_inputs, dtype=tf.float32)

實驗

注意力矩陣:


matrix of attention

參考:
http://www.itdecent.cn/p/1c6b1b0cd202
https://blog.csdn.net/u014595019/article/details/52826423
https://github.com/graykode/nlp-tutorial/blob/master/4-2.Seq2Seq(Attention)/Seq2Seq(Attention)-Tensor.py

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容