pytorch做seq2seq注意力模型的翻译

以下是对pytorch 1.0版本 的seq2seq+注意力模型做法语--英语翻译的理解(这个代码在pytorch0.4上也可以正常跑):

  1 # -*- coding: utf-8 -*-
  2 """
  3 Translation with a Sequence to Sequence Network and Attention
  4 *************************************************************
  5 **Author**: `Sean Robertson <https://github.com/spro/practical-pytorch>`_
  6 
  7 In this project we will be teaching a neural network to translate from
  8 French to English.
  9 
 10 ::
 11 
 12     [KEY: > input, = target, < output]
 13 
 14     > il est en train de peindre un tableau .
 15     = he is painting a picture .
 16     < he is painting a picture .
 17 
 18     > pourquoi ne pas essayer ce vin delicieux ?
 19     = why not try that delicious wine ?
 20     < why not try that delicious wine ?
 21 
 22     > elle n est pas poete mais romanciere .
 23     = she is not a poet but a novelist .
 24     < she not not a poet but a novelist .
 25 
 26     > vous etes trop maigre .
 27     = you re too skinny .
 28     < you re all alone .
 29 
 30 ... to varying degrees of success.
 31 
 32 This is made possible by the simple but powerful idea of the `sequence
 33 to sequence network <http://arxiv.org/abs/1409.3215>`__, in which two
 34 recurrent neural networks work together to transform one sequence to
 35 another. An encoder network condenses an input sequence into a vector,
 36 and a decoder network unfolds that vector into a new sequence.
 37 
 38 .. figure:: /_static/img/seq-seq-images/seq2seq.png
 39    :alt:
 40 
 41 To improve upon this model we'll use an `attention
 42 mechanism <https://arxiv.org/abs/1409.0473>`__, which lets the decoder
 43 learn to focus over a specific range of the input sequence.
 44 
 45 **Recommended Reading:**
 46 
 47 I assume you have at least installed PyTorch, know Python, and
 48 understand Tensors:
 49 
 50 -  https://pytorch.org/ For installation instructions
 51 -  :doc:`/beginner/deep_learning_60min_blitz` to get started with PyTorch in general
 52 -  :doc:`/beginner/pytorch_with_examples` for a wide and deep overview
 53 -  :doc:`/beginner/former_torchies_tutorial` if you are former Lua Torch user
 54 
 55 
 56 It would also be useful to know about Sequence to Sequence networks and
 57 how they work:
 58 
 59 -  `Learning Phrase Representations using RNN Encoder-Decoder for
 60    Statistical Machine Translation <http://arxiv.org/abs/1406.1078>`__
 61 -  `Sequence to Sequence Learning with Neural
 62    Networks <http://arxiv.org/abs/1409.3215>`__
 63 -  `Neural Machine Translation by Jointly Learning to Align and
 64    Translate <https://arxiv.org/abs/1409.0473>`__
 65 -  `A Neural Conversational Model <http://arxiv.org/abs/1506.05869>`__
 66 
 67 You will also find the previous tutorials on
 68 :doc:`/intermediate/char_rnn_classification_tutorial`
 69 and :doc:`/intermediate/char_rnn_generation_tutorial`
 70 helpful as those concepts are very similar to the Encoder and Decoder
 71 models, respectively.
 72 
 73 And for more, read the papers that introduced these topics:
 74 
 75 -  `Learning Phrase Representations using RNN Encoder-Decoder for
 76    Statistical Machine Translation <http://arxiv.org/abs/1406.1078>`__
 77 -  `Sequence to Sequence Learning with Neural
 78    Networks <http://arxiv.org/abs/1409.3215>`__
 79 -  `Neural Machine Translation by Jointly Learning to Align and
 80    Translate <https://arxiv.org/abs/1409.0473>`__
 81 -  `A Neural Conversational Model <http://arxiv.org/abs/1506.05869>`__
 82 
 83 
 84 **Requirements**
 85 """
 86 from __future__ import unicode_literals, print_function, division
 87 from io import open
 88 import unicodedata
 89 import string
 90 import re
 91 import random
 92 
 93 import torch
 94 import torch.nn as nn
 95 from torch import optim
 96 import torch.nn.functional as F
 97 
 98 device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 99 
