Sequence to Sequence Learning with Neural Networks–使用Seq2Seq完成翻译

  • A+
所属分类:深度学习
摘要这一篇文章会主要介绍Seq2Seq模型的原理,与一个具体的例子实现, 翻译法语到英语。这一篇内容有点长,可能看的时间会有点长。

文章简介

这一篇主要会介绍Seq2Seq模型,并使用其实现一个简单的翻译的功能。

主要参考自Pytorch官方的一个例子,TRANSLATION WITH A SEQUENCE TO SEQUENCE NETWORK AND ATTENTION。这个我会拆成两篇文章,一篇使用传统的Seq2Seq,另一篇加入Attention。

主要参考的论文 : Sequence to Sequence Learning with Neural Networks

Seq2Seq介绍

在之前的几篇文章里,RNN for Image Classification(RNN图片分类–MNIST数据集)RNN完成姓名分类,我们介绍的都是多对一的情况,但在面对翻译这个问题的时候,传统的RNN结构可能就不在适用了。

However, it is not clear how to apply an RNN to problems whose input and the output sequences have different lengths with complicated and non-monotonic relationships.(input和output没有对应关系, 且长度不一致时, 传统的RNN结构无法处理)

Seq2Seq介绍

于是,就提出了关于Seq2Seq模型,这个模型的主要思路如下:

  • Encoder : Uses a multilayered Long Short-Term Memory (LSTM) to map the input sequence to a vector of a fixed dimensionality.
  • Decoder : And then another deep LSTM to decode the target sequence from the vector.

整体的结构如下图所示(其实感觉论文里这张图片不是很好, 后面再贴几张我觉得比较好的解释Seq2Seq的图片)

Sequence to Sequence Learning with Neural Networks--使用Seq2Seq完成翻译

这里再详细说明一下。对于Seq2Seq来说,整个系统分为两个部分。

  • Encoder部分将输入的一句话转为一个vector, Decoder负责将这个vector展开, 变为翻译后的句子.
  • Decoder会将Encoder最后的hidden state作为自己的初始的hidden state.
  • Decoder初始输入为一个标志符, 之后每次将自己这次的输出作为下一次的输入.

下面的这几张图片来自 : Encoder-decoderモデルとTeacher Forcing,Scheduled Sampling,Professor Forcing

Sequence to Sequence Learning with Neural Networks--使用Seq2Seq完成翻译

对于Decoder的输入,除了上面的这种方式以外,还有Teacher Forcing这种模式.

首先是普通的形式,直接将decoder每一次的输出最为下一次的输入

Sequence to Sequence Learning with Neural Networks--使用Seq2Seq完成翻译

接着是Teacher Forcing模式,他会每次将正确答案作为输入

Sequence to Sequence Learning with Neural Networks--使用Seq2Seq完成翻译

在实际使用中,会有两种变化的形式。第一种是每次在训练的时候进行掷骰子, 有一点概率使用普通的模式,一定的概率使用Teacher Forcing模式. (我们这次实现就会使用这样的方式)

Seq2Seq中Decoder的扩展

Scheduled Sampling

Sequence to Sequence Learning with Neural Networks--使用Seq2Seq完成翻译

Professor Forcing

还有一种方式会做模型的融合,这个具体可以查看这篇论文, Professor Forcing: A New Algorithm for Training Recurrent Networks

Sequence to Sequence Learning with Neural Networks--使用Seq2Seq完成翻译

论文的一些发现

这一部分是在论文,Sequence to Sequence Learning with Neural Networks,中作者最后的一些总结。

  • First, we used two different LSTMs: one for the input sequence and another for the output sequence, because doing so increases the number model parameters at negligible computational cost and makes it natural to train the LSTM on multiple language pairs simultaneously.
  • Second, we found that deep LSTMs significantly outperformed shallow LSTMs, so we chose an LSTM with four layers. (深的LSTM效果会更好)
  • Third, we found it extremely valuable to reverse the order of the words of the input sentence. So for example, instead of mapping the sentence a, b, c to the sentence α, β, γ, the LSTM is asked to map c, b, a to α, β, γ, where α, β, γ is the translation of a, b, c.(将input的sequence逆序输入)—这个后来被证明只对部分语言有效, 即翻译前后单词的语义有对应的关系.

