从零开始的 NLP:使用序列到序列网络和注意力的翻译

原文:https://pytorch.org/tutorials/intermediate/seq2seq_translation_tutorial.html

作者Sean Robertson

这是关于“从头开始进行 NLP”的第三篇也是最后一篇教程,我们在其中编写自己的类和函数来预处理数据以完成 NLP 建模任务。 我们希望在完成本教程后,您将继续学习紧接着本教程的三本教程,torchtext如何为您处理许多此类预处理。

在这个项目中,我们将教授将法语翻译成英语的神经网络。

  1. [KEY: > input, = target, < output]
  2. > il est en train de peindre un tableau .
  3. = he is painting a picture .
  4. < he is painting a picture .
  5. > pourquoi ne pas essayer ce vin delicieux ?
  6. = why not try that delicious wine ?
  7. < why not try that delicious wine ?
  8. > elle n est pas poete mais romanciere .
  9. = she is not a poet but a novelist .
  10. < she not not a poet but a novelist .
  11. > vous etes trop maigre .
  12. = you re too skinny .
  13. < you re all alone .

……取得不同程度的成功。

通过序列到序列网络的简单但强大的构想,使这成为可能,其中两个循环神经网络协同工作,将一个序列转换为另一个序列。 编码器网络将输入序列压缩为一个向量,而解码器网络将该向量展开为一个新序列。

从零开始的 NLP:使用序列到序列网络和注意力的翻译 - 图1

为了改进此模型,我们将使用注意力机制,该机制可使解码器学会专注于输入序列的特定范围。

推荐读物

我假设您至少已经安装了 PyTorch,Python 和张量:

了解序列到序列网络及其工作方式也将很有用:

您还将找到有关《从零开始的 NLP:使用字符级 RNN 分类名称》《从零开始的 NLP:使用字符级 RNN 生成名称》的先前教程。 分别与编码器和解码器模型非常相似。

有关更多信息,请阅读介绍以下主题的论文:

要求

  1. from __future__ import unicode_literals, print_function, division
  2. from io import open
  3. import unicodedata
  4. import string
  5. import re
  6. import random
  7. import torch
  8. import torch.nn as nn
  9. from torch import optim
  10. import torch.nn.functional as F
  11. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

加载数据文件

该项目的数据是成千上万的英语到法语翻译对的集合。

开放数据栈交换上的这个问题使我指向开放翻译站点 ,该站点可从这里下载。更好的是,有人在这里做了一些额外的工作,将语言对拆分为单独的文本文件

英文对法文对太大,无法包含在仓库中,因此请先下载到data/eng-fra.txt,然后再继续。 该文件是制表符分隔的翻译对列表:

  1. I am cold. J'ai froid.

注意

从的下载数据,并将其提取到当前目录。

与字符级 RNN 教程中使用的字符编码类似,我们将一种语言中的每个单词表示为一个单向向量,或零个大向量(除单个单向索引外)(在单词的索引处)。 与某种语言中可能存在的数十个字符相比,单词更多很多,因此编码向量要大得多。 但是,我们将作弊并整理数据以使每种语言仅使用几千个单词。

从零开始的 NLP:使用序列到序列网络和注意力的翻译 - 图2

我们将需要每个单词一个唯一的索引,以便以后用作网络的输入和目标。 为了跟踪所有这些,我们将使用一个名为Lang的帮助程序类,该类具有单词→索引(word2index)和索引→单词(index2word)字典,以及每个要使用的单词word2count的计数,以便以后替换稀有词。

  1. SOS_token = 0
  2. EOS_token = 1
  3. class Lang:
  4. def __init__(self, name):
  5. self.name = name
  6. self.word2index = {}
  7. self.word2count = {}
  8. self.index2word = {0: "SOS", 1: "EOS"}
  9. self.n_words = 2 # Count SOS and EOS
  10. def addSentence(self, sentence):
  11. for word in sentence.split(' '):
  12. self.addWord(word)
  13. def addWord(self, word):
  14. if word not in self.word2index:
  15. self.word2index[word] = self.n_words
  16. self.word2count[word] = 1
  17. self.index2word[self.n_words] = word
  18. self.n_words += 1
  19. else:
  20. self.word2count[word] += 1

