torchtext语言翻译

原文:https://pytorch.org/tutorials/beginner/torchtext_translation_tutorial.html

本教程介绍了如何使用torchtext预处理包含英语和德语句子的著名数据集的数据,并使用它来训练序列到序列模型,并能将德语句子翻译成英语。

它基于 PyTorch 社区成员 Ben Trevett 的本教程,并获得 Ben 的许可。 我们通过删除一些旧代码来更新教程。

在本教程结束时,您将可以将句子预处理为张量以用于 NLP 建模,并可以使用torch.utils.data.DataLoader来训练和验证模型。

数据处理

torchtext具有工具,可用于创建可以轻松迭代的数据集,以创建语言翻译模型。 在此示例中,我们展示了如何对原始文本句子进行标记,构建词汇表以及将标记数字化为张量。

注意:本教程中的分词需要 Spacy 我们使用 Spacy 是因为它为英语以外的其他语言的分词提供了强大的支持。 torchtext提供了basic_english标记器,并支持其他英语标记器(例如 Moses),但对于语言翻译(需要多种语言),Spacy 是您的最佳选择。

要运行本教程,请先使用pipconda安装spacy。 接下来,下载英语和德语 Spacy 分词器的原始数据:

  1. python -m spacy download en
  2. python -m spacy download de
  1. import torchtext
  2. import torch
  3. from torchtext.data.utils import get_tokenizer
  4. from collections import Counter
  5. from torchtext.vocab import Vocab
  6. from torchtext.utils import download_from_url, extract_archive
  7. import io
  8. url_base = 'https://raw.githubusercontent.com/multi30k/dataset/master/data/task1/raw/'
  9. train_urls = ('train.de.gz', 'train.en.gz')
  10. val_urls = ('val.de.gz', 'val.en.gz')
  11. test_urls = ('test_2016_flickr.de.gz', 'test_2016_flickr.en.gz')
  12. train_filepaths = [extract_archive(download_from_url(url_base + url))[0] for url in train_urls]
  13. val_filepaths = [extract_archive(download_from_url(url_base + url))[0] for url in val_urls]
  14. test_filepaths = [extract_archive(download_from_url(url_base + url))[0] for url in test_urls]
  15. de_tokenizer = get_tokenizer('spacy', language='de')
  16. en_tokenizer = get_tokenizer('spacy', language='en')
  17. def build_vocab(filepath, tokenizer):
  18. counter = Counter()
  19. with io.open(filepath, encoding="utf8") as f:
  20. for string_ in f:
  21. counter.update(tokenizer(string_))
  22. return Vocab(counter, specials=['<unk>', '<pad>', '<bos>', '<eos>'])
  23. de_vocab = build_vocab(train_filepaths[0], de_tokenizer)
  24. en_vocab = build_vocab(train_filepaths[1], en_tokenizer)
  25. def data_process(filepaths):
  26. raw_de_iter = iter(io.open(filepaths[0], encoding="utf8"))
  27. raw_en_iter = iter(io.open(filepaths[1], encoding="utf8"))
  28. data = []
  29. for (raw_de, raw_en) in zip(raw_de_iter, raw_en_iter):
  30. de_tensor_ = torch.tensor([de_vocab[token] for token in de_tokenizer(raw_de)],
  31. dtype=torch.long)
  32. en_tensor_ = torch.tensor([en_vocab[token] for token in en_tokenizer(raw_en)],
  33. dtype=torch.long)
  34. data.append((de_tensor_, en_tensor_))
  35. return data
  36. train_data = data_process(train_filepaths)
  37. val_data = data_process(val_filepaths)
  38. test_data = data_process(test_filepaths)

DataLoader

我们将使用的最后torch个特定函数是DataLoader,它易于使用,因为它将数据作为第一个参数。 具体来说,正如文档所说:DataLoader结合了一个数据集和一个采样器,并在给定的数据集上提供了可迭代的。 DataLoader支持映射样式和可迭代样式的数据集,具有单进程或多进程加载,自定义加载顺序以及可选的自动批量(归类)和内存固定。

