传送门
(6条消息) NLP实战5--seq2seq实现1(pytorch)_m0_53292725的博客-CSDN博客
(6条消息) NLP实战6--seq2seq实现2(pytorch)/encoder实现_m0_53292725的博客-CSDN博客
完整框架
实现解码器
解码器主要负责实现对编码之后结果的处理,得到预测值,为后续计算损失做准备
此时需要思考:
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 博客更新速度实在是跟不上学习进度了。。虽然有点费时间,但还是坚持吧。。最近在准备一套自己习惯的复用性代码框架 顺便好好梳理一下前面的学习内容