文件全部为 Unicode,为简化起见,我们将 Unicode 字符转换为 ASCII,将所有内容都转换为小写,并修剪大多数标点符号。

  1. # Turn a Unicode string to plain ASCII, thanks to
  2. # https://stackoverflow.com/a/518232/2809427
  3. def unicodeToAscii(s):
  4. return ''.join(
  5. c for c in unicodedata.normalize('NFD', s)
  6. if unicodedata.category(c) != 'Mn'
  7. )
  8. # Lowercase, trim, and remove non-letter characters
  9. def normalizeString(s):
  10. s = unicodeToAscii(s.lower().strip())
  11. s = re.sub(r"([.!?])", r" \1", s)
  12. s = re.sub(r"[^a-zA-Z.!?]+", r" ", s)
  13. return s

要读取数据文件,我们将文件拆分为几行,然后将几行拆分为两对。 这些文件都是英语→其他语言的,因此,如果我们要从其他语言→英语进行翻译,我添加了reverse标志来反转对。

  1. def readLangs(lang1, lang2, reverse=False):
  2. print("Reading lines...")
  3. # Read the file and split into lines
  4. lines = open('data/%s-%s.txt' % (lang1, lang2), encoding='utf-8').\
  5. read().strip().split('\n')
  6. # Split every line into pairs and normalize
  7. pairs = [[normalizeString(s) for s in l.split('\t')] for l in lines]
  8. # Reverse pairs, make Lang instances
  9. if reverse:
  10. pairs = [list(reversed(p)) for p in pairs]
  11. input_lang = Lang(lang2)
  12. output_lang = Lang(lang1)
  13. else:
  14. input_lang = Lang(lang1)
  15. output_lang = Lang(lang2)
  16. return input_lang, output_lang, pairs

由于示例句子有很多,并且我们想快速训练一些东西,因此我们将数据集修剪为仅相对简短的句子。 在这里,最大长度为 10 个字(包括结尾的标点符号),我们正在过滤翻译成“我是”或“他是”等形式的句子(考虑到前面已替换掉撇号的情况)。

  1. MAX_LENGTH = 10
  2. eng_prefixes = (
  3. "i am ", "i m ",
  4. "he is", "he s ",
  5. "she is", "she s ",
  6. "you are", "you re ",
  7. "we are", "we re ",
  8. "they are", "they re "
  9. )
  10. def filterPair(p):
  11. return len(p[0].split(' ')) < MAX_LENGTH and \
  12. len(p[1].split(' ')) < MAX_LENGTH and \
  13. p[1].startswith(eng_prefixes)
  14. def filterPairs(pairs):
  15. return [pair for pair in pairs if filterPair(pair)]

准备数据的完整过程是:

  • 读取文本文件并拆分为行,将行拆分为偶对
  • 规范文本,按长度和内容过滤
  • 成对建立句子中的单词列表
  1. def prepareData(lang1, lang2, reverse=False):
  2. input_lang, output_lang, pairs = readLangs(lang1, lang2, reverse)
  3. print("Read %s sentence pairs" % len(pairs))
  4. pairs = filterPairs(pairs)
  5. print("Trimmed to %s sentence pairs" % len(pairs))
  6. print("Counting words...")
  7. for pair in pairs:
  8. input_lang.addSentence(pair[0])
  9. output_lang.addSentence(pair[1])
  10. print("Counted words:")
  11. print(input_lang.name, input_lang.n_words)
  12. print(output_lang.name, output_lang.n_words)
  13. return input_lang, output_lang, pairs
  14. input_lang, output_lang, pairs = prepareData('eng', 'fra', True)
  15. print(random.choice(pairs))

出:

  1. Reading lines...
  2. Read 135842 sentence pairs
  3. Trimmed to 10599 sentence pairs
  4. Counting words...
  5. Counted words:
  6. fra 4345
  7. eng 2803
  8. ['il a l habitude des ordinateurs .', 'he is familiar with computers .']

Seq2Seq 模型

循环神经网络(RNN)是在序列上运行并将其自身的输出用作后续步骤的输入的网络。

序列到序列网络或 seq2seq 网络或编码器解码器网络是由两个称为编码器和解码器的 RNN 组成的模型。 编码器读取输入序列并输出单个向量,而解码器读取该向量以产生输出序列。

从零开始的 NLP:使用序列到序列网络和注意力的翻译 - 图3

与使用单个 RNN 进行序列预测(每个输入对应一个输出)不同,seq2seq 模型使我们摆脱了序列长度和顺序的限制,这使其非常适合两种语言之间的翻译。

