广州建论坛网站,上海百度优化,商场大型话题活动策划网站,怎么登录已注册的网站现代循环神经网络
循环神经网络中梯度异常在实践中的意义引发了一些问题#xff1a;
早期观测值影响重大#xff1a;早期观测值对预测所有未来观测值极为重要#xff0c;如序列中第一个观测值包含校验和#xff0c;需在序列末尾辨别其是否正确#xff0c;若无特殊机制存…现代循环神经网络
循环神经网络中梯度异常在实践中的意义引发了一些问题
早期观测值影响重大早期观测值对预测所有未来观测值极为重要如序列中第一个观测值包含校验和需在序列末尾辨别其是否正确若无特殊机制存储早期重要信息就需给该观测值指定很大梯度因为它影响后续所有观测值。存在无关词元在对网页内容进行情感分析等任务时可能存在一些与观测值无关的词元如辅助 HTML 代码希望有机制能在隐状态表示中跳过此类词元。序列存在逻辑中断序列的各个部分之间可能存在逻辑中断如书的章节之间、证券的熊市和牛市之间的过渡最好有一种方法来重置内部状态表示。
解决方案
长短期记忆LSTM是学术界提出的用于解决上述问题的最早方法之一。门控循环单元GRU是一个稍微简化的变体通常能提供与 LSTM 同等的效果且计算速度明显更快。
门控循环单元GRU
关注一个序列 不是每个观察值都是同等重要只想记住相关的观察需要 能关注的机制更新门 update gate将重要的数据放入该门能遗忘的机制重置门 reset gate到目前为止前面东西不重要了。
更新门和重置门 其中
通过重置门和更新门控制隐状态的更新与重置。重置门决定保留过去状态的程度更新门控制新状态中旧状态的占比两者都用 sigmoid 函数将输入转换到 (0, 1) 区间进行凸组合
候选隐状态备胎
将重置门 Rt与 常规隐状态更新机制集成得到在时间步 t 的候选隐状态candidate hidden state 重置门接近 1 时类似普通循环神经网络接近 0 时候选隐状态取决于当前输入。
隐状态真正的 更新门接近 1 时保留旧状态接近 0 时新隐状态接近候选隐状态有助于处理梯度消失问题和捕获长序列依赖。
总之门控循环单元具有以下两个显著特征 重置门有助于捕获序列中的短期依赖关系 更新门有助于捕获序列中的长期依赖关系
总结
代码实现 - 从零实现
# 门循环单元GRU
# 从零开始实现import torch
from torch import nn
from d2l import torch as d2lbatch_size, num_steps 32, 35
train_iter, vocab d2l.load_data_time_machine(batch_size, num_steps)# 初始化模型参数
def get_params(vocab_size, num_hiddens, device):num_inputs num_outputs vocab_sizedef normal(shape):return torch.randn(sizeshape, devicedevice)*0.01def three():return (normal((num_inputs, num_hiddens)),normal((num_hiddens, num_hiddens)),torch.zeros(num_hiddens, devicedevice))W_xz, W_hz, b_z three() # 更新门参数W_xr, W_hr, b_r three() # 重置门参数W_xh, W_hh, b_h three() # 候选隐状态参数# 输出层参数W_hq normal((num_hiddens, num_outputs))b_q torch.zeros(num_outputs, devicedevice)# 附加梯度params [W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q]for param in params:param.requires_grad_(True)return params# 定义模型
# 定义隐状态的初始化函数
# 返回一个形状为批量大小隐藏单元个数的张量张量的值全部为零。def init_gru_state(batch_size, num_hiddens, device):return (torch.zeros((batch_size, num_hiddens), devicedevice), )# 定义门控循环单元模型
def gru(inputs, state, params):W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q paramsH, stateoutputs []for X in inputs:Z torch.sigmoid((X W_xz) (H W_hz) b_z)R torch.sigmoid((X W_xr) (H W_hr) b_r)H_tilda torch.tanh((X W_xh) ((R * H) W_hh) b_h)H Z * H (1 - Z) * H_tildaY H W_hq b_qoutputs.append(Y)return torch.cat(outputs, dim0), (H,)# 训练与预测
vocab_size, num_hiddens, device len(vocab), 256, d2l.try_gpu()
num_epochs, lr 500, 1
model d2l.RNNModelScratch(len(vocab), num_hiddens, device, get_params,init_gru_state, gru)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)结果
代码实现 - 简洁实现
# 使用高级API,运行速度很快使用的是编译好的运算符而不是Python来处理之前阐述的许多细节
num_inputs vocab_size
gru_layer nn.GRU(num_inputs, num_hiddens)
model d2l.RNNModel(gru_layer, len(vocab))
model model.to(device)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)结果
长短期记忆网络LSTM
忘记门将值朝0减少输入门决定不是忽略掉输入数据输出门决定是不是使用隐状态
效果上和GRU一样都是是不是要忘掉过去的状态看现在的输入或者不看现在输入的数据而看前一时刻的状态
忘记门、输入门、输出门
公式
候选记忆单元
公式 候选记忆元如下图所示
记忆单元
上一个时刻的记忆单元会作为状态加入与GRU不同之处 如果遗忘门始终为1且输入门始终为0 则过去的记忆元Ct-1 将随时间被保存并传递到当前时间步。 引入这种设计是为了缓解梯度消失问题 并更好地捕获序列中的长距离依赖关系。
这样就得到了计算记忆元的流程图
隐状态
公式将隐藏状态约束到-11 只要输出门接近1就能够有效地将所有记忆信息传递给预测部分 而对于输出门接近0只保留记忆元内的所有信息而不需要更新隐状态。如下提供了数据流的图形化演示。
总结 长短期记忆网络有三种类型的门输入门、遗忘门和输出门。 长短期记忆网络的隐藏层输出包括“隐状态”和“记忆元”。只有隐状态会传递到输出层而记忆元完全属于内部信息。 长短期记忆网络可以缓解梯度消失和梯度爆炸。
代码实现 - 从零实现
# 长短期记忆网络LSTMimport torch
from torch import nn
from d2l import torch as d2lbatch_size, num_steps 32, 35
train_iter, vocab d2l.load_data_time_machine(batch_size, num_steps)# [初始化模型参数
# 超参数num_hiddens定义隐藏单元的数量
def get_lstm_params(vocab_size, num_hiddens, device):num_inputs num_outputs vocab_sizedef normal(shape):return torch.randn(sizeshape, devicedevice)*0.01def three():return (normal((num_inputs, num_hiddens)),normal((num_hiddens, num_hiddens)),torch.zeros(num_hiddens, devicedevice))W_xi, W_hi, b_i three() # 输入门参数W_xf, W_hf, b_f three() # 遗忘门参数W_xo, W_ho, b_o three() # 输出门参数W_xc, W_hc, b_c three() # 候选记忆元参数# 输出层参数W_hq normal((num_hiddens, num_outputs))b_q torch.zeros(num_outputs, devicedevice)# 附加梯度params [W_xi, W_hi, b_i, W_xf, W_hf, b_f, W_xo, W_ho, b_o, W_xc, W_hc,b_c, W_hq, b_q]for param in params:param.requires_grad_(True)return params# 定义模型
# 初始化函数
# 长短期记忆网络的隐状态需要返回一个额外的记忆元 单元的值为0形状为批量大小隐藏单元数def init_lstm_state(batch_size, num_hiddens, device):return (torch.zeros((batch_size, num_hiddens), devicedevice),torch.zeros((batch_size, num_hiddens), devicedevice))# 实际模型的定义
def lstm(inputs, state, params):[W_xi, W_hi, b_i, W_xf, W_hf, b_f, W_xo, W_ho, b_o, W_xc, W_hc, b_c,W_hq, b_q] params(H, C) stateoutputs []for X in inputs:I torch.sigmoid((X W_xi) (H W_hi) b_i)F torch.sigmoid((X W_xf) (H W_hf) b_f)O torch.sigmoid((X W_xo) (H W_ho) b_o)C_tilda torch.tanh((X W_xc) (H W_hc) b_c)C F * C I * C_tildaH O * torch.tanh(C)Y (H W_hq) b_qoutputs.append(Y)return torch.cat(outputs, dim0), (H, C)# 训练和预测
vocab_size, num_hiddens, device len(vocab), 256, d2l.try_gpu()
num_epochs, lr 500, 1
model d2l.RNNModelScratch(len(vocab), num_hiddens, device, get_lstm_params,init_lstm_state, lstm)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)结果
代码实现 - 简洁实现
# 简洁实现
num_inputs vocab_size
lstm_layer nn.LSTM(num_inputs, num_hiddens)
model d2l.RNNModel(lstm_layer, len(vocab))
model model.to(device)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)深度循环神经网络
回顾RNN主要就是如何得到更多的非线性 —— 多加几个隐藏层。
更深的 总结
在深度循环神经网络中隐状态的信息被传递到当前层的下一时间步和下一层的当前时间步。 有许多不同风格的深度循环神经网络 如长短期记忆网络、门控循环单元、或经典循环神经网络。 这些模型在深度学习框架的高级API中都有涵盖。 总体而言深度循环神经网络需要大量的调参如学习率和修剪 来确保合适的收敛模型的初始化也需要谨慎。
代码实现
# 深度循环神经网络import torch
from torch import nn
from d2l import torch as d2lbatch_size, num_steps 32, 35
train_iter, vocab d2l.load_data_time_machine(batch_size, num_steps)# 通过num_layers的值来设定隐藏层数vocab_size, num_hiddens, num_layers len(vocab), 256, 2
num_inputs vocab_size
device d2l.try_gpu()
lstm_layer nn.LSTM(num_inputs, num_hiddens, num_layers)
model d2l.RNNModel(lstm_layer, len(vocab))
model model.to(device)# 训练和预测
num_epochs, lr 500, 2
d2l.train_ch8(model, train_iter, vocab, lr*1.0, num_epochs, device)结果
双向循环神经网络
对于某些序列可以从前往后看也可以从后往前看。 首先给一个例子我们考虑以下三个在文本序列中填空的任务。 根据可获得的信息量我们可以用不同的词填空 如“很高兴”“happy”、“不”“not”和“非常”“very”。 很明显每个短语的“下文”传达了重要信息如果有的话 而这些信息关乎到选择哪个词来填空 所以无法利用这一点的序列模型将在相关任务上表现不佳。 例如如果要做好命名实体识别 例如识别“Green”指的是“格林先生”还是绿色 不同长度的上下文范围重要性是相同的。
取决于过去和未来的上下文可以填很不一样的词到目前为止RNN只看过去在填空的时候也可以看未来
双向RNN
一个前向RNN隐层一个方向RNN隐层合并两个隐状态得到输出 公式
总结
特性使用来自**序列两端过去和未来**的观测信息预测当前观测。问题在预测下一个词元时因测试期间只有过去数据精度较差计算速度慢前向传播需在双向层进行前向和后向递归反向传播依赖前向传播结果导致梯度求解链长。应用场景实际应用较少用于填充缺失单词、词元注释如命名实体识别以及作为序列处理流水线步骤对序列编码如机器翻译不能预测未来。
代码实现
首先双向RNN不能训练语言模型所以下面这个是一个错误的应用只是为了演示该模型。与之前不同的是改为bidirectionalTrue
import torch
from torch import nn
from d2l import torch as d2l# 加载数据
batch_size, num_steps, device 32, 35, d2l.try_gpu()
train_iter, vocab d2l.load_data_time_machine(batch_size, num_steps)
# 通过设置“bidirectiveTrue”来定义双向LSTM模型
vocab_size, num_hiddens, num_layers len(vocab), 256, 2
num_inputs vocab_size
# 改为 bidirectionalTrue就是双向rnn
lstm_layer nn.LSTM(num_inputs, num_hiddens, num_layers, bidirectionalTrue)
model d2l.RNNModel(lstm_layer, len(vocab))
model model.to(device)
# 训练模型
num_epochs, lr 500, 1
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)结果很差因为没有未来信息精度很低预测信息很不靠谱。
编码器-解码器架构
机器翻译和数据集
机器翻译指的是将文本序列从一种语言自动翻译成另一种语言。使用单词级词元化时的词表大小将明显大于使用字符级词元化时的词表大小。为了缓解这一问题我们可以将低频词元视为相同的未知词元。通过截断和填充文本序列可以保证所有的文本序列都具有相同的长度以便以小批量的方式加载。
代码实现
# 机器翻译与数据集
# 语言模型是自然语言处理的关键 而机器翻译是语言模型最成功的基准测试
# 机器翻译machine translation指的是 将序列从一种语言自动翻译成另一种语言。 import os
import torch
from d2l import torch as d2l# 下载和预处理数据集
# 下载一个由Tatoeba项目的双语句子对https://www.manythings.org/anki/ 组成的“英法”数据集
#
# 英语是源语言source language 法语是目标语言target languaged2l.DATA_HUB[fra-eng] (d2l.DATA_URL fra-eng.zip,94646ad1522d915e7b0f9296181140edcf86a4f5)def read_data_nmt():载入“英语法语”数据集data_dir d2l.download_extract(fra-eng)with open(os.path.join(data_dir, fra.txt), r,encodingutf-8) as f:return f.read()raw_text read_data_nmt()
print(raw_text[:75])# 预处理
# 用空格代替不间断空格non-breaking space
# 使用小写字母替换大写字母
# 在单词和标点符号之间插入空格。def preprocess_nmt(text):预处理“英语法语”数据集def no_space(char, prev_char):return char in set(,.!?) and prev_char ! # 使用空格替换不间断空格# 使用小写字母替换大写字母text text.replace(\u202f, ).replace(\xa0, ).lower()# 在单词和标点符号之间插入空格out [ char if i 0 and no_space(char, text[i - 1]) else charfor i, char in enumerate(text)]return .join(out)text preprocess_nmt(raw_text)
print(text[:80])# 词元化
# tokenize_nmt函数对前num_examples个文本序列对进行词元 其中每个词元要么是一个词要么是一个标点符号。
# 此函数返回两个词元列表source和target
# source[i]是源语言这里是英语第 个文本序列的词元列表
# target[i]是目标语言这里是法语第 个文本序列的词元列表。
def tokenize_nmt(text, num_examplesNone):词元化“英语法语”数据数据集source, target [], []for i, line in enumerate(text.split(\n)):if num_examples and i num_examples:breakparts line.split(\t)if len(parts) 2:source.append(parts[0].split( ))target.append(parts[1].split( ))return source, targetsource, target tokenize_nmt(text)
source[:6], target[:6]# 绘制每个文本序列所包含的词元数量的直方图
#save
def show_list_len_pair_hist(legend, xlabel, ylabel, xlist, ylist):绘制列表长度对的直方图d2l.set_figsize()_, _, patches d2l.plt.hist([[len(l) for l in xlist], [len(l) for l in ylist]])d2l.plt.xlabel(xlabel)d2l.plt.ylabel(ylabel)for patch in patches[1].patches:patch.set_hatch(/)d2l.plt.legend(legend)show_list_len_pair_hist([source, target], # tokens per sequence,count, source, target);# 构建两个词表
# 将出现次数少于2次的低频率词元 视为相同的未知“unk”词元
# 在小批量时用于将序列填充到相同长度的填充词元“pad” 以及序列的开始词元“bos”和结束词元“eos”
src_vocab d2l.Vocab(source, min_freq2,reserved_tokens[pad, bos, eos])
len(src_vocab)# 加载数据集
def truncate_pad(line, num_steps, padding_token):截断或填充文本序列if len(line) num_steps:return line[:num_steps] # 截断return line [padding_token] * (num_steps - len(line)) # 填充truncate_pad(src_vocab[source[0]], 10, src_vocab[pad])# 定义一个函数 将文本序列 [转换成小批量数据集用于训练
def build_array_nmt(lines, vocab, num_steps):将机器翻译的文本序列转换成小批量lines [vocab[l] for l in lines]lines [l [vocab[eos]] for l in lines]array torch.tensor([truncate_pad(l, num_steps, vocab[pad]) for l in lines])valid_len (array ! vocab[pad]).type(torch.int32).sum(1)return array, valid_len# 训练模型
def load_data_nmt(batch_size, num_steps, num_examples600):返回翻译数据集的迭代器和词表text preprocess_nmt(read_data_nmt())source, target tokenize_nmt(text, num_examples)src_vocab d2l.Vocab(source, min_freq2,reserved_tokens[pad, bos, eos])tgt_vocab d2l.Vocab(target, min_freq2,reserved_tokens[pad, bos, eos])src_array, src_valid_len build_array_nmt(source, src_vocab, num_steps)tgt_array, tgt_valid_len build_array_nmt(target, tgt_vocab, num_steps)data_arrays (src_array, src_valid_len, tgt_array, tgt_valid_len)data_iter d2l.load_array(data_arrays, batch_size)return data_iter, src_vocab, tgt_vocab# 读出“英语法语”数据集中的第一个小批量数据
train_iter, src_vocab, tgt_vocab load_data_nmt(batch_size2, num_steps8)
for X, X_valid_len, Y, Y_valid_len in train_iter:print(X:, X.type(torch.int32))print(X的有效长度:, X_valid_len)print(Y:, Y.type(torch.int32))print(Y的有效长度:, Y_valid_len)break编码器和解码器
回顾
在CNN中将“特征提取”看作编码器softmax回归看作解码器— 编码器将输入编程成中间表达形式特征解码器将中间表示解码成输出。在RNN中编码器将文本表示成向量解码器向量表示成输出。
引出
机器翻译是序列转换模型的一个核心问题 其输入和输出都是长度可变的序列。 为了处理这种类型的输入和输出 我们可以设计一个包含两个主要组件的架构 第一个组件是一个编码器encoder 它接受一个长度可变的序列作为输入 并将其转换为具有固定形状的编码状态。第二个组件是解码器decoder 它将固定形状的编码状态映射到长度可变的序列。
这被称为编码器-解码器encoder-decoder架构。 一个模块被分为两部分编码器处理输入解码器负责输出
代码实现
# 编码器-解码器架构
# 机器翻译是序列转换模型的一个核心问题 其输入和输出都是长度可变的序列。
# 为了处理这种类型的输入和输出 我们可以设计一个包含两个主要组件的架构
# 第一个组件是一个编码器encoder 它接受一个长度可变的序列作为输入 并将其转换为具有固定形状的编码状态。
# 第二个组件是解码器decoder 它将固定形状的编码状态映射到长度可变的序列。
# 这被称为编码器-解码器encoder-decoder架构# 编码器from torch import nnclass Encoder(nn.Module):编码器-解码器架构的基本编码器接口def __init__(self, **kwarge):super(Encoder, self).__init__(**kwarge)def forward(self, X, *args):raise NotImplementedError# 解码器
# 用于将编码器的输出enc_outputs转换为编码后的状态class Decoder(nn.Module):def __init__(self, **kwargs):super(Decoder, self).__init__(**kwargs)# 中间状态编码的输出enc_outputsdef init_state(self, enc_outputs, *args):raise NotImplementedErrordef forward(self, X, state):raise NotImplementedError# 合并编码器和解码器
class EncoderDecoder(nn.Module):编码器-解码器架构的基类def __init__(self, encoder, decoder, **kwargs):super(EncoderDecoder, self).__init__(**kwargs)self.encoder encoderself.decoder decoderdef forward(self, enc_X, dec_X, *args):enc_outputs self.encoder(enc_X, *args)dec_state self.decoder.init_state(enc_outputs, *args)return self.decoder(dec_X, dec_state)序列到序列学习seq2seq
在生物中将DNA序列转为RNA。
机器翻译
机器翻译是最常见的应用给定一个源语言的句子自动翻译成目标语言这两个句子都可以有不同的长度
Seq2Seq 编码器是一个RNN读取输入的句子 可以是双向的经常用在Encoder中 解码器使用另一个RNN来输入 编码器解码器细节 编码器是没有输出的RNN编码器最后时间步的隐状态用作解码器的初始隐状态 训练 训练时解码器使用目标句子作为输入推理的时候就是根据当前的输入和状态来生成输出。
衡量生成序列的好坏的BLEU
pn是预测中所有n-gram的精度 表示 n 元语法的精确度它是两个数量的比值 第一个是预测序列与标签序列中匹配的n元语法的数量 第二个是预测序列中 n 元语法的数量的比率标签序列A B C D E F 和预测序列A B B C D有p1 4/5p2 3/4p2 1/3p4 0BLEU定义 前部分min()代表 惩罚过段的预测后部分有高权重。
总结
seq2seq从一个句子生成另一个句子根据“编码器-解码器”架构的设计 我们可以使用两个循环神经网络来设计一个序列到序列学习的模型。在实现编码器和解码器时我们可以使用多层循环神经网络。将编码器最后时间隐状态来初始解码器隐状态来完成信息传递在“编码器解码器”训练中强制教学方法将原始输出序列而非预测结果输入解码器。BLEU是一种常用的评估方法它通过测量预测序列和标签序列之间的 元语法的匹配度来评估预测。
代码实现
# 序列到序列学习 import collections
import math
import torch
from torch import nn
from d2l import torch as d2l# 实现循环神经网络编码器
class Seq2SeqEncoder(d2l.Encoder):用于序列到序列学习的循环神经网络编码器def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,dropout0, **kwargs):super(Seq2SeqEncoder, self).__init__(**kwargs)# 使用嵌入层embedding layer 获得输入序列中每个词元的特征向量。# 嵌入层的权重是一个矩阵# 其行数等于输入词表的大小vocab_size 其列数等于特征向量的维度embed_sizeself.embedding nn.Embedding(vocab_size, embed_size)self.rnn nn.GRU(embed_size, num_hiddens, num_layers,dropoutdropout)# 没有输出层def forward(self, X, *args):# 输出X的形状(batch_size,num_steps,embed_size)X self.embedding(X)# 在循环神经网络模型中第一个轴对应于时间步X X.permute(1, 0, 2)# 如果未提及状态则默认为0output, 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 。
# 完成所有时间步之后最后一层的隐状态的输出是一个张量形状时间步数批量大小隐藏单元数encoder Seq2SeqEncoder(vocab_size10, embed_size8, num_hiddens16,num_layers2)
encoder.eval() # dropout就不会生效了
X torch.zeros((4, 7), dtypetorch.long)
output, state encoder(X)
# output形状时间步数批量大小隐藏单元数
# state形状隐藏层的数量批量大小隐藏单元的数量
output.shape,state.shape# 解码器
# 直接使用编码器最后一个时间步的隐状态来初始化解码器的隐状态
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)self.rnn nn.GRU(embed_size num_hiddens, num_hiddens, num_layers,dropoutdropout)self.dense nn.Linear(num_hiddens, vocab_size)def init_state(self, enc_outputs, *args):return enc_outputs[1]def forward(self, X, state):# 输出X的形状(batch_size,num_steps,embed_size)X self.embedding(X).permute(1, 0, 2)# 广播context使其具有与X相同的num_stepscontext 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函数 [通过零值化屏蔽不相关的项]
# 以便后面任何不相关预测的计算都是与零的乘积结果都等于零。
def sequence_mask(X, valid_len, value0):在序列中屏蔽不相关的项maxlen X.size(1)mask torch.arange((maxlen), dtypetorch.float32,deviceX.device)[None, :] valid_len[:, None]X[~mask] valuereturn XX torch.tensor([[1, 2, 3], [4, 5, 6]])
sequence_mask(X, torch.tensor([1, 2]))# 还可以使用此函数屏蔽最后几个轴上的所有项
X torch.ones(2, 3, 4)
sequence_mask(X, torch.tensor([1, 2]), value-1)# 通过扩展softmax交叉熵损失函数来遮蔽不相关的预测
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):weights torch.ones_like(label)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# 创建三个相同序列进行代码健全性检查
loss MaskedSoftmaxCELoss()
loss(torch.ones(3, 4, 10), torch.ones((3, 4), dtypetorch.long),torch.tensor([4, 2, 0]))# 训练
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()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]bos 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)# 预测
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)src_tokens d2l.truncate_pad(src_tokens, num_steps, src_vocab[pad])# 添加批量轴enc_X torch.unsqueeze(torch.tensor(src_tokens, dtypetorch.long, devicedevice), dim0)enc_outputs net.encoder(enc_X, enc_valid_len)dec_state net.decoder.init_state(enc_outputs, enc_valid_len)# 添加批量轴dec_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()# 保存注意力权重稍后讨论if save_attention_weights:attention_weight_seq.append(net.decoder.attention_weights)# 一旦序列结束词元被预测输出序列的生成就完成了if pred tgt_vocab[eos]: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})束搜索
beam search
贪心搜索greedy search
在seq2seq中我们使用贪心搜索来预测序列 将当前时刻预测概率最大的值输出 但贪心很可能不是最优的 效率高但可能不是最优的
穷举搜索
如果目标是获得最优序列 我们可以考虑使用穷举搜索exhaustive search 穷举地列举所有可能的输出序列及其条件概率 然后计算输出条件概率最高的一个。
最优算法对所有可能的序列计算它的概率然后选取最好的那个。如果输出字典大小为n序列最长为T那么需要考虑 n^T个序列 n 10000, T 10: n^T 10^40优点肯定能找到最优但是计算不可行
束搜索折中 保持最好的k个候选束宽k 在每个时刻对每个候选新加一项n种可能在 kn 个选项中选出最好的k个。 候选输出序列是A、C、AB、CE、ABD和CED 时间复杂度O(knT) 每个候选的最终分数是 L是最终候选序列的长度通常α 0.75
总结
束搜索的结果介于贪心搜索和穷举搜索之间k1是贪心。通过灵活地选择束宽束搜索可以在正确率和计算代价之间进行权衡。