PyTorch 1.5 Tutorials : テキスト : TorchText で言語翻訳 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 07/01/2020 (1.5.1)
* 本ページは、PyTorch 1.5 Tutorials の以下のページを翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
- Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
| 株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション |
| E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/ |
| Facebook: https://www.facebook.com/ClassCatJP/ |
テキスト : TorchText で言語翻訳
このチュートリアルは、英語とドイツ語の両者のセンテンスを含む良く知られたデータセットからのデータを前処理するために torchtext の幾つかの便利なクラスをどのように使用するか、そしてドイツ語センテンスを英語に翻訳できる attention つきの sequence-to-sequence モデルを訓練するためにどのようにそれを使用するかを示します。
It is based off of this tutorial from PyTorch community member Ben Trevett and was created by Seth Weidman with Ben’s permission.
このチュートリアルの最後までで、貴方は次ができるでしょう :
- 以下の torchtext の便利なクラスを使用してセンテンスを NLP モデリングのために一般に使用される形式に前処理します :
Field と TranslationDataset
torchtext は言語翻訳モデルを作成する目的のために容易に iterate できるデータセットを作成するためのユティリティを持ちます。一つの主要なクラスは Field で、これは各センテンスが前処理されるべき方法を指定します、そしてもう一つは TranslationDataset です ; torchtext は幾つかのそのようなデータセットを持ちます ; このチュートリアルでは Multi30k データセット を使用します、これは英語とドイツ語の約 30,000 センテンス (平均して長さおよそ 13 単語) を含みます。
Note: このチュートリアルでのトークン化は Spacy を必要とします。Spacy を使用するのはそれが英語以外の言語でのトークン化のための強力なサポートを提供するからです。torchtext は basic_english tokenzier を提供し英語のための他の tokenzier (e.g. Moses (訳注: リンク切れ)) もサポートしますが言語翻訳のためには – そこではマルチ言語が必要です – Spacy が最善の方法です。
このチュートリアルを実行するためには、最初に pip か conda を使用して spacy をインストールします。次に、英語とドイツ語 Spacy tokenizer のための生データをダウンロードします :
python -m spacy download en python -m spacy download de
Spacy がインストールされていれば、次のコードは Field で定義された tokenizer に基づいて TranslationDataset のセンテンスの各々をトークン化します :
from torchtext.datasets import Multi30k
from torchtext.data import Field, BucketIterator
SRC = Field(tokenize = "spacy",
tokenizer_language="de",
init_token = '',
eos_token = '',
lower = True)
TRG = Field(tokenize = "spacy",
tokenizer_language="en",
init_token = '',
eos_token = '',
lower = True)
train_data, valid_data, test_data = Multi30k.splits(exts = ('.de', '.en'),
downloading training.tar.gz downloading validation.tar.gz downloading mmt_task1_test2016.tar.gz
train_data を定義した今、torchtext の Field の非常に有用な特徴を見ることができます : 今では build_vocab メソッドは各言語に関連する語彙を作成することを可能にします。
SRC.build_vocab(train_data, min_freq = 2) TRG.build_vocab(train_data, min_freq = 2)
コードのこれらの行がひとたび実行されれば、SRC.vocab.stoi はキーとして語彙のトークンを持ち値として対応するインデックスを持つ辞書になります ; SRC.vocab.itos はキーと値が交換された同じ辞書になります。このチュートリアルではこの事実を広く利用はしませんが、これは貴方が出会う他の NLP タスクで有用でありがちでしょう。
BucketIterator
使用する最後の torchtext 固有の特徴は BucketIterator で、これは簡単に利用できます、何故ならそれは最初の引数として TranslationDataset を取るからです。特に doc が述べるように : 同様の長さのサンプルを一緒にバッチ化する iterator を定義します。各新しいエポックのために新たにシャッフルされたバッチを生成しながら必要なパディングの総量を最小化します。使用される bucketing 手続きについてはプールを見てください。
import torch
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
BATCH_SIZE = 128
train_iterator, valid_iterator, test_iterator = BucketIterator.splits(
(train_data, valid_data, test_data),
batch_size = BATCH_SIZE,
device = device)
これらの iterator は単に DataLoader のように呼び出せます ; 下で、train と evaluate 関数で、それらは単純に以下で呼び出されます :
for i, batch in enumerate(iterator):
それから各バッチは src と trg 属性を持ちます :
src = batch.src trg = batch.trg
nn.Module と Optimizer を定義する
torchtext 視点からはそれが殆ど総てです : データセットが構築されて iterator が定義され、このチュートリアルの残りは Optimizer と一緒に、単純にモデルを nn.Module として定義してから、それを訓練します。
私達のモデルは特に、ここ で記述されているアーキテクチャに従います (ここ で著しく多くコメントされたバージョンを見つけられます)。
Note: このモデルは言語翻訳のために使用できる単なるサンプル・モデルです ; それを選択したのはそれがタスクのための標準的なモデルであるためで、翻訳のために使用する推奨モデルであるからではありません。貴方が気付いているかもしれないように、最先端モデルは現在は Transformer に基づいています ; Transformer 層を実装するための PyTorch の機能は ここ で見ることができます ; そして特に、下のモデルで使用される “attention” は transformer モデルで現れる multi-headed self-attention とは異なります。
import random
from typing import Tuple
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch import Tensor
class Encoder(nn.Module):
def __init__(self,
input_dim: int,
emb_dim: int,
enc_hid_dim: int,
dec_hid_dim: int,
dropout: float):
super().__init__()
self.input_dim = input_dim
self.emb_dim = emb_dim
self.enc_hid_dim = enc_hid_dim
self.dec_hid_dim = dec_hid_dim
self.dropout = dropout
self.embedding = nn.Embedding(input_dim, emb_dim)
self.rnn = nn.GRU(emb_dim, enc_hid_dim, bidirectional = True)
self.fc = nn.Linear(enc_hid_dim * 2, dec_hid_dim)
self.dropout = nn.Dropout(dropout)
def forward(self,
src: Tensor) -> Tuple[Tensor]:
embedded = self.dropout(self.embedding(src))
outputs, hidden = self.rnn(embedded)
hidden = torch.tanh(self.fc(torch.cat((hidden[-2,:,:], hidden[-1,:,:]), dim = 1)))
return outputs, hidden
class Attention(nn.Module):
def __init__(self,
enc_hid_dim: int,
dec_hid_dim: int,
attn_dim: int):
super().__init__()
self.enc_hid_dim = enc_hid_dim
self.dec_hid_dim = dec_hid_dim
self.attn_in = (enc_hid_dim * 2) + dec_hid_dim
self.attn = nn.Linear(self.attn_in, attn_dim)
def forward(self,
decoder_hidden: Tensor,
encoder_outputs: Tensor) -> Tensor:
src_len = encoder_outputs.shape[0]
repeated_decoder_hidden = decoder_hidden.unsqueeze(1).repeat(1, src_len, 1)
encoder_outputs = encoder_outputs.permute(1, 0, 2)
energy = torch.tanh(self.attn(torch.cat((
repeated_decoder_hidden,
encoder_outputs),
dim = 2)))
attention = torch.sum(energy, dim=2)
return F.softmax(attention, dim=1)
class Decoder(nn.Module):
def __init__(self,
output_dim: int,
emb_dim: int,
enc_hid_dim: int,
dec_hid_dim: int,
dropout: int,
attention: nn.Module):
super().__init__()
self.emb_dim = emb_dim
self.enc_hid_dim = enc_hid_dim
self.dec_hid_dim = dec_hid_dim
self.output_dim = output_dim
self.dropout = dropout
self.attention = attention
self.embedding = nn.Embedding(output_dim, emb_dim)
self.rnn = nn.GRU((enc_hid_dim * 2) + emb_dim, dec_hid_dim)
self.out = nn.Linear(self.attention.attn_in + emb_dim, output_dim)
self.dropout = nn.Dropout(dropout)
def _weighted_encoder_rep(self,
decoder_hidden: Tensor,
encoder_outputs: Tensor) -> Tensor:
a = self.attention(decoder_hidden, encoder_outputs)
a = a.unsqueeze(1)
encoder_outputs = encoder_outputs.permute(1, 0, 2)
weighted_encoder_rep = torch.bmm(a, encoder_outputs)
weighted_encoder_rep = weighted_encoder_rep.permute(1, 0, 2)
return weighted_encoder_rep
def forward(self,
input: Tensor,
decoder_hidden: Tensor,
encoder_outputs: Tensor) -> Tuple[Tensor]:
input = input.unsqueeze(0)
embedded = self.dropout(self.embedding(input))
weighted_encoder_rep = self._weighted_encoder_rep(decoder_hidden,
encoder_outputs)
rnn_input = torch.cat((embedded, weighted_encoder_rep), dim = 2)
output, decoder_hidden = self.rnn(rnn_input, decoder_hidden.unsqueeze(0))
embedded = embedded.squeeze(0)
output = output.squeeze(0)
weighted_encoder_rep = weighted_encoder_rep.squeeze(0)
output = self.out(torch.cat((output,
weighted_encoder_rep,
embedded), dim = 1))
return output, decoder_hidden.squeeze(0)
class Seq2Seq(nn.Module):
def __init__(self,
encoder: nn.Module,
decoder: nn.Module,
device: torch.device):
super().__init__()
self.encoder = encoder
self.decoder = decoder
self.device = device
def forward(self,
src: Tensor,
trg: Tensor,
teacher_forcing_ratio: float = 0.5) -> Tensor:
batch_size = src.shape[1]
max_len = trg.shape[0]
trg_vocab_size = self.decoder.output_dim
outputs = torch.zeros(max_len, batch_size, trg_vocab_size).to(self.device)
encoder_outputs, hidden = self.encoder(src)
# first input to the decoder is the token
output = trg[0,:]
for t in range(1, max_len):
output, hidden = self.decoder(output, hidden, encoder_outputs)
outputs[t] = output
teacher_force = random.random() < teacher_forcing_ratio
top1 = output.max(1)[1]
output = (trg[t] if teacher_force else top1)
return outputs
INPUT_DIM = len(SRC.vocab)
OUTPUT_DIM = len(TRG.vocab)
# ENC_EMB_DIM = 256
# DEC_EMB_DIM = 256
# ENC_HID_DIM = 512
# DEC_HID_DIM = 512
# ATTN_DIM = 64
# ENC_DROPOUT = 0.5
# DEC_DROPOUT = 0.5
ENC_EMB_DIM = 32
DEC_EMB_DIM = 32
ENC_HID_DIM = 64
DEC_HID_DIM = 64
ATTN_DIM = 8
ENC_DROPOUT = 0.5
DEC_DROPOUT = 0.5
enc = Encoder(INPUT_DIM, ENC_EMB_DIM, ENC_HID_DIM, DEC_HID_DIM, ENC_DROPOUT)
attn = Attention(ENC_HID_DIM, DEC_HID_DIM, ATTN_DIM)
dec = Decoder(OUTPUT_DIM, DEC_EMB_DIM, ENC_HID_DIM, DEC_HID_DIM, DEC_DROPOUT, attn)
model = Seq2Seq(enc, dec, device).to(device)
def init_weights(m: nn.Module):
for name, param in m.named_parameters():
if 'weight' in name:
nn.init.normal_(param.data, mean=0, std=0.01)
else:
nn.init.constant_(param.data, 0)
model.apply(init_weights)
optimizer = optim.Adam(model.parameters())
def count_parameters(model: nn.Module):
return sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f'The model has {count_parameters(model):,} trainable parameters')
The model has 1,856,653 trainable parameters
Note: 特に言語翻訳モデルのパフォーマンスをスコアするとき、nn.CrossEntropyLoss 関数にターゲットが単純にパディングされているインデックスは無視するように知らせなければなりません。
PAD_IDX = TRG.vocab.stoi[''] criterion = nn.CrossEntropyLoss(ignore_index=PAD_IDX)
最後に、このモデルを訓練して評価できます :
import math
import time
def train(model: nn.Module,
iterator: BucketIterator,
optimizer: optim.Optimizer,
criterion: nn.Module,
clip: float):
model.train()
epoch_loss = 0
for _, batch in enumerate(iterator):
src = batch.src
trg = batch.trg
optimizer.zero_grad()
output = model(src, trg)
output = output[1:].view(-1, output.shape[-1])
trg = trg[1:].view(-1)
loss = criterion(output, trg)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), clip)
optimizer.step()
epoch_loss += loss.item()
return epoch_loss / len(iterator)
def evaluate(model: nn.Module,
iterator: BucketIterator,
criterion: nn.Module):
model.eval()
epoch_loss = 0
with torch.no_grad():
for _, batch in enumerate(iterator):
src = batch.src
trg = batch.trg
output = model(src, trg, 0) #turn off teacher forcing
output = output[1:].view(-1, output.shape[-1])
trg = trg[1:].view(-1)
loss = criterion(output, trg)
epoch_loss += loss.item()
return epoch_loss / len(iterator)
def epoch_time(start_time: int,
end_time: int):
elapsed_time = end_time - start_time
elapsed_mins = int(elapsed_time / 60)
elapsed_secs = int(elapsed_time - (elapsed_mins * 60))
return elapsed_mins, elapsed_secs
N_EPOCHS = 10
CLIP = 1
best_valid_loss = float('inf')
for epoch in range(N_EPOCHS):
start_time = time.time()
train_loss = train(model, train_iterator, optimizer, criterion, CLIP)
valid_loss = evaluate(model, valid_iterator, criterion)
end_time = time.time()
epoch_mins, epoch_secs = epoch_time(start_time, end_time)
print(f'Epoch: {epoch+1:02} | Time: {epoch_mins}m {epoch_secs}s')
print(f'\tTrain Loss: {train_loss:.3f} | Train PPL: {math.exp(train_loss):7.3f}')
print(f'\t Val. Loss: {valid_loss:.3f} | Val. PPL: {math.exp(valid_loss):7.3f}')
test_loss = evaluate(model, test_iterator, criterion)
print(f'| Test Loss: {test_loss:.3f} | Test PPL: {math.exp(test_loss):7.3f} |')
Epoch: 01 | Time: 0m 37s
Train Loss: 5.661 | Train PPL: 287.311
Val. Loss: 5.199 | Val. PPL: 181.146
Epoch: 02 | Time: 0m 36s
Train Loss: 4.965 | Train PPL: 143.295
Val. Loss: 5.016 | Val. PPL: 150.773
Epoch: 03 | Time: 0m 36s
Train Loss: 4.721 | Train PPL: 112.268
Val. Loss: 4.996 | Val. PPL: 147.827
Epoch: 04 | Time: 0m 37s
Train Loss: 4.568 | Train PPL: 96.378
Val. Loss: 4.865 | Val. PPL: 129.629
Epoch: 05 | Time: 0m 37s
Train Loss: 4.476 | Train PPL: 87.904
Val. Loss: 4.807 | Val. PPL: 122.313
Epoch: 06 | Time: 0m 37s
Train Loss: 4.387 | Train PPL: 80.429
Val. Loss: 4.771 | Val. PPL: 118.051
Epoch: 07 | Time: 0m 37s
Train Loss: 4.287 | Train PPL: 72.761
Val. Loss: 4.731 | Val. PPL: 113.381
Epoch: 08 | Time: 0m 37s
Train Loss: 4.181 | Train PPL: 65.443
Val. Loss: 4.816 | Val. PPL: 123.433
Epoch: 09 | Time: 0m 37s
Train Loss: 4.077 | Train PPL: 58.947
Val. Loss: 4.764 | Val. PPL: 117.166
Epoch: 10 | Time: 0m 37s
Train Loss: 3.988 | Train PPL: 53.949
Val. Loss: 4.599 | Val. PPL: 99.416
| Test Loss: 4.619 | Test PPL: 101.387 |
Next steps
- ここ で torchtext を使用する Ben Trevett のチュートリアルの残りを確認してください。
- 次の単語予測を通した言語モデルのために他の torchtext 特徴を nn.Transformer と一緒に使用するチュートリアルに期待してください (= stay tuned)。
以上