7.1利用pytorch构造神经翻译机(代码片段)

王小小小草 王小小小草     2022-12-03     680

关键词:

欢迎订阅本专栏:《PyTorch深度学习实践》
订阅地址:https://blog.csdn.net/sinat_33761963/category_9720080.html

  • 第二章:认识Tensor的类型、创建、存储、api等,打好Tensor的基础,是进行PyTorch深度学习实践的重中之重的基础。
  • 第三章:学习PyTorch如何读入各种外部数据
  • 第四章:利用PyTorch从头到尾创建、训练、评估一个模型,理解与熟悉PyTorch实现模型的每个步骤,用到的模块与方法。
  • 第五章:学习如何利用PyTorch提供的3种方法去创建各种模型结构。
  • 第六章:利用PyTorch实现简单与经典的模型全过程:简单二分类、手写字体识别、词向量的实现、自编码器实现。
  • 第七章:利用PyTorch实现复杂模型:翻译机(nlp领域)、生成对抗网络(GAN)、强化学习(RL)、风格迁移(cv领域)。
  • 第八章:PyTorch的其他高级用法:模型在不同框架之间的迁移、可视化、多个GPU并行计算。

机器翻译其实是走得比较先的AI应用了,后来的许多自然语言生成的任务多少都在借鉴着机器翻译的一些研究与实践成果。如果你是做自然语言处理的,那么下面这篇论文是必读无疑的,关于sequence to sequence ,关于attention的知识也是必须知道。

论文:Bahdanau, Neural Machine Translation By Jointly Language to Align and Translate

本节就是针对以上论文中模型的一个简单实现。

先导入本节代码需要的包:

from __future__ import unicode_literals, print_function, division
import unicodedata
import re
import random
import torch
import torch.nn as nn
import torch.nn.functional as F

use_cuda = False

7.1.1 准备数据

原始数据

机器翻译模型输入是语言A,输出是翻译后的结果语言B。在本项目的data/translation_data/路径下有一份平行语料,chinese1为中文,english1为英文,每行一个样本,两份数据的行之间是对应的翻译结果。

数据处理

  • (1)预处理数据,将文本从Unicode格式转换为ASCII格式,去除大部分标点符号,大写字母转换成小写字母,这些操作用normaliz_string函数进行封装。
  • (2)过滤数据,过滤掉一些不符合设定标注的句子,比如过滤掉长度大于30的句子, 用filter_pairs函数封装。
  • (3)构建词典,分别对两个语言的语料构建词典,即词对应数字编码,用类LangEmbed()来构建。

下面看看代码具体的实现:

1)预处理数据的函数:

def unicode2ascii(s):
    return ''.join(
        c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn'
    )

def normalize_string(s):
    s = unicode2ascii(s.lower().strip()) # 小写、去前后空格、转ascii格式
    s = re.sub("[.!?。!?]", "\\1", s)  # 处理标点
    return s

2)过滤数据的函数, 为了简化,我们设置保留长度小于30的句子。

MAX_LENGTH = 10

def filter_pairs(pairs):
    p = []
    for pair in pairs:
        if len(pair[0]) <= MAX_LENGTH and len(pair[1]) <= MAX_LENGTH:
            p.append(pair)
            
    return p

3)构建词典的类

class LangEmbed():
    def __init__(self, name):
        self.name = name
        self.word2index = 
        self.word2count = 
        self.index2word = 0:"SOS", 1:"EOS"
        self.n_word=2
        
    def add_sentence(self, sentence):
        for word in sentence.split():
            self.add_word(word)
        
    def add_word(self, word):
        if word not in self.word2index:
            self.word2index[word] = self.n_word
            self.word2count[word] = 1
            self.index2word[self.n_word] = word
            self.n_word += 1
        else:
            self.word2count[word] += 1

4)读入数据整个整个预处理流程, 返回两个语言的词典,与语料对

