神經網絡機器翻譯的實現


本文系qitta的文章翻譯而成,由renzhe0009實現。轉載請注明以上信息,謝謝合作。

本文主要講解以recurrent neural network為主,以及使用Chainer和自然語言處理其中的encoder-decoder翻譯模型。

並將英中機器翻譯用代碼實現。

Recurrent Neural Network

最基本的recurrent neural network(RNN),像下面的圖一樣,最典型的是追加3層神經網絡隱含層的反饋。
rnn.png
 
這是非常簡單的模型,本文接下來介紹的翻譯模型就是由RNN作成。RNN是比以前的N - gram模型精度性能更加優越的模型。
上圖寫成式子的話就是

在chainer(本次實現所使用的程序庫)中,我們就使用上面的式子。
在這里暫且先是考慮“輸入單詞ID,預測下一個單詞ID”的RNN語言模式吧。
首先定義模型。模式是學習可能的參數的集合,上圖的W∗∗就是這個意思。這個場合W∗∗是全部線性算子(矩陣),所以使用chainer . functions內的Linear嗎EmbedID。EmbedID是輸入方面在one-hot向量的情況的Linear,代替vector。
 
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:過陣子回學校再把代碼整理下發布。

 

 

 

 


免責聲明!

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



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