机器翻译的神经网络实现

本节课我们讲述了利用编码器-解码器架构实现汉-英机器翻译。

整个代码包括了数据预处理、编码器+简单解码器以及编码器+带有注意力机制的解码器三个部分组成。

本文件是集智AI学园http://campus.swarma.org 出品的“火炬上的深度学习”第VIII课的配套源代码


In [1]:
# 用到的包
#from __future__ import unicode_literals, print_function, division
# 进行系统操作,如io、正则表达式的包
from io import open
import unicodedata
import string
import re
import random
#import time
#import math

#Pytorch必备的包
import torch
import torch.nn as nn
from torch.autograd import Variable
from torch import optim
import torch.nn.functional as F
import torch.utils.data as DataSet


# 绘图所用的包
import matplotlib.pyplot as plt
import numpy as np


# 判断本机是否有支持的GPU
use_cuda = torch.cuda.is_available()
# 即时绘图
%matplotlib inline

一、数据准备

从硬盘读取语料文件,进行基本的预处理


In [2]:
# 读取平行语料库
# 这是人民日报语料库
lines = open('data/chinese.txt', encoding = 'utf-8')
chinese = lines.read().strip().split('\n')
lines = open('data/english.txt', encoding = 'utf-8')
english = lines.read().strip().split('\n')
print(len(chinese))
print(len(english))


100000
100000

In [3]:
# 定义两个特殊符号,分别对应句子头和句子尾
SOS_token = 0
EOS_token = 1


# 定义一个语言类,方便进行自动的建立、词频的统计等
# 在这个对象中,最重要的是两个字典:word2index,index2word
# 故名思议,第一个字典是将word映射到索引,第二个是将索引映射到word
class Lang:
    def __init__(self, name):
        self.name = name
        self.word2index = {}
        self.word2count = {}
        self.index2word = {0: "SOS", 1: "EOS"}
        self.n_words = 2  # Count SOS and EOS

    def addSentence(self, sentence):
        # 在语言中添加一个新句子,句子是用空格隔开的一组单词
        # 将单词切分出来,并分别进行处理
        for word in sentence.split(' '):
            self.addWord(word)

    def addWord(self, word):
        # 插入一个单词,如果单词已经在字典中,则更新字典中对应单词的频率
        # 同时建立反向索引,可以从单词编号找到单词
        if word not in self.word2index:
            self.word2index[word] = self.n_words
            self.word2count[word] = 1
            self.index2word[self.n_words] = word
            self.n_words += 1
        else:
            self.word2count[word] += 1

# Turn a Unicode string to plain ASCII, thanks to
# http://stackoverflow.com/a/518232/2809427
# 将unicode编码转变为ascii编码
def unicodeToAscii(s):
    return ''.join(
        c for c in unicodedata.normalize('NFD', s)
        if unicodedata.category(c) != 'Mn'
    )

# 把输入的英文字符串转成小写
def normalizeEngString(s):
    s = unicodeToAscii(s.lower().strip())
    s = re.sub(r"([.!?])", r" \1", s)
    s = re.sub(r"[^a-zA-Z.!?]+", r" ", s)
    return s

# 对输入的单词对做过滤,保证每句话的单词数不能超过MAX_LENGTH
def filterPair(p):
    return len(p[0].split(' ')) < MAX_LENGTH and \
        len(p[1].split(' ')) < MAX_LENGTH

# 输入一个句子,输出一个单词对应的编码序列
def indexesFromSentence(lang, sentence):
    return [lang.word2index[word] for word in sentence.split(' ')]


# 和上面的函数功能类似,不同在于输出的序列等长=MAX_LENGTH
def indexFromSentence(lang, sentence):
    indexes = indexesFromSentence(lang, sentence)
    indexes.append(EOS_token)
    for i in range(MAX_LENGTH - len(indexes)):
        indexes.append(EOS_token)
    return(indexes)

# 从一个词对到下标
def indexFromPair(pair):
    input_variable = indexFromSentence(input_lang, pair[0])
    target_variable = indexFromSentence(output_lang, pair[1])
    return (input_variable, target_variable)

# 从一个列表到句子
def SentenceFromList(lang, lst):
    result = [lang.index2word[i] for i in lst if i != EOS_token]
    if lang.name == 'Chinese':
        result = ' '.join(result)
    else:
        result = ' '.join(result)
    return(result)

# 计算准确度的函数
def rightness(predictions, labels):
    """计算预测错误率的函数,其中predictions是模型给出的一组预测结果,batch_size行num_classes列的矩阵,labels是数据之中的正确答案"""
    pred = torch.max(predictions.data, 1)[1] # 对于任意一行(一个样本)的输出值的第1个维度,求最大,得到每一行的最大元素的下标
    rights = pred.eq(labels.data).sum() #将下标与labels中包含的类别进行比较,并累计得到比较正确的数量
    return rights, len(labels) #返回正确的数量和这一次一共比较了多少元素

In [5]:
# 处理数据形成训练数据
# 设置句子的最大长度
MAX_LENGTH = 20

#对英文做标准化处理
pairs = [[normalizeEngString(eng), chi] for chi, eng in zip(chinese, english)]

# 对句子对做过滤,处理掉那些超过MAX_LENGTH长度的句子
input_lang = Lang('English')
output_lang = Lang('Chinese')
pairs = [pair for pair in pairs if filterPair(pair)]
print('有效句子对:', len(pairs))

# 建立两个字典(中文的和英文的)
for pair in pairs:
    input_lang.addSentence(pair[0])
    output_lang.addSentence(pair[1])
print("总单词数:")
print(input_lang.name, input_lang.n_words)
print(output_lang.name, output_lang.n_words)


# 形成训练集,首先,打乱所有句子的顺序
random_idx = np.random.permutation(range(len(pairs)))
pairs = [pairs[i] for i in random_idx]

# 将语言转变为单词的编码构成的序列
pairs = [indexFromPair(pair) for pair in pairs]
    
# 形成训练集、校验集和测试集
valid_size = len(pairs) // 10
if valid_size > 10000:
    valid_size = 10000