def prepare_data():
    ch = open('data/translation_data/chinese1.txt', encoding='utf-8').readlines()
    en = open('data/translation_data/english1.txt', encoding='utf-8').readlines()
    pairs = [[normalize_string(ch[i]), normalize_string(en[i])] for i in range(len(ch)) ]
    print("read %s sentence paris" % (len(pairs)))
    
    pairs = filter_pairs(pairs)
    print("%s sentence after filter" % (len(pairs)))
    
    ch_lang = LangEmbed('ch')
    en_lang = LangEmbed('en')
    for pair in pairs:
        ch_lang.add_sentence(pair[0])
        en_lang.add_sentence(pair[1])
    print('number of chinese words', ch_lang.n_word)
    print('number of english words', en_lang.n_word)
    
    return ch_lang, en_lang, pairs

ch_lang, en_lang, pairs = prepare_data()
print(pairs[:5])
for i in range(10):
    print(i, ":", ch_lang.index2word[i])
read 10009 sentence paris
36 sentence after filter
number of chinese words 102
number of english words 93
[['" 导弹 起 竖 完毕 \\x01', '" missile erection is ready \\x01'], ['" " 发射 \\x01 "', '" fire \\x01 "'], ['赶快 行动 吧 \\x01 回到 主页', 'get into action at once \\x01'], ['第一 , 它 是 轻 度 的 \\x01', 'it is mild \\x01'], ['这是 为什麽 \\x01', 'why \\x01']]
0 : SOS
1 : EOS
2 : "
3 : 导弹
4 : 起
5 : 竖
6 : 完毕
7 : 
8 : 发射
9 : 赶快

7.1.2 构建模型

分别构建encoder与decoder两部分

1)encoder编码器的构建

  • 这里我们选择GRU单元,它有2个输入:当前步的词向量,上一步的隐向量;有1个输出:当前步的隐向量。使用nn.GRUCell来创建。

  • 输入GRU单元的是词嵌入向量,即输入的词one-hot向量先乘以词嵌入矩阵,得到词嵌入向量。注意这个词嵌入矩阵可以在创建时随机初始化,并通过训练来更新矩阵的值,也可以是直接使用外部的训练好的词嵌入矩阵,并选择训练时不更新该矩阵。这里选择的是第一种方式。词嵌入矩阵用nn.embedding来创建,第一个参数是输入的维度,第二个参数是输出的维度.

  • init_hidden函数是为了在外部调用初始化第一步时输入的hidden用的,这里初始化值为0,大小为(1,hidden_size)的矩阵, 第一维是batch_size,因为每一句的长度不一样,我们一次只输入一句话的一个词。

class EncoderRnn(nn.Module):
    def __init__(self, input_size, hidden_size):
        super(EncoderRnn, self).__init__()
        
        self.hidden_size = hidden_size
        self.embedding = nn.Embedding(input_size, hidden_size)
        self.gru = nn.GRUCell(hidden_size, hidden_size)
        
    def forward(self, inputs, hidden):
        emb = self.embedding(inputs)
        hidden = self.gru(emb, hidden)  # gru有两个输入一个输出
        return hidden
    
    def init_hidden(self):
        h0 = torch.zeros(1, self.hidden_size)
        if use_cuda:
            return h0.cuda
        else:
            return h0

2)decoder解码器的构建

  • 仍然使用gru单元,gru当前步的输入是上一步的输出y, 和上一步的隐向量,gru的输出是当前步的隐向量
  • gru输出的隐向量需要过一个线性计算和softmax, 使得输出为词典大小的向量,是当前步对每个词的预测概率
class DecoderRnn(nn.Module):
    def __init__(self, hidden_size, output_size):
        super(DecoderRnn, self).__init__()
        self.hidden_size = hidden_size
        self.embedding = nn.Embedding(output_size, hidden_size)
        self.gru = nn.GRUCell(hidden_size, hidden_size)
        self.out = nn.Linear(hidden_size, output_size)
        
    def forward(self, inputs, hidden):
        emb = self.embedding(inputs)
        emb = F.relu(emb)
        hidden = self.gru(emb, hidden)
        output = F.log_softmax(self.out(hidden))
        return output, hidden
        
    def init_hidden(self):
        h0 = torch.zeros(1, self.hidden_size)
        if use_cuda:
            return h0.cuda
        else:
            return h0
        

7.1.3 训练