考虑一下句子Je ne suis pas le chat noir -> I am not the black cat。 输入句子中的大多数单词在输出句子中具有直接翻译,但是顺序略有不同,例如chat noirblack cat。 由于采用ne/pas结构,因此在输入句子中还有一个单词。 直接从输入单词的序列中产生正确的翻译将是困难的。

使用 seq2seq 模型,编码器创建单个向量,在理想情况下,该向量将输入序列的“含义”编码为单个向量—在句子的 N 维空间中的单个点。

编码器

seq2seq 网络的编码器是 RNN,它为输入句子中的每个单词输出一些值。 对于每个输入字,编码器输出一个向量和一个隐藏状态,并将隐藏状态用于下一个输入字。

从零开始的 NLP:使用序列到序列网络和注意力的翻译 - 图4

  1. class EncoderRNN(nn.Module):
  2. def __init__(self, input_size, hidden_size):
  3. super(EncoderRNN, self).__init__()
  4. self.hidden_size = hidden_size
  5. self.embedding = nn.Embedding(input_size, hidden_size)
  6. self.gru = nn.GRU(hidden_size, hidden_size)
  7. def forward(self, input, hidden):
  8. embedded = self.embedding(input).view(1, 1, -1)
  9. output = embedded
  10. output, hidden = self.gru(output, hidden)
  11. return output, hidden
  12. def initHidden(self):
  13. return torch.zeros(1, 1, self.hidden_size, device=device)

解码器

解码器是另一个 RNN,它采用编码器输出向量并输出单词序列来创建翻译。

简单解码器

在最简单的 seq2seq 解码器中,我们仅使用编码器的最后一个输出。 该最后的输出有时称为上下文向量,因为它从整个序列中编码上下文。 该上下文向量用作解码器的初始隐藏状态。

在解码的每个步骤中,为解码器提供输入标记和隐藏状态。 初始输入标记是字符串开始<SOS>标记,第一个隐藏状态是上下文向量(编码器的最后一个隐藏状态)。

从零开始的 NLP:使用序列到序列网络和注意力的翻译 - 图5

  1. class DecoderRNN(nn.Module):
  2. def __init__(self, hidden_size, output_size):
  3. super(DecoderRNN, self).__init__()
  4. self.hidden_size = hidden_size
  5. self.embedding = nn.Embedding(output_size, hidden_size)
  6. self.gru = nn.GRU(hidden_size, hidden_size)
  7. self.out = nn.Linear(hidden_size, output_size)
  8. self.softmax = nn.LogSoftmax(dim=1)
  9. def forward(self, input, hidden):
  10. output = self.embedding(input).view(1, 1, -1)
  11. output = F.relu(output)
  12. output, hidden = self.gru(output, hidden)
  13. output = self.softmax(self.out(output[0]))
  14. return output, hidden
  15. def initHidden(self):
  16. return torch.zeros(1, 1, self.hidden_size, device=device)

我鼓励您训练并观察该模型的结果,但是为了节省空间,我们将直接努力并引入注意力机制。

注意力解码器

如果仅上下文向量在编码器和解码器之间传递,则该单个向量承担对整个句子进行编码的负担。

注意使解码器网络可以针对解码器自身输出的每一步,“专注”于编码器输出的不同部分。 首先,我们计算一组注意力权重。 将这些与编码器输出向量相乘以创建加权组合。 结果(在代码中称为attn_applied)应包含有关输入序列特定部分的信息,从而帮助解码器选择正确的输出字。

从零开始的 NLP:使用序列到序列网络和注意力的翻译 - 图6

另一个前馈层attn使用解码器的输入和隐藏状态作为输入来计算注意力权重。 由于训练数据中包含各种大小的句子,因此要实际创建和训练该层,我们必须选择可以应用的最大句子长度(输入长度​​,用于编码器输出)。 最大长度的句子将使用所有注意权重,而较短的句子将仅使用前几个。