pairs = pairs[ : - valid_size]
valid_pairs = pairs[-valid_size : -valid_size // 2]
test_pairs = pairs[- valid_size // 2 :]

# 利用PyTorch的dataset和dataloader对象,将数据加载到加载器里面,并且自动分批

batch_size = 1024 #一撮包含30个数据记录,这个数字越大,系统在训练的时候,每一个周期处理的数据就越多,这样处理越快,但总的数据量会减少

print('训练记录:', len(pairs))
print('校验记录:', len(valid_pairs))
print('测试记录:', len(test_pairs))

# 形成训练对列表,用于喂给train_dataset
pairs_X = [pair[0] for pair in pairs]
pairs_Y = [pair[1] for pair in pairs]
valid_X = [pair[0] for pair in valid_pairs]
valid_Y = [pair[1] for pair in valid_pairs]
test_X = [pair[0] for pair in test_pairs]
test_Y = [pair[1] for pair in test_pairs]


# 形成训练集
train_dataset = DataSet.TensorDataset(torch.LongTensor(pairs_X), torch.LongTensor(pairs_Y))
# 形成数据加载器
train_loader = DataSet.DataLoader(train_dataset, batch_size = batch_size, shuffle = True, num_workers=8)


# 校验数据
valid_dataset = DataSet.TensorDataset(torch.LongTensor(valid_X), torch.LongTensor(valid_Y))
valid_loader = DataSet.DataLoader(valid_dataset, batch_size = batch_size, shuffle = True, num_workers=8)

# 测试数据
test_dataset = DataSet.TensorDataset(torch.LongTensor(test_X), torch.LongTensor(test_Y))
test_loader = DataSet.DataLoader(test_dataset, batch_size = batch_size, shuffle = True, num_workers = 8)


有效句子对: 19919
总单词数:
English 13493
Chinese 18671
训练记录: 17928
校验记录: 995
测试记录: 996

二、构建编码器及简单的解码器RNN


In [7]:
# 构建编码器RNN
class EncoderRNN(nn.Module):
    def __init__(self, input_size, hidden_size, n_layers=1):
        super(EncoderRNN, self).__init__()
        self.n_layers = n_layers
        self.hidden_size = hidden_size
        # 第一层Embeddeing
        self.embedding = nn.Embedding(input_size, hidden_size)
        # 第二层GRU,注意GRU中可以定义很多层,主要靠num_layers控制
        self.gru = nn.GRU(hidden_size, hidden_size, batch_first = True, 
                          num_layers = self.n_layers, bidirectional = True)

    def forward(self, input, hidden):
        #前馈过程
        #input尺寸: batch_size, length_seq
        embedded = self.embedding(input)
        #embedded尺寸:batch_size, length_seq, hidden_size
        output = embedded
        output, hidden = self.gru(output, hidden)
        # output尺寸:batch_size, length_seq, hidden_size
        # hidden尺寸:num_layers * directions, batch_size, hidden_size
        return output, hidden

    def initHidden(self, batch_size):
        # 对隐含单元变量全部进行初始化
        #num_layers * num_directions, batch, hidden_size
        result = Variable(torch.zeros(self.n_layers * 2, batch_size, self.hidden_size))
        if use_cuda:
            return result.cuda()
        else:
            return result

# 解码器网络
class DecoderRNN(nn.Module):
    def __init__(self, hidden_size, output_size, n_layers=1):
        super(DecoderRNN, self).__init__()
        self.n_layers = n_layers
        self.hidden_size = hidden_size
        # 嵌入层
        self.embedding = nn.Embedding(output_size, hidden_size)
        # GRU单元
        # 设置batch_first为True的作用就是为了让GRU接受的张量可以和其它单元类似,第一个维度为batch_size
        self.gru = nn.GRU(hidden_size, hidden_size, batch_first = True,
                        num_layers = self.n_layers, bidirectional = True)
        # 最后的全链接层
        self.out = nn.Linear(hidden_size * 2, output_size)
        self.softmax = nn.LogSoftmax()

    def forward(self, input, hidden):
        # input大小:batch_size, length_seq
        output = self.embedding(input)
        # embedded大小:batch_size, length_seq, hidden_size
        output = F.relu(output)
        output, hidden = self.gru(output, hidden)
        # output大小:batch_size, length_seq, hidden_size * directions
        # hidden大小:n_layers * directions, batch_size, hidden_size
        output = self.softmax(self.out(output[:, -1, :]))
        # output大小:batch_size * output_size
        # 从output中取时间步重新开始
        
        return output, hidden

    def initHidden(self):
        # 初始化隐含单元的状态,输入变量的尺寸:num_layers * directions, batch_size, hidden_size
        result = Variable(torch.zeros(self.n_layers * 2, batch_size, self.hidden_size))
        if use_cuda:
            return result.cuda()
        else:
            return result

In [7]:
# 开始训练过程
# 定义网络结构
hidden_size = 512
max_length = MAX_LENGTH
n_layers = 1

encoder = EncoderRNN(input_lang.n_words, hidden_size, n_layers = n_layers)
decoder = DecoderRNN(hidden_size, output_lang.n_words, n_layers = n_layers)

if use_cuda:
    # 如果本机有GPU可用,则将模型加载到GPU上
    encoder = encoder.cuda()
    decoder = decoder.cuda()

learning_rate = 0.001
# 为两个网络分别定义优化器
encoder_optimizer = optim.Adam(encoder.parameters(), lr=learning_rate)
decoder_optimizer = optim.Adam(decoder.parameters(), lr=learning_rate)

# 定义损失函数
criterion = nn.NLLLoss()
teacher_forcing_ratio = 0.5

plot_losses = []

# 开始200轮的循环
num_epoch = 100
for epoch in range(num_epoch):
    print_loss_total = 0
    # 对训练数据循环
    for data in train_loader:
        input_variable = Variable(data[0]).cuda() if use_cuda else Variable(data[0])
        # input_variable的大小:batch_size, length_seq
        target_variable = Variable(data[1]).cuda() if use_cuda else Variable(data[1])
        # target_variable的大小:batch_size, length_seq
        
        # 初始化编码器状态
        encoder_hidden = encoder.initHidden(data[0].size()[0])
        # 清空梯度
        encoder_optimizer.zero_grad()
        decoder_optimizer.zero_grad()

        loss = 0

        # 开始编码器的计算,对时间步的循环由系统自动完成
        encoder_outputs, encoder_hidden = encoder(input_variable, encoder_hidden)
        # encoder_outputs的大小:batch_size, length_seq, hidden_size*direction
        # encoder_hidden的大小:direction*n_layer, batch_size, hidden_size
        
        # 开始解码器的工作
        # 输入给解码器的第一个字符
        decoder_input = Variable(torch.LongTensor([[SOS_token]] * target_variable.size()[0]))
        # decoder_input大小:batch_size, length_seq
        decoder_input = decoder_input.cuda() if use_cuda else decoder_input

        # 让解码器的隐藏层状态等于编码器的隐藏层状态
        decoder_hidden = encoder_hidden
        # decoder_hidden大小:direction*n_layer, batch_size, hidden_size

        # 以teacher_forcing_ratio的比例用target中的翻译结果作为监督信息
        use_teacher_forcing = True if random.random() < teacher_forcing_ratio else False
        base = torch.zeros(target_variable.size()[0])
        if use_teacher_forcing:
            # 教师监督: 将下一个时间步的监督信息输入给解码器
            # 对时间步循环
            for di in range(MAX_LENGTH):
                # 开始一步解码
                decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden)
                # decoder_ouput大小:batch_size, output_size
                # 计算损失函数
                loss += criterion(decoder_output, target_variable[:, di])
                # 将训练数据当做下一时间步的输入
                decoder_input = target_variable[:, di].unsqueeze(1)  # Teacher forcing
                # decoder_input大小:batch_size, length_seq
                
        else:
            # 没有教师训练: 使用解码器自己的预测作为下一时间步的输入
            # 开始对时间步进行循环
            for di in range(MAX_LENGTH):
                # 进行一步解码
                decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden)
                #decoder_ouput大小:batch_size, output_size(vocab_size)
                
                #从输出结果(概率的对数值)中选择出一个数值最大的单词作为输出放到了topi中
                topv, topi = decoder_output.data.topk(1, dim = 1)
                #topi 尺寸:batch_size, k
                ni = topi[:, 0]

                # 将输出结果ni包裹成Variable作为解码器的输入
                decoder_input = Variable(ni.unsqueeze(1))
                # decoder_input大小:batch_size, length_seq
                decoder_input = decoder_input.cuda() if use_cuda else decoder_input

                #计算损失函数
                loss += criterion(decoder_output, target_variable[:, di])
        
            
        
        # 开始反向传播
        loss.backward()
        loss = loss.cpu() if use_cuda else loss
        # 开始梯度下降
        encoder_optimizer.step()
        decoder_optimizer.step()
        # 累加总误差
        print_loss_total += loss.data.numpy()[0]

    # 计算训练时候的平均误差
    print_loss_avg = print_loss_total / len(train_loader)
        
    # 开始跑校验数据集
    valid_loss = 0
    rights = []
    # 对校验数据集循环
    for data in valid_loader:
        input_variable = Variable(data[0]).cuda() if use_cuda else Variable(data[0])
        # input_variable的大小:batch_size, length_seq
        target_variable = Variable(data[1]).cuda() if use_cuda else Variable(data[1])
        # target_variable的大小:batch_size, length_seq

        encoder_hidden = encoder.initHidden(data[0].size()[0])

        loss = 0
        encoder_outputs, encoder_hidden = encoder(input_variable, encoder_hidden)
        # encoder_outputs的大小:batch_size, length_seq, hidden_size*direction
        # encoder_hidden的大小:direction*n_layer, batch_size, hidden_size

        decoder_input = Variable(torch.LongTensor([[SOS_token]] * target_variable.size()[0]))
        # decoder_input大小:batch_size, length_seq
        decoder_input = decoder_input.cuda() if use_cuda else decoder_input

        decoder_hidden = encoder_hidden
        # decoder_hidden大小:direction*n_layer, batch_size, hidden_size

        # 没有教师监督: 使用解码器自己的预测作为下一时间步解码器的输入
        for di in range(MAX_LENGTH):
            # 一步解码器运算
            decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden)
            #decoder_ouput大小:batch_size, output_size(vocab_size)
            
            # 选择输出最大的项作为解码器的预测答案
            topv, topi = decoder_output.data.topk(1, dim = 1)
            #topi 尺寸:batch_size, k
            ni = topi[:, 0]
            decoder_input = Variable(ni.unsqueeze(1))
            # decoder_input大小:batch_size, length_seq
            decoder_input = decoder_input.cuda() if use_cuda else decoder_input
            
            # 计算预测的准确率,记录在right中,right为一个二元组,分别存储猜对的个数和总数
            right = rightness(decoder_output, target_variable[:, di].unsqueeze(1))
            rights.append(right)
            
            # 计算损失函数
            loss += criterion(decoder_output, target_variable[:, di])
        loss = loss.cpu() if use_cuda else loss
        # 累加校验时期的损失函数
        valid_loss += loss.data.numpy()[0]
    # 打印每一个Epoch的输出结果
    right_ratio = 1.0 * np.sum([i[0] for i in rights]) / np.sum([i[1] for i in rights])
    print('进程:%d%% 训练损失:%.4f,校验损失:%.4f,词正确率:%.2f%%' % (epoch * 1.0 / num_epoch * 100, 
                                                    print_loss_avg,
                                                    valid_loss / len(valid_loader),
                                                    100.0 * right_ratio))
    # 记录基本统计指标
    plot_losses.append([print_loss_avg, valid_loss / len(valid_loader), right_ratio])