1)首先我们构建一个函数,它的功能是对输入的一个样本进行前向后向并输出损失。

teacher_forcing_ratio = 0.5
SOS_token = 0
EOS_token = 1

def train(inputs, targets, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion, max_len=MAX_LENGTH):
    # 1.前期工作==================================================
    encoder_hidden = encoder.init_hidden() # 初始化encoder的gru参数
    
    encoder_optimizer.zero_grad()  # encoder梯度置零
    decoder_optimizer.zero_grad()  # decoder梯度置零
    
    input_length = inputs.size()[0]   # 输入句子的词数
    target_length = targets.size()[0]  # 输出句子的词数
    
    loss = 0
    
    
    # 2.句子进行encoder端的计算==================================
    # 2.1初始化encoder端的输出:输出每步的隐向量,n个词即n步
    encoder_outputs = torch.zeros(max_len, encoder.hidden_size)
    if use_cuda:
        encoder_outputs = encoder_outputs.cuda()
    
    # 2.2将一句话中的每个词依次输入enocder中,前一步的隐向量是当前步的输入
    for ei in range(input_length):
        encoder_hidden = encoder(inputs[ei], encoder_hidden)
     
    
    # 3.句子进行decoder端的计算==================================
    # 3.1 设置第一步的输入x,即预测第一个词时的输入为句子起始标识符SOS_token
    decoder_input = torch.LongTensor([SOS_token])
    if use_cuda:
        decoder_input = decoder_inputs.cuda()
        
    decoder_hidden = encoder_hidden # encoder端的最后一步输出隐向量,是decoder端的第一步的输入隐向量
        
    # 3.2设置是否设置teacher_forcing
    if random.random() < teacher_forcing_ratio:
        use_teacher_forcing = True
    else:
        use_teacher_forcing = False
        
    # 3.3 进行decoder端的每步循环
    if use_teacher_forcing:
        for di in range(target_length): # 循环每步
            decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden) # 经过decoder计算
            loss += criterion(decoder_output, targets[di])  # 直接用decoder的输出去求损失
            decoder_input = targets[di] # decoder的输出直接作为encoder的输入
    else:
        for di in range(target_length):
            decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden)# 经过decoder计算(与上不变)
            topv, topi = decoder_output.data.topk(1)  # 获得概率最大的值,及其索引
            ni = topi[0][0]  # 获取其在词典中对应的索引,因为decoder_output是二维(1,output_size)
            decoder_input = torch.LongTensor([ni]) # 下一步的input,是上一步的output的概率最大词的索引
            if use_cuda:
                decoder_input = decoder_input.cuda()
            loss += criterion(decoder_output, targets[di])
            if ni == EOS_token:
                break
      
    
    # 3.4 反向传播与更新参数=======================================
    loss.backward()
    encoder_optimizer.step()
    decoder_optimizer.step()
    
    return loss.item() / target_length
  • 解释一下topk(i),是找出tensor中值最大的那个数,并返回它的值和索引。所以上面代码上topv, topi分别指decoder输出向量中的最大值与其所在的索引位置
a = torch.Tensor([[2,4,3,1]])
print(a.topk(1))
(tensor([[4.]]), tensor([[1]]))
  • 解释一下teacher_forcing(重要)

设置了一定的比例的数据是进行teacher_forcing,一部分不进行teacher_forcing。两者的区别是,a.前者的当前步骤的input是真实目标句子中的前一个词(相当于是用真实的结果当老师去提点了),后者的当前步骤的input是上一步预测出概率最大的词。 b.后者设置了循环终止条件。

  • 解释一下损失

函数最终返回的是总损失除以目标句子的词数。

2)接着构建一个迭代处理所有句子的训练函数

先提前设置一个有用的功能函数:index_from_pairs(),即将输入的句子对,根据词典将文字转换成索引的形式。

def sentence2index(lang, sentence):
    return [lang.word2index[w] for w in sentence.split()]

def index_from_sentence(lang, sentence):
    index = sentence2index(lang, sentence)
    index.append(EOS_token) # 追加一个句子结尾符
    re = torch.LongTensor(index).view(-1, 1)  # 增加一个维度
    if use_cuda:
        re = re.cuda()
        
    return re