关于最后一部分, 作者发现将训练的句子按逆序的顺序输入网络会有更好的效果。

关于Vector的可视化结果

One of the attractive features of our model is its ability to turn a sequence of words into a vector of fixed dimensionality. (作者将encoder之后的vector降维后进行可视化,效果图如下所示)

The two-dimensional projections are obtainedusing PCA. (原文是使用了PCA的降维方法)

Sequence to Sequence Learning with Neural Networks--使用Seq2Seq完成翻译

于是他得到了下面的一些结论:

The figure clearly shows that the representations are sensitive to the order of words while being fairly insensitive to the replacement of an active voice with a passive voice. (对语句的顺序是敏感的, 如左侧的图, 交换Mary和John之后聚类的结果是不同的; 但是对主动语态与被动语态是不敏感的, 如右图, 只与表达的意思是有关的)

案例实现

下面会简单重复一下Seq2Seq的实验,主要代码均参考自TRANSLATION WITH A SEQUENCE TO SEQUENCE NETWORK AND ATTENTION,我只简单修改了一下train部分的代码。

这一部分最终想要实现的目标如下:

  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 .

主要的思想,再用一句话概括就是:

An encoder network condenses an input sequence into a vector, and a decoder network unfolds that vector into a new sequence. 

导入需要的库

  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")

数据预处理

接下来定义一些函数,用作数据的预处理。关于数据的下载,请点击下面这个链接:

Download the data from here and extract it to the current directory.

  1. SOS_token = 0
  2. EOS_token = 1
  3. class Lang:
  4.     """word → index (word2index) and index → word (index2word) dictionaries
  5.        A count of each word word2count to use to later replace rare words.
  6.     """
  7.     def __init__(self, name):
  8.         self.name = name
  9.         self.word2index = {}
  10.         self.word2count = {}
  11.         self.index2word = {0: "SOS", 1: "EOS"}
  12.         self.n_words = 2  # Count SOS and EOS
  13.     def addSentence(self, sentence):
  14.         for word in sentence.split(' '):
  15.             self.addWord(word)
  16.     def addWord(self, word):
  17.         if word not in self.word2index:
  18.             self.word2index[word] = self.n_words
  19.             self.word2count[word] = 1
  20.             self.index2word[self.n_words] = word
  21.             self.n_words += 1
  22.         else:
  23.             self.word2count[word] += 1

其中SOS_token表示句子开头, EOS_token表示句子的结尾.

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

上面的主要作用是, 转换为ASCII, 大写变小写, 留下重要的标点, 去掉大部分的标点. 看下面的例子.

  1. # 转换为ASCII, 大写变小写, 留下重要的标点, 去掉大部分的标点
  2. normalizeString('I am a Boy!~$%^&')
  3. """
  4. 'i am a boy ! '
  5. """

接着定义函数,来逐行读取文件,并对每一行做预处理。因为我们每一行文件都包含原语言和翻译后的语言,所以返回的pair. 源文件数据格式如下所示:

  1. I am cold.    J'ai froid.

下面是具体读取文件的函数.

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

为了加快训练的速度, 我们把句子长度最大设置为10, 同时我们过滤句子后使得其开头变为如i am, he is等词汇. (这一部分只是为了加快训练的速度)

  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. # 会去掉单词个数超过10个的句子
  2. # 会去掉不是以特定开头的句子
  3. filterPairs([['i am a girl','i am a boy'],
  4.              ['how are you','how are you'],
  5.             ['i am a girl i am a girl i am a girl','i am a girl i am a girl']])
  6. """
  7. [['i am a girl', 'i am a boy']]
  8. """

接下来把上面整个数据处理的流程写成一个函数。

整个数据处理的流程如下所示:

  • Read text file and split into lines, split lines into pairs
  • Normalize text, filter by length and content
  • Make word lists from sentences in pairs
  1. def prepareData(lang1, lang2, reverse=False):
  2.     """开始读取语言的文件
  3.     """
  4.     # 读取文件, 返回的是句子对
  5.     input_lang, output_lang, pairs = readLangs(lang1, lang2, reverse)
  6.     print("Read %s sentence pairs" % len(pairs))
  7.     # 过滤掉句子对中较长的句子, 和
  8.     pairs = filterPairs(pairs)
  9.     print("Trimmed to %s sentence pairs" % len(pairs))
  10.     print("Counting words...")
  11.     for pair in pairs:
  12.         input_lang.addSentence(pair[0])
  13.         output_lang.addSentence(pair[1])
  14.     print("Counted words:")
  15.     print(input_lang.name, input_lang.n_words)
  16.     print(output_lang.name, output_lang.n_words)
  17.     return input_lang, output_lang, pairs

