NLP实战7--seq2seq实现3(pytorch)/decoder实现

传送门

(6条消息) NLP实战5--seq2seq实现1(pytorch)_m0_53292725的博客-CSDN博客

(6条消息) NLP实战6--seq2seq实现2(pytorch)/encoder实现_m0_53292725的博客-CSDN博客

完整框架

NLP实战7--seq2seq实现3(pytorch)/decoder实现_第1张图片 

实现解码器

解码器主要负责实现对编码之后结果的处理,得到预测值,为后续计算损失做准备

此时需要思考:

1.使用什么样的损失函数,预测只需要是什么格式!! 这个思想非常重要 要清楚的了解需要得到一个什么样的值, 是什么样的形状

针对当前问题,我们可以理解为当前的问题是一个分类的问题,即每次的输出其实是选择一个概率最大的词

真实的形状是[batch_size, max_len],从而我们知道输出的结果需要是一个[batch_size, max_len, vocab_size]的形状

即预测值的最后一个维度进行计算log_softmax,然后和真实值计算损失

2.如何把编码结果[1, batch_size, max_len]进行操作,得到预测的值?解码器也是一个RNN,即也可以使用LSTM OR GRU的结构,所以在解码器中:

通过循环,每次计算的一个time step的内容

编码器的结果作为初始的隐层状态,定义一个[batch_size, 1] 的全为SOS的数据作为最开始的输入,告诉解码器,要开始工作了

通过解码器预测出一个输出[batch_size, hidden_size](会进行形状的调整为[batch_size, vocab_size]),把这个输出作为输入再使用解码器进行解码

然后重复上述的循环,循环次数就是句子的最大长度,那么就可以得到max_len个输出

把所有的输出结果进行concat,得到[batch_size, max_len, vocab_size]

3.在RNN的训练过程中,使用前一个预测的结果作为下一个step的输入,可能会导致一步错步步错的结果,如何提高模型的收敛速度?

可以考虑在训练的过程中,把真实值作为下一步的输入,这样可以避免步步错的局面

同时在使用真实值的同时,仍然使用预测值作为下一步的输入,两种输入随即使用

上述这种机制我们成为 Teacher forcing 就像是一个指导老师,在每一步都会对我们的行为进行纠偏,从而达到在多次训练之后能够需要其中的规律

完整代码实现:

import torch
import torch.nn as nn
import config
import random
import torch.nn.functional as F
from word_sequence import num_sequence

class NumDecoder(nn.Module):
    def __init__(self):
        super(NumDecoder, self).__init__()
        self.max_seq_len = config.max_len
        self.vocab_size = len(num_sequence)
        self.embedding_dim = config.embedding_dim
        self.dropout = config.dropout
        self.embedding = nn.Embedding(num_embeddings=self.vocab_size, embedding_dim=self.embedding_dim, padding_idx=num_sequence.PAD)
        self.gru = nn.GRU(input_size=self.embedding_dim, hiddem_size=config.hidden_size,
                            num_layers=1, batch_first=True, dropout=self.dropout)
        self.log_softmax = nn.LogSoftmax()
        self.fc = nn.Linear(config.hidden_size, self.vocab_size)
    def forward(self, encoder_hidden, target, target_length):
        # encoder_hidden [batch_size, hidden_size]
        # target [batch_size, max_len]
        # 初始的输入全为SOS的输入
        decoder_input = torch.LongTensor([[num_sequence.SOS]]*config.batch_size)
        # 解码器的输出,用来后保存所有的输出结果
        decoder_outputs = torch.zeros(config.batch_size, config.max_len, self.vocab_size)
        decoder_hidden = encoder_hidden # [batch_size, hidden_size]
        for t in range(config.max_len):
            decoder_output_t , decoder_hidden = self.forward_step(decoder_input, decoder_hidden)
            # 在不同的time step上进行复制,decoder_output_t [batch_size, vocab_size]
            decoder_outputs[:,t,:] = decoder_output_t
            
            # 在训练的过程中,使用teacher forcing,进行编码
            use_teacher_forcing = random.random() > 0.5
            if use_teacher_forcing:
                # 下一次的输入使用真实值
                decoder_input = target[:, t].unsqueeze(1)  # [batch_size, 1]
            else:
                #使用预测值,topk中k = 1,即获取最后一个维度最大的一个值
                value, index = torch.topk(decoder_output_t, 1) # index [batch_size, 1]
                decoder_input = index
        return decoder_outputs, decoder_hidden

    def forward_step(self, decoder_input, decoder_hidden):
        """
        :param decoder_input:[batch_size, 1]
        :param decoder_hidden: [1, batch_size, hidden_size]
        :return: out:[batch_size, vocab_size], decoder_hidden: [1, batch_size, hidden_size] 
        """
        embeded = self.embedding(decoder_input)  # embeded: [batch_size, 1, embedding_dim]
        out, decoder_hidden = self.gru(embeded, decoder_hidden) # out [1, batch_size, hidden_size]
        out = out.squeeze(0) # 去除第0维度的1
        # 进行全连接形状变化,同时进行求取log_softmax
        out = F.log_softmax(self.fc(out), dim=-1) # out [batch_size, 1, vocab_size]
        out = out.squeeze(1)
        return out, decoder_hidden
        