def index_from_pairs(pair):
    inputs = index_from_sentence(ch_lang, pair[0])
    targets = index_from_sentence(en_lang, pair[1])
    return [inputs, targets]

现在来写完整训练的函数

def train_iters( n_iter, print_every=500, learning_rate=0.01, hidden_size=256):
    print_loss_total = 0
    
    # 模型、优化器、损失
    encoder = EncoderRnn(ch_lang.n_word, hidden_size)
    decoder = DecoderRnn(hidden_size, en_lang.n_word)
    if use_cuda:
        encoder = encoder.cuda()
        decoder = decoder.cuda()
    
    encoder_optimizer = torch.optim.SGD(encoder.parameters(), lr=learning_rate)
    decoder_optimizer = torch.optim.SGD(decoder.parameters(), lr=learning_rate)
    
    criterion = nn.NLLLoss()
    
    # 循环迭代,每次迭代为一个样本
    for i in range(1, n_iter+1):
        random.shuffle(pairs)
        training_pairs = [index_from_pairs(pair) for pair in pairs]  # 所有句子转换成索引形式
        
        for idx, training_pair in enumerate(training_pairs):
            input_index = training_pair[0]
            target_index = training_pair[1]
            loss = train(input_index, target_index, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion) # 调用train
            print_loss_total += loss
            
            if idx % print_every == 0:
                print_loss_avg = print_loss_total / print_every
                print_loss_total = 0
                print('iteration:%s, idx:%d, average loss:%.4f' % (i, idx, print_loss_avg))
                

train_iters(n_iter=10)
        
C:\\Users\\CC\\Anaconda3\\lib\\site-packages\\ipykernel_launcher.py:13: UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dim=X as an argument.
  del sys.path[0]


iteration:1, idx:0, average loss:0.0093
iteration:2, idx:0, average loss:0.1971
iteration:3, idx:0, average loss:0.2324
iteration:4, idx:0, average loss:0.2300
iteration:5, idx:0, average loss:0.2226
iteration:6, idx:0, average loss:0.2226
iteration:7, idx:0, average loss:0.2126
iteration:8, idx:0, average loss:0.2066
iteration:9, idx:0, average loss:0.2026
iteration:10, idx:0, average loss:0.1983

7.1.4 评估

def evaluate(encoder, decoder, sentence, max_length=MAX_LENGTH):
    inputs = index_from_sentence(ch_lang, sentence)
    inputs_length = inputs.size()[0]
    
    encoder_hidden = encoder.init_hidden()
    
    encoder_outputs = torch.zeros(max_length, encoder.hidden_size)
    encoder_outputs = encoder_outputs.cuda() if use_cuda else encoder_outputs
    
    for ei in range(inputs_length):
        encoder_hidden = encoder(inputs[ei], encoder_hidden)
        
    decoder_inputs = torch.LongTensor([SOS_token])
    decoder_inputs = decoder_inputs.cuda() if use_cuda else decoder_inputs
    
    decoder_hidden = encoder_hidden
    
    decoder_words = []
    
    for di in range(max_length):
        decoder_output, decoder_hidden = decoder(decoder_inputs, decoder_hidden)
        topv, topi = decoder_output.data.topk(1)
        ni = topi[0][0].item()
        
        if ni == EOS_token:
            decoder_words.append('<EOS>')
            break
        else:
            decoder_words.append(en_lang.index2word[ni])
        
        decoder_input = torch.LongTensor([[ni]])
        decoder_inputs = decoder_inputs.cuda() if use_cuda else decoder_inputs
        
    return decoder_words
        
def evaluat_randomly(encoder, decoder, n=10):
    for i in range(n):
        pair = random.choice(pairs)
        print('source:', pair[0])
        print('target:', pair[1])
        output_word = evaluate(encoder, decoder, pair[0])
        output_word = ' '.join(output_word)
        print('predict:', output_word)
        print()

encoder = EncoderRnn(ch_lang.n_word, 256)
decoder = DecoderRnn(256, en_lang.n_word)
if use_cuda:
    encoder = encoder.cuda()
    decoder = decoder.cuda()