从零开始的 NLP:使用序列到序列网络和注意力的翻译 - 图7

  1. class AttnDecoderRNN(nn.Module):
  2. def __init__(self, hidden_size, output_size, dropout_p=0.1, max_length=MAX_LENGTH):
  3. super(AttnDecoderRNN, self).__init__()
  4. self.hidden_size = hidden_size
  5. self.output_size = output_size
  6. self.dropout_p = dropout_p
  7. self.max_length = max_length
  8. self.embedding = nn.Embedding(self.output_size, self.hidden_size)
  9. self.attn = nn.Linear(self.hidden_size * 2, self.max_length)
  10. self.attn_combine = nn.Linear(self.hidden_size * 2, self.hidden_size)
  11. self.dropout = nn.Dropout(self.dropout_p)
  12. self.gru = nn.GRU(self.hidden_size, self.hidden_size)
  13. self.out = nn.Linear(self.hidden_size, self.output_size)
  14. def forward(self, input, hidden, encoder_outputs):
  15. embedded = self.embedding(input).view(1, 1, -1)
  16. embedded = self.dropout(embedded)
  17. attn_weights = F.softmax(
  18. self.attn(torch.cat((embedded[0], hidden[0]), 1)), dim=1)
  19. attn_applied = torch.bmm(attn_weights.unsqueeze(0),
  20. encoder_outputs.unsqueeze(0))
  21. output = torch.cat((embedded[0], attn_applied[0]), 1)
  22. output = self.attn_combine(output).unsqueeze(0)
  23. output = F.relu(output)
  24. output, hidden = self.gru(output, hidden)
  25. output = F.log_softmax(self.out(output[0]), dim=1)
  26. return output, hidden, attn_weights
  27. def initHidden(self):
  28. return torch.zeros(1, 1, self.hidden_size, device=device)

注意

还有其他形式的注意,可以通过使用相对位置方法来解决长度限制问题。 在《基于注意力的神经机器翻译的有效方法》中阅读“本地注意力”。

训练

准备训练数据

为了训练,对于每一对,我们将需要一个输入张量(输入句子中单词的索引)和目标张量(目标句子中单词的索引)。 创建这些向量时,我们会将EOS标记附加到两个序列上。

  1. def indexesFromSentence(lang, sentence):
  2. return [lang.word2index[word] for word in sentence.split(' ')]
  3. def tensorFromSentence(lang, sentence):
  4. indexes = indexesFromSentence(lang, sentence)
  5. indexes.append(EOS_token)
  6. return torch.tensor(indexes, dtype=torch.long, device=device).view(-1, 1)
  7. def tensorsFromPair(pair):
  8. input_tensor = tensorFromSentence(input_lang, pair[0])
  9. target_tensor = tensorFromSentence(output_lang, pair[1])
  10. return (input_tensor, target_tensor)

训练模型

为了训练,我们通过编码器运行输入语句,并跟踪每个输出和最新的隐藏状态。 然后,为解码器提供<SOS>标记作为其第一个输入,为编码器提供最后的隐藏状态作为其第一个隐藏状态。

“教师强制”的概念是使用实际目标输出作为每个下一个输入,而不是使用解码器的猜测作为下一个输入。 使用教师强制会导致其收敛更快,但是当使用受过训练的网络时,可能会显示不稳定

您可以观察到以教师为主导的网络的输出,这些输出阅读的是连贯的语法,但是却偏离了正确的翻译-直观地,它已经学会了代表输出语法,并且一旦老师说了最初的几个单词就可以“理解”含义,但是首先,它还没有正确地学习如何从翻译中创建句子。

由于 PyTorch 的 Autograd 具有给我们的自由,我们可以通过简单的if语句随意选择是否使用教师强迫。 调高teacher_forcing_ratio以使用更多。

  1. teacher_forcing_ratio = 0.5
  2. def train(input_tensor, target_tensor, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion, max_length=MAX_LENGTH):
  3. encoder_hidden = encoder.initHidden()
  4. encoder_optimizer.zero_grad()
  5. decoder_optimizer.zero_grad()
  6. input_length = input_tensor.size(0)
  7. target_length = target_tensor.size(0)
  8. encoder_outputs = torch.zeros(max_length, encoder.hidden_size, device=device)
  9. loss = 0
  10. for ei in range(input_length):
  11. encoder_output, encoder_hidden = encoder(
  12. input_tensor[ei], encoder_hidden)
  13. encoder_outputs[ei] = encoder_output[0, 0]
  14. decoder_input = torch.tensor([[SOS_token]], device=device)
  15. decoder_hidden = encoder_hidden
  16. use_teacher_forcing = True if random.random() < teacher_forcing_ratio else False
  17. if use_teacher_forcing:
  18. # Teacher forcing: Feed the target as the next input
  19. for di in range(target_length):
  20. decoder_output, decoder_hidden, decoder_attention = decoder(
  21. decoder_input, decoder_hidden, encoder_outputs)
  22. loss += criterion(decoder_output, target_tensor[di])
  23. decoder_input = target_tensor[di] # Teacher forcing
  24. else:
  25. # Without teacher forcing: use its own predictions as the next input
  26. for di in range(target_length):
  27. decoder_output, decoder_hidden, decoder_attention = decoder(
  28. decoder_input, decoder_hidden, encoder_outputs)
  29. topv, topi = decoder_output.topk(1)
  30. decoder_input = topi.squeeze().detach() # detach from history as input
  31. loss += criterion(decoder_output, target_tensor[di])
  32. if decoder_input.item() == EOS_token:
  33. break
  34. loss.backward()
  35. encoder_optimizer.step()
  36. decoder_optimizer.step()
  37. return loss.item() / target_length

