要查看圖文版教程跨晴,請(qǐng)移步到:http://studyai.com/pytorch-1.4/beginner/torchtext_translation_tutorial.html
本教程介紹如何使用 torchtext 提供的幾個(gè)便利的類(lèi)來(lái)預(yù)處理包含英語(yǔ)和德語(yǔ)句子的知名數(shù)據(jù)集中的數(shù)據(jù), 并使用它來(lái)訓(xùn)練一個(gè)帶有注意力機(jī)制的序列到序列的模型,將德語(yǔ)句子翻譯成英語(yǔ)著蟹。
本教程是基于PyTorch社區(qū)成員 Ben Trevett 創(chuàng)作的 這個(gè)教程
在本教程結(jié)束時(shí),你將能夠:
使用以下 torchtext 提供的類(lèi)將句子預(yù)處理為NLP建模的常用格式:
TranslationDataset
Field
BucketIterator
Field 和 TranslationDataset
torchtext 有用于創(chuàng)建數(shù)據(jù)集的實(shí)用程序,這些數(shù)據(jù)集可以很容易地迭代以創(chuàng)建語(yǔ)言翻譯模型违寿。 一個(gè)關(guān)鍵類(lèi)是 Field, 它指定每個(gè)句子的預(yù)處理方式湃交, 另一個(gè)是 TranslationDataset ; torchtext 有幾個(gè)這樣的數(shù)據(jù)集藤巢;在本教程中搞莺,我們將使用 Multi30k dataset , 它包含大約30000個(gè)英語(yǔ)和德語(yǔ)句子(平均長(zhǎng)度約13個(gè)單詞)掂咒。
請(qǐng)注意: 本教程的詞語(yǔ)切分標(biāo)記(tokenization)需要 Spacy 才沧。 我們使用Spacy是因?yàn)樗谟⒄Z(yǔ)以外的語(yǔ)言中為詞語(yǔ)切分標(biāo)記提供了強(qiáng)大的支持。 torchtext 提供了一個(gè)基本的英語(yǔ)標(biāo)記器绍刮,并支持其他英語(yǔ)標(biāo)記器 (例如 Moses )温圆, 但對(duì)于語(yǔ)言翻譯-則需要多種語(yǔ)言-而Spacy是最好的選擇。
要運(yùn)行此教程录淡,首先要使用 pip 或 conda 安裝 spacy 捌木。 接著, 下載 English 和 German 原始數(shù)據(jù)的 Spacy tokenizers:
python -m spacy download en
python -m spacy download de
安裝好 Spacy 之后, 下面的代碼將基于 Field 中定義的標(biāo)記器(tokenizer)標(biāo)記 TranslationDataset 中的每個(gè)句子
from torchtext.datasets import Multi30k
from torchtext.data import Field, BucketIterator
SRC = Field(tokenize = "spacy",
tokenizer_language="de",
init_token = '<sos>',
eos_token = '<eos>',
lower = True)
TRG = Field(tokenize = "spacy",
tokenizer_language="en",
init_token = '<sos>',
eos_token = '<eos>',
lower = True)
train_data, valid_data, test_data = Multi30k.splits(exts = ('.de', '.en'),
fields = (SRC, TRG))
現(xiàn)在我們已經(jīng)定義了 train_data ,我們可以看到 torchtext 的 Field 的一個(gè)非常有用的功能: build_vocab 方法現(xiàn)在允許我們創(chuàng)建與每種語(yǔ)言相關(guān)聯(lián)的詞匯表(vocabulary)
SRC.build_vocab(train_data, min_freq = 2)
TRG.build_vocab(train_data, min_freq = 2)
一旦運(yùn)行上面這些代碼行后嫉戚, SRC.vocab.stoi 將是一個(gè)字典刨裆,其中詞匯表中的標(biāo)記為鍵,其相應(yīng)索引為值彬檀; SRC.vocab.itos 將是同一個(gè)字典帆啃,只是其中的鍵和值被交換了。 在本教程中窍帝,我們不會(huì)廣泛地使用這個(gè)事實(shí)努潘,但是在您將遇到的其他NLP任務(wù)中,這可能會(huì)很有用坤学。
BucketIterator
我們將要使用 torchtext 的最后一個(gè)特別的功能是 BucketIterator, 因?yàn)樗邮芤粋€(gè) TranslationDataset 作為其第一個(gè)參數(shù)疯坤,所以非常簡(jiǎn)單易用。 就像文檔中描述的那樣: 定義一個(gè)迭代器深浮,用于將相似長(zhǎng)度的樣本組織到同一個(gè)batch中压怠。 在為每個(gè)新的回合(new epoch)生產(chǎn)新的隨機(jī)batch時(shí),最小化所需的填充量(padding)飞苇。
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)
這些迭代器(iterators)可以被調(diào)用菌瘫,就像調(diào)用 DataLoader 的迭代器一樣; 下面, 在 函數(shù) train 和 evaluate 中, 可以簡(jiǎn)單地使用以下方式來(lái)調(diào)用:
for i, batch in enumerate(iterator):
每一個(gè) batch 都有 src 和 trg 屬性:
src = batch.src
trg = batch.trg
定義 nn.Module 和 Optimizer
從 torchtext 的角度來(lái)看:隨著數(shù)據(jù)集的構(gòu)建和迭代器(iterator)的定義, 本教程的其余部分只是將我們的模型定義為 nn.Module 以及創(chuàng)建一個(gè) Optimizer布卡,然后對(duì)其進(jìn)行訓(xùn)練雨让。
具體來(lái)說(shuō),我們的模型遵循了 這里 描述的架構(gòu)(您可以在 這里 找到一個(gè)更具注釋性的版本)忿等。
注意: 這個(gè)模型只是一個(gè)可用于語(yǔ)言翻譯的示例模型栖忠;我們選擇它是因?yàn)樗窃撊蝿?wù)的一個(gè)標(biāo)準(zhǔn)模型, 而不是因?yàn)樗潜粡?qiáng)烈推薦用于翻譯任務(wù)的模型。正如您可能知道的娃闲,最先進(jìn)的模型當(dāng)前基于Transformers虚汛; 您可以在 這里 看到PyTorch實(shí)現(xiàn) Transformer layers 的能力; 特別是皇帮,下面模型中使用的 “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 <sos> 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')
注意:在對(duì)語(yǔ)言翻譯模型的性能進(jìn)行評(píng)分時(shí),我們必須告訴 nn.CrossEntropyLoss 函數(shù)忽略目標(biāo)只是填充的索引 (ignore the indices where the target is simply padding.)属拾。
PAD_IDX = TRG.vocab.stoi['<pad>']
criterion = nn.CrossEntropyLoss(ignore_index=PAD_IDX)
最后将谊,我們可以訓(xùn)練和評(píng)估這個(gè)模型:
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} |')
下一步
使用 torchtext 繼續(xù)學(xué)習(xí) Ben Trevett 的教程的剩余部分—- 在這里
請(qǐng)繼續(xù)關(guān)注使用其他 torchtext 功能特性以及 nn.Transformer 的教程,以便學(xué)習(xí)通過(guò)預(yù)測(cè)下一個(gè)單詞進(jìn)行語(yǔ)言建模渐白!