evaluat_randomly(encoder, decoder)

source: " 这就是 我们 的 底线 
target: this is our bottom line 
predict: raised raised always always always always always always always always

source: 这 又是 为什麽 
target: again , why is that 
predict: raised raised always always always always always always always always

source: 我爱你中国
target: i love you ,china
predict: ready raised always always always always always always always always

source: 这 叫 什 麽 
target: what is that called 
predict: raised raised always always always always always always always always

source: 素质 是 高 还是 低 
target: are they competent 
predict: raised raised raised always always always always always always always

source: 素质 是 高 还是 低 
target: are they competent 
predict: raised raised raised always always always always always always always

source: 是否 先进 
target: are they advanced weapons 
predict: raised raised raised always always always always always always always

source: 一 只 只 手 高高 举起 
target: a hand was raised high 
predict: kosovo raised always always always always always always always always

source: 再说 科索沃 
target: or take kosovo 
predict: kosovo raised always always always always always always always always

source: " " 发射  "
target: " fire  "
predict: raised raised always always always always always always always always



C:\\Users\\CC\\Anaconda3\\lib\\site-packages\\ipykernel_launcher.py:13: UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dim=X as an argument.
  del sys.path[0]

以上结果还不成形,因为迭代次数和语料的问题,大家可以拿更好的语料,并将迭代次数增多进行测试

7.1.5 Attention机制

以上完成了一个翻译机的全部过程,但这是非常基础的网络结构,通常情况下我们会加入attention机制,那么模型结构encoder与decoder需要稍作改写。

encoder端的改写:主要是将gru的outputs也作为输出返回,这个outputs会用于decoder端的attention计算相似分值

class EncoderRnn(nn.Module):
    def __init__(self, input_size, hidden_size):
        super(EncoderRnn, self).__init__()
        
        self.hidden_size = hidden_size
        self.embedding = nn.Embedding(input_size, hidden_size)
        self.gru = nn.GRU(hidden_size, hidden_size)  # 直接调用GRU
        
    查看详情  

pytorch基础——机器翻译的神经网络实现(代码片段)

...式的包fromioimportopenimportunicodedataimportstringimportreimportrandom#Pytorch必备的包importtorchimporttorch.nnasnnfromtorch.autogradimportVariablefromtorchimportoptimimporttorch.nn.functionalasFimporttorch.utils.dataasDataSet#绘图所用的包importmatplotlib.pyplotaspltimportnumpyasnp#判... 查看详情

翻译:2.6概率论深入神经网络pytorch(代码片段)

以某种形式,机器学习就是做出预测。考虑到患者的临床病史,我们可能想要预测患者明年心脏病发作的可能性。在异常检测中,我们可能想要评估飞机喷气发动机的一组读数是否正常运行。在强化学习中,我们... 查看详情

《神经网络与pytorch实战》肖智清著部分代码复现与注释,包括使用pytorch搭建cnnrnnlstm等基础神经网络(代码片段)

目录第二章:初识pytorch第三章:使用pytorch进行科学计算:使用蒙特卡洛法计算圆周率第四章:求解优化问题利用Pytorch求解图4-1中函数在给定点的梯度值:利用torch.optim.SGD类最小化函数f(X)绘制Himmelblau函数用优... 查看详情

(翻译)60分钟入门深度学习工具-pytorch(代码片段)

60分钟入门深度学习工具-PyTorch作者:SoumithChintala原文翻译自:一、Pytorch是什么?二、AUTOGRAD三、神经网络四、训练一个分类器五、数据并行他是一个基于Python的科学计算包,目标用户有两类为了使用GPU来替代numpy一个深度学习研... 查看详情

5pytorch构建模型的三种方式(代码片段)

...编码器实现。第七章:利用PyTorch实现复杂模型:翻译机(nlp领域)、生成对抗网络(GAN)、强化学习(RL)、风格迁移(cv领域)。第八章:PyTorch的其他高级用法:模型在不同框架之间的迁移、可视... 查看详情

神经网络和深度学习-用pytorch实现线性回归(代码片段)