本人版

""" 实现编码器"""
import torch.nn as nn
import torch
import config
import torch.nn.functional as F
class Decoder(nn.Module):
    def __init__(self):
        super(Decoder, self).__init__()
        self.embedding = nn.Embedding(num_embeddings=len(config.num_sequence), embedding_dim=config.embedding_dim,
                                      padding_idx=config.num_sequence.PAD)
        self.gru = nn.GRU(input_size=config.embedding_dim,
                          hidden_size=config.hidden_size,
                          num_layers=config.num_layer,
                          batch_first=True)
        self.fc = nn.Linear(config.hidden_size, len(config.num_sequence))
    def forward(self, target, encoder_hidden):

        # 1.获取encoder的输出作为decoder第一次的隐藏状态
        decoder_hidden = encoder_hidden
        batch_size = target.size(0)
        # 2.准备第一次decoder第一个时间步的输入,[batch_size, 1] SOS
        decoder_input = torch.LongTensor(torch.ones([batch_size, 1], dtype=torch.int64) * config.num_sequence.SOS)
        # 3.在第一个时间步上进行计算得到第一个时间步的输出,hidden_state

        # 4.把前一个时间步的输出进行计算,得到第一个最后输出的结果
        # 5.把前一次的hidden_state 作为当前时间步的hidden_state的输入,把前一次的输出作为当前时间步的输入
        # 6.循环4-5步

        # 保存预测的结果
        decoder_outputs = torch.zeros([batch_size, config.max_len+2, len(config.num_sequence)])
        for t in range(config.max_len+2):
            decoder_output_t, decoder_hidden = self.forward_step(decoder_input, decoder_hidden)
            # 保存 decoder_output_t到decoder_outputs中
            decoder_outputs[:, t, :] = decoder_output_t

            # 当前时刻的输出, 当前时刻的隐藏状态
            value, index = torch.topk(decoder_output_t, 1)
            decoder_input = index

        return decoder_outputs, decoder_hidden

    def forward_step(self, decoder_input, decoder_hidden):
        """计算每个时间步上的结果  decoder_input [batch_size, 1] decoder_hidden[1, batch_size, hidden_size]"""
        decoder_input_embeded = self.embedding(decoder_input)
        # out [batch_size, 1, hidden_size]
        # decoder_hidden [1, batch_size, hidden_size]
        out, decoder_hidden =self.gru(decoder_input_embeded, decoder_hidden)
        out = out.squeeze(1) # 把第一个维度干掉 #[batch_size, hidden_size]
        output = F.log_softmax(self.fc(out), dim=-1)  # [batch_size, vocab_size]
        return output, decoder_hidden

然后就是调用我们构造的encoder decoder,完成seq2seq模型的搭建

实现代码:

"""
把encoder和decoder进行合并,得到seq2seq模型
"""
import torch.nn as nn
from encoder import Encoder
from decoder import Decoder
class Seq2seq(nn.Module):
    def __init__(self):
        super(Seq2seq, self).__init__()
        self.encoder = Encoder()
        self.decoder = Decoder()

    def forward(self, input, target, input_length, target_length):
        encoder_outputs, encoder_hidden = self.encoder(input, input_length)
        decoder_outputs, decoder_hidden = self.decoder(target, encoder_hidden)
        return decoder_outputs, decoder_hidden

后面就是常规的训练 预测过程了 这里就不写了

贯穿始终的仍然是 shape shape shape  要非常明确你需要什么样的shape 每一步shape有哪些变化  归根结底就是矩阵乘法

//btw 博客更新速度实在是跟不上学习进度了。。虽然有点费时间,但还是坚持吧。。最近在准备一套自己习惯的复用性代码框架 顺便好好梳理一下前面的学习内容

你可能感兴趣的:(NLP实战记录,自然语言处理,pytorch,深度学习)