请注意collate_fn(可选),它将合并样本列表以形成张量的小批量。 在从映射样式数据集中使用批量加载时使用。

  1. import torch
  2. device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  3. BATCH_SIZE = 128
  4. PAD_IDX = de_vocab['<pad>']
  5. BOS_IDX = de_vocab['<bos>']
  6. EOS_IDX = de_vocab['<eos>']
  7. from torch.nn.utils.rnn import pad_sequence
  8. from torch.utils.data import DataLoader
  9. def generate_batch(data_batch):
  10. de_batch, en_batch = [], []
  11. for (de_item, en_item) in data_batch:
  12. de_batch.append(torch.cat([torch.tensor([BOS_IDX]), de_item, torch.tensor([EOS_IDX])], dim=0))
  13. en_batch.append(torch.cat([torch.tensor([BOS_IDX]), en_item, torch.tensor([EOS_IDX])], dim=0))
  14. de_batch = pad_sequence(de_batch, padding_value=PAD_IDX)
  15. en_batch = pad_sequence(en_batch, padding_value=PAD_IDX)
  16. return de_batch, en_batch
  17. train_iter = DataLoader(train_data, batch_size=BATCH_SIZE,
  18. shuffle=True, collate_fn=generate_batch)
  19. valid_iter = DataLoader(val_data, batch_size=BATCH_SIZE,
  20. shuffle=True, collate_fn=generate_batch)
  21. test_iter = DataLoader(test_data, batch_size=BATCH_SIZE,
  22. shuffle=True, collate_fn=generate_batch)

定义我们的nn.ModuleOptimizer

这大部分是从torchtext角度出发的:构建了数据集并定义了迭代器,本教程的其余部分仅将模型定义为nn.Module以及Optimizer,然后对其进行训练。

