close

9.3 機器翻譯-Seq2Seq

前言

上一節通過影評資料分類任務介紹了seq2cls的場景,本節介紹seq2seq的任務——機器翻譯。

機器翻譯是典型的seq2seq任務,本節將採用傳統基於RNNseq2seq結構模型進行,seq2seq任務有一些特點,並且需要重點理解,包括

  1. 特殊token, , , 。分別表示起始、結束、未知和填充。尤其是起始"結束",它們是序列生成任務中重要的概念。
  2. 序列最大長度:序列最大長度直接影響模型性能,過長導致模型難以學習,過短導致無法完成任務,本案例選擇長度為20
  3. Teacher forcing 教師強制學習:本概念也是序列生成任務中,在訓練階段所涉及的重要概念,表示decoder的輸入採用標籤,而非自回歸式。
  4. 推理代碼邏輯:模型推理輸出序列時,採用的是自回歸方式,因而代碼與訓練時要做修改。

任務介紹

機器翻譯是經典的seq2seq任務,日常生活中也常用到翻譯工具,因此任務比較好理解。例如以下幾個句子就是本節要處理的任務。

  1. That mountain is easy to climb. 那座山很容易爬。
  2. It's too soon. 太早了。
  3. Japan is smaller than Canada. 日本比加拿大小。

現在需要構建模型,接收英文句子序列,進行綜合理解,然後輸出中文句子序列,在神經網路中,通常採用encoder-decoder架構,例如《動手學》中的示意圖。

<<AI人工智慧 PyTorch自學>> 9.3 機器翻譯-

  • 編碼器,對輸入進行處理,獲得對輸入句子的綜合理解資訊——狀態。
  • 解碼器,根據狀態,以及解碼器的輸入,逐個token的生成輸出,直到輸出特殊token——,模型停止輸出。
  • 解碼器的輸入,採用自回歸方式(推理時),含義是當前時刻的輸入,來自上一時刻的輸出,特別地,第0個時刻,是沒有上一個時刻,所以採用特殊token——作為輸入。

資料模組

數據下載

本案例資料集Tatoeba下載自https://www.manythings.org/anki/,該專案是説明不同語言的人學習英語,因此是英語與其它幾十種語言的翻譯文本。

