当前位置:   article > 正文

手把手教你搭建seq2seq实现法语向英语的翻译_python 基于gru的seq2seq模型架构实现翻译的过程

python 基于gru的seq2seq模型架构实现翻译的过程

基于Seq2Seq模型实现法语向英语的翻译

章节


简介

本文基于PyTorch实现seq2seq模型来实现法语向英语的翻译,会带你了解从seq2seq模型的主要原理,在实战中需要对文本的预处理,到训练和可视化一个完整的过程。读完本文,不仅可以通过这个项目来了解PyTorch的语法以及RNN(GRU)的操作,而且可以真正明白seq2seq模型的内涵。同时,你也可以实现自己的一个翻译器,效果如下:

input = elle a cinq ans de moins que moi .
output = she is two years younger than me . <EOS>
input = elle est trop petit .
output = she s too trusting . <EOS>
input = je ne crains pas de mourir .
output = i m not afraid of dying . <EOS>
input = c est un jeune directeur plein de talent .
output = he s a fast person . <EOS>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

数据集,如果你想要自己跑一波demo,完整代码,别忘了把数据集下载到本地更改路径


Seq2Seq模型

seq2seq模型顾名思义就是序列到序列模型(sequence to sequence)。我们可以把这个模型当做一顶魔法帽,帽子的输入和输出均为序列,举个例子,翻译就是一个序列到序列的任务,输入法文句子,输出则是英文句子(见下图)。

seq2seq模型主要由Encoder以及Decoder这两大部分组成,这两者分别执行将序列编码以及解码的工作,而在这个项目里面我们以GRU作为主要组成部分。GRU可以近似为LSTM的变种,比LSTM结构更简单,计算更加方便,而实际效果和LSTM相差无几。另外,关于LSTM 以及 GRU的详细解释可以看我的另一篇RNN综述,本文偏重代码实现而非原理解释。

首先来看一下Encoder,首先会对输入进行一个词嵌入,词嵌入的好处是可以对长短不一的输入进行统一长度,方便计算。GRU每一个时间步上的输入有两个,一个是上一个时间步的隐藏状态(hidden_state),另一个是当前时间步的文本输入。

在这里插入图片描述

seq2seq模型中一个一个的单词其实是每一个时间步的输入和输出,而在训练模型时,单词是转换为索引(注意这个索引其实是在预处理部分决定的,常见的索引有单词在整个单词集中出现的顺序),在torch中还要转成tensor格式,比如说第一个单词,它的索引是2,那么它其实是tensor([2])。Embedding层正是我们对于输入的每一个词进行词嵌入处理,虽然我们每一次只输入一个单词,但我们在初始化的时候会将训练集中所有的单词一起预先处理好,然后当每一个单词索引进来之后,就好比查字典一样,找到自己对应词嵌入之后的tensor。注意,这里不是把所有的单词直接送进去,每一个单词索引有一个Embedding,这里的意思是告诉Embedding层一共有多少个embedding。这样的好处是直接固定了,不会随着不断输入而改变Embedding层参数。

所以,Embedding层第一个参数其实是训练集中单词的数量,第二个参数指的是每一个单词拥有多少维的编码。单词索引送进去了,tensor([2]),假设Embedding层参数是(2,4),则经过词嵌入后的结果是tensor([[0.1,2.1,3.1,0.9]]),会发现第二个size其实是需要嵌入的维度。

包的引入

from __future__ import unicode_literals, print_function, division
from io import open
import unicodedata
import re
import random
import time
import math
import torch
import torch.nn as nn
from torch import optim
import torch.nn.functional as F
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
# 若无GPU,则CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
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)        # gru的输入为三维,两个参数均指的是最后一维的大小
        # tensor([1,1,hidden_size])
        self.gru = nn.GRU(hidden_size, hidden_size)

    def forward(self, input, hidden):
        # embedded.size() ==> tensor([1,1,hidden_size])
        # -1的好处是机器会自动计算
        # 这里用view扩维的原因是gru必须接受三维的输入
        embedded = self.embedding(input).view(1, 1, -1)
        output = embedded        
        output, hidden = self.gru(output, hidden)
        return output, hidden
    
    def initHidden(self):
        # 初始化隐层状态全为0
        # hidden ==> tensor([1,1,hidden_size])
        return torch.zeros(1, 1, self.hidden_size, device=device)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GAzVevXr-1622178192128)(https://github.com/sherlcok314159/ML/blob/main/NN/Images/decoder.png)]

接下来介绍Decoder,在本文中仅使用Encoder中最后一个输出的hidden来作为Decoder的初始的hidden,因为编码器最后一个hidden常常含有整个序列的上下文信息,有时会被称为上下文变量。

这里的第一个文本输入其实是<sos>(start of sentence),与Encoder不同的是,这里经过词嵌入之后还做了relu处理,增强模型非线性的表达能力。

输入会经过一个softmax来获得一个概率分布,最后取最大概率的那个作为当前预测的结果

上一个时间步的hidden总会作为当前时间步的hidden输入,而当前时间步的文本输入是上一个时间步的预测结果。

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.GRU(hidden_size, hidden_size)
        # input_features ==> hidden_size
        # output_features ==> output_size
        self.out = nn.Linear(hidden_size, output_size)        # Log(Softmax(X))
        self.softmax = nn.LogSoftmax(dim=1)

    def forward(self, input, hidden):
        output = self.embedding(input).view(1, 1, -1)
        output = F.relu(output)
        output, hidden = self.gru(output, hidden)
        # output.size() ==> [1,1,hidden_size]
        # output的第一个1是我们用以适合gru输入扩充的
        # 所以用output[0]选取前面的
        output = self.softmax(self.out(output[0]))
        return output, hidden

    def initHidden(self):
        return torch.zeros(1, 1, self.hidden_size, device=device)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

在刚刚的基础上进行升级,在Decoder上引入注意力机制,对比一下,没加注意力之前,Decoder是直接接受全部的Encoder输出,而attention加入之后可以更准确地聚焦到Encoder输出的不同部分,具体是用注意力权重矩阵去乘以Encoder的输出向量用以创建加权组合,从而帮助Decoder选择正确的输出。

实现时将Decoder的文本输入和隐藏状态作为输入,分别对应图中的input,prev_hidden(上一个时间步的隐藏状态)。文本输入进来经过词嵌入之后应用了dropout,可以一定程度减少模型过拟合,增强模型的泛化能力。通过前馈层attn之后进行softmax处理再和Encoder的输出矩阵做点乘处理,再拼接起来加一个relu。注意,上一个时间步的隐藏状态会继续作为gru的状态输入。

在这里插入图片描述

class AttnDecoderRNN(nn.Module):
    def __init__(self, hidden_size, output_size, dropout_p=0.1, max_length=MAX_LENGTH):
        super(AttnDecoderRNN, self).__init__
  • 1
  • 2
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/379309
推荐阅读
相关标签
  

闽ICP备14008679号