具体来说,我们的模型遵循此处描述的架构(您可以在这里找到注释更多的版本。

注意:此模型只是可用于语言翻译的示例模型; 我们选择它是因为它是任务的标准模型,而不是因为它是用于翻译的推荐模型。 如您所知,目前最先进的模型基于“转换器”; 您可以看到 PyTorch 的实现Transformer层的功能; 特别是,以下模型中使用的“注意”与转换器模型中存在的多头自我注意不同。

  1. import random
  2. from typing import Tuple
  3. import torch.nn as nn
  4. import torch.optim as optim
  5. import torch.nn.functional as F
  6. from torch import Tensor
  7. class Encoder(nn.Module):
  8. def __init__(self,
  9. input_dim: int,
  10. emb_dim: int,
  11. enc_hid_dim: int,
  12. dec_hid_dim: int,
  13. dropout: float):
  14. super().__init__()
  15. self.input_dim = input_dim
  16. self.emb_dim = emb_dim
  17. self.enc_hid_dim = enc_hid_dim
  18. self.dec_hid_dim = dec_hid_dim
  19. self.dropout = dropout
  20. self.embedding = nn.Embedding(input_dim, emb_dim)
  21. self.rnn = nn.GRU(emb_dim, enc_hid_dim, bidirectional = True)
  22. self.fc = nn.Linear(enc_hid_dim * 2, dec_hid_dim)
  23. self.dropout = nn.Dropout(dropout)
  24. def forward(self,
  25. src: Tensor) -> Tuple[Tensor]:
  26. embedded = self.dropout(self.embedding(src))
  27. outputs, hidden = self.rnn(embedded)
  28. hidden = torch.tanh(self.fc(torch.cat((hidden[-2,:,:], hidden[-1,:,:]), dim = 1)))
  29. return outputs, hidden
  30. class Attention(nn.Module):
  31. def __init__(self,
  32. enc_hid_dim: int,
  33. dec_hid_dim: int,
  34. attn_dim: int):
  35. super().__init__()
  36. self.enc_hid_dim = enc_hid_dim
  37. self.dec_hid_dim = dec_hid_dim
  38. self.attn_in = (enc_hid_dim * 2) + dec_hid_dim
  39. self.attn = nn.Linear(self.attn_in, attn_dim)
  40. def forward(self,
  41. decoder_hidden: Tensor,
  42. encoder_outputs: Tensor) -> Tensor:
  43. src_len = encoder_outputs.shape[0]
  44. repeated_decoder_hidden = decoder_hidden.unsqueeze(1).repeat(1, src_len, 1)
  45. encoder_outputs = encoder_outputs.permute(1, 0, 2)
  46. energy = torch.tanh(self.attn(torch.cat((
  47. repeated_decoder_hidden,
  48. encoder_outputs),
  49. dim = 2)))
  50. attention = torch.sum(energy, dim=2)
  51. return F.softmax(attention, dim=1)
  52. class Decoder(nn.Module):
  53. def __init__(self,
  54. output_dim: int,
  55. emb_dim: int,
  56. enc_hid_dim: int,
  57. dec_hid_dim: int,
  58. dropout: int,
  59. attention: nn.Module):
  60. super().__init__()
  61. self.emb_dim = emb_dim
  62. self.enc_hid_dim = enc_hid_dim
  63. self.dec_hid_dim = dec_hid_dim
  64. self.output_dim = output_dim
  65. self.dropout = dropout
  66. self.attention = attention
  67. self.embedding = nn.Embedding(output_dim, emb_dim)
  68. self.rnn = nn.GRU((enc_hid_dim * 2) + emb_dim, dec_hid_dim)
  69. self.out = nn.Linear(self.attention.attn_in + emb_dim, output_dim)
  70. self.dropout = nn.Dropout(dropout)
  71. def _weighted_encoder_rep(self,
  72. decoder_hidden: Tensor,
  73. encoder_outputs: Tensor) -> Tensor:
  74. a = self.attention(decoder_hidden, encoder_outputs)
  75. a = a.unsqueeze(1)
  76. encoder_outputs = encoder_outputs.permute(1, 0, 2)
  77. weighted_encoder_rep = torch.bmm(a, encoder_outputs)
  78. weighted_encoder_rep = weighted_encoder_rep.permute(1, 0, 2)
  79. return weighted_encoder_rep
  80. def forward(self,
  81. input: Tensor,
  82. decoder_hidden: Tensor,
  83. encoder_outputs: Tensor) -> Tuple[Tensor]:
  84. input = input.unsqueeze(0)
  85. embedded = self.dropout(self.embedding(input))
  86. weighted_encoder_rep = self._weighted_encoder_rep(decoder_hidden,
  87. encoder_outputs)
  88. rnn_input = torch.cat((embedded, weighted_encoder_rep), dim = 2)
  89. output, decoder_hidden = self.rnn(rnn_input, decoder_hidden.unsqueeze(0))
  90. embedded = embedded.squeeze(0)
  91. output = output.squeeze(0)
  92. weighted_encoder_rep = weighted_encoder_rep.squeeze(0)
  93. output = self.out(torch.cat((output,
  94. weighted_encoder_rep,
  95. embedded), dim = 1))
  96. return output, decoder_hidden.squeeze(0)
  97. class Seq2Seq(nn.Module):
  98. def __init__(self,
  99. encoder: nn.Module,
  100. decoder: nn.Module,
  101. device: torch.device):
  102. super().__init__()
  103. self.encoder = encoder
  104. self.decoder = decoder
  105. self.device = device
  106. def forward(self,
  107. src: Tensor,
  108. trg: Tensor,
  109. teacher_forcing_ratio: float = 0.5) -> Tensor:
  110. batch_size = src.shape[1]
  111. max_len = trg.shape[0]
  112. trg_vocab_size = self.decoder.output_dim
  113. outputs = torch.zeros(max_len, batch_size, trg_vocab_size).to(self.device)
  114. encoder_outputs, hidden = self.encoder(src)
  115. # first input to the decoder is the <sos> token
  116. output = trg[0,:]
  117. for t in range(1, max_len):
  118. output, hidden = self.decoder(output, hidden, encoder_outputs)
  119. outputs[t] = output
  120. teacher_force = random.random() < teacher_forcing_ratio
  121. top1 = output.max(1)[1]
  122. output = (trg[t] if teacher_force else top1)
  123. return outputs
  124. INPUT_DIM = len(de_vocab)
  125. OUTPUT_DIM = len(en_vocab)
  126. # ENC_EMB_DIM = 256
  127. # DEC_EMB_DIM = 256
  128. # ENC_HID_DIM = 512
  129. # DEC_HID_DIM = 512
  130. # ATTN_DIM = 64
  131. # ENC_DROPOUT = 0.5
  132. # DEC_DROPOUT = 0.5
  133. ENC_EMB_DIM = 32
  134. DEC_EMB_DIM = 32
  135. ENC_HID_DIM = 64
  136. DEC_HID_DIM = 64
  137. ATTN_DIM = 8
  138. ENC_DROPOUT = 0.5
  139. DEC_DROPOUT = 0.5
  140. enc = Encoder(INPUT_DIM, ENC_EMB_DIM, ENC_HID_DIM, DEC_HID_DIM, ENC_DROPOUT)
  141. attn = Attention(ENC_HID_DIM, DEC_HID_DIM, ATTN_DIM)
  142. dec = Decoder(OUTPUT_DIM, DEC_EMB_DIM, ENC_HID_DIM, DEC_HID_DIM, DEC_DROPOUT, attn)
  143. model = Seq2Seq(enc, dec, device).to(device)
  144. def init_weights(m: nn.Module):
  145. for name, param in m.named_parameters():
  146. if 'weight' in name:
  147. nn.init.normal_(param.data, mean=0, std=0.01)
  148. else:
  149. nn.init.constant_(param.data, 0)
  150. model.apply(init_weights)
  151. optimizer = optim.Adam(model.parameters())
  152. def count_parameters(model: nn.Module):
  153. return sum(p.numel() for p in model.parameters() if p.requires_grad)
  154. print(f'The model has {count_parameters(model):,} trainable parameters')

出:

  1. The model has 3,491,552 trainable parameters

注意:特别是对语言翻译模型的表现进行评分时,我们必须告诉nn.CrossEntropyLoss函数忽略仅填充目标的索引。

  1. PAD_IDX = en_vocab.stoi['<pad>']
  2. criterion = nn.CrossEntropyLoss(ignore_index=PAD_IDX)

最后,我们可以训练和评估该模型:

  1. import math
  2. import time
  3. def train(model: nn.Module,
  4. iterator: torch.utils.data.DataLoader,
  5. optimizer: optim.Optimizer,
  6. criterion: nn.Module,
  7. clip: float):
  8. model.train()
  9. epoch_loss = 0
  10. for _, (src, trg) in enumerate(iterator):
  11. src, trg = src.to(device), trg.to(device)
  12. optimizer.zero_grad()
  13. output = model(src, trg)
  14. output = output[1:].view(-1, output.shape[-1])
  15. trg = trg[1:].view(-1)
  16. loss = criterion(output, trg)
  17. loss.backward()
  18. torch.nn.utils.clip_grad_norm_(model.parameters(), clip)
  19. optimizer.step()
  20. epoch_loss += loss.item()
  21. return epoch_loss / len(iterator)
  22. def evaluate(model: nn.Module,
  23. iterator: torch.utils.data.DataLoader,
  24. criterion: nn.Module):
  25. model.eval()
  26. epoch_loss = 0
  27. with torch.no_grad():
  28. for _, (src, trg) in enumerate(iterator):
  29. src, trg = src.to(device), trg.to(device)
  30. output = model(src, trg, 0) #turn off teacher forcing
  31. output = output[1:].view(-1, output.shape[-1])
  32. trg = trg[1:].view(-1)
  33. loss = criterion(output, trg)
  34. epoch_loss += loss.item()
  35. return epoch_loss / len(iterator)
  36. def epoch_time(start_time: int,
  37. end_time: int):
  38. elapsed_time = end_time - start_time
  39. elapsed_mins = int(elapsed_time / 60)
  40. elapsed_secs = int(elapsed_time - (elapsed_mins * 60))
  41. return elapsed_mins, elapsed_secs
  42. N_EPOCHS = 10
  43. CLIP = 1
  44. best_valid_loss = float('inf')
  45. for epoch in range(N_EPOCHS):
  46. start_time = time.time()
  47. train_loss = train(model, train_iter, optimizer, criterion, CLIP)
  48. valid_loss = evaluate(model, valid_iter, criterion)
  49. end_time = time.time()
  50. epoch_mins, epoch_secs = epoch_time(start_time, end_time)
  51. print(f'Epoch: {epoch+1:02} | Time: {epoch_mins}m {epoch_secs}s')
  52. print(f'\tTrain Loss: {train_loss:.3f} | Train PPL: {math.exp(train_loss):7.3f}')
  53. print(f'\t Val. Loss: {valid_loss:.3f} | Val. PPL: {math.exp(valid_loss):7.3f}')
  54. test_loss = evaluate(model, test_iter, criterion)
  55. print(f'| Test Loss: {test_loss:.3f} | Test PPL: {math.exp(test_loss):7.3f} |')

出:

  1. Epoch: 01 | Time: 0m 59s
  2. Train Loss: 5.790 | Train PPL: 327.039
  3. Val. Loss: 5.250 | Val. PPL: 190.532
  4. Epoch: 02 | Time: 0m 59s
  5. Train Loss: 4.762 | Train PPL: 116.990
  6. Val. Loss: 5.037 | Val. PPL: 153.939
  7. Epoch: 03 | Time: 0m 59s
  8. Train Loss: 4.527 | Train PPL: 92.475
  9. Val. Loss: 4.924 | Val. PPL: 137.525
  10. Epoch: 04 | Time: 0m 59s
  11. Train Loss: 4.344 | Train PPL: 76.977
  12. Val. Loss: 4.801 | Val. PPL: 121.673
  13. Epoch: 05 | Time: 0m 59s
  14. Train Loss: 4.210 | Train PPL: 67.356
  15. Val. Loss: 4.758 | Val. PPL: 116.536
  16. Epoch: 06 | Time: 0m 59s
  17. Train Loss: 4.125 | Train PPL: 61.875
  18. Val. Loss: 4.691 | Val. PPL: 109.004
  19. Epoch: 07 | Time: 0m 59s
  20. Train Loss: 4.043 | Train PPL: 56.979
  21. Val. Loss: 4.639 | Val. PPL: 103.446
  22. Epoch: 08 | Time: 0m 59s
  23. Train Loss: 3.947 | Train PPL: 51.771
  24. Val. Loss: 4.589 | Val. PPL: 98.396
  25. Epoch: 09 | Time: 0m 59s
  26. Train Loss: 3.874 | Train PPL: 48.135
  27. Val. Loss: 4.514 | Val. PPL: 91.324
  28. Epoch: 10 | Time: 0m 59s
  29. Train Loss: 3.785 | Train PPL: 44.021
  30. Val. Loss: 4.467 | Val. PPL: 87.126
  31. | Test Loss: 4.433 | Test PPL: 84.168 |

后续步骤

  • 查看其余的 Ben Trevetttorchtext使用教程。
  • 敬请关注使用其他torchtext函数以及nn.Transformer通过下一个单词预测进行语言建模的教程!

脚本的总运行时间:(10 分钟 13.398 秒)

下载 Python 源码:torchtext_translation_tutorial.py

下载 Jupyter 笔记本:torchtext_translation_tutorial.ipynb

由 Sphinx 画廊生成的画廊