100 ######################################################################
101 # Loading data files
102 # ==================
103 #
104 # The data for this project is a set of many thousands of English to
105 # French translation pairs.
106 #
107 # `This question on Open Data Stack
108 # Exchange <http://opendata.stackexchange.com/questions/3888/dataset-of-sentences-translated-into-many-languages>`__
109 # pointed me to the open translation site http://tatoeba.org/ which has
110 # downloads available at http://tatoeba.org/eng/downloads - and better
111 # yet, someone did the extra work of splitting language pairs into
112 # individual text files here: http://www.manythings.org/anki/
113 #
114 # The English to French pairs are too big to include in the repo, so
115 # download to ``data/eng-fra.txt`` before continuing. The file is a tab
116 # separated list of translation pairs:
117 #
118 # ::
119 #
120 #     I am cold.    J'ai froid.
121 #
122 # .. Note::
123 #    Download the data from
124 #    `here <https://download.pytorch.org/tutorial/data.zip>`_
125 #    and extract it to the current directory.
126 
127 ######################################################################
128 # Similar to the character encoding used in the character-level RNN
129 # tutorials, we will be representing each word in a language as a one-hot
130 # vector, or giant vector of zeros except for a single one (at the index
131 # of the word). Compared to the dozens of characters that might exist in a
132 # language, there are many many more words, so the encoding vector is much
133 # larger. We will however cheat a bit and trim the data to only use a few
134 # thousand words per language.
135 #
136 # .. figure:: /_static/img/seq-seq-images/word-encoding.png
137 #    :alt:
138 #
139 #
140 
141 
142 ######################################################################
143 # We'll need a unique index per word to use as the inputs and targets of
144 # the networks later. To keep track of all this we will use a helper class
145 # called ``Lang`` which has word → index (``word2index``) and index → word
146 # (``index2word``) dictionaries, as well as a count of each word
147 # ``word2count`` to use to later replace rare words.
148 #
149 
150 SOS_token = 0
151 EOS_token = 1
152 
153 
154 # 每个单词需要对应唯一的索引作为稍后的网络输入和目标.为了追踪这些索引
155 # 则使用一个帮助类 Lang ,类中有 词 → 索引 (word2index) 和 索引 → 词
156 # (index2word) 的字典, 以及每个词word2count 用来替换稀疏词汇.
157 
158 
159 # 此处创建的Lang 对象来表示源/目标语言,它包含三部分:word2index、
160 # index2word 和word2count,分别表示单词到id、id 到单词和单词的词频。
161 # word2count的作用是用于过滤一些低频词(把它变成unknown)
162 
163 class Lang:
164     def __init__(self, name):
165         self.name = name
166         self.word2index = {}
167         self.word2count = {}
168         self.index2word = {0: "SOS", 1: "EOS"}
169         self.n_words = 2  # Count SOS and EOS
170 
171     def addSentence(self, sentence):
172         for word in sentence.split(' '):
173             self.addWord(word)  # 用于添加单词
174 
175     def addWord(self, word):
176         if word not in self.word2index:  # 是不是新的词
177             # 如果不在word2index里,则需要新的定义字典
178             self.word2index[word] = self.n_words
179             self.word2count[word] = 1
180             self.index2word[self.n_words] = word
181             self.n_words += 1  # 相当于每次index+1
182         else:
183             self.word2count[word] += 1  # 计算每次词的个数
184 
185 
186 ######################################################################
187 # The files are all in Unicode, to simplify we will turn Unicode
188 # characters to ASCII, make everything lowercase, and trim most
189 # punctuation.
190 #
191 
192 # Turn a Unicode string to plain ASCII, thanks to
193 # http://stackoverflow.com/a/518232/2809427
194 
195 # 此处是为了将Unicode字符串转换为纯ASCII
196 # 原文件是Unicode编码
197 def unicodeToAscii(s):
198     return ''.join(
199         c for c in unicodedata.normalize('NFD', s)
200         if unicodedata.category(c) != 'Mn'
201     )
202 
203 
204 # Lowercase, trim, and remove non-letter characters
205 
206 # 小写,修剪和删除非字母字符
207 def normalizeString(s):
208     s = unicodeToAscii(s.lower().strip())
209     s = re.sub(r"([.!?])", r" \1", s)
210     s = re.sub(r"[^a-zA-Z.!?]+", r" ", s)
211     return s
212 
213 
214 ######################################################################
215 # To read the data file we will split the file into lines, and then split
216 # lines into pairs. The files are all English → Other Language, so if we
217 # want to translate from Other Language → English I added the ``reverse``
218 # flag to reverse the pairs.
219 #
220 
221 
222 # 要读取数据文件,我们将把文件分成行,然后将行成对分开. 这些文件
223 # 都是英文→其他语言,所以如果我们想从其他语言翻译→英文,我们添加了
224 # 翻转标志 reverse来翻转词语对.
225 def readLangs(lang1, lang2, reverse=False):
226     print("Reading lines...")
227 
228     # Read the file and split into lines
229     # 读取文件并按行分开
230     lines = open('data/%s-%s.txt' % (lang1, lang2), encoding='utf-8'). \
231         read().strip().split('\n')
232 
233     # Split every line into pairs and normalize
234     # 将每一行分成两列并进行标准化
235     pairs = [[normalizeString(s) for s in l.split('\t')] for l in lines]
236 
237     # Reverse pairs, make Lang instances
238     # 翻转对,Lang实例化
239     if reverse:
240         pairs = [list(reversed(p)) for p in pairs]
241         input_lang = Lang(lang2)
242         output_lang = Lang(lang1)
243     else:
244         input_lang = Lang(lang1)
245         output_lang = Lang(lang2)
246 
247     return input_lang, output_lang, pairs
248 
249 
250 ######################################################################
251 # Since there are a *lot* of example sentences and we want to train
252 # something quickly, we'll trim the data set to only relatively short and
253 # simple sentences. Here the maximum length is 10 words (that includes
254 # ending punctuation) and we're filtering to sentences that translate to
255 # the form "I am" or "He is" etc. (accounting for apostrophes replaced
256 # earlier).
257 #
258 
259 # 由于例句较多,为了方便快速训练,则会将数据集裁剪为相对简短的句子.
260 # 这里的单词的最大长度是10词(包括结束标点符号),
261 # 保留”I am” 和”He is” 开头的数据
262 
263 MAX_LENGTH = 10
264 
265 eng_prefixes = (
266     "i am ", "i m ",
267     "he is", "he s ",
268     "she is", "she s",
269     "you are", "you re ",
270     "we are", "we re ",
271     "they are", "they re "
272 )
273 
274 
275 def filterPair(p):
276     return len(p[0].split(' ')) < MAX_LENGTH and \
277            len(p[1].split(' ')) < MAX_LENGTH and \
278            p[1].startswith(eng_prefixes)
279     # 是否满足长度
280 
281 
282 def filterPairs(pairs):
283     return [pair for pair in pairs if filterPair(pair)]
284 
285 
286 ######################################################################
287 # The full process for preparing the data is:
288 #
289 # -  Read text file and split into lines, split lines into pairs
290 # -  Normalize text, filter by length and content
291 # -  Make word lists from sentences in pairs
292 #
293 
294 def prepareData(lang1, lang2, reverse=False):
295     input_lang, output_lang, pairs = readLangs(lang1, lang2, reverse)
296     # 读入数据lang1,lang2,并翻转
297     print("Read %s sentence pairs" % len(pairs))
298     # 一共读入了多少对
299     pairs = filterPairs(pairs)
300     # 符合条件的配对有多少对
301     print("Trimmed to %s sentence pairs" % len(pairs))
302     print("Counting words...")
303     for pair in pairs:
304         input_lang.addSentence(pair[0])
305         output_lang.addSentence(pair[1])
306     print("Counted words:")
307     print(input_lang.name, input_lang.n_words)
308     print(output_lang.name, output_lang.n_words)
309     return input_lang, output_lang, pairs
310 
311 
312 # 对数据进行预处理
313 input_lang, output_lang, pairs = prepareData('eng', 'fra', True)
314 print(random.choice(pairs))  # 随机展示一对
315 
316 
317 ######################################################################
318 # The Seq2Seq Model
319 # =================
320 #
321 # A Recurrent Neural Network, or RNN, is a network that operates on a
322 # sequence and uses its own output as input for subsequent steps.
323 #
324 # A `Sequence to Sequence network <http://arxiv.org/abs/1409.3215>`__, or
325 # seq2seq network, or `Encoder Decoder
326 # network <https://arxiv.org/pdf/1406.1078v3.pdf>`__, is a model
327 # consisting of two RNNs called the encoder and decoder. The encoder reads
328 # an input sequence and outputs a single vector, and the decoder reads
329 # that vector to produce an output sequence.
330 #
331 # .. figure:: /_static/img/seq-seq-images/seq2seq.png
332 #    :alt:
333 #
334 # Unlike sequence prediction with a single RNN, where every input
335 # corresponds to an output, the seq2seq model frees us from sequence
336 # length and order, which makes it ideal for translation between two
337 # languages.
338 #
339 # Consider the sentence "Je ne suis pas le chat noir" → "I am not the
340 # black cat". Most of the words in the input sentence have a direct
341 # translation in the output sentence, but are in slightly different
342 # orders, e.g. "chat noir" and "black cat". Because of the "ne/pas"
343 # construction there is also one more word in the input sentence. It would
344 # be difficult to produce a correct translation directly from the sequence
345 # of input words.
346 #
347 # With a seq2seq model the encoder creates a single vector which, in the
348 # ideal case, encodes the "meaning" of the input sequence into a single
349 # vector — a single point in some N dimensional space of sentences.
350 #
351 
352 
353 ######################################################################
354 # The Encoder
355 # -----------
356 #
357 # The encoder of a seq2seq network is a RNN that outputs some value for
358 # every word from the input sentence. For every input word the encoder
359 # outputs a vector and a hidden state, and uses the hidden state for the
360 # next input word.
361 #
362 # .. figure:: /_static/img/seq-seq-images/encoder-network.png
363 #    :alt:
364 #
365 #
366 
367 class EncoderRNN(nn.Module):
368     def __init__(self, input_size, hidden_size):
369         super(EncoderRNN, self).__init__()
370         self.hidden_size = hidden_size
371         # 定义隐藏层
372         self.embedding = nn.Embedding(input_size, hidden_size)
373         # word embedding的定义可以这么理解,例如nn.Embedding(2, 4)
374         # 2表示有2个词,4表示4维度,其实也就是一个2x4的矩阵,
375         # 如果有100个词,每个词10维,就可以写为nn.Embedding(100, 10)
376         # 注意这里的词向量的建立只是初始的词向量,并没有经过任何修改优化
377         # 需要建立神经网络通过learning的办法修改word embedding里面的参数
378         # 使得word embedding每一个词向量能够表示每一个不同的词。
379         self.gru = nn.GRU(hidden_size, hidden_size)  # 用到了上面提到的GRU模型
380 
381     def forward(self, input, hidden):
382         embedded = self.embedding(input).view(1, 1, -1)  # -1是指自适应,view相当于reshape函数
383         output = embedded
384         output, hidden = self.gru(output, hidden)
385         return output, hidden
386 
387     def initHidden(self):  # 初始化
388         return torch.zeros(1, 1, self.hidden_size, device=device)
389 
390 
391 ######################################################################
392 # The Decoder
393 # -----------
394 #
395 # The decoder is another RNN that takes the encoder output vector(s) and
396 # outputs a sequence of words to create the translation.
397 #
398 
399 
400 ######################################################################
401 # Simple Decoder
402 # ^^^^^^^^^^^^^^
403 #
404 # In the simplest seq2seq decoder we use only last output of the encoder.
405 # This last output is sometimes called the *context vector* as it encodes
406 # context from the entire sequence. This context vector is used as the
407 # initial hidden state of the decoder.
408 #
409 # At every step of decoding, the decoder is given an input token and
410 # hidden state. The initial input token is the start-of-string ``<SOS>``
411 # token, and the first hidden state is the context vector (the encoder's
412 # last hidden state).
413 #
414 # .. figure:: /_static/img/seq-seq-images/decoder-network.png
415 #    :alt:
416 #
417 #
418 
419 class DecoderRNN(nn.Module):
420     # DecoderRNN与encoderRNN结构类似,结合图片即可搞清逻辑
421     def __init__(self, hidden_size, output_size):
422         super(DecoderRNN, self).__init__()
423         self.hidden_size = hidden_size
424 
425         self.embedding = nn.Embedding(output_size, hidden_size)
426         self.gru = nn.GRU(hidden_size, hidden_size)
427         self.out = nn.Linear(hidden_size, output_size)
428         self.softmax = nn.LogSoftmax(dim=1)
429 
430     def forward(self, input, hidden):
431         output = self.embedding(input).view(1, 1, -1)  # -1是指自适应,view相当于reshape函数
432         output = F.relu(output)
433         output, hidden = self.gru(output, hidden)  # 此处使用gru神经网络
434         # 对上述结果使用softmax,就是图片中左边倒数第二个
435         output = self.softmax(self.out(output[0]))
436         return output, hidden
437 
438     def initHidden(self):
439         return torch.zeros(1, 1, self.hidden_size, device=device)
440 
441 
442 ######################################################################
443 # I encourage you to train and observe the results of this model, but to
444 # save space we'll be going straight for the gold and introducing the
445 # Attention Mechanism.
446 #
447 
448 
449 ######################################################################
450 # Attention Decoder
451 # ^^^^^^^^^^^^^^^^^
452 #
453 # If only the context vector is passed betweeen the encoder and decoder,
454 # that single vector carries the burden of encoding the entire sentence.
455 #
456 # Attention allows the decoder network to "focus" on a different part of
457 # the encoder's outputs for every step of the decoder's own outputs. First
458 # we calculate a set of *attention weights*. These will be multiplied by
459 # the encoder output vectors to create a weighted combination. The result
460 # (called ``attn_applied`` in the code) should contain information about
461 # that specific part of the input sequence, and thus help the decoder
462 # choose the right output words.
463 #
464 # .. figure:: https://i.imgur.com/1152PYf.png
465 #    :alt:
466 #
467 # Calculating the attention weights is done with another feed-forward
468 # layer ``attn``, using the decoder's input and hidden state as inputs.
469 # Because there are sentences of all sizes in the training data, to
470 # actually create and train this layer we have to choose a maximum
471 # sentence length (input length, for encoder outputs) that it can apply
472 # to. Sentences of the maximum length will use all the attention weights,
473 # while shorter sentences will only use the first few.
474 #
475 # .. figure:: /_static/img/seq-seq-images/attention-decoder-network.png
476 #    :alt:
477 #
478 #
479 
480 class AttnDecoderRNN(nn.Module):
481     def __init__(self, hidden_size, output_size, dropout_p=0.1, max_length=MAX_LENGTH):
482         super(AttnDecoderRNN, self).__init__()
483         self.hidden_size = hidden_size
484         self.output_size = output_size
485         self.dropout_p = dropout_p
486         self.max_length = max_length
487 
488         self.embedding = nn.Embedding(self.output_size, self.hidden_size)
489         self.attn = nn.Linear(self.hidden_size * 2, self.max_length)
490         self.attn_combine = nn.Linear(self.hidden_size * 2, self.hidden_size)
491         self.dropout = nn.Dropout(self.dropout_p)
492         self.gru = nn.GRU(self.hidden_size, self.hidden_size)
493         self.out = nn.Linear(self.hidden_size, self.output_size)
494 
495     def forward(self, input, hidden, encoder_outputs):
496         # 对于输入的input内容进行embedding和dropout操作
497         # dropout是指随机丢弃一些神经元
498         embedded = self.embedding(input).view(1, 1, -1)
499         embedded = self.dropout(embedded)
500 
501         # 此处相当于学出来了attention的权重
502         # 需要注意的是torch的concatenate函数是torch.cat,是在已有的维度上拼接,
503         # 而stack是建立一个新的维度,然后再在该纬度上进行拼接。
504         attn_weights = F.softmax(
505             self.attn(torch.cat((embedded[0], hidden[0]), 1)), dim=1)
506 
507         # 将attention权重作用在encoder_outputs上
508         # 对存储在两个批batch1和batch2内的矩阵进行批矩阵乘操作。
509         # batch1和 batch2都为包含相同数量矩阵的3维张量。
510         # 如果batch1是形为b×n×m的张量,batch1是形为b×m×p的张量,
511         # 则out和mat的形状都是n×p
512         attn_applied = torch.bmm(attn_weights.unsqueeze(0),
513                                  encoder_outputs.unsqueeze(0))
514         # 拼接操作,将embedded和attn_Applied拼接起来
515         output = torch.cat((embedded[0], attn_applied[0]), 1)
516         # 返回一个新的张量,对输入的制定位置插入维度 1
517         output = self.attn_combine(output).unsqueeze(0)
518 
519         output = F.relu(output)
520         output, hidden = self.gru(output, hidden)
521 
522         output = F.log_softmax(self.out(output[0]), dim=1)
523         return output, hidden, attn_weights
524 
525     def initHidden(self):
526         return torch.zeros(1, 1, self.hidden_size, device=device)
527 
528 
529 ######################################################################
530 # .. note:: There are other forms of attention that work around the length
531 #   limitation by using a relative position approach. Read about "local
532 #   attention" in `Effective Approaches to Attention-based Neural Machine
533 #   Translation <https://arxiv.org/abs/1508.04025>`__.
534 #
535 # Training
536 # ========
537 #
538 # Preparing Training Data
539 # -----------------------
540 #
541 # To train, for each pair we will need an input tensor (indexes of the
542 # words in the input sentence) and target tensor (indexes of the words in
543 # the target sentence). While creating these vectors we will append the
544 # EOS token to both sequences.
545 #
546 
547 def indexesFromSentence(lang, sentence):
548     return [lang.word2index[word] for word in sentence.split(' ')]
549 
550 
551 def tensorFromSentence(lang, sentence):
552     # 获得词的索引
553     indexes = indexesFromSentence(lang, sentence)
554     # 将EOS标记添加到两个序列中
555     indexes.append(EOS_token)
556     return torch.tensor(indexes, dtype=torch.long, device=device).view(-1, 1)
557 
558 
559 def tensorsFromPair(pair):
560     # 每一对为需要输入的张量(输入句子中的词的索引)和目标张量
561     # (目标语句中的词的索引)
562     input_tensor = tensorFromSentence(input_lang, pair[0])
563     target_tensor = tensorFromSentence(output_lang, pair[1])
564     return (input_tensor, target_tensor)
565 
566 
567 ######################################################################
568 # Training the Model
569 # ------------------
570 #
571 # To train we run the input sentence through the encoder, and keep track
572 # of every output and the latest hidden state. Then the decoder is given
573 # the ``<SOS>`` token as its first input, and the last hidden state of the
574 # encoder as its first hidden state.
575 #
576 # "Teacher forcing" is the concept of using the real target outputs as
577 # each next input, instead of using the decoder's guess as the next input.
578 # Using teacher forcing causes it to converge faster but `when the trained
579 # network is exploited, it may exhibit
580 # instability <http://minds.jacobs-university.de/sites/default/files/uploads/papers/ESNTutorialRev.pdf>`__.
581 #
582 # You can observe outputs of teacher-forced networks that read with
583 # coherent grammar but wander far from the correct translation -
584 # intuitively it has learned to represent the output grammar and can "pick
585 # up" the meaning once the teacher tells it the first few words, but it
586 # has not properly learned how to create the sentence from the translation
587 # in the first place.
588 #
589 # Because of the freedom PyTorch's autograd gives us, we can randomly
590 # choose to use teacher forcing or not with a simple if statement. Turn
591 # ``teacher_forcing_ratio`` up to use more of it.
592 #
593 
594 teacher_forcing_ratio = 0.5
595 
596 
597 # teacher forcing即指使用教师强迫其能够更快的收敛
598 # 不过当训练好的网络被利用时,容易表现出不稳定性
599 # teacher_forcing_ratio即指教师训练比率
600 # 用于训练的函数
601 
602 
603 def train(input_tensor, target_tensor, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion,
604           max_length=MAX_LENGTH):
605     # encoder即指EncoderRNN(input_lang.n_words, hidden_size)
606     # attn_decoder即指 AttnDecoderRNN(hidden_size, output_lang.n_words, dropout_p=0.1)
607     # hidden=256
608     encoder_hidden = encoder.initHidden()
609 
610     # encoder_optimizer 即指optim.SGD(encoder.parameters(), lr=learning_rate)
611     # decoder_optimizer 即指optim.SGD(decoder.parameters(), lr=learning_rate)
612     # nn.Parameter()是Variable的一种,常被用于模块参数(module parameter)。
613     # Parameters 是 Variable 的子类。Paramenters和Modules一起使用的时候会有一些特殊的属性,
614     # 即:当Paramenters赋值给Module的属性的时候,他会自动的被加到 Module的 参数列表中
615     # (即:会出现在 parameters() 迭代器中)。将Varibale赋值给Module属性则不会有这样的影响。
616     # 这样做的原因是:我们有时候会需要缓存一些临时的状态(state), 比如:模型中RNN的最后一个隐状态。
617     # 如果没有Parameter这个类的话,那么这些临时变量也会注册成为模型变量。
618     encoder_optimizer.zero_grad()
619     decoder_optimizer.zero_grad()
620 
621     # 得到长度
622     input_length = input_tensor.size(0)
623     target_length = target_tensor.size(0)
624 
625     # 初始化outour值
626     encoder_outputs = torch.zeros(max_length, encoder.hidden_size, device=device)
627 
628     loss = 0
629 
630     # 以下循环是学习过程
631     for ei in range(input_length):
632         encoder_output, encoder_hidden = encoder(input_tensor[ei], encoder_hidden)
633         encoder_outputs[ei] = encoder_output[0, 0]  # 这里为什么取 0,0
634 
635     # 定义decoder的Input值
636     decoder_input = torch.tensor([[SOS_token]], device=device)
637 
638     decoder_hidden = encoder_hidden
639 
640     use_teacher_forcing = True if random.random() < teacher_forcing_ratio else False
641 
642     if use_teacher_forcing:
643         # Teacher forcing: Feed the target as the next input
644         # 教师强制: 将目标作为下一个输入
645         # 你观察教师强迫网络的输出,这些网络是用连贯的语法阅读的,但却远离了正确的翻译 -
646         # 直观地来看它已经学会了代表输出语法,并且一旦老师告诉它前几个单词,就可以"拾取"它的意思,
647         # 但它没有适当地学会如何从翻译中创建句子.
648         for di in range(target_length):
649             # 通过decoder得到输出值
650             decoder_output, decoder_hidden, decoder_attention = decoder(
651                 decoder_input, decoder_hidden, encoder_outputs)
652             # 定义损失函数并计算
653             loss += criterion(decoder_output, target_tensor[di])
654             decoder_input = target_tensor[di]  # Teacher forcing
655 
656     else:
657         # Without teacher forcing: use its own predictions as the next input
658         # 没有教师强迫: 使用自己的预测作为下一个输入
659         for di in range(target_length):
660             # 通过decoder得到输出值
661             decoder_output, decoder_hidden, decoder_attention = decoder(
662                 decoder_input, decoder_hidden, encoder_outputs)
663 
664             # topk:第k个最小元素,返回第k个最小元素
665             # 返回前k个最大元素,注意是前k个,largest=False,返回前k个最小元素
666             # 此函数的功能是求取1-D 或N-D Tensor的最低维度的前k个最大的值,返回值为两个Tuple
667             # 其中values是前k个最大值的Tuple,indices是对应的下标,默认返回结果是从大到小排序的。
668             topv, topi = decoder_output.topk(1)
669             decoder_input = topi.squeeze().detach()  # detach from history as input
670 
671             loss += criterion(decoder_output, target_tensor[di])
672             if decoder_input.item() == EOS_token:
673                 break
674     # 反向传播
675     loss.backward()
676 
677     # 更新参数
678     encoder_optimizer.step()
679     decoder_optimizer.step()
680 
681     return loss.item() / target_length
682 
683 
684 ######################################################################
685 # This is a helper function to print time elapsed and estimated time
686 # remaining given the current time and progress %.
687 #
688 
689 import time
690 import math
691 
692 
693 # 根据当前时间和进度百分比,这是一个帮助功能,用于打印经过的时间和估计的剩余时间.
694 
695 def asMinutes(s):
696     m = math.floor(s / 60)
697     s -= m * 60
698     return '%dm %ds' % (m, s)
699 
700 
701 def timeSince(since, percent):
702     now = time.time()
703     s = now - since
704     es = s / (percent)
705     rs = es - s
706     return '%s (- %s)' % (asMinutes(s), asMinutes(rs))
707 
708 
709 ######################################################################
710 # The whole training process looks like this:
711 #
712 # -  Start a timer
713 # -  Initialize optimizers and criterion
714 # -  Create set of training pairs
715 # -  Start empty losses array for plotting
716 #
717 # Then we call ``train`` many times and occasionally print the progress (%
718 # of examples, time so far, estimated time) and average loss.
719 #
720 
721 def trainIters(encoder, decoder, n_iters, print_every=1000, plot_every=100, learning_rate=0.01):
722     start = time.time()
723     plot_losses = []
724     print_loss_total = 0  # Reset every print_every
725     plot_loss_total = 0  # Reset every plot_every
726 
727     encoder_optimizer = optim.SGD(encoder.parameters(), lr=learning_rate)
728     decoder_optimizer = optim.SGD(decoder.parameters(), lr=learning_rate)
729 
730     # 获取训练的一对样本
731     training_pairs = [tensorsFromPair(random.choice(pairs))
732                       for i in range(n_iters)]
733     # 定义出的损失函数
734     criterion = nn.NLLLoss()
735 
736     for iter in range(1, n_iters + 1):
737         training_pair = training_pairs[iter - 1]
738         input_tensor = training_pair[0]
739         target_tensor = training_pair[1]
740 
741         # 训练的过程并用于当损失函数
742         loss = train(input_tensor, target_tensor, encoder,
743                      decoder, encoder_optimizer, decoder_optimizer, criterion)
744         print_loss_total += loss
745         plot_loss_total += loss
746 
747         if iter % print_every == 0:
748             print_loss_avg = print_loss_total / print_every
749             print_loss_total = 0
750             # 打印进度(样本的百分比,到目前为止的时间,估计的时间)和平均损失.
751             print('%s (%d %d%%) %.4f' % (timeSince(start, iter / n_iters),
752                                          iter, iter / n_iters * 100, print_loss_avg))
753 
754         if iter % plot_every == 0:
755             plot_loss_avg = plot_loss_total / plot_every
756             plot_losses.append(plot_loss_avg)
757             plot_loss_total = 0
758     # 绘制图像
759     showPlot(plot_losses)
760 
761 
762 ######################################################################
763 # Plotting results
764 # ----------------
765 #
766 # Plotting is done with matplotlib, using the array of loss values
767 # ``plot_losses`` saved while training.
768 #
769 
770 import matplotlib.pyplot as plt
771 
772 plt.switch_backend('agg')
773 import matplotlib.ticker as ticker
774 import numpy as np
775 
776 
777 # 使用matplotlib进行绘图,使用训练时保存的损失值plot_losses数组.
778 def showPlot(points):
779     plt.figure()
780     fig, ax = plt.subplots()
781     # this locator puts ticks at regular intervals
782     # 这个定位器会定期发出提示信息
783     loc = ticker.MultipleLocator(base=0.2)
784     ax.yaxis.set_major_locator(loc)
785     plt.plot(points)
786 
787 
788 ######################################################################
789 # Evaluation
790 # ==========
791 #
792 # Evaluation is mostly the same as training, but there are no targets so
793 # we simply feed the decoder's predictions back to itself for each step.
794 # Every time it predicts a word we add it to the output string, and if it
795 # predicts the EOS token we stop there. We also store the decoder's
796 # attention outputs for display later.
797 #
798 
799 def evaluate(encoder, decoder, sentence, max_length=MAX_LENGTH):
800     with torch.no_grad():
801         # 从sentence中得到对应的变量
802         input_tensor = tensorFromSentence(input_lang, sentence)
803         # 长度
804         input_length = input_tensor.size()[0]
805 
806         # encoder即指EncoderRNN(input_lang.n_words, hidden_size)
807         # attn_decoder即指 AttnDecoderRNN(hidden_size,
808         # output_lang.n_words, dropout_p=0.1)
809         # hidden=256
810         encoder_hidden = encoder.initHidden()
811 
812         # 初始化outputs值
813         encoder_outputs = torch.zeros(max_length, encoder.hidden_size, device=device)
814 
815         # 以下是学习过程
816         for ei in range(input_length):
817             encoder_output, encoder_hidden = encoder(input_tensor[ei],
818                                                      encoder_hidden)
819             encoder_outputs[ei] += encoder_output[0, 0]
820 
821         # 定义好decoder部分的input值
822         decoder_input = torch.tensor([[SOS_token]], device=device)  # SOS
823 
824         # 设置好隐藏层
825         decoder_hidden = encoder_hidden
826 
827         decoded_words = []
828         decoder_attentions = torch.zeros(max_length, max_length)
829 
830         for di in range(max_length):
831             # 得到结果
832             decoder_output, decoder_hidden, decoder_attention = decoder(decoder_input, decoder_hidden, encoder_outputs)
833 
834             # attention部分的数据
835             decoder_attentions[di] = decoder_attention.data
836             # 选择output中的第一个值
837             topv, topi = decoder_output.data.topk(1)
838             if topi.item() == EOS_token:
839                 decoded_words.append('<EOS>')
840                 break
841             else:
842                 decoded_words.append(output_lang.index2word[topi.item()])  # 将output_lang添加到decoded
843 
844             decoder_input = topi.squeeze().detach()
845 
846         return decoded_words, decoder_attentions[:di + 1]
847 
848 
849 ######################################################################
850 # We can evaluate random sentences from the training set and print out the
851 # input, target, and output to make some subjective quality judgements:
852 #
853 
854 # 从训练集中评估随机的句子并打印出输入,目标和输出以作出一些主观质量判断
855 def evaluateRandomly(encoder, decoder, n=10):
856     for i in range(n):
857         pair = random.choice(pairs)
858         print('>', pair[0])
859         print('=', pair[1])
860         output_words, attentions = evaluate(encoder, decoder, pair[0])
861         output_sentence = ' '.join(output_words)
862         print('<', output_sentence)
863         print('')
864 
865 
866 ######################################################################
867 # Training and Evaluating
868 # =======================
869 #
870 # With all these helper functions in place (it looks like extra work, but
871 # it makes it easier to run multiple experiments) we can actually
872 # initialize a network and start training.
873 #
874 # Remember that the input sentences were heavily filtered. For this small
875 # dataset we can use relatively small networks of 256 hidden nodes and a
876 # single GRU layer. After about 40 minutes on a MacBook CPU we'll get some
877 # reasonable results.
878 #
879 # .. Note::
880 #    If you run this notebook you can train, interrupt the kernel,
881 #    evaluate, and continue training later. Comment out the lines where the
882 #    encoder and decoder are initialized and run ``trainIters`` again.
883 #
884 
885 hidden_size = 256
886 # 编码部分
887 encoder1 = EncoderRNN(input_lang.n_words, hidden_size).to(device)
888 # 加入了attention机制的解码部分
889 attn_decoder1 = AttnDecoderRNN(hidden_size, output_lang.n_words, dropout_p=0.1).to(device)
890 # 训练部分
891 trainIters(encoder1, attn_decoder1, 75000, print_every=5000)
892 
893 ######################################################################
894 # 随机生成一组结果
895 evaluateRandomly(encoder1, attn_decoder1)
896 
897 ######################################################################
898 # Visualizing Attention
899 # ---------------------
900 #
901 # A useful property of the attention mechanism is its highly interpretable
902 # outputs. Because it is used to weight specific encoder outputs of the
903 # input sequence, we can imagine looking where the network is focused most
904 # at each time step.
905 #
906 # You could simply run ``plt.matshow(attentions)`` to see attention output
907 # displayed as a matrix, with the columns being input steps and rows being
908 # output steps:
909 #
910 
911 output_words, attentions = evaluate(encoder1, attn_decoder1, "je suis trop froid .")
912 plt.matshow(attentions.numpy())
913 
914 
915 ######################################################################
916 # For a better viewing experience we will do the extra work of adding axes
917 # and labels:
918 
919 def showAttention(input_sentence, output_words, attentions):
920     # Set up figure with colorbar
921     fig = plt.figure()
922     ax = fig.add_subplot(111)
923     cax = ax.matshow(attentions.numpy(), cmap='bone')
924     fig.colorbar(cax)
925 
926     # Set up axes
927     ax.set_xticklabels([''] + input_sentence.split(' ') +
928                        ['<EOS>'], rotation=90)
929     ax.set_yticklabels([''] + output_words)
930 
931     # Show label at every tick
932     ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
933     ax.yaxis.set_major_locator(ticker.MultipleLocator(1))
934 
935     plt.show()
936 
937 
938 def evaluateAndShowAttention(input_sentence):
939     output_words, attentions = evaluate(
940         encoder1, attn_decoder1, input_sentence)
941     print('input =', input_sentence)
942     print('output =', ' '.join(output_words))
943     showAttention(input_sentence, output_words, attentions)
944 
945 
946 evaluateAndShowAttention("elle a cinq ans de moins que moi .")
947 evaluateAndShowAttention("elle est trop petit .")
948 evaluateAndShowAttention("je ne crains pas de mourir .")
949 evaluateAndShowAttention("c est un jeune directeur plein de talent .")
950 
951 ######################################################################
952 # Exercises
953 # =========
954 #
955 # -  Try with a different dataset
956 #
957 #    -  Another language pair
958 #    -  Human → Machine (e.g. IOT commands)
959 #    -  Chat → Response
960 #    -  Question → Answer
961 #
962 # -  Replace the embeddings with pre-trained word embeddings such as word2vec or
963 #    GloVe
964 # -  Try with more layers, more hidden units, and more sentences. Compare
965 #    the training time and results.
966 # -  If you use a translation file where pairs have two of the same phrase
967 #    (``I am test \t I am test``), you can use this as an autoencoder. Try
968 #    this:
969 #
970 #    -  Train as an autoencoder
971 #    -  Save only the Encoder network
972 #    -  Train a new Decoder for translation from there
973 #