进程:0% 训练损失:109.9733,校验损失:92.9247,词正确率:41.17%
进程:1% 训练损失:87.8239,校验损失:87.3932,词正确率:42.02%
进程:2% 训练损失:83.8612,校验损失:83.7487,词正确率:42.87%
进程:3% 训练损失:80.1325,校验损失:79.9911,词正确率:43.30%
进程:4% 训练损失:75.8655,校验损失:76.6219,词正确率:43.80%
进程:5% 训练损失:71.7143,校验损失:74.5334,词正确率:44.13%
进程:6% 训练损失:70.2667,校验损失:69.2976,词正确率:44.70%
进程:7% 训练损失:66.1796,校验损失:66.0941,词正确率:45.48%
进程:8% 训练损失:62.3394,校验损失:62.4132,词正确率:46.03%
进程:9% 训练损失:58.8069,校验损失:59.8183,词正确率:46.85%
进程:10% 训练损失:56.3496,校验损失:59.4263,词正确率:46.47%
进程:11% 训练损失:54.3407,校验损失:57.1895,词正确率:45.73%
进程:12% 训练损失:49.8752,校验损失:53.4917,词正确率:48.78%
进程:13% 训练损失:48.7215,校验损失:49.8750,词正确率:49.44%
进程:14% 训练损失:44.2087,校验损失:47.5318,词正确率:50.91%
进程:15% 训练损失:43.6451,校验损失:44.8748,词正确率:51.74%
进程:16% 训练损失:40.9163,校验损失:42.7357,词正确率:52.91%
进程:17% 训练损失:39.4894,校验损失:41.1448,词正确率:53.88%
进程:18% 训练损失:35.8536,校验损失:40.9991,词正确率:53.54%
进程:19% 训练损失:35.1695,校验损失:40.8788,词正确率:53.12%
进程:20% 训练损失:29.9313,校验损失:39.0692,词正确率:55.01%
进程:21% 训练损失:34.6380,校验损失:35.0035,词正确率:57.53%
进程:22% 训练损失:30.6411,校验损失:33.3453,词正确率:58.25%
进程:23% 训练损失:28.9975,校验损失:33.8572,词正确率:57.74%
进程:24% 训练损失:28.7352,校验损失:30.6883,词正确率:60.91%
进程:25% 训练损失:25.1459,校验损失:30.8286,词正确率:61.04%
进程:26% 训练损失:25.4422,校验损失:28.2195,词正确率:63.95%
进程:27% 训练损失:25.2868,校验损失:26.8499,词正确率:65.10%
进程:28% 训练损失:22.6872,校验损失:25.4940,词正确率:66.30%
进程:28% 训练损失:21.9534,校验损失:24.3684,词正确率:67.64%
进程:30% 训练损失:20.6166,校验损失:24.5506,词正确率:67.43%
进程:31% 训练损失:20.3859,校验损失:22.2673,词正确率:70.71%
进程:32% 训练损失:19.3241,校验损失:20.7919,词正确率:72.35%
进程:33% 训练损失:18.4089,校验损失:19.9725,词正确率:73.11%
进程:34% 训练损失:16.5613,校验损失:19.2713,词正确率:74.05%
进程:35% 训练损失:16.6961,校验损失:17.8819,词正确率:76.64%
进程:36% 训练损失:13.5601,校验损失:17.4919,词正确率:77.29%
进程:37% 训练损失:13.0188,校验损失:16.3079,词正确率:79.64%
进程:38% 训练损失:13.8475,校验损失:15.3929,词正确率:80.25%
进程:39% 训练损失:12.9926,校验损失:14.4679,词正确率:81.19%
进程:40% 训练损失:10.8313,校验损失:13.4967,词正确率:83.40%
进程:41% 训练损失:10.9589,校验损失:12.6593,词正确率:84.78%
进程:42% 训练损失:10.4597,校验损失:11.2758,词正确率:86.57%
进程:43% 训练损失:7.7984,校验损失:11.1251,词正确率:86.94%
进程:44% 训练损失:8.2984,校验损失:9.5720,词正确率:89.11%
进程:45% 训练损失:7.0131,校验损失:8.0899,词正确率:91.76%
进程:46% 训练损失:6.6116,校验损失:7.2747,词正确率:92.79%
进程:47% 训练损失:5.9018,校验损失:6.7912,词正确率:93.70%
进程:48% 训练损失:5.5871,校验损失:6.2620,词正确率:94.35%
进程:49% 训练损失:4.4288,校验损失:5.7701,词正确率:94.80%
进程:50% 训练损失:4.6398,校验损失:4.7925,词正确率:96.33%
进程:51% 训练损失:4.0038,校验损失:4.0295,词正确率:97.42%
进程:52% 训练损失:3.3316,校验损失:3.6054,词正确率:97.73%
进程:53% 训练损失:3.2009,校验损失:3.2548,词正确率:98.11%
进程:54% 训练损失:2.7510,校验损失:2.8545,词正确率:98.48%
进程:55% 训练损失:2.5064,校验损失:2.5360,词正确率:98.82%
进程:56% 训练损失:2.1529,校验损失:2.2399,词正确率:99.08%
进程:56% 训练损失:1.8295,校验损失:1.9691,词正确率:99.33%
进程:57% 训练损失:1.8098,校验损失:1.8042,词正确率:99.39%
进程:59% 训练损失:1.5742,校验损失:1.5520,词正确率:99.60%
进程:60% 训练损失:1.4404,校验损失:1.4918,词正确率:99.58%
进程:61% 训练损失:1.3609,校验损失:1.3185,词正确率:99.68%
进程:62% 训练损失:1.2226,校验损失:1.2292,词正确率:99.69%
进程:63% 训练损失:1.0967,校验损失:1.0601,词正确率:99.85%
进程:64% 训练损失:1.0242,校验损失:1.0493,词正确率:99.79%
进程:65% 训练损失:0.9239,校验损失:0.9991,词正确率:99.78%
进程:66% 训练损失:0.8992,校验损失:0.9052,词正确率:99.84%
进程:67% 训练损失:0.8174,校验损失:0.8423,词正确率:99.87%
进程:68% 训练损失:0.7659,校验损失:0.7535,词正确率:99.92%
进程:69% 训练损失:0.7232,校验损失:0.7276,词正确率:99.93%
进程:70% 训练损失:0.6898,校验损失:0.6796,词正确率:99.92%
进程:71% 训练损失:0.6575,校验损失:0.6689,词正确率:99.91%
进程:72% 训练损失:0.6216,校验损失:0.6343,词正确率:99.88%
进程:73% 训练损失:0.5839,校验损失:0.5954,词正确率:99.92%
进程:74% 训练损失:0.5610,校验损失:0.6170,词正确率:99.87%
进程:75% 训练损失:0.5312,校验损失:0.5037,词正确率:99.98%
进程:76% 训练损失:0.5049,校验损失:0.5962,词正确率:99.85%
进程:77% 训练损失:0.4809,校验损失:0.5132,词正确率:99.95%
进程:78% 训练损失:0.4734,校验损失:0.4857,词正确率:99.93%
进程:79% 训练损失:0.4537,校验损失:0.4486,词正确率:99.94%
进程:80% 训练损失:0.4347,校验损失:0.4569,词正确率:99.88%
进程:81% 训练损失:0.4175,校验损失:0.4086,词正确率:99.95%
进程:82% 训练损失:0.3987,校验损失:0.3806,词正确率:99.96%
进程:83% 训练损失:0.3839,校验损失:0.3908,词正确率:99.93%
进程:84% 训练损失:0.3645,校验损失:0.3480,词正确率:99.98%
进程:85% 训练损失:0.3605,校验损失:0.3604,词正确率:99.93%
进程:86% 训练损失:0.3399,校验损失:0.3309,词正确率:99.96%
进程:87% 训练损失:0.3326,校验损失:0.3214,词正确率:99.95%
进程:88% 训练损失:0.3105,校验损失:0.3067,词正确率:99.97%
进程:89% 训练损失:0.3063,校验损失:0.2923,词正确率:99.97%
进程:90% 训练损失:0.2909,校验损失:0.3389,词正确率:99.91%
进程:91% 训练损失:0.2896,校验损失:0.2897,词正确率:99.93%
进程:92% 训练损失:0.2824,校验损失:0.2756,词正确率:99.94%
进程:93% 训练损失:0.2685,校验损失:0.3117,词正确率:99.92%
进程:94% 训练损失:0.2722,校验损失:0.2689,词正确率:99.95%
进程:95% 训练损失:0.2575,校验损失:0.2574,词正确率:99.96%
进程:96% 训练损失:0.2544,校验损失:0.2400,词正确率:99.97%
进程:97% 训练损失:0.2493,校验损失:0.2389,词正确率:99.97%
进程:98% 训练损失:0.2419,校验损失:0.2246,词正确率:99.95%
进程:99% 训练损失:0.2351,校验损失:0.2233,词正确率:99.96%

In [8]:
# 将统计指标绘图
a = [i[0] for i in plot_losses]
b = [i[1] for i in plot_losses]
c = [i[2] * 100 for i in plot_losses]
plt.plot(a, label = 'Training Loss')
plt.plot(b, label = 'Validation Loss')
plt.plot(c, label = 'Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Loss & Accuracy')
plt.legend()


Out[8]:
<matplotlib.legend.Legend at 0x7f3fb8bb0fd0>

In [10]:
# 在测试集上测试模型运行的效果

# 首先,在测试集中随机选择20个句子作为测试
indices = np.random.choice(range(len(test_X)), 20)