用pytorch实现线性回归用pytorch的工具包来实现线性模型的训练过程准备数据集设计模型构造损失函数和优化器(使用pytorchAPI)训练过程:前馈、反馈、更新准备数据在PyTorch中,计算图是小批处理的,所以X和Y... 查看详情

深度学习为什么选择pytorch?史上最详细pytorch入门教程(代码片段)

目录前言一、Pytorch介绍1.常见的深度学习框架2.Pytorch框架的崛起3.Pytorch与Tensorflow多方位比较二、Tensors1.Tensor的创建2.Tensor的操作3.Tensor与Numpy三、Autograd的讲解1.模型中的前向传播与反向传播2.利用autograd计算梯度四、构建神经网络... 查看详情

机器翻译注意力机制及其pytorch实现(代码片段)

前面阐述注意力理论知识,后面简单描述PyTorch利用注意力实现机器翻译EffectiveApproachestoAttention-basedNeuralMachineTranslation简介Attention介绍在翻译的时候,选择性的选择一些重要信息。详情看这篇文章 。本着简单和有效的原则,... 查看详情

lenet神经网络-pytorch实现(代码片段)

...;再经过全连接层,最后使用softmax分类作为输出层。2.pytorch网络设计网络构造代码部分:classLeNet5(nn.Module):def__in 查看详情

基于pytorch解读googlenet现代卷积神经网络(代码片段)

1概述GoogLeNet关键点:保证算力情况下增大宽度和深度宽度:利用Inception结构同时执行多个网络结构深度:利用辅助分类器防止梯度消失多尺度训练和预测适用于多种计算机视觉任务多尺度训练对全卷积网络有效,... 查看详情

基于pytorch完整的训练一个神经网络并进行验证(代码片段)

原视频链接:Pytorch深度学习快速入门教程(绝对通俗易懂!)【小土堆】土堆老师的Github地址之前学的也不少了,现在要去训练一个完整的神经网络,利用Pytorch和CIFAR10数据集准备数据集importtorchvision#导入torchvision##... 查看详情

pytorch应用:构建神经网络(代码片段)

Pytorch初步应用:构建一个神经网络学习目标1.构建神经网络的典型流程2.定义一个Pytorch实现的神经网络3.损失函数4.反向传播(backpropagation)5.更新网络参数6.总结学习目标掌握用Pytorch构建神经网络的基本流程.掌握用Pytorch构建神... 查看详情

利用卷积神经网络实现手写字识别(代码片段)

本文我们介绍一下卷积神经网络,然后基于pytorch实现一个卷积神经网络,并实现手写字识别卷积神经网络介绍传统神经网络处理图片问题的不足让我们先复习一下神经网络的工作流程:搭建一个神经网络将要训练的... 查看详情

翻译:3.3.线性回归的简明实现pytorch(代码片段)

过去几年对深度学习的广泛而强烈的兴趣激发了公司、学者和业余爱好者开发各种成熟的开源框架,以自动执行基于梯度的学习算法的重复工作。在第3.2节中,我们仅依靠(i)张量进行数据存储和线性代数;(ii)用于计... 查看详情

全网最详细使用pytorch实现循环神经网络(代码片段)

目录1.什么是循环神经网络2.PyTorch中的循环神经网络3.创建循环神经网络模型小结4.训练循环神经网络模型5.评估循环神经网络模型欢迎来到这篇使用PyTorch实现循环神经网络的教程!在这里,我将向您展示如何使用PyTorch创... 查看详情

4.6pytorch使用损失函数(代码片段)

...现、自编码器实现。第七章利用PyTorch实现复杂模型:翻译机(nlp领域)、生成对抗网络(GAN)、强化学习(RL)、风格迁移(cv领域)。第八章:PyTorch的其他高级用法:模型在不同框架之间的迁移、可... 查看详情

8.1pytorch模型迁移(代码片段)

...编码器实现。第七章:利用PyTorch实现复杂模型:翻译机(nlp领域)、生成对抗网络(GAN)、强化学习(RL)、风格迁移(cv领域)。第八章:PyTorch的其他高级用法:模型在不同框架之间的迁移、可视... 查看详情