最后,开始读取数据即可。

  1. # 开始读取数据
  2. input_lang, output_lang, pairs = prepareData('eng', 'fra', True)

我们看一下最终的效果, paris中保存了语言对.

  1. print(random.choice(pairs))
  2. """
  3. ['il a accepte de faire le travail .', 'he s agreed to do the job .']
  4. """

input_lang和output_lang中分别保存了原始语言和翻译后的语言的单词, 单词对应的index, 和每个单词出现的频数.

  1. # 不同单词出现的次数
  2. output_lang.word2index
  3. """
  4. ...
  5. 'stuck': 70,
  6.  'timid': 71,
  7.  'tired': 72,
  8.  'tough': 73,
  9.  'yours': 74,
  10.  'she': 75,
  11. ...
  12. """

Seq2Seq模型

接下来就是来创建Model了。在这里,我想再次强调一下Seq2Seq的思想。对比传统的单层的RNN来说, 可以不需要输入和输出是相同的长度的.

下面是完整的思想, 这里的原文还是很不错的.

Unlike sequence prediction with a single RNN, where every input corresponds to an output, the seq2seq model frees us from sequence length and order, which makes it ideal for translation between two languages.

Consider the sentence "Je ne suis pas le chat noir" → "I am not the black cat". Most of the words in the input sentence have a direct translation in the output sentence, but are in slightly different orders, e.g. "chat noir" and "black cat". Because of the "ne/pas" construction there is also one more word in the input sentence. It would be difficult to produce a correct translation directly from the sequence of input words.

With a seq2seq model the encoder creates a single vector which, in the ideal case, encodes the "meaning" of the input sequence into a single vector — a single point in some N dimensional space of sentences.

下面分开,具体的讲一下Encoder和Decoder的架构。

Encoder

The encoder of a seq2seq network is a RNN that outputs some value for every word from the input sentence. For every input word the encoder outputs a vector and a hidden state, and uses the hidden state for the next input word.

具体实现代码如下,这个部分还是与之前写RNN的代码是相同的。

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

我们做一下Encoder的测试, 确保这部分是没问题的。

  1. test_data = tensorsFromPair(random.choice(pairs))
  2. test_data[0]
  3. """
  4. tensor([[ 123],
  5.         [ 201],
  6.         [ 342],
  7.         [1515],
  8.         [   5],
  9.         [   1]], device='cuda:0')
  10. """
  11. # encoder测试
  12. encoder1 = EncoderRNN(input_lang.n_words, 256).to(device)
  13. output, hidden = encoder1(test_data[0].unsqueeze(1))
  14. output.shape
  15. """
  16. torch.Size([6, 1, 256])
  17. """

Decoder

The decoder is another RNN that takes the encoder output vector(s) and outputs a sequence of words to create the translation.

In the simplest seq2seq decoder we use only last output of the encoder. This last output is sometimes called the context vector as it encodes context from the entire sequence. This context vector is used as the initial hidden state of the decoder.

At every step of decoding, the decoder is given an input token and hidden state. The initial input token is the start-of-string token, and the first hidden state is the context vector (the encoder’s last hidden state).

关于decoder的部分需要注意的是,hidden_state不是初始化的了,而是要传入encoder最后的hidden_state, 我一开始写的时候没注意到这点,最后模型的loss一直无法下降,所以这里需要注意一下。整体的代码如下。

  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.output_size = output_size
  6.         self.embedding = nn.Embedding(self.output_size, self.hidden_size)
  7.         self.gru = nn.GRU(self.hidden_size, self.hidden_size)
  8.         self.out = nn.Linear(self.hidden_size, self.output_size)
  9.         self.softmax = nn.LogSoftmax(dim=1)
  10.     def forward(self, x, hidden_state):
  11.         # 这里的hidden_state需要传入, 传入的是encoder最后输出的那个向量
  12.         self.hidden = hidden_state
  13.         # 只能预测一个, 输入下一个
  14.         output = self.embedding(x).view(1,1,-1)
  15.         output = F.relu(output)
  16.         output, hidden = self.gru(output, self.hidden)
  17.         output = self.out(output[0])
  18.         output = self.softmax(output)
  19.         return output, hidden

