高端大气酒店网站源码,海南seo快速排名优化多少钱,想让网站被谷歌收录怎么做,北京市建筑工程信息交易网序言
这一节是对于“编码器-解码器”模型的实际应用#xff0c;编码器和解码器架构可以使用长度可变的序列作为输入#xff0c;并将其转换为固定形状的隐状态#xff08;编码器实现#xff09;。本小节将使用“fra-eng”数据集#xff08;这也是《动手学习深度学习-Pytor…序言
这一节是对于“编码器-解码器”模型的实际应用编码器和解码器架构可以使用长度可变的序列作为输入并将其转换为固定形状的隐状态编码器实现。本小节将使用“fra-eng”数据集这也是《动手学习深度学习-Pytorch版》提供的数据集进行序列到序列的学习。在d2l官方文档中有很多的内容是根据英文版直译过来的其中有很多空乏的句子特别是对于每个模块的描述中下面我提供一种全新的思路来理解整个代码不得不说沐神团队的代码绝对值得推敲~。 这里也是按照官方给的目录架构对于整个项目复现在复现的过程中详细理解每一行代码的作用去除无关内容~同时关注数据的变化特别是在源和目标的shape变化方面。当然需要注明的是源指的是数据集中所有的英语短语其按照batch_size的大小装入模型同时增加了num_steps维度也就是“时间步”【那对于区分时间步和batch_size的概念有个类似的方式便于理解将它们映射到图像中batch_size是每一次取出多少个样本图像而num_steps可以理解为图像本身的维度问题】。下面将会按着官方给出的步骤进行代码复现导包、设计编码器、设计解码器、修改交叉熵损失函数、模型训练、模型预测、使用BLEU进行模型的评估。
模型复现
导包【无脑导包】
# 无脑导包
import torch
import collections # 这个包还是需要注意一下
import math
from torch import nn
from d2l import torch as d2l设计编码器
根据“编码器-解码器”的模型架构梳理出编码器的主要任务它的主要任务包括
将某一个时刻t的输入特征向量 x t x_t xt和上一个时刻的隐状态 h t − 1 h_{t-1} ht−1转变为 h t h_t ht即 h t f ( x t , h t − 1 ) h_t f(x_t , h_{t-1}) htf(xt,ht−1)编码器需要通过函数q实现把所有的隐状态转变为上下文变量 c q ( h 1 , . . . . , h T ) c q( h_1,....,h_T ) cq(h1,....,hT)使用嵌入层获取输入序列的每个词元的特征向量[嵌入层权重矩阵行数为vocab_size,列数是特征向量的维度]
明确了编码器的主要任务后下面来看具体的代码复现
#save
class Seq2SeqEncoder(d2l.Encoder):用于序列到序列学习的循环神经网络编码器def __init__(self, vocab_size, embed_size, num_hiddens, num_layers, dropout0, **kwargs):super(Seq2SeqEncoder,self).__init__(**kwargs)# 实现嵌入层Embedding 将每一个词元转变成一个词向量self.embedding nn.Embedding(vocab_size,embed_size)# print(Encoder中 self.embedding的size为:,self.embedding.size())# print(Encoder中 embed_size: ,embed_size)with open(D://pythonProject//Encoder_embed_pervir_size.txt, w) as f:f.write(str(embed_size))----------embed_size为32----------这里的embed_size为每一个词元对应的特征向量的长度self.rnn nn.GRU(embed_size,num_hiddens,num_layers,dropoutdropout)def forward(self, X, *args):with open(D://pythonProject//Encoder_Not_embed_size.txt, w) as f:f.write(str(X.size()))----------未进行embedding的X: torch.Size([64, 10]) batch_size * num_steps----------# print(Encoder中 未进行embedding前的X的size,X.size())# embedding 的形状 (vocab_size,embed_size)# 输出X的形状(batch_size,num_steps,embed_size)X self.embedding(X)# print(Encoder中 进行embedding后的X的size,X.size())with open(D://pythonProject//Encoder_embed_size.txt, w) as f:f.write(str(X.size()))----------进行了embedding的X: torch.Size([64, 10, 32])----------#torch要求在循环神经网络模型中第一个轴对应的必须是时间步X X.permute(1,0,2)# print(Encoder中 permute后的X的size,X.size())with open(D://pythonProject//Encoder_permute_size.txt, w) as f:f.write(str(X.size()))----------进行了permute的X: torch.Size([10, 64, 32]) 10为时间步----------output,state self.rnn(X)# output的输出形状: (num_steps,batch_size,num_hiddens)# state的输出形状: (num_layers,batch_size,num_hiddens)return output,state在上述的编码器中forward()完成了 1、将输入值【形状为:batch_size*num_steps】输入到嵌入层Embedding将输入的每个词元转成一个代表该词元的一个特征向量。【之所以用Embedding而不用One-Hot的原因在于虽然One-Hot可将tokens转成稀疏矩阵便于运算但是不适用于大批量数据的情况容易导致运算过慢或者占用内存的情况详细参考一文读懂Embedding的概念以及它和深度学习的关系】 1-1 注意原来X的输入形状是 torch.Size[64,10] —torch.Size(bach_size,num_steps] 经过Embedding后的X的形状为 torch.Size([64, 10, 32] —torch.Size(batch_size,num_steps,embedding_size) 即在输入的X后增加一个维度用来作为每一个takens的特征向量 with open(D://pythonProject//Encoder_Not_embed_size.txt, w) as f:f.write(str(X.size()))----------未进行embedding的X: torch.Size([64, 10]) batch_size * num_steps----------# print(Encoder中 未进行embedding前的X的size,X.size())# embedding 的形状 (vocab_size,embed_size)# 输出X的形状(batch_size,num_steps,embed_size)X self.embedding(X)# print(Encoder中 进行embedding后的X的size,X.size())with open(D://pythonProject//Encoder_embed_size.txt, w) as f:f.write(str(X.size()))----------进行了embedding的X: torch.Size([64, 10, 32])----------2、为了适应torch要求的循环神经网络模型中第一个维度需要为时间步的需求这里做了一下permute操作把第0个维度和第1个维度互换了一下关于permute的详细操作可以参考【PyTorch 两大转置函数 transpose() 和 permute() 】 permute后的矩阵形状就变成了 torch.Size([10, 64, 32]) —torch.size([num_steps,batch_size,embedding_size]) #torch要求在循环神经网络模型中第一个轴对应的必须是时间步X X.permute(1,0,2)# print(Encoder中 permute后的X的size,X.size())with open(D://pythonProject//Encoder_permute_size.txt, w) as f:f.write(str(X.size()))----------进行了permute的X: torch.Size([10, 64, 32]) 10为时间步----------3、最后编码器需要返回最后一个时间步的state隐状态和最后一个时间步的outputs。 output,state self.rnn(X)# output的输出形状: (num_steps,batch_size,num_hiddens)# state的输出形状: (num_layers,batch_size,num_hiddens)return output,state实例化编码器
下面通过设计一个两层门控循环单元编码器其隐藏单元是16给定一个小批量的输入序列X批量大小为4时间步为7。同时在完成所有时间步后最后一层的隐状态的输出是一个张量【output由编码器的循环层返回】形状为(时间步数批量大小隐藏单元数)
encoder Seq2SeqEncoder(vocab_size10, embed_size8, num_hiddens16,num_layers2)
encoder.eval()
X torch.zeros((4, 7), dtypetorch.long)
output, state encoder(X)
output.shape注这里使用的是门控循环单元GRU最后一个时间步的多层隐状态的形状是(num_layers,batch_size,num_hiddens)如果使用LSTM 则state中还应该包含记忆单元信息。
设计解码器
编码器输出的整个上下文信息变量C需要作用于整个输入序列 x 1 , . . . , x r x_1,...,x_r x1,...,xr,对输入序列进行编码。解码器的输出 y t ′ y_t yt′与上下文变量C输出子序列 y 1 , . . . , ( y t ′ − 1 ) y_1,...,(yt-1) y1,...,(yt′−1)的关系 且隐状态与上一步的隐状态、上下文变量和上一个时间步的输出有关。在获得解码器的隐状态后可以使用输出层softmax操作来计算时间步 t ′ t t′时输出 y t ′ y_t yt′的概率分布 解码器的主要任务包括
直接使用编码器的最后一个时间步的隐状态来初始化解码器的隐状态及两者具有相同的隐藏层和隐藏单元为了让上下文信息更好包含更多的信息可以用上下文变量C在所有的时间步与解码器的输入进行拼接为了输出预测词元的概率分布在最后一层采用全连接层来变换隐状态
class Seq2SeqDecoder(d2l.Decoder):用于序列到序列学习的循环神经网络解码器def __init__(self,vocab_size,embed_size,num_hiddens,num_layers,dropout0,**kwargs):super(Seq2SeqDecoder, self).__init__(**kwargs)self.embedding nn.Embedding(vocab_size,embed_size)with open(D://pythonProject//Decoder_vocab_size.txt, w) as f:f.write(str(vocab_size))----------decoder的vocab_size为201----------with open(D://pythonProject//Decoder_embed_size.txt, w) as f:f.write(str(embed_size))----------decoder的embed_size为32----------self.rnn nn.GRU(embed_sizenum_hiddens,num_hiddens,num_layers,dropoutdropout)self.dense nn.Linear(num_hiddens,vocab_size)def init_state(self,enc_outputs,*args):# enc_outputs[0]为编码器的输出# enc_outputs[1]为编码器最后一层输出的隐变量return enc_outputs[1]def forward(self, X, state):# print(Decoder中 未进行embedding的X的形状:,X.size())with open(D://pythonProject//Decoder_X_size.txt, w) as f:f.write(str(X.size()))Decoder的X的大小torch.Size([25, 10])# 输出X的形状(batch_size,num_steps,embed_size)X self.embedding(X).permute(1,0,2)with open(D://pythonProject//Decoder_X_embed_permute.txt, w) as f:f.write(str(X.size()))Decoder的X_embed_permute的大小torch.Size([10, 25, 32])# 广播context使其具有与X相同的num_steps 即X.shape[0]context state[-1].repeat(X.shape[0], 1, 1)X_and_Context torch.cat((X,context),2)output,state self.rnn(X_and_Context,state)output self.dense(output).permute(1,0,2)# output的形状:(batch_size,num_steps,vocab_size)# state的形状:(num_layers,batch_size,num_hiddens)return output, state在初始化__init__()函数中完成了将输入维度(batch_size,num_steps)进行Embedding操作其输出维度变为了(batch_size,num_steps,num_embedding) 同时将embedhiddens的大小同时送入GRU的输入层同时不使用dropout操作。最后初始化输出层要放入的Linear全连接层。 forward()函数——前向传播中首先对X进行embedding操作并进行了permulate()将第一个维度变为了num_steps。将编码器得到的state隐状态通过repeat成与X第一维度num_steps相同后利用广播机制形成最终含有上下文信息的Context并最终通过torch,cat连接到X中【维度选用2】。最后利用了rnn输出output和最后的隐状态state。
实例化解码器
decoder Seq2SeqDecoder(vocab_size10, embed_size8, num_hiddens16,num_layers2)
decoder.eval()
state decoder.init_state(encoder(X))
output, state decoder(X, state)
output.shape, state.shape有关于model.train()和model.eval()的区别可以参考torch 中的 model.eval() 是什么
修改损失函数
# 修改损失函数将填充词元的预测排除在损失函数的计算之外
下面的sequence_mask函数 通过零值化屏蔽不相关的项
#save
def sequence_mask(X,valid_len,value0):# print(mask X的形状:,X.size())with open(D://pythonProject//Mask_X_size.txt, w) as f:f.write(str(X.size()))损失函数中的Mask_X_size的大小torch.Size([25, 10]) 显然是没有进行Embedding的在序列中屏蔽不相干的项maxlen X.size(1)mask torch.arange((maxlen),dtypetorch.float32,deviceX.device)[None,:]valid_len[:,None]X[~mask] valuereturn X
X torch.tensor([[1,2,3],[4,5,6]])
res sequence_mask(X,torch.tensor([1,2]))
print(valid_len 分别为 1 和 2: ,res)
同时可以使用非0值替换要屏蔽的项
X torch.ones(2,3,4)
res sequence_mask(X,torch.tensor([1,2]),value-1)我们可以通过扩展softmax交叉熵损失函数来遮蔽不相关的预测。 最初所有预测词元的掩码都设置为1。 一旦给定了有效长度与填充 词元对应的掩码将被设置为0。 最后将所有词元的损失乘以掩码以 过滤掉损失中填充词元产生的不相关预测。
#save
class MaskedSoftmaxCELoss(nn.CrossEntropyLoss):带遮蔽的softmax交叉熵损失函数# pred的形状(batch_size,num_steps,vocab_size)# label的形状(batch_size,num_steps)# valid_len的形状(batch_size,)def forward(self, pred, label, valid_len):# 预测词元的掩码都设置为1weights torch.ones_like(label)# 一旦给定了有效长度与填充# 词元对应的掩码将被设置为0。weights sequence_mask(weights, valid_len)self.reductionnoneunweighted_loss super(MaskedSoftmaxCELoss, self).forward(pred.permute(0, 2, 1), label)weighted_loss (unweighted_loss * weights).mean(dim1)return weighted_loss训练
在训练部分需要在原始的编码器输出序列前加入特定的序列开始词元 同时作为解码器的输入—这种操作被称为强制教学。
在训练部分需要在原始的编码器输出序列前加入特定的序列开始词元bos 同时作为解码器的输入---这种操作被称为强制教学
#save
def train_seq2seq(net,data_iter,lr,num_epochs,tgt_vocab,device):训练序列到序列模型def xavier_init_weights(m):if type(m) nn.Linear:nn.init.xavier_uniform_(m.weight)if type(m) nn.GRU:for param in m._flat_weights_names:if weight in param:nn.init.xavier_uniform_(m._parameters[param])net.apply(xavier_init_weights)net.to(device)optimizer torch.optim.Adam(net.parameters(), lrlr)loss MaskedSoftmaxCELoss()注意这里使用的是net.train()net.train()animator d2l.Animator(xlabelepoch, ylabelloss,xlim[10, num_epochs])for epoch in range(num_epochs):timer d2l.Timer()metric d2l.Accumulator(2) # 训练损失总和词元数量for batch in data_iter:optimizer.zero_grad()X, X_valid_len, Y, Y_valid_len [x.to(device) for x in batch]# print(train-X:,X,train-X_valid_len:,X_valid_len)# print(train-Y:,Y,train-Y_valid_len:,Y_valid_len)with open(D://pythonProject//X_valid_len.txt, w) as f:f.write(str(X_valid_len))tensor([4, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,5])----------它的总长度为batch_size25(最后一个batch_size) 之前的都是64with open(D://pythonProject//Y_valid_len.txt, w) as f:f.write(str(Y_valid_len))tensor([4, 4, 3, 5, 5, 4, 5, 3, 4, 4, 5, 4, 4, 4, 7, 5, 5, 4, 4, 3, 4, 4, 3, 3,5])----------它的总长度为batch_size25(最后一个batch_size) 之前的都是64bos torch.tensor([tgt_vocab[bos]] * Y.shape[0],devicedevice).reshape(-1, 1)dec_input torch.cat([bos, Y[:, :-1]], 1) # 强制教学Y_hat, _ net(X, dec_input, X_valid_len)l loss(Y_hat, Y, Y_valid_len)l.sum().backward() # 损失函数的标量进行“反向传播”d2l.grad_clipping(net, 1)num_tokens Y_valid_len.sum()optimizer.step()with torch.no_grad():metric.add(l.sum(), num_tokens)if (epoch 1) % 10 0:animator.add(epoch 1, (metric[0] / metric[1],))print(floss {metric[0] / metric[1]:.3f}, {metric[1] / timer.stop():.1f} ftokens/sec on {str(device)})在机器翻译数据集上创建和训练一个循环神经网络‘编码器-解码器‘模型用于序列到序列的学习
这里需要注意的是在decoder训练的时候丢进去的数据直接是真实的label值。
embed_size, num_hiddens, num_layers, dropout 32, 32, 2, 0.1
batch_size, num_steps 64, 10
lr, num_epochs, device 0.005, 300, d2l.try_gpu()
train_iter, src_vocab, tgt_vocab d2l.load_data_nmt(batch_size, num_steps)
encoder Seq2SeqEncoder(len(src_vocab), embed_size, num_hiddens, num_layers, dropout)
decoder Seq2SeqDecoder(len(tgt_vocab), embed_size, num_hiddens, num_layers,dropout)
net d2l.EncoderDecoder(encoder, decoder)
train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)
预测
为了采用一个接着一个词元的方式预测输出序列 每个解码器当前时间步的输入都将来自于前一时间步的预测词元。 预测阶段的主要任务是
将net设置为评估模式在tokens后面加入 eos ;如果长度不够num_steps时在句子后填充 pad 拉长句子将源tokens增加维度0使得它变成一个二维向量将编码器的输出该输出包括outputs和state两个部分传入解码器的初始化隐状态函数中初始化解码器的隐状态将编码器的输入特征X转变成二维特征向量预测过程①利用预测最高可能性的词元作为解码器在下一个时间步的输入②将解码器的输出转变成二维向量如果预测的词元为 eos 则停止这个短句的预测③最后利用join函数形成最终的预测短句
# 预测
#save
def predict_seq2seq(net, src_sentence, src_vocab, tgt_vocab, num_steps,device, save_attention_weightsFalse):序列到序列模型的预测# 在预测时将net设置为评估模式net.eval()src_tokens src_vocab[src_sentence.lower().split( )] [src_vocab[eos]]enc_valid_len torch.tensor([len(src_tokens)], devicedevice)# 增加padif len(src_tokens) num_steps:with open(D://pythonProject//predict_seq2seq-truncate.txt, w) as f:f.write(str(截断))else:with open(D://pythonProject//predict_seq2seq-pad.txt, w) as f:f.write(str(拉长))src_tokens d2l.truncate_pad(src_tokens, num_steps, src_vocab[pad])# 添加批量轴---增肌维度将input是一维则dim0时数据为行方向扩dim1时为列方向扩这里的src_tokens是一个list对象print(len(src_tokens): ,len(src_tokens)) # len of src_tokens 10enc_X torch.unsqueeze(torch.tensor(src_tokens, dtypetorch.long, devicedevice), dim0)# enc_X的大小为 torch.Size([1, 10])这里将src_tokens从list对象转成了一个tensor增加了维度0with open(D://pythonProject//predict_seq2seq-enc_X-enc_X.txt, w) as f:f.write(str(enc_X.size()))enc_outputs net.encoder(enc_X, enc_valid_len)dec_state net.decoder.init_state(enc_outputs, enc_valid_len)# 添加批量轴这里将tgt_vocab从list对象转成了一个tensor增加了维度0dec_X torch.unsqueeze(torch.tensor([tgt_vocab[bos]], dtypetorch.long, devicedevice), dim0)output_seq, attention_weight_seq [], []for _ in range(num_steps):Y, dec_state net.decoder(dec_X, dec_state)# 我们使用具有预测最高可能性的词元作为解码器在下一时间步的输入dec_X Y.argmax(dim2) #返回可能性最大词元的索引位置pred dec_X.squeeze(dim0).type(torch.int32).item()print(pred:---, pred)# 保存注意力权重稍后讨论if save_attention_weights:attention_weight_seq.append(net.decoder.attention_weights)# 一旦序列结束词元被预测输出序列的生成就完成了if pred tgt_vocab[eos]:print(pred:---eos,pred)breakoutput_seq.append(pred)return .join(tgt_vocab.to_tokens(output_seq)), attention_weight_seq利用BLEU函数进行预测序列的评估
BLEU函数
正如上述式子所列当预测的长度 l e n p r e d len_{pred} lenpred小于真实的label长度 l e n l a b e l len_{label} lenlabel时说明预测成功的可能性很低此时整个分式就变得很大最后出来的值就会很小这就在一定程度上加强了短句子的权重惩罚。同时如果后面的连乘加重了长句子的权重惩罚。
# 预测序列的评估
def bleu(pred_seq, label_seq, k): #save计算BLEUpred_tokens, label_tokens pred_seq.split( ), label_seq.split( )len_pred, len_label len(pred_tokens), len(label_tokens)score math.exp(min(0, 1 - len_label / len_pred))for n in range(1, k 1):num_matches, label_subs 0, collections.defaultdict(int)for i in range(len_label - n 1):label_subs[ .join(label_tokens[i: i n])] 1for i in range(len_pred - n 1):if label_subs[ .join(pred_tokens[i: i n])] 0:num_matches 1label_subs[ .join(pred_tokens[i: i n])] - 1score * math.pow(num_matches / (len_pred - n 1), math.pow(0.5, n))return score在“fra-eng”数据集上做预测
最后利用训练好的循环神经网络“编码器解码器”模型 将几个英语句子翻译成法语并计算BLEU的最终结果。
engs [go ., i lost ., he\s calm ., i\m home .]
fras [va !, j\ai perdu ., il est calme ., je suis chez moi .]
for eng, fra in zip(engs, fras):translation, attention_weight_seq predict_seq2seq(net, eng, src_vocab, tgt_vocab, num_steps, device)print(f{eng} {translation}, bleu {bleu(translation, fra, k2):.3f})
省流—全部代码
注意这里为了debug我增加了很多写文件的操作主要是观察每一个向量的形状变化具体的结果已经通过注释的方式写到了下面代码中仅做参考~
模块torch已被修改
def read_data_nmt():# Load the English-French dataset.data_dir d2l.download_extract(fra-eng)with open(os.path.join(data_dir, fra.txt), r,encodingUTF-8) as f:return f.read()代码中出现的torch.Size([25, 10, 32])是因为将原始的数据按照batch_size进行划分最后一个batch的大小就是25# 无脑导包
import torch
import collections # 这个包还是需要注意一下
import math
from torch import nn
from d2l import torch as d2l# 实现Encoder编码器部分内容部分
编码器的任务主要包括将某一个时刻t的输入特征向量x_t和上一个时刻的隐状态h_(t-1)转变为h_t即h_t f(x_t , h_(t-1))编码器需要通过函数q实现把所有的隐状态转变为上下文变量c q( h_1,....,h_T )使用嵌入层获取输入序列的每个词元的特征向量[嵌入层权重矩阵行数为vocab_size,列数是特征向量的维度]采用GRU实现编码器
#save
class Seq2SeqEncoder(d2l.Encoder):用于序列到序列学习的循环神经网络编码器def __init__(self, vocab_size, embed_size, num_hiddens, num_layers, dropout0, **kwargs):super(Seq2SeqEncoder,self).__init__(**kwargs)# 实现嵌入层Embedding 将每一个词元转变成一个词向量self.embedding nn.Embedding(vocab_size,embed_size)# print(Encoder中 self.embedding的size为:,self.embedding.size())# print(Encoder中 embed_size: ,embed_size)with open(D://pythonProject//Encoder_embed_pervir_size.txt, w) as f:f.write(str(embed_size))----------embed_size为32----------这里的embed_size为每一个词元对应的特征向量的长度self.rnn nn.GRU(embed_size,num_hiddens,num_layers,dropoutdropout)def forward(self, X, *args):with open(D://pythonProject//Encoder_Not_embed_size.txt, w) as f:f.write(str(X.size()))----------未进行embedding的X: torch.Size([64, 10]) batch_size * num_steps----------# print(Encoder中 未进行embedding前的X的size,X.size())# embedding 的形状 (vocab_size,embed_size)# 输出X的形状(batch_size,num_steps,embed_size)X self.embedding(X)# print(Encoder中 进行embedding后的X的size,X.size())with open(D://pythonProject//Encoder_embed_size.txt, w) as f:f.write(str(X.size()))----------进行了embedding的X: torch.Size([64, 10, 32])----------#torch要求在循环神经网络模型中第一个轴对应的必须是时间步X X.permute(1,0,2)# print(Encoder中 permute后的X的size,X.size())with open(D://pythonProject//Encoder_permute_size.txt, w) as f:f.write(str(X.size()))----------进行了permute的X: torch.Size([10, 64, 32]) 10为时间步----------output,state self.rnn(X)# output的输出形状: (num_steps,batch_size,num_hiddens)# state的输出形状: (num_layers,batch_size,num_hiddens)return output,state# 编码器实例化输入
layer: 2层
hiddens: 16个
batch: 4
steps: 7
输出
tensor[时间步数,批量大小,隐藏单元数]encoder Seq2SeqEncoder(vocab_size10,embed_size8,num_hiddens16,num_layers2,dropout0)
X torch.zeros((4,7),dtypetorch.long)
output,state encoder(X) # X的维度对应于forwoard中的X的维度
# print(output.shape: ,output.shape)
with open(D://pythonProject//Encoder_output_size.txt, w) as f:f.write(str(output.shape))
----------output的形状: torch.Size([7, 4, 16]) 10为时间步----------这里使用的是门控循环单元GRU最后一个时间步的多层隐状态的形状是(num_layers,batch_size,num_hiddens)
如果使用LSTM 则state中还应该包含记忆单元信息
# 实现Decoder部分
编码器输出的整个上下文信息变量C需要作用于整个输入序列x_1,...,x_r,对输入序列进行编码
解码器输出(star)y取决于输出子序列y1,...,(star)y_(t-1),C
P((star)y|y1,...,(star)y_(t-1),C)·使用解码器时我们直接使用编码器的最后一个时间步的隐状态来初始化解码器的隐状态---两者应该具有相同的隐藏层和隐藏单元
·为了让上下文信息更好包含更多的信息可以用上下文变量C在所有的时间步与解码器的输入进行拼接
·为了输出预测词元的概率分布在最后一层采用全连接层来变换隐状态class Seq2SeqDecoder(d2l.Decoder):用于序列到序列学习的循环神经网络解码器def __init__(self,vocab_size,embed_size,num_hiddens,num_layers,dropout0,**kwargs):super(Seq2SeqDecoder, self).__init__(**kwargs)self.embedding nn.Embedding(vocab_size,embed_size)with open(D://pythonProject//Decoder_vocab_size.txt, w) as f:f.write(str(vocab_size))----------decoder的vocab_size为201----------with open(D://pythonProject//Decoder_embed_size.txt, w) as f:f.write(str(embed_size))----------decoder的embed_size为32----------self.rnn nn.GRU(embed_sizenum_hiddens,num_hiddens,num_layers,dropoutdropout)self.dense nn.Linear(num_hiddens,vocab_size)def init_state(self,enc_outputs,*args):# enc_outputs[0]为编码器的输出# enc_outputs[1]为编码器最后一层输出的隐变量return enc_outputs[1]def forward(self, X, state):# print(Decoder中 未进行embedding的X的形状:,X.size())with open(D://pythonProject//Decoder_X_size.txt, w) as f:f.write(str(X.size()))Decoder的X的大小torch.Size([25, 10])# 输出X的形状(batch_size,num_steps,embed_size)X self.embedding(X).permute(1,0,2)with open(D://pythonProject//Decoder_X_embed_permute.txt, w) as f:f.write(str(X.size()))Decoder的X_embed_permute的大小torch.Size([10, 25, 32])# 广播context使其具有与X相同的num_steps 即X.shape[0]context state[-1].repeat(X.shape[0], 1, 1)X_and_Context torch.cat((X,context),2)output,state self.rnn(X_and_Context,state)output self.dense(output).permute(1,0,2)# output的形状:(batch_size,num_steps,vocab_size)# state的形状:(num_layers,batch_size,num_hiddens)return output, state# 实例化解码器
decoder Seq2SeqDecoder(vocab_size10, embed_size8, num_hiddens16,num_layers2)
decoder.eval()
state decoder.init_state(encoder(X))
output, state decoder(X, state)
output.shape, state.shape# 修改损失函数将填充词元的预测排除在损失函数的计算之外
下面的sequence_mask函数 通过零值化屏蔽不相关的项
#save
def sequence_mask(X,valid_len,value0):# print(mask X的形状:,X.size())with open(D://pythonProject//Mask_X_size.txt, w) as f:f.write(str(X.size()))损失函数中的Mask_X_size的大小torch.Size([25, 10]) 显然是没有进行Embedding的在序列中屏蔽不相干的项maxlen X.size(1)mask torch.arange((maxlen),dtypetorch.float32,deviceX.device)[None,:]valid_len[:,None]X[~mask] valuereturn X
X torch.tensor([[1,2,3],[4,5,6]])
res sequence_mask(X,torch.tensor([1,2]))
print(valid_len 分别为 1 和 2: ,res)# 同时可以使用非0值替换要屏蔽的项
X torch.ones(2,3,4)
res sequence_mask(X,torch.tensor([1,2]),value-1)我们可以通过扩展softmax交叉熵损失函数来遮蔽不相关的预测。
最初所有预测词元的掩码都设置为1。 一旦给定了有效长度与填充
词元对应的掩码将被设置为0。 最后将所有词元的损失乘以掩码以
过滤掉损失中填充词元产生的不相关预测。#save
class MaskedSoftmaxCELoss(nn.CrossEntropyLoss):带遮蔽的softmax交叉熵损失函数# pred的形状(batch_size,num_steps,vocab_size)# label的形状(batch_size,num_steps)# valid_len的形状(batch_size,)def forward(self, pred, label, valid_len):# 预测词元的掩码都设置为1weights torch.ones_like(label)# 一旦给定了有效长度与填充# 词元对应的掩码将被设置为0。weights sequence_mask(weights, valid_len)self.reductionnoneunweighted_loss super(MaskedSoftmaxCELoss, self).forward(pred.permute(0, 2, 1), label)weighted_loss (unweighted_loss * weights).mean(dim1)return weighted_loss
# 使用三个相同的序列 来进行代码健全性检查 分别指定这些序列的有效长度是4,2,0
# 得出的损失结果为 第一个序列是第二个序列的两倍第三个序列的损失直接为0# 训练
在训练部分需要在原始的编码器输出序列前加入特定的序列开始词元bos 同时作为解码器的输入---这种操作被称为强制教学
#save
def train_seq2seq(net,data_iter,lr,num_epochs,tgt_vocab,device):训练序列到序列模型def xavier_init_weights(m):if type(m) nn.Linear:nn.init.xavier_uniform_(m.weight)if type(m) nn.GRU:for param in m._flat_weights_names:if weight in param:nn.init.xavier_uniform_(m._parameters[param])net.apply(xavier_init_weights)net.to(device)optimizer torch.optim.Adam(net.parameters(), lrlr)loss MaskedSoftmaxCELoss()注意这里使用的是net.train()net.train()animator d2l.Animator(xlabelepoch, ylabelloss,xlim[10, num_epochs])for epoch in range(num_epochs):timer d2l.Timer()metric d2l.Accumulator(2) # 训练损失总和词元数量for batch in data_iter:optimizer.zero_grad()X, X_valid_len, Y, Y_valid_len [x.to(device) for x in batch]# print(train-X:,X,train-X_valid_len:,X_valid_len)# print(train-Y:,Y,train-Y_valid_len:,Y_valid_len)with open(D://pythonProject//X_valid_len.txt, w) as f:f.write(str(X_valid_len))tensor([4, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,5])----------它的总长度为batch_size25(最后一个batch_size) 之前的都是64with open(D://pythonProject//Y_valid_len.txt, w) as f:f.write(str(Y_valid_len))tensor([4, 4, 3, 5, 5, 4, 5, 3, 4, 4, 5, 4, 4, 4, 7, 5, 5, 4, 4, 3, 4, 4, 3, 3,5])----------它的总长度为batch_size25(最后一个batch_size) 之前的都是64bos torch.tensor([tgt_vocab[bos]] * Y.shape[0],devicedevice).reshape(-1, 1)dec_input torch.cat([bos, Y[:, :-1]], 1) # 强制教学Y_hat, _ net(X, dec_input, X_valid_len)l loss(Y_hat, Y, Y_valid_len)l.sum().backward() # 损失函数的标量进行“反向传播”d2l.grad_clipping(net, 1)num_tokens Y_valid_len.sum()optimizer.step()with torch.no_grad():metric.add(l.sum(), num_tokens)if (epoch 1) % 10 0:animator.add(epoch 1, (metric[0] / metric[1],))print(floss {metric[0] / metric[1]:.3f}, {metric[1] / timer.stop():.1f} ftokens/sec on {str(device)})
在机器翻译数据集上创建和训练一个循环神经网络‘编码器-解码器‘模型用于序列到序列的学习
embed_size, num_hiddens, num_layers, dropout 32, 32, 2, 0.1
batch_size, num_steps 64, 10
lr, num_epochs, device 0.005, 300, d2l.try_gpu()
train_iter, src_vocab, tgt_vocab d2l.load_data_nmt(batch_size, num_steps)
encoder Seq2SeqEncoder(len(src_vocab), embed_size, num_hiddens, num_layers, dropout)
decoder Seq2SeqDecoder(len(tgt_vocab), embed_size, num_hiddens, num_layers,dropout)
net d2l.EncoderDecoder(encoder, decoder)
train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)# 预测
#save
def predict_seq2seq(net, src_sentence, src_vocab, tgt_vocab, num_steps,device, save_attention_weightsFalse):序列到序列模型的预测# 在预测时将net设置为评估模式net.eval()src_tokens src_vocab[src_sentence.lower().split( )] [src_vocab[eos]]enc_valid_len torch.tensor([len(src_tokens)], devicedevice)# 增加padif len(src_tokens) num_steps:with open(D://pythonProject//predict_seq2seq-truncate.txt, w) as f:f.write(str(截断))else:with open(D://pythonProject//predict_seq2seq-pad.txt, w) as f:f.write(str(拉长))src_tokens d2l.truncate_pad(src_tokens, num_steps, src_vocab[pad])# 添加批量轴---增肌维度将input是一维则dim0时数据为行方向扩dim1时为列方向扩这里的src_tokens是一个list对象print(len(src_tokens): ,len(src_tokens)) # len of src_tokens 10enc_X torch.unsqueeze(torch.tensor(src_tokens, dtypetorch.long, devicedevice), dim0)# enc_X的大小为 torch.Size([1, 10])这里将src_tokens从list对象转成了一个tensor增加了维度0with open(D://pythonProject//predict_seq2seq-enc_X-enc_X.txt, w) as f:f.write(str(enc_X.size()))enc_outputs net.encoder(enc_X, enc_valid_len)dec_state net.decoder.init_state(enc_outputs, enc_valid_len)# 添加批量轴这里将tgt_vocab从list对象转成了一个tensor增加了维度0dec_X torch.unsqueeze(torch.tensor([tgt_vocab[bos]], dtypetorch.long, devicedevice), dim0)output_seq, attention_weight_seq [], []for _ in range(num_steps):Y, dec_state net.decoder(dec_X, dec_state)# 我们使用具有预测最高可能性的词元作为解码器在下一时间步的输入dec_X Y.argmax(dim2) #返回可能性最大词元的索引位置pred dec_X.squeeze(dim0).type(torch.int32).item()print(pred:---, pred)# 保存注意力权重稍后讨论if save_attention_weights:attention_weight_seq.append(net.decoder.attention_weights)# 一旦序列结束词元被预测输出序列的生成就完成了if pred tgt_vocab[eos]:print(pred:---eos,pred)breakoutput_seq.append(pred)return .join(tgt_vocab.to_tokens(output_seq)), attention_weight_seq
# 预测序列的评估
def bleu(pred_seq, label_seq, k): #save计算BLEUpred_tokens, label_tokens pred_seq.split( ), label_seq.split( )len_pred, len_label len(pred_tokens), len(label_tokens)score math.exp(min(0, 1 - len_label / len_pred))for n in range(1, k 1):num_matches, label_subs 0, collections.defaultdict(int)for i in range(len_label - n 1):label_subs[ .join(label_tokens[i: i n])] 1for i in range(len_pred - n 1):if label_subs[ .join(pred_tokens[i: i n])] 0:num_matches 1label_subs[ .join(pred_tokens[i: i n])] - 1score * math.pow(num_matches / (len_pred - n 1), math.pow(0.5, n))return score
最后利用训练好的循环神经网络“编码器解码器”模型 将几个英语句子翻译成法语并计算BLEU的最终结果。
engs [go ., i lost ., he\s calm ., i\m home .]
fras [va !, j\ai perdu ., il est calme ., je suis chez moi .]
for eng, fra in zip(engs, fras):translation, attention_weight_seq predict_seq2seq(net, eng, src_vocab, tgt_vocab, num_steps, device)print(f{eng} {translation}, bleu {bleu(translation, fra, k2):.3f}) 文章转载自: http://www.morning.tnkwj.cn.gov.cn.tnkwj.cn http://www.morning.pwghp.cn.gov.cn.pwghp.cn http://www.morning.jqmqf.cn.gov.cn.jqmqf.cn http://www.morning.tdttz.cn.gov.cn.tdttz.cn http://www.morning.nfsrs.cn.gov.cn.nfsrs.cn http://www.morning.xbnkm.cn.gov.cn.xbnkm.cn http://www.morning.tsgxz.cn.gov.cn.tsgxz.cn http://www.morning.smfbw.cn.gov.cn.smfbw.cn http://www.morning.prhfc.cn.gov.cn.prhfc.cn http://www.morning.rzmsl.cn.gov.cn.rzmsl.cn http://www.morning.cwgt.cn.gov.cn.cwgt.cn http://www.morning.yixingshengya.com.gov.cn.yixingshengya.com http://www.morning.dyfmh.cn.gov.cn.dyfmh.cn http://www.morning.yltnl.cn.gov.cn.yltnl.cn http://www.morning.qyxnf.cn.gov.cn.qyxnf.cn http://www.morning.nfsrs.cn.gov.cn.nfsrs.cn http://www.morning.c7493.cn.gov.cn.c7493.cn http://www.morning.jgnjl.cn.gov.cn.jgnjl.cn http://www.morning.bjsites.com.gov.cn.bjsites.com http://www.morning.dhmll.cn.gov.cn.dhmll.cn http://www.morning.ranglue.com.gov.cn.ranglue.com http://www.morning.lmzpk.cn.gov.cn.lmzpk.cn http://www.morning.mnsts.cn.gov.cn.mnsts.cn http://www.morning.cjmmt.cn.gov.cn.cjmmt.cn http://www.morning.rbbzn.cn.gov.cn.rbbzn.cn http://www.morning.smj78.cn.gov.cn.smj78.cn http://www.morning.hcxhz.cn.gov.cn.hcxhz.cn http://www.morning.pccqr.cn.gov.cn.pccqr.cn http://www.morning.lsssx.cn.gov.cn.lsssx.cn http://www.morning.cyjjp.cn.gov.cn.cyjjp.cn http://www.morning.wcrcy.cn.gov.cn.wcrcy.cn http://www.morning.rjrh.cn.gov.cn.rjrh.cn http://www.morning.ymjgx.cn.gov.cn.ymjgx.cn http://www.morning.bssjz.cn.gov.cn.bssjz.cn http://www.morning.xfxnq.cn.gov.cn.xfxnq.cn http://www.morning.zwmjq.cn.gov.cn.zwmjq.cn http://www.morning.wqkzf.cn.gov.cn.wqkzf.cn http://www.morning.fbdtd.cn.gov.cn.fbdtd.cn http://www.morning.cptzd.cn.gov.cn.cptzd.cn http://www.morning.bpmnh.cn.gov.cn.bpmnh.cn http://www.morning.tbnpn.cn.gov.cn.tbnpn.cn http://www.morning.rqrh.cn.gov.cn.rqrh.cn http://www.morning.rkmsm.cn.gov.cn.rkmsm.cn http://www.morning.byrlg.cn.gov.cn.byrlg.cn http://www.morning.tgydf.cn.gov.cn.tgydf.cn http://www.morning.pbtdr.cn.gov.cn.pbtdr.cn http://www.morning.pljdy.cn.gov.cn.pljdy.cn http://www.morning.bpmnh.cn.gov.cn.bpmnh.cn http://www.morning.lhzqn.cn.gov.cn.lhzqn.cn http://www.morning.tfrmx.cn.gov.cn.tfrmx.cn http://www.morning.nzzws.cn.gov.cn.nzzws.cn http://www.morning.mcjrf.cn.gov.cn.mcjrf.cn http://www.morning.nrfrd.cn.gov.cn.nrfrd.cn http://www.morning.qllcp.cn.gov.cn.qllcp.cn http://www.morning.mprtj.cn.gov.cn.mprtj.cn http://www.morning.dxqfh.cn.gov.cn.dxqfh.cn http://www.morning.gfnsh.cn.gov.cn.gfnsh.cn http://www.morning.snyqb.cn.gov.cn.snyqb.cn http://www.morning.skdhm.cn.gov.cn.skdhm.cn http://www.morning.xqgh.cn.gov.cn.xqgh.cn http://www.morning.ygrkg.cn.gov.cn.ygrkg.cn http://www.morning.pkpqh.cn.gov.cn.pkpqh.cn http://www.morning.ykrg.cn.gov.cn.ykrg.cn http://www.morning.mzgq.cn.gov.cn.mzgq.cn http://www.morning.tkchg.cn.gov.cn.tkchg.cn http://www.morning.skbbt.cn.gov.cn.skbbt.cn http://www.morning.lsxabc.com.gov.cn.lsxabc.com http://www.morning.rqbkc.cn.gov.cn.rqbkc.cn http://www.morning.hlxpz.cn.gov.cn.hlxpz.cn http://www.morning.wqmyh.cn.gov.cn.wqmyh.cn http://www.morning.zgztn.cn.gov.cn.zgztn.cn http://www.morning.bswxt.cn.gov.cn.bswxt.cn http://www.morning.msgcj.cn.gov.cn.msgcj.cn http://www.morning.cwwbm.cn.gov.cn.cwwbm.cn http://www.morning.dkslm.cn.gov.cn.dkslm.cn http://www.morning.wqrdx.cn.gov.cn.wqrdx.cn http://www.morning.nqlkb.cn.gov.cn.nqlkb.cn http://www.morning.dysgr.cn.gov.cn.dysgr.cn http://www.morning.bdfph.cn.gov.cn.bdfph.cn http://www.morning.nhdw.cn.gov.cn.nhdw.cn