語言模型是自然語言處理的關鍵,而機器翻譯是語言模型最成功的基準測試,因為機器翻譯正是將輸入序列轉換成輸出序列的序列轉換模型的核心問題。序列轉碼模型在各類現代人工智能應用中國呢發揮著至關重要的作用,因此我們將其作為本章剩余部分和第10章的重點,為此,本節將介紹機器翻譯問題及其后文需要使用的數據集。
機器翻譯指的是將序列從一種語言自動翻譯成另一種語言,這個研究領域可以追溯到數字計算機發明后不久的20世紀40年代,特別是在第二次世界大戰中使用計算機破譯語言編碼。幾十年來,幾十年來,在使用神經網絡進行端到端學習興起之前,統計學方法在這一領域一直占據主導地位,因為統計機器翻譯涉及翻譯模型和語言模型等組成部分的統計分析,而基于神經網絡的方法通常被稱為神經機器翻譯,用于將兩種翻譯模型區分開來。
本書的關注點是神經網絡機器翻譯方法,強調的是端到端的學習,與8.3 節中的語料庫是單一語言的語言模型問題不同,機器翻譯的數據集是由源語言和目標語言的文本序列對組成的,因此,我們需要一種完全不同的方法來預處理機器翻譯數據集,而不是復用語言模型的預處理程序,我們看一下如何將預處理后的數據加載到小批量中用于訓練。
import os
import torch
from d2l import torch as d2l
9.5.1 下載和預處理數據集
搜索Tah-delimited Bilingual Sentence Pairs, 下載一個由Tatoba項目的雙語句子對組成的英語-語法 數據集,數據集中的每一行都是制表符分隔的文本序列對,序列對由英語文本序列和翻譯后的語法文本序列組成。注意,每個文本序列可以是一個句子,也可以是包含多個句子的一個段落,在這個將英語翻譯成語法的機器翻譯問題中,英語是源語言,法語是目標語言。
d2l.DATA_HUB["fra-eng"] = (d2l.DATA_URL + 'fra-eng.zip', )
def read_data_nmt():
輸入英語。法語的數據集
data_dir = d2l.download_extract('fra-eng')
with open(os.path.join(data_dir, 'fra.txt'), 'r',
encoding='utf-8') as f:
return f.read()
raw_text = read_data_net()
下載數據集后,原始文本數據需要經過幾個預處理步驟,我們用空格代替不間斷空格,用小些字母替換大寫字母,并在檔次和標準符號之間插入空格。
def preprocess_nmt(text):
預處理 英語 法語 數據集
def no_space(char, prev_char):
return char in set('.17') and prev_char !=''
用空格替換不間斷空格
用小寫字母替換大寫字母
text = text.replace('\u202f', '').replace().lower()
在單詞和標點符號之間插入空格
out = ['' + char if i > 0 and no_space(char, text[i - 1]) else char]
for i, char in enumerate(text)
return ''.join(out)
text = preprocess_nmt(raw_text)
print(text[:80])
9.5.2 詞元化
在機器翻譯中,我們更喜歡單詞級詞元化。下面的tokenize_nmt 函數對前num_examples 個文本序列對進行詞元,其中每個詞元要么是一個詞,要么是一個標點符號。此函數返回兩個詞元的表 source和target, source[i] 是源語言 第i個文本序列的詞元列表,target[i]是目標語言第i個文本序列詞元列表。
def tokenize_nmt(text, num_exampels = None):
詞元化 英語,法語 數據集
source, target = [],[]
for i, line in enumerate(text.split('\n')):
if num_examples and i > num_examples:
break;
parts = line.split('\t')
if len(parts) == 2:
source.append(parts[0]).split((''))
target.append(parts[1].split(''))
return source, target
source, target = tokenize_nmt(text)
source[:6], target[:6]
繪制每個文本序列所包含的詞元數的直方圖。在這個簡單的英語-法語數據集中,大多數文本序列的詞元數少于20個。
def show_list_len_pair_hist(legend, xlabel, ylable, xlist, ylist):
繪制列表長度對的直方圖
d2l.set_figsize()
patchs = d2l.plt.hist([[len(l) for l in xlist], [len(l) for l in ylist]])
d2l.plt.xlabel(xlabel)
d2l.plt.ylabel(ylabel)
for patch in patches[l].patches:
patch.set_hatch('/')
d2l.plt.legend(legend)
show_list_len_pair_hist(['source', 'target'], '# tokens per sequence', 'count', source, target)
9.5.3 詞表
由于機器翻譯數據集由語言對組成,我們可以分別為源語言和目標語言構建兩個詞表,使用單詞級詞元化時,詞表大小將明顯大于使用字符級詞元化時的詞表大小。為了緩解這一問題,這里我們將出現次數少于2次的低頻詞元視為相同的未知詞元。除此之外,我們還制定了額外的特定詞元,在小批量時用于將序列填充利用相同長度的填充詞元,以及序列的開始詞元和結束詞元。這些特殊詞元在自然語言處理任務中比較常用。
src_vocab = d2l.vocab(source, min_freq=2, reserved_tokens['pad'], 'bos', 'eos')
9.5.4 加載數據集
回想一下,語言模型中的序列樣本都有一個固定的長度,這個樣本是一個句子的某一部分還是跨越多個句子的一個片段,這個固定長度是由8.3節中的num_steps參數指定的。在機器翻譯中,每個樣本都是由源和目標組成的文本序列對,其中的每個文本序列可能具有不同的長度。
為了提高計算效率,我們仍然可以通過截斷和填充方式實現一次只處理一個小批量的文本序列,假設同一個小批量中的每個序列都應該具有相同的長度num_steps,那么如果文本序列的詞元數少于num_streps時,我們將在其末尾添加特定的pad詞元,直到其長度達到num_steps;我們將截斷文本序列,只取其前num_steps個詞元,并且丟棄剩余的詞元。每個文本序列將具有相同的長度,以便一相同的形狀的小批量進行加載。
下面的truncate_pad函數將截斷或者填充文本序列。
def truncate_pad(line, num_steps, padding_token):
截斷或填充文本序列
if len(line) > num_steps:
return line[:num_steps] 截斷
return line [padding_token] * (num_steps - len(line)) 填充
truncate_pad(src_vocab(source[0], 10, src_vocab('pad')))
我們定義一個可以將文本序列轉換成小批量數據集用于訓練的函數,我們將特定的eos詞元添加到所有序列的末尾,用于表示序列的結束。當模型通過一個詞元接一個詞eos 詞元添加到所有序列的末尾,用于表示序列的結束。當模型通過一個詞元接一個詞元地生成序列進行預測時,此外,我們還記錄了每個文本序列的長度,統計長度時剔除了填充詞元,在稍后將要介紹一些模型會需要這個額長度信息。
def build_array_nmt(lines, vocab, num_steps):
將機器翻譯的文本序列轉換成小批量
lines = [vocab[l] for l in lines]
lines = [l + [vocab['eos']] for l in lines]
array = torch.tensor([truncate_pad(l, num_steps, vocab['pad']) for l in lines])
valid_len = (array != vocab['pad']).type(torch.int32).sum(l)
return array, valid_len
9.5.5 訓練模型
我們定義load_data_nmt函數來返回的數據迭代器,以及源語言和目標語言的兩種詞表
def load_data_nmt(batch_size, num_steps, num_examples = 600):
返回翻譯數據集的迭代器和詞表
text = preprocess_nmt(read_data_nmt())
source, target = tokenize_nmt(text, num_examples)
src_vocab = d2l.vocab(source, min_freq=2, reserved_tokens=['pad', 'bos', 'eos'])
tgt_vocab = d2l.vocab(target, min_freq=2, reserved_tokens=['pad','bos','eos'])
src_array, src_valid_len = build_array_nmt(source, src_vocab, num_steps)
tgt_array, tgt_valid_len = build_array_nmt(target, tgt_vocab, num_steps)
data_arrays = (src_array, src_valid_len, tgt_array, tgt_valid_len)
data_iter = d2l.load_array(data_arrays, batch_size)
return data_iter, src_vocab, tgt_vocab
下面我們讀出英語 法語 數據集中的第一個小批量數據
train_iter, src_vocab, tgt_vocab = load_data_nmt(batch_size=2, num_steps=8)
for X, X_valid_len, Y, Yvalid_len in train_iter:
print('X:', X.type(torch.int32))
print('X的有效長度', X_valid_len)
print('Y:', Y.type(torch.int32))
break;
小結:
機器翻譯指的是將文本序列從一種語言自動翻譯成另一種語言
使用單詞級詞元化時的詞表大小,將明顯大于使用字符級詞元化時的詞表大小,為了緩解這一問題,可以將低頻詞元視為相同的未知詞元。
通過截斷和填充文本序列,可以保證所有的文本序列都具有相同的長度,以便以小批量的方式加載。