其中就包括本案例使用的英中文本,共計29668條(Mandarin Chinese - English cmn-eng.zip (29668)

資料以txt形式存儲,一行是一對翻譯文本,例如長這樣:

1.That mountain is easy to climb. 那座山很容易爬。

2.It's too soon. 太早了。

3.Japan is smaller than Canada. 日本比加拿大小。

資料集劃分

對於29668條資料進行8:2劃分為訓練、驗證,這裡採用配套代碼a_data_split.py進行劃分,即可在統計目錄下獲得train.txttext.txt

詞表構建

文本任務首要任務是為文本構建詞表,這裡採用與上節一樣的方法,首先對文本進行分詞,然後統計語料庫中所有的詞,最後根據最大上限、最小詞頻等約束,構建詞表。本部分配套代碼是b_gen_vocabulary.py

詞表的構建過程中,涉及兩個知識點:中文分詞和特殊token

1. 中文分詞

對於英文,分詞可以直接採用空格。而對於中文,就需要用特定的分詞方法,這裡採用的是jieba分詞工具,以下是英文和中文的分詞代碼。

source.append(parts[0].split(' '))

target.append(list(jieba.cut(parts[1])))  # 分詞

Copy

2. 特殊token

由於seq2seq任務的特殊性,在解碼器部分,通常需要一個token告訴模型,現在是開始,同時還需要有個token讓模型輸出,以此告訴人類,模型輸出完畢,不要再繼續生成了。

因此相較于文本分類,還多了,兩個特殊token,有的時候,開始token也會用表示。

PAD_TAG = "<pad>"  # PAD補全句子長度

BOS_TAG = "<bos>"  # BOS表示開始

EOS_TAG = "<eos>"  # EOS表示結束

UNK_TAG = "<unk>"  # EOS表示結束

PAD = 0  # PAD字元對應的數位

BOS = 1  # BOS字元對應的數位

EOS = 2  # EOS字元對應的數位

UNK = 3  # UNK字元對應的數位

Copy

運行代碼後,詞表字典保存到了result目錄下,並得到如下輸出,表明英文中有2518個詞,中文有3365,但經過最大長度3000的截斷後,只剩下2996,另外4個是特殊token

100%|██████████| 23635/23635 [00:00<00:00, 732978.24it/s]

原始詞表長度:2518,截斷後長度:2518

2522

保存詞頻統計圖:vocab_en.npy_word_freq.jpg

100%|██████████| 23635/23635 [00:00<00:00, 587040.62it/s]

保存統計圖:vocab_en.npy_length_freq.jpg

原始詞表長度:3365,截斷後長度:2996

3000

Copy

Dataset編寫

NMTDataset的編寫邏輯與上一小節的Dataset類似,首先在類初始化的時候載入原始資料,並進行分詞;在getitem反覆運算時,再進行tokenindex操作,這裡會涉及增加結束符、填充符、未知符。

核心代碼如下:

def __init__(self, path_txt, vocab_path_en, vocab_path_fra, max_len=32):

    self.path_txt = path_txt

    self.vocab_path_en = vocab_path_en

    self.vocab_path_fra = vocab_path_fra

    self.max_len = max_len

 

    self.word2index = WordToIndex()

    self._init_vocab()

    self._get_file_info()

 

def __getitem__(self, item):

 

    # 獲取切分好的句子list,一個元素是一個詞

    sentence_src, sentence_trg = self.source_list[item], self.target_list[item]

    # 進行填充, 增加結束符,索引轉換

    token_idx_src = self.word2index.encode(sentence_src, self.vocab_en, self.max_len)

    token_idx_trg = self.word2index.encode(sentence_trg, self.vocab_fra, self.max_len)

    str_len, trg_len = len(sentence_src) + 1, len(sentence_trg) + 1  # 有效長度, +1是填充的結束符 <eos>.

 

    return np.array(token_idx_src, dtype=np.int64), str_len,  np.array(token_idx_trg, dtype=np.int64), trg_len

 

def _get_file_info(self):

 

    text_raw = read_data_nmt(self.path_txt)

    text_clean = text_preprocess(text_raw)

    self.source_list, self.target_list = text_split(text_clean)

Copy

模型模組

seq2seq模型,由編碼器和解碼器兩部分構成。

對於編碼器,需要的是其對輸入句子的全文理解,因此可採用RNN中輸出的hidden state特徵來表示,在這裡均採用LSTM作為基礎模型。

對於解碼器,同樣是一個LSTM,它接收3個資料,一個是輸入,另外兩個是hidden statecell state。解碼器的輸入就是自回歸式的,上一時刻輸出的單詞傳到當前時刻。

這裡借助《動手學》的示意圖,理解seq2seq模型的樣子。

<<AI人工智慧 PyTorch自學>> 9.3 機器翻譯-

首先構建一個EncoderLSTM,這個比較簡單,只看它的forward,對於輸入的句子x,輸出hidden_state, cell_state

def forward(self, x):

    # Shape -----------> (26, 32, 300) [Sequence_length , batch_size , embedding dims]

    embedding = self.dropout(self.embedding(x))

 

    # Shape --> outputs (26, 32, 1024) [Sequence_length , batch_size , hidden_size]

    # Shape --> (hs, cs) (2, 32, 1024) , (2, 32, 1024) [num_layers, batch_size, hidden_size]

    outputs, (hidden_state, cell_state) = self.LSTM(embedding)

 

    return hidden_state, cell_state

Copy

然後構建一個DecoderLSTM,解碼器除了需要對資料進行提特徵,獲得hidden_state, cell_state,還需要進行當前時刻,單詞的輸出,即token級的分類任務。

所以,它的forward返回有三個資訊,包括輸出的token預測向量,LSTMhidden_state, cell_state,這裡需要注意,在代碼實現時,解碼器輸出的隱狀態預設包括了來自編碼器的,因此後續時間步不再需要從編碼器拿隱狀態特徵了。更直觀的就是,解碼器返回的hidden_state, cell_state,會是下一次forward輸入的hidden_state, cell_state

def forward(self, x, hidden_state, cell_state):

    x = x.unsqueeze(0# x.shape == [1, batch_size]

    embedding = self.dropout(self.embedding(x))

 

    outputs, (hidden_state, cell_state) = self.LSTM(embedding, (hidden_state, cell_state))

 

    predictions = self.fc(outputs)

 

    predictions = predictions.squeeze(0)

 

    return predictions, hidden_state, cell_state

Copy

最後,編寫一個Seq2Seq類,將編碼器和解碼器有序的組合起來,在這裡,核心任務是解碼器中,如何有序的進行每個時刻的資料處理。

Seq2Seq類用於訓練階段,會設計teacher forcing(強制學習)的概念,它表示在訓練階段,解碼器的輸入時自回歸,還是根據標籤進行輸入,採用標籤進行輸入的方法稱為teacher forcing

這裡仔細研究一下forward的後半部分——解碼器部分,前半部分就是編碼器進行一次性的編碼,獲得隱狀態特徵。

對於解碼器,採用for迴圈,依次進行token輸出,最終存儲在outputs中,for迴圈需要設置最大步數,一般根據任務的資料長度而定,這裡設置為32

接著,解碼器工作,解碼器會輸出:預測的token分類向量,隱狀態資訊,其中隱狀態資訊會在下一個for迴圈時,輸入到解碼器中。

再往下看,解碼器的輸入x,則是根據一個條件判斷,以一定的概率採用標籤,一定的概率採用自回歸方式。這裡概率通常設置為0.5,如果設置為1,表明採用強制學習,永遠採用標籤作為輸入,也就是強制學習機制(teacher forcing)。

# Shape of x (32 elements)

x = target[0# <bos> token

 

for i in range(1, target_len):

    # output.shape ==  (bs, vocab_length)

    # hidden_state.shape == [num_layers, batch_size size, hidden_size]

    output, hidden_state, cell_state = self.Decoder_LSTM(x, hidden_state, cell_state)

    outputs[i] = output

    best_guess = output.argmax(1# 0th dimension is batch size, 1st dimension is word embedding

    x = target[i] if random.random() < tfr else best_guess  # Either pass the next word correctly from the dataset or use the earlier predicted word

 

# Shape --> outputs (14, 32, 5766)

return outputs

Copy

模型訓練

資料和模型準備好之後,可以運行train_seq2seq.py進行訓練,seq2seq的訓練還有些許不同,主要包括:

  1. 採用blue進行指標評價;
  2. 損失函數加入ignore_index

BLEUIBM2002提出的,用於機器翻譯任務的評價,發表在ACL,引用次數10000+,原文題目是“BLEU: a Method for Automatic Evaluation of Machine Translation”

它的總體思想就是準確率,假如給定標準譯文reference,模型生成的句子是candidate,句子長度為ncandidate中有m個單詞出現在referencem/n就是bleu1-gram的計算公式。

當統計不再是一個單詞,而是連續的N個單詞時,就有了n-gram的概念,片語的概念稱為n-gram,片語長度通常選擇1, 2, 3, 4

舉一個例子來看看實際的計算:

candinate: the cat sat on the mat

reference: the cat is on the mat

BLEU-1 5/6 = 0.83

BLEU-2: 3/5 = 0.6

BLEU-3: 1/4 = 0.25

BLEU-4: 0/3 = 0

分子表示candidate中預測到了的片語的次數,如BLEU-1中,5分別表示, the, cat, on, the, mat預測中了。BLEU-2中,3分別表示, the cat, on the, the mat預測中了。以此類推。

針對BLEU還有些改進計算方法,可參考BLEU詳解

由於句子長度不一致,而訓練又需要將資料構造成統一長度的句子來組batch,因此會加入很多特殊token——,對應的index0,所以會在計算損失函數的時候,這部分的loss是不計算的,可以巧妙的通過設置ignore_index來實現。 nn.CrossEntropyLoss(ignore_index=0)

<<AI人工智慧 PyTorch自學>> 9.3 機器翻譯-

由於資料量少,以及模型參數未精心設計,模型存在過擬合,這裡僅作為seq2seq任務的學習和理解,不對模型性能做進一步提升,因為後續有更強大的解決方案——基於Transformer

模型推理

運行配套代碼c_inference.py,可以看到模型的表現如下所示,整體上像一個模型翻譯的樣子,但效果遠不如意,這裡包含多方面原因。

  1. 資料量過少,訓練資料僅2萬多條,而中英文的詞表就3000
  2. 模型過於簡單,傳統RNN構建的seq2seq在上下文理解能力上表現欠佳,後續可加入注意力機制,或是基於Transformer架構來提升。
  3. 輸入: he didn't answer the phone , so i sent him an email . <eos>
  4. 標籤: 沒有 <unk> 所以 <unk> <unk> <eos>
  5. 翻譯: <unk> 所以 <unk> <eos>
  6.  
  7. 輸入: just as he was going out , there was a great earthquake . <eos>
  8. 標籤: 出門 時候 發生 <unk> <eos>
  9. 翻譯: <unk> <unk> <unk> <unk> <eos>
  10.  
  11. 輸入: tom hugged mary . <eos>
  12. 標籤:湯姆 擁抱 瑪麗 <eos>
  13. 翻譯:<unk> 瑪麗 <eos>
  14.  
  15. 輸入: there are many americans who can speak japanese . <eos>
  16. 標籤: 很多 美國 <unk> 日語 <eos>
  17. 翻譯: <unk> <unk> 非常 <unk> <eos>
  18.  
  19. 輸入: i'm not good at <unk> . <eos>
  20. 標籤: 擅長 <unk> <eos>
  21. 翻譯: 擅長 運動 <eos>

Copy

小結

本節通過機器翻譯瞭解seq2seq任務,主要涉及一些特殊的、新的知識點,包括:

  1. 特殊token:解碼器需要兩個特殊的token,起始token和結束token,用於控制生成序列的起始和結束。
  2. 強制學習:訓練時,解碼器的輸入採用標籤
  3. 編碼器-解碼器的自回歸推理邏輯:需要for迴圈的形式,串列的依次生成句子中的每個單詞
  4. 中文分詞工具jieba

 

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 HCHUNGW 的頭像
    HCHUNGW

    HCHUNGW的部落格

    HCHUNGW 發表在 痞客邦 留言(0) 人氣()