这是一个帮助函数,用于在给定当前时间和进度% 的情况下打印经过的时间和估计的剩余时间。

  1. import time
  2. import math
  3. def asMinutes(s):
  4. m = math.floor(s / 60)
  5. s -= m * 60
  6. return '%dm %ds' % (m, s)
  7. def timeSince(since, percent):
  8. now = time.time()
  9. s = now - since
  10. es = s / (percent)
  11. rs = es - s
  12. return '%s (- %s)' % (asMinutes(s), asMinutes(rs))

整个训练过程如下所示:

  • 启动计时器
  • 初始化优化器和标准
  • 创建一组训练对
  • 启动空损失数组进行绘图

然后,我们多次调用train,并偶尔打印进度(示例的百分比,到目前为止的时间,估计的时间)和平均损失。

  1. def trainIters(encoder, decoder, n_iters, print_every=1000, plot_every=100, learning_rate=0.01):
  2. start = time.time()
  3. plot_losses = []
  4. print_loss_total = 0 # Reset every print_every
  5. plot_loss_total = 0 # Reset every plot_every
  6. encoder_optimizer = optim.SGD(encoder.parameters(), lr=learning_rate)
  7. decoder_optimizer = optim.SGD(decoder.parameters(), lr=learning_rate)
  8. training_pairs = [tensorsFromPair(random.choice(pairs))
  9. for i in range(n_iters)]
  10. criterion = nn.NLLLoss()
  11. for iter in range(1, n_iters + 1):
  12. training_pair = training_pairs[iter - 1]
  13. input_tensor = training_pair[0]
  14. target_tensor = training_pair[1]
  15. loss = train(input_tensor, target_tensor, encoder,
  16. decoder, encoder_optimizer, decoder_optimizer, criterion)
  17. print_loss_total += loss
  18. plot_loss_total += loss
  19. if iter % print_every == 0:
  20. print_loss_avg = print_loss_total / print_every
  21. print_loss_total = 0
  22. print('%s (%d %d%%) %.4f' % (timeSince(start, iter / n_iters),
  23. iter, iter / n_iters * 100, print_loss_avg))
  24. if iter % plot_every == 0:
  25. plot_loss_avg = plot_loss_total / plot_every
  26. plot_losses.append(plot_loss_avg)
  27. plot_loss_total = 0
  28. showPlot(plot_losses)

绘制结果

使用训练时保存的损失值数组plot_losses,使用 matplotlib 进行绘制。

  1. import matplotlib.pyplot as plt
  2. plt.switch_backend('agg')
  3. import matplotlib.ticker as ticker
  4. import numpy as np
  5. def showPlot(points):
  6. plt.figure()
  7. fig, ax = plt.subplots()
  8. # this locator puts ticks at regular intervals
  9. loc = ticker.MultipleLocator(base=0.2)
  10. ax.yaxis.set_major_locator(loc)
  11. plt.plot(points)

评估

评估与训练基本相同,但是没有目标,因此我们只需将解码器的预测反馈给每一步。 每当它预测一个单词时,我们都会将其添加到输出字符串中,如果它预测到EOS标记,我们将在此处停止。 我们还将存储解码器的注意输出,以供以后显示。

  1. def evaluate(encoder, decoder, sentence, max_length=MAX_LENGTH):
  2. with torch.no_grad():
  3. input_tensor = tensorFromSentence(input_lang, sentence)
  4. input_length = input_tensor.size()[0]
  5. encoder_hidden = encoder.initHidden()
  6. encoder_outputs = torch.zeros(max_length, encoder.hidden_size, device=device)
  7. for ei in range(input_length):
  8. encoder_output, encoder_hidden = encoder(input_tensor[ei],
  9. encoder_hidden)
  10. encoder_outputs[ei] += encoder_output[0, 0]
  11. decoder_input = torch.tensor([[SOS_token]], device=device) # SOS
  12. decoder_hidden = encoder_hidden
  13. decoded_words = []
  14. decoder_attentions = torch.zeros(max_length, max_length)
  15. for di in range(max_length):
  16. decoder_output, decoder_hidden, decoder_attention = decoder(
  17. decoder_input, decoder_hidden, encoder_outputs)
  18. decoder_attentions[di] = decoder_attention.data
  19. topv, topi = decoder_output.data.topk(1)
  20. if topi.item() == EOS_token:
  21. decoded_words.append('<EOS>')
  22. break
  23. else:
  24. decoded_words.append(output_lang.index2word[topi.item()])
  25. decoder_input = topi.squeeze().detach()
  26. return decoded_words, decoder_attentions[:di + 1]

我们可以从训练集中评估随机句子,并打印出输入,目标和输出以做出一些主观的质量判断:

  1. def evaluateRandomly(encoder, decoder, n=10):
  2. for i in range(n):
  3. pair = random.choice(pairs)
  4. print('>', pair[0])
  5. print('=', pair[1])
  6. output_words, attentions = evaluate(encoder, decoder, pair[0])
  7. output_sentence = ' '.join(output_words)
  8. print('<', output_sentence)
  9. print('')

训练和评估

有了所有这些辅助函数(看起来像是额外的工作,但它使运行多个实验更加容易),我们实际上可以初始化网络并开始训练。

请记住,输入语句已被大量过滤。 对于这个小的数据集,我们可以使用具有 256 个隐藏节点和单个 GRU 层的相对较小的网络。 在 MacBook CPU 上运行约 40 分钟后,我们会得到一些合理的结果。

注意

如果运行此笔记本,则可以进行训练,中断内核,评估并在以后继续训练。 注释掉编码器和解码器已初始化的行,然后再次运行trainIters

  1. hidden_size = 256
  2. encoder1 = EncoderRNN(input_lang.n_words, hidden_size).to(device)
  3. attn_decoder1 = AttnDecoderRNN(hidden_size, output_lang.n_words, dropout_p=0.1).to(device)
  4. trainIters(encoder1, attn_decoder1, 75000, print_every=5000)
  • ../_img/sphx_glr_seq2seq_translation_tutorial_001.png
  • ../_img/sphx_glr_seq2seq_translation_tutorial_002.png

出:

  1. 2m 6s (- 29m 28s) (5000 6%) 2.8538
  2. 4m 7s (- 26m 49s) (10000 13%) 2.3035
  3. 6m 10s (- 24m 40s) (15000 20%) 1.9812
  4. 8m 13s (- 22m 37s) (20000 26%) 1.7083
  5. 10m 15s (- 20m 31s) (25000 33%) 1.5199
  6. 12m 17s (- 18m 26s) (30000 40%) 1.3580
  7. 14m 18s (- 16m 20s) (35000 46%) 1.2002
  8. 16m 18s (- 14m 16s) (40000 53%) 1.0832
  9. 18m 21s (- 12m 14s) (45000 60%) 0.9719
  10. 20m 22s (- 10m 11s) (50000 66%) 0.8879
  11. 22m 23s (- 8m 8s) (55000 73%) 0.8130
  12. 24m 25s (- 6m 6s) (60000 80%) 0.7509
  13. 26m 27s (- 4m 4s) (65000 86%) 0.6524
  14. 28m 27s (- 2m 1s) (70000 93%) 0.6007
  15. 30m 30s (- 0m 0s) (75000 100%) 0.5699
  1. evaluateRandomly(encoder1, attn_decoder1)

出:

  1. > nous sommes desolees .
  2. = we re sorry .
  3. < we re sorry . <EOS>
  4. > tu plaisantes bien sur .
  5. = you re joking of course .
  6. < you re joking of course . <EOS>
  7. > vous etes trop stupide pour vivre .
  8. = you re too stupid to live .
  9. < you re too stupid to live . <EOS>
  10. > c est un scientifique de niveau international .
  11. = he s a world class scientist .
  12. < he is a successful person . <EOS>
  13. > j agis pour mon pere .
  14. = i am acting for my father .
  15. < i m trying to my father . <EOS>
  16. > ils courent maintenant .
  17. = they are running now .
  18. < they are running now . <EOS>
  19. > je suis tres heureux d etre ici .
  20. = i m very happy to be here .
  21. < i m very happy to be here . <EOS>
  22. > vous etes bonne .
  23. = you re good .
  24. < you re good . <EOS>
  25. > il a peur de la mort .
  26. = he is afraid of death .
  27. < he is afraid of death . <EOS>
  28. > je suis determine a devenir un scientifique .
  29. = i am determined to be a scientist .
  30. < i m ready to make a cold . <EOS>

可视化注意力

注意力机制的一个有用特性是其高度可解释的输出。 因为它用于加权输入序列的特定编码器输出,所以我们可以想象一下在每个时间步长上网络最关注的位置。

您可以简单地运行plt.matshow(attentions)以将注意力输出显示为矩阵,其中列为输入步骤,行为输出步骤:

  1. output_words, attentions = evaluate(
  2. encoder1, attn_decoder1, "je suis trop froid .")
  3. plt.matshow(attentions.numpy())

../_img/sphx_glr_seq2seq_translation_tutorial_003.png

为了获得更好的观看体验,我们将做一些额外的工作来添加轴和标签:

  1. def showAttention(input_sentence, output_words, attentions):
  2. # Set up figure with colorbar
  3. fig = plt.figure()
  4. ax = fig.add_subplot(111)
  5. cax = ax.matshow(attentions.numpy(), cmap='bone')
  6. fig.colorbar(cax)
  7. # Set up axes
  8. ax.set_xticklabels([''] + input_sentence.split(' ') +
  9. ['<EOS>'], rotation=90)
  10. ax.set_yticklabels([''] + output_words)
  11. # Show label at every tick
  12. ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
  13. ax.yaxis.set_major_locator(ticker.MultipleLocator(1))
  14. plt.show()
  15. def evaluateAndShowAttention(input_sentence):
  16. output_words, attentions = evaluate(
  17. encoder1, attn_decoder1, input_sentence)
  18. print('input =', input_sentence)
  19. print('output =', ' '.join(output_words))
  20. showAttention(input_sentence, output_words, attentions)
  21. evaluateAndShowAttention("elle a cinq ans de moins que moi .")
  22. evaluateAndShowAttention("elle est trop petit .")
  23. evaluateAndShowAttention("je ne crains pas de mourir .")
  24. evaluateAndShowAttention("c est un jeune directeur plein de talent .")
  • ../_img/sphx_glr_seq2seq_translation_tutorial_004.png
  • ../_img/sphx_glr_seq2seq_translation_tutorial_005.png
  • ../_img/sphx_glr_seq2seq_translation_tutorial_006.png
  • ../_img/sphx_glr_seq2seq_translation_tutorial_007.png

出:

  1. input = elle a cinq ans de moins que moi .
  2. output = she s five years younger than i am . <EOS>
  3. input = elle est trop petit .
  4. output = she s too loud . <EOS>
  5. input = je ne crains pas de mourir .
  6. output = i m not scared to die . <EOS>
  7. input = c est un jeune directeur plein de talent .
  8. output = he s a talented young writer . <EOS>

练习

  • 尝试使用其他数据集
    • 另一对语言
    • 人机 → 机器(例如 IOT 命令)
    • 聊天 → 回复
    • 问题 → 答案
  • 用预训练的单词嵌入(例如 word2vec 或 GloVe)替换嵌入
  • 尝试使用更多的层,更多的隐藏单元和更多的句子。 比较训练时间和结果。
  • 如果您使用翻译对,其中成对具有两个相同的词组(I am test \t I am test),则可以将其用作自编码器。 尝试这个:
    • 训练为自编码器
    • 仅保存编码器网络
    • 从那里训练新的解码器进行翻译

脚本的总运行时间:(30 分钟 37.929 秒)

下载 Python 源码:seq2seq_translation_tutorial.py

下载 Jupyter 笔记本:seq2seq_translation_tutorial.ipynb

由 Sphinx 画廊生成的画廊