本文系qitta的文章翻譯而成,由renzhe0009實現。轉載請注明以上信息,謝謝合作。
本文主要講解以recurrent neural network為主,以及使用Chainer和自然語言處理其中的encoder-decoder翻譯模型。
並將英中機器翻譯用代碼實現。
Recurrent Neural Network

from chainer import FunctionSet from chainer.functions import * model = FunctionSet( w_xh = EmbedID(VOCAB_SIZE, HIDDEN_SIZE), # 輸入層(one-hot) -> 隱藏層 w_hh = Linear(HIDDEN_SIZE, HIDDEN_SIZE), # 隱藏層 -> 隱藏層 w_hy = Linear(HIDDEN_SIZE, VOCAB_SIZE), # 隱藏層 -> 輸出層 )
VOCAB_SIZE
是單詞的數量、HIDDEN_SIZE是隱藏層的維數
。
然后,定義實際的解析函數forward。在這里基本是按照上圖的網絡結構來再現模型的定義和實際的輸入數據,最終進行求值計算。語言模型的情況下,是用下面的式表示句子的結合概率。
以下是代碼的例子。
import math import numpy as np from chainer import Variable from chainer.functions import * def forward(sentence, model): # sentence是strの排列結果。 sentence = [convert_to_your_word_id(word) for word in sentence] # 單詞轉換為ID h = Variable(np.zeros((1, HIDDEN_SIZE), dtype=np.float32)) # 隱藏層的初值 log_joint_prob = float(0) # 句子的結合概率 for word in sentence: x = Variable(np.array([[word]], dtype=np.int32)) # 下一次的輸入層 y = softmax(model.w_hy(h)) # 下一個單詞的概率分布 log_joint_prob += math.log(y.data[0][word]) #結合概率的分布 h = tanh(model.w_xh(x) + model.w_hh(h)) #隱藏層的更新 return log_joint_prob #返回結合概率的計算結果
這樣就可以求出句子的概率了。但是,上面並沒有計算損失函數。所以我們使用softmax函數來進行計算。
也就是用chainer.functions.softmax_cross_entropy
def forward(sentence, model): ... accum_loss = Variable(np.zeros((), dtype=np.float32)) # 累計損失的初値 ... for word in sentence: x = Variable(np.array([[word]], dtype=np.int32)) #下次的輸入 (=現在的正確值) u = model.w_hy(h) accum_loss += softmax_cross_entropy(u, x) # 累計損失 y = softmax(u) ... return log_joint_prob, accum_loss # 累計損失全部返回
現在就可以進行學習了。
from chainer.optimizers import * ... def train(sentence_set, model): opt = SGD() # 使用梯度下降法 opt.setup(model) # 學習初期化 for sentence in sentence_set: opt.zero_grad(); # 勾配の初期化 log_joint_prob, accum_loss = forward(sentence, model) # 損失的計算 accum_loss.backward() # 誤差反向傳播 opt.clip_grads(10) # 剔除過大的梯度 opt.update() # 參數更新
那么基本上chainer的RNN代碼就是這樣實現的了。
Encoder-decode翻譯模型
encoder-decoder是現在廣泛使用的利用神經網絡的翻譯模型。
和過去的方法相比也能夠達到很高精度,現在深受NLP研究者們喜愛的翻譯模型。
encoder-decoder有很多種,以下是我在本文中實現的模型。

很簡單的想法,准備輸入方面(encoder)和輸出方面(decoder)的2個RNN,在中間節點上連接。
這個模型的有趣之處在於,為了在輸出方面一起生成終端符號,翻譯的結束是由模型自己決定的。但是反過來講,為了不生成無限的單詞死循環,實際處理的時候,做一些限制還是有必要的。
i和j是embedding(詞向量)層。
整個模型的計算式如下
對隱藏層p和q的位移,使用了LSTM神經網絡。但是encoder方面實質的損失的計算位置y的距離很遠,一般的傳遞函數很難進行學習。
所以LSTM神經網絡的長距離時序依存關系的優點就能夠體現出來。
上式的位移W∗∗一共有8種。用以下的代碼來定義。
model = FunctionSet( w_xi = EmbedID(SRC_VOCAB_SIZE, SRC_EMBED_SIZE), #輸入層(one-hot) -> 輸入詞向量層 w_ip = Linear(SRC_EMBED_SIZE, 4 * HIDDEN_SIZE), # 輸入詞向量層-> 輸入隱藏層 w_pp = Linear(HIDDEN_SIZE, 4 * HIDDEN_SIZE), # 輸入隱藏層 -> 輸入隱藏層 w_pq = Linear(HIDDEN_SIZE, 4 * HIDDEN_SIZE), # 輸入隱藏層-> 輸出隱藏層 w_yq = EmbedID(TRG_VOCAB_SIZE, 4 * HIDDEN_SIZE), #輸出層(one-hot) -> 輸出隱藏層 w_qq = Linear(HIDDEN_SIZE, 4 * HIDDEN_SIZE), #輸出隱藏層 -> 輸出隱藏層 w_qj = Linear(HIDDEN_SIZE, TRG_EMBED_SIZE), # 輸出隱藏層 -> 輸出詞向量層 w_jy = Linear(TRG_EMBED_SIZE, TRG_VOCAB_SIZE), # 輸出隱藏層 -> 輸出隱藏層 )
接下來是forward函數。
因為LSTM帶有內部結構,注意p和q的計算需要多一個Variable
。
# src_sentence: 需要翻譯的句子 e.g. ['他', '在', '走'] # trg_sentence: 正解的翻譯句子 e.g. ['he', 'runs'] # training: 機械學習的預測。 def forward(src_sentence, trg_sentence, model, training): # 轉換單詞ID # 對正解的翻訳追加終端符號 src_sentence = [convert_to_your_src_id(word) for word in src_sentence] trg_sentence = [convert_to_your_trg_id(word) for wprd in trg_sentence] + [END_OF_SENTENCE] # LSTM內部狀態的初期値 c = Variable(np.zeros((1, HIDDEN_SIZE), dtype=np.float32)) # encoder for word in reversed(src_sentence): x = Variable(np.array([[word]], dtype=np.int32)) i = tanh(model.w_xi(x)) c, p = lstm(c, model.w_ip(i) + model.w_pp(p)) # encoder -> decoder c, q = lstm(c, model.w_pq(p)) # decoder if training: # 學習時使用y作為正解的翻譯、forward結果作為累計損失來返回 accum_loss = np.zeros((), dtype=np.float32) for word in trg_sentence: j = tanh(model.w_qj(q)) y = model.w_jy(j) t = Variable(np.array([[word]], dtype=np.int32)) accum_loss += softmax_cross_entropy(y, t) c, q = lstm(c, model.w_yq(t), model.w_qq(q)) return accum_loss else: # 預測時翻譯器生成的y作為下次的輸入,forward的結果作為生成了的單詞句子 # 選擇y中最大概率的單詞、沒必要用softmax。 hyp_sentence = [] while len(hyp_sentence) < 100: # 剔除生成100個單詞以上的句子 j = tanh(model.w_qj(q)) y = model.w_jy(j) word = y.data.argmax(1)[0] if word == END_OF_SENTENCE: break # 生成了終端符號,結束。 hyp_sentence.append(convert_to_your_trg_str(word)) c, q = lstm(c, model.w_yq(y), model.w_qq(q)) return hyp_sentence
稍微有點長,這段代碼和之前的圖結合起來讀就會明白了。
最終結果如下:
第一次epoch的結果。
第100次epoch的結果。
src是英文原文。trg是正確譯文。hyp是預測譯文。
因為現在手頭只有筆記本電腦,內存不足,所以把參數都調低了,不然無法執行。你們懂的。
看起來還不賴吧。參數調高必然能取得更好的效果。
Have fun!
Ps:過陣子回學校再把代碼整理下發布。