我们还是测试一下这部分是否存在问题。

  1. # Decoder的输入
  2. test_data = torch.tensor([[SOS_token]]).to(device)
  3. # output_lang.n_words = 2803
  4. # output的单词数量有2803个
  5. # 这里的hidden是上面encoder输出的hidden_state
  6. decoder1 = DecoderRNN(256, output_lang.n_words).to(device)
  7. output, hidden = decoder1(test_data, hidden)
  8. # 这里有2803个单词, 每个单词的概率
  9. output.shape
  10. """
  11. torch.Size([1, 2803])
  12. """

 开始训练!!!

训练数据的准备

To train, for each pair we will need an input tensor (indexes of the words in the input sentence) and target tensor (indexes of the words in the target sentence). While creating these vectors we will append the EOS token to both sequences.

这一部分主要完成的作用是将每句话都转为Index, 并且在句末加上终止符.

  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)

我们看一下一个例子的效果.

  1. # 将一句话中的每个字母转为Index, 并在结尾加上终止符
  2. tensorFromSentence(output_lang, 'i am a boy')
  3. """
  4. tensor([[  2],
  5.         [ 16],
  6.         [ 42],
  7.         [472],
  8.         [  1]], device='cuda:0')
  9. """

 一些辅助函数

下面会定义一些辅助函数,首先是用来打印训练的耗时

  1. # helper function
  2. import time
  3. import math
  4. def asMinutes(s):
  5.     """将秒转换为分钟
  6.     """
  7.     m = math.floor(s / 60)
  8.     s -= m * 60
  9.     return '%dm %ds' % (m, s)
  10. def timeSince(since, percent):
  11.     """打印已经花费的时间和预计花费的时间
  12.        预计花费的时间, 用 完成百分比的时间/现在完成的百分比 来预测
  13.     """
  14.     now = time.time()
  15.     s = now - since
  16.     es = s / (percent)
  17.     rs = es - s
  18.     return '%s (- %s)' % (asMinutes(s), asMinutes(rs))

接着是最后用来绘制loss的下降曲线的函数。

  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(figsize=(14,7))
  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)

定义训练函数

接下来就是定义训练使用的函数了,会有两个函数。首先是第一个train, 这个每次训练一个pair,如下所示:

  1. teacher_forcing_ratio = 0.5 # 50%的概率使用teacher_forcing的模式
  2. def train(input_tensor, target_tensor, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion, max_length=MAX_LENGTH):
  3.     encoder_optimizer.zero_grad()
  4.     decoder_optimizer.zero_grad()
  5.     input_length = input_tensor.size(0)
  6.     target_length = target_tensor.size(0)
  7.     # encoder_outputs = torch.zeros(max_length, encoder.hidden_size).to(device)
  8.     # Encoder
  9.     encoder_output, encoder_hidden = encoder1(input_tensor.unsqueeze(1))
  10.     # Decoder
  11.     loss = 0
  12.     decoder_hidden = encoder_hidden
  13.     decoder_input = torch.tensor([[SOS_token]]).to(device)
  14.     # 判断是使用哪一种模式
  15.     use_teacher_forcing = True if random.random() < teacher_forcing_ratio else False
  16.     if use_teacher_forcing:
  17.         # Teacher forcing: Feed the target as the next input
  18.         for di in range(target_length):
  19.             decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden)
  20.             loss = loss + criterion(decoder_output, target_tensor[di])
  21.             decoder_input = target_tensor[di] # Teacher Forcing
  22.     else:
  23.         # Without teacher forcing: use its own predictions as the next input
  24.         for di in range(target_length):
  25.             decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden)
  26.             topv, topi = decoder_output.topk(1)
  27.             decoder_input = topi.squeeze().detach() # detach from history as input
  28.             loss = loss + criterion(decoder_output, target_tensor[di])
  29.             if decoder_input.item() == EOS_token:
  30.                 break
  31.     # 反向传播, 进行优化
  32.     loss.backward()
  33.     encoder_optimizer.step()
  34.     decoder_optimizer.step()
  35.     return loss.item() / target_length