# 对每个句子进行循环
for ind in indices:
    data = [test_X[ind]]
    target = [test_Y[ind]]
    # 把源语言的句子打印出来
    print(SentenceFromList(input_lang, data[0]))
    input_variable = Variable(torch.LongTensor(data)).cuda() if use_cuda else Variable(torch.LongTensor(data))
    # input_variable的大小:batch_size, length_seq
    target_variable = Variable(torch.LongTensor(target)).cuda() if use_cuda else Variable(torch.LongTensor(target))
    # target_variable的大小:batch_size, length_seq

    # 初始化编码器
    encoder_hidden = encoder.initHidden(input_variable.size()[0])

    loss = 0
    
    # 编码器开始编码,结果存储到了encoder_hidden中
    encoder_outputs, encoder_hidden = encoder(input_variable, encoder_hidden)
    # encoder_outputs的大小:batch_size, length_seq, hidden_size*direction
    # encoder_hidden的大小:direction*n_layer, batch_size, hidden_size

    # 将SOS作为解码器的第一个输入
    decoder_input = Variable(torch.LongTensor([[SOS_token]] * target_variable.size()[0]))
    # decoder_input大小:batch_size, length_seq
    decoder_input = decoder_input.cuda() if use_cuda else decoder_input

    # 将编码器的隐含层单元数值拷贝给解码器的隐含层单元
    decoder_hidden = encoder_hidden
    # decoder_hidden大小:direction*n_layer, batch_size, hidden_size

    # 没有教师指导下的预测: 使用解码器自己的预测作为解码器下一时刻的输入
    output_sentence = []
    decoder_attentions = torch.zeros(max_length, max_length)
    rights = []
    # 按照输出字符进行时间步循环
    for di in range(MAX_LENGTH):
        # 解码器一个时间步的计算
        decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden)
        #decoder_ouput大小:batch_size, output_size(vocab_size)
        
        # 解码器的输出
        topv, topi = decoder_output.data.topk(1, dim = 1)
        #topi 尺寸:batch_size, k
        ni = topi[:, 0]
        decoder_input = Variable(ni.unsqueeze(1))
        ni = ni.cpu().numpy()[0]
        
        # 将本时间步输出的单词编码加到output_sentence里面
        output_sentence.append(ni)
        # decoder_input大小:batch_size, length_seq
        decoder_input = decoder_input.cuda() if use_cuda else decoder_input
        
        # 计算输出字符的准确度
        right = rightness(decoder_output, target_variable[:, di].unsqueeze(1))
        rights.append(right)
    # 解析出编码器给出的翻译结果
    sentence = SentenceFromList(output_lang, output_sentence)
    # 解析出标准答案
    standard = SentenceFromList(output_lang, target[0])
    
    # 将句子打印出来
    print('机器翻译:', sentence)
    print('标准翻译:', standard)
    # 输出本句话的准确率
    right_ratio = 1.0 * np.sum([i[0] for i in rights]) / np.sum([i[1] for i in rights])
    print('词准确率:', 100.0 * right_ratio)
    print('\n')


among the largest enterprises in taiwan are information related enterprises .
机器翻译: 在 台湾 百 大企业 中 , 信息 产业 企业 占 了 二十八 .
标准翻译: 在 台湾 百 大企业 中 , 信息 产业 企业 占 了 二十八 .
词准确率: 100.0


only with the adoption of the one country two systems principle can the taiwan issue be resolved peacefully .
机器翻译: 只有 实行 " 一国两制 " 方针 , 才能 使 台湾 问题 得到 和平 解决 .
标准翻译: 只有 实行 " 一国两制 " 方针 , 才能 使 台湾 问题 得到 和平 解决 .
词准确率: 100.0


 without computers or without going on the web i can still make the same money . 
机器翻译: " 没有 计算机 , 不 上网 , 我 一样 能 赚钱 . "
标准翻译: " 没有 计算机 , 不 上网 , 我 一样 能 赚钱 . "
词准确率: 100.0


 it is a pressing task to raise the position of patents .
机器翻译: " 专利 的 ' 地位 ' 亟待 提高 .
标准翻译: " 专利 的 ' 地位 ' 亟待 提高 .
词准确率: 100.0


article . the creation of a testamentary trust shall follow the provisions of the inheritance law concerning wills .
机器翻译: 第十三 设立 遗嘱 信托 , 应当 遵守 继承法 关於 遗嘱 的 规定 .
标准翻译: 第十三 设立 遗嘱 信托 , 应当 遵守 继承法 关於 遗嘱 的 规定 .
词准确率: 100.0


 the core product of the network economic era internet may likewise become the tool of hegemonism . 
机器翻译: " 网络 经济 时代 的 核心 产物 --- 因特网 , 同样 可能 成为 霸权主义 的 工具 " .
标准翻译: " 网络 经济 时代 的 核心 产物 --- 因特网 , 同样 可能 成为 霸权主义 的 工具 " .
词准确率: 100.0


during this period president jiang also met with egyptian president mubarak in alexandria egypt .
机器翻译: 在此 期间 , 江主席 还 在 埃及 亚历山 大港 同 埃及 总统 穆巴拉克 会晤 .
标准翻译: 在此 期间 , 江主席 还 在 埃及 亚历山 大港 同 埃及 总统 穆巴拉克 会晤 .
词准确率: 100.0


i feel greatly honored to have this experience .
机器翻译: 对 於 这 段 经历 , 个人 深感 荣幸 .
标准翻译: 对 於 这 段 经历 , 个人 深感 荣幸 .
词准确率: 100.0


taiwan certainly understands the importance of internationalization .
机器翻译: 台湾 不是不 明白 国际化 的 重要 .
标准翻译: 台湾 不是不 明白 国际化 的 重要 .
词准确率: 100.0


but emancipation of the mind and seeking truth from facts is a powerful force for guiding social progress .
机器翻译: 而 解放 思想 , 实事求是 , 则是 引导 社会 敖那看 力量 .
标准翻译: 而 解放 思想 , 实事求是 , 则是 引导 社会 敖那看 力量 .
词准确率: 100.0


total volume of retail sales in society in was . trillion yuan . percent of gdp .
机器翻译: 1999年 我国 社会 消费品 零售 总额 为 31135亿 , 占 GDP 的 37.9% .
标准翻译: 1999年 我国 社会 消费品 零售 总额 为 31135亿 , 占 GDP 的 37.9% .
词准确率: 100.0


us attitude and policy is the most important basis for such a mentality or strategy .
机器翻译: 美国 的 态度 和 政策 是 这种 心态 或 战略 的 首要 依据 .
标准翻译: 美国 的 态度 和 政策 是 这种 心态 或 战略 的 首要 依据 .
词准确率: 100.0


this is a rule proven by countless examples . we should keep it firmly in our minds .
机器翻译: 这个 凝聚 了 无数 经验 教训 的 规律性 认识 , 应当 为 我们 所 牢记 .
标准翻译: 这个 凝聚 了 无数 经验 教训 的 规律性 认识 , 应当 为 我们 所 牢记 .
词准确率: 100.0


croatia wishes to develop friendly relations with china in all fields and supports the one china policy .
机器翻译: 克罗地亚 希望 在 各个 领域 与 中国 发展 友好 关系 , 并 支持 一个 中国 的 政策 .
标准翻译: 克罗地亚 希望 在 各个 领域 与 中国 发展 友好 关系 , 并 支持 一个 中国 的 政策 .
词准确率: 100.0


this is certainly not saying something frightening just to scare people .
机器翻译: 这 决 不是 危言耸听 .
标准翻译: 这 决 不是 危言耸听 .
词准确率: 100.0


chi haotian conveyed president jiang zemin s cordial regards and favorable blessings to king sihanouk .
机器翻译: 迟浩田 转达 了 江泽民 主席 对 西哈努克 国王 的 亲切 问候 和 良好 祝愿 .
标准翻译: 迟浩田 转达 了 江泽民 主席 对 西哈努克 国王 的 亲切 问候 和 良好 祝愿 .
词准确率: 100.0


organization personnel departments are responsible for the concrete procedures of investigation and verification .
机器翻译: 具体 调查 核实 工作 , 由 组织 ( 人事 ) 部门 进行 .
标准翻译: 具体 调查 核实 工作 , 由 组织 ( 人事 ) 部门 进行 .
词准确率: 100.0


the deputies freely aired their views and the atmosphere at the two meetings was very lively .
机器翻译: 代表 们 畅所欲言 , 两 会场 气氛 都 很 活跃 .
标准翻译: 代表 们 畅所欲言 , 两 会场 气氛 都 很 活跃 .
词准确率: 100.0


therefore making an appropriate amendment to the existing ordinance is necessary .
机器翻译: 因此 , 对 现行 条例 进行 适当 充实 和 修改 是 必要 的 .
标准翻译: 因此 , 对 现行 条例 进行 适当 充实 和 修改 是 必要 的 .
词准确率: 100.0


among these there are five factors which should attract our serious attention .
机器翻译: 这 其中 有 五 因素 应该 引起 我们 的 高度 关注 .
标准翻译: 这 其中 有 五 因素 应该 引起 我们 的 高度 关注 .
词准确率: 100.0


Attention Model


In [12]:
# 重新处理数据形成训练数据、校验数据与测试数据,主要是MAX_Length更大了
# 设置句子的最大长度
MAX_LENGTH = 20

#对英文做标准化处理
pairs = [[normalizeEngString(eng), chi] for chi, eng in zip(chinese, english)]

# 对句子对做过滤,处理掉那些超过MAX_LENGTH长度的句子
input_lang = Lang('English')
output_lang = Lang('Chinese')
pairs = [pair for pair in pairs if filterPair(pair)]
print('有效句子对:', len(pairs))

# 建立两个字典(中文的和英文的)
for pair in pairs:
    input_lang.addSentence(pair[0])
    output_lang.addSentence(pair[1])
print("总单词数:")
print(input_lang.name, input_lang.n_words)
print(output_lang.name, output_lang.n_words)


# 形成训练集,首先,打乱所有句子的顺序
random_idx = np.random.permutation(range(len(pairs)))
pairs = [pairs[i] for i in random_idx]

# 将语言转变为单词的编码构成的序列
pairs = [indexFromPair(pair) for pair in pairs]
    
# 形成训练集、校验集和测试集
valid_size = len(pairs) // 10
if valid_size > 10000:
    valid_size = 10000
pairs = pairs[ : - valid_size]
valid_pairs = pairs[-valid_size : -valid_size // 2]
test_pairs = pairs[- valid_size // 2 :]

# 利用PyTorch的dataset和dataloader对象,将数据加载到加载器里面,并且自动分批

batch_size = 128 #一撮包含30个数据记录,这个数字越大,系统在训练的时候,每一个周期处理的数据就越多,这样处理越快,但总的数据量会减少

print('训练记录:', len(pairs))
print('校验记录:', len(valid_pairs))
print('测试记录:', len(test_pairs))

# 形成训练对列表,用于喂给train_dataset
pairs_X = [pair[0] for pair in pairs]
pairs_Y = [pair[1] for pair in pairs]
valid_X = [pair[0] for pair in valid_pairs]
valid_Y = [pair[1] for pair in valid_pairs]
test_X = [pair[0] for pair in test_pairs]
test_Y = [pair[1] for pair in test_pairs]


# 形成训练集
train_dataset = DataSet.TensorDataset(torch.LongTensor(pairs_X), torch.LongTensor(pairs_Y))
# 形成数据加载器
train_loader = DataSet.DataLoader(train_dataset, batch_size = batch_size, shuffle = True, num_workers=8)


# 校验数据
valid_dataset = DataSet.TensorDataset(torch.LongTensor(valid_X), torch.LongTensor(valid_Y))
valid_loader = DataSet.DataLoader(valid_dataset, batch_size = batch_size, shuffle = True, num_workers=8)

# 测试数据
test_dataset = DataSet.TensorDataset(torch.LongTensor(test_X), torch.LongTensor(test_Y))
test_loader = DataSet.DataLoader(test_dataset, batch_size = batch_size, shuffle = True, num_workers = 8)


有效句子对: 19919
总单词数:
English 13493
Chinese 18671
训练记录: 17928
校验记录: 995
测试记录: 996

In [13]:
# 定义基于注意力的解码器RNN
class AttnDecoderRNN(nn.Module):
    def __init__(self, hidden_size, output_size, n_layers=1, dropout_p=0.1, max_length=MAX_LENGTH):
        super(AttnDecoderRNN, self).__init__()
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.n_layers = n_layers
        self.dropout_p = dropout_p
        self.max_length = max_length

        # 词嵌入层
        self.embedding = nn.Embedding(self.output_size, self.hidden_size)
        
        # 注意力网络(一个前馈神经网络)
        self.attn = nn.Linear(self.hidden_size * (2 * n_layers + 1), self.max_length)
    
        # 注意力机制作用完后的结果映射到后面的层
        self.attn_combine = nn.Linear(self.hidden_size * 3, self.hidden_size)
        
        # dropout操作层
        self.dropout = nn.Dropout(self.dropout_p)
        
        # 定义一个双向GRU,并设置batch_first为True以方便操作
        self.gru = nn.GRU(self.hidden_size, self.hidden_size, bidirectional = True,
                         num_layers = self.n_layers, batch_first = True)
        self.out = nn.Linear(self.hidden_size * 2, self.output_size)

    def forward(self, input, hidden, encoder_outputs):
        # 解码器的一步操作
        # input大小:batch_size, length_seq
        embedded = self.embedding(input)
        # embedded大小:batch_size, length_seq, hidden_size
        embedded = embedded[:, 0, :]
        # embedded大小:batch_size, hidden_size
        embedded = self.dropout(embedded)
        
        # 将hidden张量数据转化成batch_size排在第0维的形状
        # hidden大小:direction*n_layer, batch_size, hidden_size
        temp_for_transpose = torch.transpose(hidden, 0, 1).contiguous()
        temp_for_transpose = temp_for_transpose.view(temp_for_transpose.size()[0], -1)
        hidden_attn = temp_for_transpose
        
        # 注意力层的输入
        # hidden_attn大小:batch_size, direction*n_layers*hidden_size
        input_to_attention = torch.cat((embedded, hidden_attn), 1)
        # input_to_attention大小:batch_size, hidden_size * (1 + direction * n_layers)
        
        # 注意力层输出的权重
        attn_weights = F.softmax(self.attn(input_to_attention))
        # attn_weights大小:batch_size, max_length
        
        # 当输入数据不标准的时候,对weights截取必要的一段
        attn_weights = attn_weights[:, : encoder_outputs.size()[1]]
        # attn_weights大小:batch_size, length_seq_of_encoder
        attn_weights = attn_weights.unsqueeze(1)
        # attn_weights大小:batch_size, 1, length_seq 中间的1是为了bmm乘法用的
        
        # 将attention的weights矩阵乘encoder_outputs以计算注意力完的结果
        # encoder_outputs大小:batch_size, seq_length, hidden_size*direction
        attn_applied = torch.bmm(attn_weights, encoder_outputs) 
        # attn_applied大小:batch_size, 1, hidden_size*direction
        # bmm: 两个矩阵相乘。忽略第一个batch纬度,缩并时间维度
        
        # 将输入的词向量与注意力机制作用后的结果拼接成一个大的输入向量
        output = torch.cat((embedded, attn_applied[:,0,:]), 1)
        # output大小:batch_size, hidden_size * (direction + 1)
        
        # 将大输入向量映射为GRU的隐含层
        output = self.attn_combine(output).unsqueeze(1)
        # output大小:batch_size, length_seq, hidden_size
        output = F.relu(output)
        
        # output的结果再dropout
        output = self.dropout(output)

        # 开始解码器GRU的运算
        output, hidden = self.gru(output, hidden)
        
        
        # output大小:batch_size, length_seq, hidden_size * directions
        # hidden大小:n_layers * directions, batch_size, hidden_size
        
        #取出GRU运算最后一步的结果喂给最后一层全链接层
        output = self.out(output[:, -1, :])
        # output大小:batch_size * output_size
        
        # 取logsoftmax,计算输出结果
        output = F.log_softmax(output)
        # output大小:batch_size * output_size
        return output, hidden, attn_weights

    def initHidden(self, batch_size):
        # 初始化解码器隐单元,尺寸为n_layers * directions, batch_size, hidden_size
        result = Variable(torch.zeros(self.n_layers * 2, batch_size, self.hidden_size))
        if use_cuda:
            return result.cuda()
        else:
            return result

In [15]:
# 开始带有注意力机制的RNN训练

#定义网络架构
hidden_size = 512
max_length = MAX_LENGTH
n_layers = 1
encoder = EncoderRNN(input_lang.n_words, hidden_size, n_layers = n_layers)
decoder = AttnDecoderRNN(hidden_size, output_lang.n_words, dropout_p=0.5,
                         max_length = max_length, n_layers = n_layers)

if use_cuda:
    encoder = encoder.cuda()
    decoder = decoder.cuda()

print_loss_total = 0  # Reset every print_every
learning_rate = 0.001
encoder_optimizer = optim.Adam(encoder.parameters(), lr=learning_rate)
decoder_optimizer = optim.Adam(decoder.parameters(), lr=learning_rate)

criterion = nn.NLLLoss()
#criterion = Batch_NLLLoss
teacher_forcing_ratio = 0.5

num_epoch = 100

# 开始训练周期循环
plot_losses = []
for epoch in range(num_epoch):
    # 将解码器置于训练状态,让dropout工作
    decoder.train()
    print_loss_total = 0
    # 对训练数据进行循环
    for data in train_loader:
        input_variable = Variable(data[0]).cuda() if use_cuda else Variable(data[0])
        # input_variable的大小:batch_size, length_seq
        target_variable = Variable(data[1]).cuda() if use_cuda else Variable(data[1])
        # target_variable的大小:batch_size, length_seq
        
        #清空梯度
        encoder_optimizer.zero_grad()
        decoder_optimizer.zero_grad()
        
        encoder_hidden = encoder.initHidden(data[0].size()[0])

        loss = 0

        #编码器开始工作
        encoder_outputs, encoder_hidden = encoder(input_variable, encoder_hidden)
        # encoder_outputs的大小:batch_size, length_seq, hidden_size*direction
        # encoder_hidden的大小:direction*n_layer, batch_size, hidden_size

        # 解码器开始工作
        decoder_input = Variable(torch.LongTensor([[SOS_token]] * target_variable.size()[0]))
        # decoder_input大小:batch_size, length_seq
        decoder_input = decoder_input.cuda() if use_cuda else decoder_input

        # 将编码器的隐含层单元取值作为编码的结果传递给解码器
        decoder_hidden = encoder_hidden
        # decoder_hidden大小:direction*n_layer, batch_size, hidden_size

        # 同时按照两种方式训练解码器:用教师监督的信息作为下一时刻的输入和不用监督的信息,用自己预测结果作为下一时刻的输入
        use_teacher_forcing = True if random.random() < teacher_forcing_ratio else False
        if use_teacher_forcing:
            # 用监督信息作为下一时刻解码器的输入
            # 开始时间不得循环
            for di in range(MAX_LENGTH):
                # 输入给解码器的信息包括输入的单词decoder_input, 解码器上一时刻的因曾单元状态,
                # 编码器各个时间步的输出结果
                decoder_output, decoder_hidden, decoder_attention = decoder(
                    decoder_input, decoder_hidden, encoder_outputs)
                #decoder_ouput大小:batch_size, output_size
                #计算损失函数,得到下一时刻的解码器的输入
                loss += criterion(decoder_output, target_variable[:, di])
                decoder_input = target_variable[:, di].unsqueeze(1)  # Teacher forcing
                # decoder_input大小:batch_size, length_seq
        else:
            # 没有教师监督,用解码器自己的预测作为下一时刻的输入

            # 对时间步进行循环
            for di in range(MAX_LENGTH):
                decoder_output, decoder_hidden, decoder_attention = decoder(
                    decoder_input, decoder_hidden, encoder_outputs)
                #decoder_ouput大小:batch_size, output_size(vocab_size)
                # 获取解码器的预测结果,并用它来作为下一时刻的输入
                topv, topi = decoder_output.data.topk(1, dim = 1)
                #topi 尺寸:batch_size, k
                ni = topi[:, 0]

                decoder_input = Variable(ni.unsqueeze(1))
                # decoder_input大小:batch_size, length_seq
                decoder_input = decoder_input.cuda() if use_cuda else decoder_input

                # 计算损失函数
                loss += criterion(decoder_output, target_variable[:, di])
        
        
        
        # 反向传播开始
        loss.backward()
        loss = loss.cpu() if use_cuda else loss
        # 开始梯度下降
        encoder_optimizer.step()
        decoder_optimizer.step()
        print_loss_total += loss.data.numpy()[0]

    print_loss_avg = print_loss_total / len(train_loader)
        
    valid_loss = 0
    rights = []
    # 将解码器的training设置为False,以便关闭dropout
    decoder.eval()
    
    #对所有的校验数据做循环
    for data in valid_loader:
        input_variable = Variable(data[0]).cuda() if use_cuda else Variable(data[0])
        # input_variable的大小:batch_size, length_seq
        target_variable = Variable(data[1]).cuda() if use_cuda else Variable(data[1])
        # target_variable的大小:batch_size, length_seq

        encoder_hidden = encoder.initHidden(data[0].size()[0])

        loss = 0
        encoder_outputs, encoder_hidden = encoder(input_variable, encoder_hidden)
        # encoder_outputs的大小:batch_size, length_seq, hidden_size*direction
        # encoder_hidden的大小:direction*n_layer, batch_size, hidden_size

        decoder_input = Variable(torch.LongTensor([[SOS_token]] * target_variable.size()[0]))
        # decoder_input大小:batch_size, length_seq
        decoder_input = decoder_input.cuda() if use_cuda else decoder_input

        decoder_hidden = encoder_hidden
        # decoder_hidden大小:direction*n_layer, batch_size, hidden_size

        # 开始每一步的预测
        for di in range(MAX_LENGTH):
            decoder_output, decoder_hidden, decoder_attention = decoder(
                decoder_input, decoder_hidden, encoder_outputs)
            #decoder_ouput大小:batch_size, output_size(vocab_size)
            topv, topi = decoder_output.data.topk(1, dim = 1)
            #topi 尺寸:batch_size, k
            ni = topi[:, 0]

            decoder_input = Variable(ni.unsqueeze(1))
            # decoder_input大小:batch_size, length_seq
            decoder_input = decoder_input.cuda() if use_cuda else decoder_input
            right = rightness(decoder_output, target_variable[:, di].unsqueeze(1))
            rights.append(right)
            loss += criterion(decoder_output, target_variable[:, di])
        loss = loss.cpu() if use_cuda else loss
        valid_loss += loss.data.numpy()[0]
    # 计算平均损失、准确率等指标并打印输出
    right_ratio = 1.0 * np.sum([i[0] for i in rights]) / np.sum([i[1] for i in rights])
    print('进程:%d%% 训练损失:%.4f,校验损失:%.4f,词正确率:%.2f%%' % (epoch * 1.0 / num_epoch * 100, 
                                                    print_loss_avg,
                                                    valid_loss / len(valid_loader),
                                                    100.0 * right_ratio))
    plot_losses.append([print_loss_avg, valid_loss / len(valid_loader), right_ratio])


进程:0% 训练损失:89.6669,校验损失:79.2543,词正确率:44.23%
进程:1% 训练损失:74.8581,校验损失:67.2460,词正确率:46.35%
进程:2% 训练损失:64.7946,校验损失:55.5135,词正确率:48.69%
进程:3% 训练损失:54.2651,校验损失:48.5774,词正确率:49.04%
进程:4% 训练损失:45.6932,校验损失:40.5560,词正确率:53.01%
进程:5% 训练损失:39.3723,校验损失:36.4155,词正确率:55.50%
进程:6% 训练损失:35.1993,校验损失:32.9861,词正确率:57.75%
进程:7% 训练损失:32.0932,校验损失:29.8463,词正确率:60.23%
进程:8% 训练损失:29.0793,校验损失:26.7694,词正确率:62.58%
进程:9% 训练损失:26.2716,校验损失:25.6091,词正确率:64.27%
进程:10% 训练损失:24.8974,校验损失:22.5243,词正确率:67.12%
进程:11% 训练损失:22.0474,校验损失:21.1891,词正确率:68.76%
进程:12% 训练损失:20.5010,校验损失:19.1615,词正确率:71.42%
进程:13% 训练损失:19.2679,校验损失:18.7403,词正确率:71.50%
进程:14% 训练损失:18.2607,校验损失:17.1220,词正确率:73.90%
进程:15% 训练损失:17.0232,校验损失:16.1669,词正确率:75.39%
进程:16% 训练损失:16.5166,校验损失:15.0679,词正确率:76.99%
进程:17% 训练损失:15.0868,校验损失:14.7526,词正确率:77.27%
进程:18% 训练损失:13.9732,校验损失:13.3596,词正确率:79.93%
进程:19% 训练损失:14.1486,校验损失:12.7356,词正确率:80.41%
进程:20% 训练损失:13.3840,校验损失:12.2194,词正确率:81.17%
进程:21% 训练损失:13.1702,校验损失:11.2797,词正确率:82.30%
进程:22% 训练损失:12.4909,校验损失:11.2673,词正确率:82.97%
进程:23% 训练损失:11.8266,校验损失:10.9031,词正确率:83.89%
进程:24% 训练损失:11.4997,校验损失:10.5409,词正确率:84.33%
进程:25% 训练损失:10.8413,校验损失:9.2903,词正确率:85.76%
进程:26% 训练损失:10.4766,校验损失:8.9163,词正确率:86.69%
进程:27% 训练损失:10.0020,校验损失:8.7104,词正确率:87.04%
进程:28% 训练损失:10.0676,校验损失:8.3438,词正确率:88.57%
进程:28% 训练损失:9.9576,校验损失:7.9379,词正确率:88.39%
进程:30% 训练损失:9.2678,校验损失:7.8831,词正确率:88.60%
进程:31% 训练损失:8.8264,校验损失:7.3391,词正确率:89.41%
进程:32% 训练损失:8.9541,校验损失:7.0666,词正确率:89.51%
进程:33% 训练损失:8.4297,校验损失:7.1037,词正确率:89.85%
进程:34% 训练损失:8.4342,校验损失:6.9194,词正确率:90.01%
进程:35% 训练损失:8.7774,校验损失:6.8119,词正确率:89.89%
进程:36% 训练损失:8.4603,校验损失:6.5479,词正确率:90.18%
进程:37% 训练损失:7.7845,校验损失:5.9770,词正确率:91.63%
进程:38% 训练损失:7.6211,校验损失:5.4062,词正确率:92.30%
进程:39% 训练损失:7.4247,校验损失:6.1076,词正确率:91.17%
进程:40% 训练损失:7.0546,校验损失:5.8023,词正确率:92.03%
进程:41% 训练损失:7.5543,校验损失:5.7240,词正确率:92.04%
进程:42% 训练损失:7.3090,校验损失:5.3772,词正确率:92.73%
进程:43% 训练损失:7.0847,校验损失:5.3736,词正确率:92.41%
进程:44% 训练损失:6.6455,校验损失:4.7111,词正确率:93.40%
进程:45% 训练损失:6.6174,校验损失:4.4118,词正确率:93.82%
进程:46% 训练损失:6.0971,校验损失:4.6046,词正确率:93.92%
进程:47% 训练损失:6.5217,校验损失:4.8337,词正确率:93.46%
进程:48% 训练损失:6.4342,校验损失:5.0569,词正确率:93.01%
进程:49% 训练损失:6.8248,校验损失:5.0146,词正确率:93.16%
进程:50% 训练损失:6.7347,校验损失:4.7396,词正确率:93.58%
进程:51% 训练损失:6.6211,校验损失:4.5945,词正确率:93.67%
进程:52% 训练损失:6.2874,校验损失:4.4743,词正确率:94.12%
进程:53% 训练损失:6.2553,校验损失:4.6408,词正确率:93.59%
进程:54% 训练损失:6.2155,校验损失:3.8962,词正确率:94.55%
进程:55% 训练损失:5.7323,校验损失:4.0279,词正确率:94.39%
进程:56% 训练损失:5.5396,校验损失:3.7844,词正确率:95.09%
进程:56% 训练损失:5.6002,校验损失:4.0665,词正确率:94.28%
进程:57% 训练损失:5.7555,校验损失:3.8975,词正确率:94.82%
进程:59% 训练损失:5.7133,校验损失:3.6394,词正确率:94.98%
进程:60% 训练损失:5.9161,校验损失:3.7131,词正确率:95.16%
进程:61% 训练损失:5.3781,校验损失:3.7303,词正确率:95.16%
进程:62% 训练损失:5.2828,校验损失:3.2557,词正确率:95.63%
进程:63% 训练损失:5.5715,校验损失:3.3735,词正确率:95.60%
进程:64% 训练损失:5.5557,校验损失:3.0932,词正确率:96.01%
进程:65% 训练损失:5.1992,校验损失:3.1573,词正确率:95.99%
进程:66% 训练损失:5.6073,校验损失:3.7790,词正确率:94.88%
进程:67% 训练损失:5.5888,校验损失:3.5976,词正确率:95.38%
进程:68% 训练损失:5.3647,校验损失:3.4114,词正确率:95.58%
进程:69% 训练损失:5.1835,校验损失:3.2325,词正确率:95.69%
进程:70% 训练损失:4.9639,校验损失:3.6286,词正确率:95.33%
进程:71% 训练损失:5.1501,校验损失:3.1121,词正确率:95.85%
进程:72% 训练损失:5.3315,校验损失:2.9514,词正确率:96.07%
进程:73% 训练损失:5.2332,校验损失:2.9593,词正确率:96.10%
进程:74% 训练损失:5.1398,校验损失:3.2003,词正确率:95.80%
进程:75% 训练损失:5.1510,校验损失:3.5113,词正确率:95.53%
进程:76% 训练损失:5.0883,校验损失:3.0001,词正确率:96.12%
进程:77% 训练损失:5.0356,校验损失:3.0254,词正确率:96.10%
进程:78% 训练损失:5.1487,校验损失:3.2843,词正确率:95.59%
进程:79% 训练损失:4.9457,校验损失:2.9960,词正确率:96.08%
进程:80% 训练损失:5.1405,校验损失:2.7970,词正确率:96.37%
进程:81% 训练损失:5.1590,校验损失:2.7372,词正确率:96.54%
进程:82% 训练损失:5.0949,校验损失:3.4122,词正确率:95.61%
进程:83% 训练损失:4.8899,校验损失:2.7931,词正确率:96.55%
进程:84% 训练损失:4.7795,校验损失:2.6878,词正确率:96.61%
进程:85% 训练损失:4.9654,校验损失:2.9914,词正确率:96.16%
进程:86% 训练损失:4.3562,校验损失:2.7543,词正确率:96.34%
进程:87% 训练损失:4.4136,校验损失:2.3619,词正确率:97.05%
进程:88% 训练损失:4.1356,校验损失:2.2241,词正确率:97.10%
进程:89% 训练损失:4.1142,校验损失:2.6187,词正确率:96.78%
进程:90% 训练损失:4.3638,校验损失:2.8109,词正确率:96.63%
进程:91% 训练损失:4.8397,校验损失:2.7567,词正确率:96.55%
进程:92% 训练损失:4.9861,校验损失:2.7469,词正确率:96.44%
进程:93% 训练损失:5.5848,校验损失:3.6194,词正确率:95.29%
进程:94% 训练损失:4.8042,校验损失:2.8652,词正确率:96.22%
进程:95% 训练损失:5.1062,校验损失:3.0192,词正确率:96.23%
进程:96% 训练损失:5.0399,校验损失:2.8923,词正确率:96.30%
进程:97% 训练损失:4.7730,校验损失:2.9350,词正确率:96.53%
进程:98% 训练损失:4.6652,校验损失:2.4807,词正确率:96.68%
进程:99% 训练损失:4.6319,校验损失:2.5480,词正确率:96.68%

In [16]:
# 绘制统计指标曲线图
torch.save(encoder, 'encoder-final.mdl')
torch.save(decoder, 'decoder-final.mdl')
a = [i[0] for i in plot_losses]
b = [i[1] for i in plot_losses]
c = [i[2] * 100 for i in plot_losses]
plt.plot(a, label = 'Training Loss')
plt.plot(b, label = 'Validation Loss')
plt.plot(c, label = 'Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Loss & Accuracy')
plt.legend()


/home/fuyang/Workspace/deep_learning_tutorial/p3ml-venv/lib/python3.6/site-packages/torch/serialization.py:147: UserWarning: Couldn't retrieve source code for container of type EncoderRNN. It won't be checked for correctness upon loading.
  "type " + obj.__name__ + ". It won't be checked "
/home/fuyang/Workspace/deep_learning_tutorial/p3ml-venv/lib/python3.6/site-packages/torch/serialization.py:147: UserWarning: Couldn't retrieve source code for container of type AttnDecoderRNN. It won't be checked for correctness upon loading.
  "type " + obj.__name__ + ". It won't be checked "
Out[16]:
<matplotlib.legend.Legend at 0x7f2458310898>

In [17]:
# 从测试集中随机挑选20个句子来测试翻译的结果
indices = np.random.choice(range(len(test_X)), 20)
for ind in indices:
    data = [test_X[ind]]
    target = [test_Y[ind]]
    print(SentenceFromList(input_lang, data[0]))
    input_variable = Variable(torch.LongTensor(data)).cuda() if use_cuda else Variable(torch.LongTensor(data))
    # input_variable的大小:batch_size, length_seq
    target_variable = Variable(torch.LongTensor(target)).cuda() if use_cuda else Variable(torch.LongTensor(target))
    # target_variable的大小:batch_size, length_seq

    encoder_hidden = encoder.initHidden(input_variable.size()[0])

    loss = 0
    encoder_outputs, encoder_hidden = encoder(input_variable, encoder_hidden)
    # encoder_outputs的大小:batch_size, length_seq, hidden_size*direction
    # encoder_hidden的大小:direction*n_layer, batch_size, hidden_size

    decoder_input = Variable(torch.LongTensor([[SOS_token]] * target_variable.size()[0]))
    # decoder_input大小:batch_size, length_seq
    decoder_input = decoder_input.cuda() if use_cuda else decoder_input

    decoder_hidden = encoder_hidden
    # decoder_hidden大小:direction*n_layer, batch_size, hidden_size

    # Without teacher forcing: use its own predictions as the next input
    output_sentence = []
    decoder_attentions = torch.zeros(max_length, max_length)
    rights = []
    for di in range(MAX_LENGTH):
        decoder_output, decoder_hidden, decoder_attention = decoder(
            decoder_input, decoder_hidden, encoder_outputs)
        #decoder_ouput大小:batch_size, output_size(vocab_size)
        topv, topi = decoder_output.data.topk(1, dim = 1)
        decoder_attentions[di] = decoder_attention.data
        #topi 尺寸:batch_size, k
        ni = topi[:, 0]
        decoder_input = Variable(ni.unsqueeze(1))
        ni = ni.cpu().numpy()[0] if use_cuda else ni.numpy()[0]
        output_sentence.append(ni)
        # decoder_input大小:batch_size, length_seq
        decoder_input = decoder_input.cuda() if use_cuda else decoder_input
        right = rightness(decoder_output, target_variable[:, di].unsqueeze(1))
        rights.append(right)
    sentence = SentenceFromList(output_lang, output_sentence)
    standard = SentenceFromList(output_lang, target[0])
    print('机器翻译:', sentence)
    print('标准翻译:', standard)
    # 输出本句话的准确率
    right_ratio = 1.0 * np.sum([i[0] for i in rights]) / np.sum([i[1] for i in rights])
    print('词准确率:', 100.0 * right_ratio)
    print('\n')


however this is only wishful thinking .
机器翻译: 但 这 只是 一厢情愿 .
标准翻译: 但 这 只是 一厢情愿 .
词准确率: 100.0


the overall situation in the past three months since macao s return to the motherland has been good .
机器翻译: 澳门 回归祖国 近 3个 月 来 , 总的 形势 是 好的 .
标准翻译: 澳门 回归祖国 近 3个 月 来 , 总的 形势 是 好的 .
词准确率: 100.0


li peng extended a warm welcome to bouteflika .
机器翻译: 李鹏 对 布特弗利卡 访华 表示 热烈 欢迎 .
标准翻译: 李鹏 对 布特弗利卡 访华 表示 热烈 欢迎 .
词准确率: 100.0


confrontation on the human rights issue will lead nowhere .
机器翻译: 在 人权 问题 上 搞 对抗 , 是 没有 出路 的 .
标准翻译: 在 人权 问题 上 搞 对抗 , 是 没有 出路 的 .
词准确率: 100.0


answer it should be said that we have done a lot of fruitful work in this regard .
机器翻译: 答 : 应该说 , 我们 在 这 方面 做 了 大量 卓有成效 的 工作 .
标准翻译: 答 : 应该说 , 我们 在 这 方面 做 了 大量 卓有成效 的 工作 .
词准确率: 100.0


since when china and the united states established diplomatic relations sino us relations has developed amid struggle .
机器翻译: 从 一九七九年 中美 建交 至今 , 中美 关系 一直 在 中美 中 向前 发展 .
标准翻译: 从 一九七九年 中美 建交 至今 , 中美 关系 一直 在 斗争 中 向前 发展 .
词准确率: 95.0


they symbolize the two broad trends in china s modernization .
机器翻译: 这 两 件 事 , 标志 著 我国 现代化 发展 的 大趋势 大趋势 .
标准翻译: 这 两 件 事 , 标志 著 我国 现代化 发展 的 两 大趋势 .
词准确率: 95.0


this major shift in the dprk s foreign policy has attracted wide attention in the international community .
机器翻译: 朝鲜 对外 政策 的 这 一 重大 转变 , 引起 国际 社会 的 广泛 关注 .
标准翻译: 朝鲜 对外 政策 的 这 一 重大 转变 , 引起 国际 社会 的 广泛 关注 .
词准确率: 100.0


in this way president jiang brought them closer together .
机器翻译: " 江主席 一 语 拉 近 了 彼此的 距离 .
标准翻译: " 江主席 一 语 拉 近 了 彼此的 距离 .
词准确率: 100.0


more than fishing boats were sent out on april .
机器翻译: 七日 , 仅 渔船 就 出动 八百 多 艘 .
标准翻译: 七日 , 仅 渔船 就 出动 八百 多 艘 .
词准确率: 100.0


the current level of enjoying human rights by the chinese people is higher than any period in history .
机器翻译: 中国 人民 享受 人权 的 水平高 於 於 历史 上 任何 时期 .
标准翻译: 中国 人民 享受 人权 的 水平高 於 历史 上 任何 一个 时期 .
词准确率: 80.0


industrial output is growing faster with the operating quality of industrial enterprises markedly improved .
机器翻译: 生产 生产 增长 较快 , 工业 企业 运行 质量 明显 提高 .
标准翻译: 工业 生产 增长 较快 , 工业 企业 运行 质量 明显 提高 .
词准确率: 95.0


there are many well known friends here today and all the seats are occupied by distinguished guests .
机器翻译: 这里 , 这里 胜 友 如云 , 高朋满座 .
标准翻译: 今天 , 这里 胜 友 如云 , 高朋满座 .
词准确率: 95.0


the spirit of struggling persistently is the spiritual backbone in advancing the great cause of socialist modernization construction .
机器翻译: 不懈 奋斗 的 精神 , 就是 我们 推进 社会主义 现代化 建设 伟大 的 精神 支柱 .
标准翻译: 不懈 奋斗 的 精神 , 就是 我们 推进 社会主义 现代化 建设 伟大 事业 的 精神 支柱 .
词准确率: 75.0


the guests and the hosts cordially talked with each other and toasted to the two countries friendship .
机器翻译: 宾主 亲切 交谈 , 中 柬 柬 友谊 频频 举杯 .
标准翻译: 宾主 亲切 交谈 , 为 中 柬 友谊 频频 举杯 .
词准确率: 90.0


sino foreign joint ventures are encouraged to market their products in overseas markets .
机器翻译: 鼓励 合营 企业 向 中国 境外 销售 产品 .
标准翻译: 鼓励 合营 企业 向 中国 境外 销售 产品 .
词准确率: 100.0


in addition the nato countries are reluctant to always be slavishly dependent on the united states .
机器翻译: 还有 , 北约 18 国 不 愿意 总是 仰 美国 之 鼻息 过日子 .
标准翻译: 还有 , 北约 18 国 不 愿意 总是 仰 美国 之 鼻息 过日子 .
词准确率: 100.0


it was the first visit of us state secretary to central asian countries since their independence .
机器翻译: 这是 中亚 各国 独立 以来 美国 国务卿 的 首次 访问 .
标准翻译: 这是 中亚 各国 独立 以来 美国 国务卿 的 首次 访问 .
词准确率: 100.0


 it is necessary to use the credit leverage to promote the adjustment of economic structure .
机器翻译: ( 四 ) 运用 信贷 杠杆 促进 经济 结构 调整 .
标准翻译: ( 四 ) 运用 信贷 杠杆 促进 经济 结构 调整 .
词准确率: 100.0


seventh educational and scientific undertakings should be developed .
机器翻译: 第七 , 教育 教育 和 科学 事业 .
标准翻译: 第七 , 发展 教育 和 科学 事业 .
词准确率: 95.0



In [31]:
# 通过几个特殊的句子翻译,考察注意力机制关注的情况
data = '人民币 汇率 继续 保持 稳定 .'
#data = '五 是 干部 交流 工作 迈出 较大 步伐 .'
data = 'he pledged that he would make no compromise when pushing ahead with this scheme .'
data = np.array([indexFromSentence(input_lang, data)])

input_variable = Variable(torch.LongTensor(data)).cuda() if use_cuda else Variable(torch.LongTensor(data))
# input_variable的大小:batch_size, length_seq
target_variable = Variable(torch.LongTensor(target)).cuda() if use_cuda else Variable(torch.LongTensor(target))
# target_variable的大小:batch_size, length_seq

encoder_hidden = encoder.initHidden(input_variable.size()[0])

loss = 0
encoder_outputs, encoder_hidden = encoder(input_variable, encoder_hidden)
# encoder_outputs的大小:batch_size, length_seq, hidden_size*direction
# encoder_hidden的大小:direction*n_layer, batch_size, hidden_size

decoder_input = Variable(torch.LongTensor([[SOS_token]] * target_variable.size()[0]))
# decoder_input大小:batch_size, length_seq
decoder_input = decoder_input.cuda() if use_cuda else decoder_input

decoder_hidden = encoder_hidden
# decoder_hidden大小:direction*n_layer, batch_size, hidden_size

output_sentence = []
decoder_attentions = torch.zeros(max_length, max_length)
for di in range(MAX_LENGTH):
    decoder_output, decoder_hidden, decoder_attention = decoder(
        decoder_input, decoder_hidden, encoder_outputs)
    #decoder_ouput大小:batch_size, output_size(vocab_size)
    topv, topi = decoder_output.data.topk(1, dim = 1)
    
    # 在每一步,获取了注意力的权重向量,并将其存储到了decoder_attentions之中
    decoder_attentions[di] = decoder_attention.data
    #topi 尺寸:batch_size, k
    ni = topi[:, 0]
    decoder_input = Variable(ni.unsqueeze(1))
    ni = ni.cpu().numpy()[0] if use_cuda else ni.numpy()[0]
    output_sentence.append(ni)
    # decoder_input大小:batch_size, length_seq
    decoder_input = decoder_input.cuda() if use_cuda else decoder_input
    right = rightness(decoder_output, target_variable[:, di].unsqueeze(1))
    rights.append(right)
sentence = SentenceFromList(output_lang, output_sentence)
print('机器翻译:', sentence)
print('\n')


机器翻译: 他 表示 , 在 推动 这项 计划 方面 不会 作出 妥协 .



In [33]:
# 通过几个特殊的句子翻译,考察注意力机制关注的情况
data = '人民币 汇率 继续 保持 稳定 .'
#data = '五 是 干部 交流 工作 迈出 较大 步伐 .'
data = 'she pledged that she would make no compromise when pushing ahead with this scheme .'
data = np.array([indexFromSentence(input_lang, data)])

input_variable = Variable(torch.LongTensor(data)).cuda() if use_cuda else Variable(torch.LongTensor(data))
# input_variable的大小:batch_size, length_seq
target_variable = Variable(torch.LongTensor(target)).cuda() if use_cuda else Variable(torch.LongTensor(target))
# target_variable的大小:batch_size, length_seq

encoder_hidden = encoder.initHidden(input_variable.size()[0])

loss = 0
encoder_outputs, encoder_hidden = encoder(input_variable, encoder_hidden)
# encoder_outputs的大小:batch_size, length_seq, hidden_size*direction
# encoder_hidden的大小:direction*n_layer, batch_size, hidden_size

decoder_input = Variable(torch.LongTensor([[SOS_token]] * target_variable.size()[0]))
# decoder_input大小:batch_size, length_seq
decoder_input = decoder_input.cuda() if use_cuda else decoder_input

decoder_hidden = encoder_hidden
# decoder_hidden大小:direction*n_layer, batch_size, hidden_size

output_sentence = []
decoder_attentions = torch.zeros(max_length, max_length)
for di in range(MAX_LENGTH):
    decoder_output, decoder_hidden, decoder_attention = decoder(
        decoder_input, decoder_hidden, encoder_outputs)
    #decoder_ouput大小:batch_size, output_size(vocab_size)
    topv, topi = decoder_output.data.topk(1, dim = 1)
    
    # 在每一步,获取了注意力的权重向量,并将其存储到了decoder_attentions之中
    decoder_attentions[di] = decoder_attention.data
    #topi 尺寸:batch_size, k
    ni = topi[:, 0]
    decoder_input = Variable(ni.unsqueeze(1))
    ni = ni.cpu().numpy()[0] if use_cuda else ni.numpy()[0]
    output_sentence.append(ni)
    # decoder_input大小:batch_size, length_seq
    decoder_input = decoder_input.cuda() if use_cuda else decoder_input
    right = rightness(decoder_output, target_variable[:, di].unsqueeze(1))
    rights.append(right)
sentence = SentenceFromList(output_lang, output_sentence)
print('机器翻译:', sentence)
print('\n')


机器翻译: 她 在 这 就 到 时候 , 就 不会 作出 得 .



In [34]:
# 将每一步存储的注意力权重组合到一起就形成了注意力矩阵,绘制为图
plt.matshow(decoder_attentions.numpy())


Out[34]:
<matplotlib.image.AxesImage at 0x116bddda0>

In [ ]: