ディープラーニング入門 (63) リカレント ニューラル ネットワーク - 機械翻訳データセット
序文
核心的な内容はブログリンク1ブログリンク2からです作者をたくさん応援していただければ幸いです
この記事は忘れないための記録用です
リカレント ニューラル ネットワーク - 機械翻訳データセット
教科書
言語モデルは自然言語処理の鍵であり、言語モデルの最も成功したベンチマーク机器翻译
です。なぜなら、機械翻訳は入力シーケンスを出力シーケンスに変換するという中核的な問題だからです序列转换模型(sequence transduction)
。逐次変換モデルは、現代のさまざまな人工知能アプリケーションで重要な役割を果たします。この目的を達成するために、このセクションでは機械翻訳の問題と、後で使用するデータセットについて説明します。
机器翻译(machine translation)
ある言語から別の言語へのシーケンスの自動翻訳を指します。実際、この研究分野の起源は、デジタル コンピューターの発明直後、特に第二次世界大戦中に言語暗号を解読するためにコンピューターが使用された直後の 1940 年代にまで遡ります。ニューラル ネットワークを使用したエンドツーエンド学習が台頭するまで、統計的手法が数十年間この分野を支配していました。翻訳モデルや言語モデルなどのコンポーネントの統計分析が含まれるため统计机器翻译(statistical machine translation)
、神经机器翻译(neural machine translation)
2 つの翻訳モデルを区別するためにニューラル ネットワーク ベースの手法が呼び出されることがよくあります。
この本は、エンドツーエンドの学習に重点を置いて、ニューラル機械翻訳手法に焦点を当てています。コーパスが単一言語である言語モデルとデータセットのセクションの言語モデルの問題とは異なり、機械翻訳のデータセットは、ソース言語とターゲット言語のテキスト シーケンスのペアで構成されます。したがって、機械翻訳用のデータセットを前処理するには、言語モデルの前処理手順を再利用するのとはまったく異なるアプローチが必要です。次に、前処理されたデータをトレーニング用のミニバッチにロードする方法を見ていきます。
import os
import torch
from d2l import torch as d2l
1 データセットをダウンロードして前処理する
まず、Tatoeba プロジェクトからバイリンガル文ペアで構成される「英語-フランス語」データセットをダウンロードします。データセットの各行はタブ区切りのテキスト シーケンス ペアであり、シーケンス ペアは英語テキスト シーケンスと翻訳されたフランス語テキスト シーケンスで構成されます。構成。各テキスト シーケンスは、複数の文を含む 1 つの文または段落になる可能性があることに注意してください。この英語からフランス語への機械翻訳の問題では、英語は源语言(source language)
、フランス語は です目标语言(target language)
。
d2l.DATA_HUB['fra-eng'] = (d2l.DATA_URL + 'fra-eng.zip',
'94646ad1522d915e7b0f9296181140edcf86a4f5')
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_nmt()
print(raw_text[:75])
出力
Go. Va !
Hi. Salut !
Run! Cours !
Run! Courez !
Who? Qui ?
Wow! Ça alors !
データセットをダウンロードした後、生のテキスト データはいくつかの前処理ステップを通過する必要があります。たとえば、スペースを置換したり不间断空格(non-breaking space)
、大文字を小文字に置換したり、単語と句読点の間にスペースを挿入したりします。
def preprocess_nmt(text):
"""预处理“英语-法语”数据集"""
def no_space(char, prev_char):
return char in set(',.!?') and prev_char != ' '
# 使用空格替换不间断空格
# 使用小写字母替换大写字母
text = text.replace('\u202f', ' ').replace('\xa0', ' ').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])
出力
go . va !
hi . salut !
run ! cours !
run ! courez !
who ? qui ?
wow ! ça alors !
2 見出し語化
言語モデルとデータセットのセクションの文字レベルの見出し語化とは異なり、機械翻訳では単語レベルの見出し語化が好まれます (最先端のモデルでは、より高度な見出し語化技術が使用される場合があります)。次のtokenize_nmt
関数は、num_examples
前のテキスト シーケンスのペアをトークン化します。各トークンは単語または句読点です。この関数は 2 つのトークン リストを返します。source
とtarget
:source[i]
はソース言語 (ここでは英語) の i 番目のテキスト シーケンスのトークン リストで、 はtarget[i]
ターゲット言語 (ここではフランス語) の i 番目のテキスト シーケンスのトークン リストです。
def tokenize_nmt(text, num_examples=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]
出力:
([['go', '.'],
['hi', '.'],
['run', '!'],
['run', '!'],
['who', '?'],
['wow', '!']],
[['va', '!'],
['salut', '!'],
['cours', '!'],
['courez', '!'],
['qui', '?'],
['ça', 'alors', '!']])
各テキスト シーケンスに含まれるトークンの数のヒストグラムをプロットしてみましょう。この単純な「英語-フランス語」データセットでは、ほとんどのテキスト シーケンスのトークンは 20 未満です。
def show_list_len_pair_hist(legend, xlabel, ylabel, xlist, ylist):
"""绘制列表长度对的直方图"""
d2l.set_figsize()
_, _, patches = 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[1].patches:
patch.set_hatch('/')
d2l.plt.legend(legend)
show_list_len_pair_hist(['source', 'target'], '# tokens per sequence',
'count', source, target);
出力
3 語彙
機械翻訳データセットは言語のペアで構成されているため、ソース言語とターゲット言語のそれぞれに対して 2 つの語彙を構築できます。単語レベルの見出し語化を使用する場合、語彙サイズは文字レベルの見出し語化を使用する場合よりも大幅に大きくなります。この問題を軽減するために、ここでは 2 回未満しか出現しない低頻度の補題を同じ未知の (「 」<unk>
) 補題として扱います。これに加えて、小さなバッチでシーケンスを同じ長さに埋め込むためのフィラー トークン (「 」)、シーケンス開始<pad>
トークン (「<bos>
」) および終了トークン要素 (" <eos>
") など、追加の特定のトークンも指定します。これらの特別なトークンは、自然言語処理タスクでよく使用されます。
src_vocab = d2l.Vocab(source, min_freq=2,
reserved_tokens=['<pad>', '<bos>', '<eos>'])
len(src_vocab)
4 データセットをロードする
言語モデルのシーケンス サンプルは、サンプルが文の一部であっても、複数の文にまたがる断片であっても、固定長であることを思い出してください。この固定長は、「言語モデルとデータセット」セクションのnum_steps
(タイムステップ数またはトークン数) パラメーターによって指定されます。機械翻訳では、各サンプルはソースとターゲットで構成されるテキスト シーケンスのペアであり、各テキスト シーケンスの長さは異なる場合があります。
計算効率を向上させるために、截断(truncation)
とを使用して填充(padding)
一度に処理できるテキスト シーケンスの小さなバッチは 1 つだけです。同じミニバッチ内の各シーケンスが同じ長さであると仮定するとnum_steps
、テキスト シーケンス内のトークンの数が 未満の場合は、その長さが に達するまで特定の " " トークンを末尾にnum_steps
追加し続けます。それ以外の場合は、テキスト シーケンスを切り詰め、最初のトークンのみを取得し、残りのトークンを破棄します。このようにして、各テキスト シーケンスは同じ長さになり、同じ形状のミニバッチにロードされます。<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>'])
出力
[47, 4, 1, 1, 1, 1, 1, 1, 1, 1]
次に、テキスト シーケンスをトレーニング用のミニバッチに変換する関数を定義します。<eos>
シーケンスの終わりを示すために、すべてのシーケンスの末尾に特別な " " トークンを追加します。モデルがシーケンスを 1 つずつ生成して予測する場合、生成された「<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(1)
return array, valid_len
5 トレーニングモデル
最後に、データ反復子を返す関数と、ソース言語とターゲット言語の 2 つの語彙を定義します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, Y_valid_len in train_iter:
print('X:', X.type(torch.int32))
print('X的有效长度:', X_valid_len)
print('Y:', Y.type(torch.int32))
print('Y的有效长度:', Y_valid_len)
break
出力:
X: tensor([[ 6, 143, 4, 3, 1, 1, 1, 1],
[ 54, 5, 3, 1, 1, 1, 1, 1]], dtype=torch.int32)
X的有效长度: tensor([4, 3])
Y: tensor([[ 6, 0, 4, 3, 1, 1, 1, 1],
[93, 5, 3, 1, 1, 1, 1, 1]], dtype=torch.int32)
Y的有效长度: tensor([4, 3])
6 まとめ
-
機械翻訳とは、ある言語から別の言語への一連のテキストの自動翻訳を指します。
-
単語レベルの見出し語化を使用する場合の語彙サイズは、文字レベルの見出し語化を使用する場合よりも大幅に大きくなります。この問題を軽減するために、低頻度トークンを同じ未知のトークンとして扱うことができます。
-
テキスト シーケンスを切り詰めてパディングすることにより、すべてのテキスト シーケンスが同じ長さになることが保証され、小さなバッチでロードできるようになります。