下面是一个整体的训练流程。

  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.     # 初始化优化器
  7.     encoder_optimizer = optim.SGD(encoder.parameters(), lr=learning_rate)
  8.     decoder_optimizer = optim.SGD(decoder.parameters(), lr=learning_rate)
  9.     # 初始化样本
  10.     training_pairs = [tensorsFromPair(random.choice(pairs)) for i in range(n_iters)]
  11.     criterion = nn.NLLLoss()
  12.     for iter in range(1, n_iters+1):
  13.         training_pair = training_pairs[iter-1]
  14.         input_tensor = training_pair[0]
  15.         target_tensor = training_pair[1]
  16.         loss = train(input_tensor, target_tensor, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion)
  17.         print_loss_total = print_loss_total + loss
  18.         plot_loss_total = 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.                                          iteriter / 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)

正式开始训练

接下来就是激动人心的时刻了,开始正式的训练。

  1. hidden_size = 256
  2. encoder1 = EncoderRNN(input_lang.n_words, hidden_size).to(device)
  3. decoder1 = DecoderRNN(hidden_size, output_lang.n_words).to(device)
  4. trainIters(encoder1, decoder1, n_iters=100000, print_every=500, plot_every=10)

最终的结果如下所示,Loss总体来说是一直在下降的。

Sequence to Sequence Learning with Neural Networks--使用Seq2Seq完成翻译

模型的评测

Evaluation is mostly the same as training, but there are no targets so we simply feed the decoder's predictions back to itself for each step. Every time it predicts a word we add it to the output string, and if it predicts the EOS token we stop there. We also store the decoder's attention outputs for display later.

最后就是模型的评测的阶段了,我们使用他来实际进行一下句子的翻译。

  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
  6.         encoder_output, encoder_hidden = encoder1(input_tensor.unsqueeze(1))
  7.         # Decoder
  8.         decoder_input = torch.tensor([[SOS_token]], device=device)  # SOS
  9.         decoder_hidden = encoder_hidden
  10.         decoded_words = []
  11.         for di in range(max_length):
  12.             decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden  )
  13.             topv, topi = decoder_output.data.topk(1)
  14.             if topi.item() == EOS_token:
  15.                 """遇到终止符就停止
  16.                 """
  17.                 decoded_words.append('<EOS>')
  18.                 break
  19.             else:
  20.                 """把decode的word加入数组中
  21.                 """
  22.                 decoded_words.append(output_lang.index2word[topi.item()])
  23.             # 下一个的输入是上一个的输出
  24.             decoder_input = topi.squeeze().detach()
  25.         return decoded_words

下面是随机挑选一些句子来进行翻译:

  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 = evaluate(encoder, decoder, pair[0])
  7.         output_sentence = ' '.join(output_words)
  8.         print('<', output_sentence)
  9.         print('')

最后我们看一下最终的效果:

  1. evaluateRandomly(encoder1, decoder1, n=70)
  2. """
  3. > je suis egalement heureuse .
  4. = i m happy too .
  5. < i m happy too . <EOS>
  6. > j attends de toi un travail serieux .
  7. = i am expecting some serious work from you .
  8. < i am expecting expecting serious serious serious . <EOS>
  9. > tu es chanceuse .
  10. = you re fortunate .
  11. < you re fortunate . <EOS>
  12. """

整体来说还是可以的,但是我们仔细看会发现一些问题,如会出现一个单词反复出现很多次,如第二句中的serious. 这一部分的改进就不在这里细说了。

总结

上面就是所有关于简单的Sequence to Sequence(Seq2Seq)的介绍了, 介绍了包括原理和一个简单的例子实现。下面有Notebook的代码链接,大家可以下载之后自己尝试一下这个模型。

notebook的代码链接如下Seq2Seq模型代码

  • 微信公众号
  • 关注微信公众号
  • weinxin
  • QQ群
  • 我们的QQ群号
  • weinxin
王 茂南

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: