动手学深度学习:循环神经网络的简洁实现(RNN 高级 API 实战指南)
人工智能深度学习机器学习教程【免费下载链接】d2l-zh《动手学深度学习》面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。项目地址https://gitcode.com/GitHub_Trending/d2/d2l-zh点击查看免费下载导读在《动手学深度学习》中文版的循环神经网络章节中从零开始实现 RNNsec_rnn_scratch帮助读者理解隐藏状态更新的数学细节但手写反向传播与循环并不高效。本文基于本书「循环神经网络的简洁实现」一节讲解如何借助深度学习框架高级 APIMXNet Gluon、PyTorch、TensorFlow、PaddlePaddle在时光机器数据集上快速构建、训练并预测一个字符级语言模型。读完本文你将掌握rnn.RNN/nn.RNN/SimpleRNNCell/SimpleRNN等高层循环层的正确用法、RNNModel包装类的设计思路隐藏层与输出层分离、隐状态初始化的形状约定以及如何复用d2l.load_data_time_machine、d2l.train_ch8、d2l.predict_ch8完成从数据装载到训练预测的完整流程。为什么需要“简洁实现”上一节的从零实现RNNModelScratch需要读者手动初始化循环核权重、逐时间步展开 forward 函数、自行实现梯度裁剪与更新器。虽然教学意义充分但存在两点不足不便于复用循环层、输出层、隐状态初始化全部手写与框架生态脱节运行速度慢缺乏框架层针对循环展开的算子融合与内存优化。本节给出的简洁实现直接调用框架提供的 RNN 层把“循环”本身交给框架开发者只需关注把 RNN 层包装成完整语言模型这一件事。从源码结构看d2l/torch.py与d2l/mxnet.py中同时保留了RNNModelScratch和RNNModel两个类正是一对“从零实现 vs 高级 API”的对照设计便于读者逐行对比。1. 数据准备时光机器数据集无论使用哪个框架第一步都相同调用d2l.load_data_time_machine获取数据迭代器与词表。batch_size, num_steps 32, 35 train_iter, vocab d2l.load_data_time_machine(batch_size, num_steps)batch_size 32每个小批量包含 32 个子序列num_steps 35每个子序列的时间步长度序列切分窗口返回的train_iter是SeqDataLoader实例可迭代对象vocab是字符级词表Vocab。从源码实现看该函数的核心链路位于 d2l/torch.py其余框架版本在 d2l/mxnet.py、d2l/tensorflow.py、d2l/paddle.pydef load_data_time_machine(batch_size, num_steps, use_random_iterFalse, max_tokens10000): 返回时光机器数据集的迭代器和词表 data_iter SeqDataLoader( batch_size, num_steps, use_random_iter, max_tokens) return data_iter, data_iter.vocab其中SeqDataLoader在构造时调用load_corpus_time_machine(max_tokens)见 d2l/mxnet.py对《时光机器》原文按字符分词tokenize(lines, char)、构建词表Vocab、并默认只取前max_tokens10000个词元作为语料。use_random_iter参数控制小批量划分方式use_random_iterFalse默认顺序分区seq_data_iter_sequential将相邻子序列按顺序组织上一个批量末尾的隐状态可以直接传给下一个批量use_random_iterTrue随机抽样seq_data_iter_random从随机偏移开始切分并打乱子序列顺序此时每个批量都必须重新初始化隐状态。这一参数的选择会直接影响下一节隐状态传递的写法值得记住。2. 定义模型用高级 API 构造 RNN 层2.1 各框架的 RNN 层构造本节构造一个隐藏层、256 个隐藏单元的循环层MXNetGluonnum_hiddens 256 rnn_layer rnn.RNN(num_hiddens) rnn_layer.initialize()PyTorchnum_hiddens 256 rnn_layer nn.RNN(len(vocab), num_hiddens)注意 PyTorch 的nn.RNN必须显式传入输入特征维度因此第一个参数是len(vocab)词表大小这与 Gluon 的用法不同。TensorFlowKerasnum_hiddens 256 rnn_cell tf.keras.layers.SimpleRNNCell(num_hiddens, kernel_initializerglorot_uniform) rnn_layer tf.keras.layers.RNN(rnn_cell, time_majorTrue, return_sequencesTrue, return_stateTrue)TensorFlow 采用“Cell 包装层”的两段式构造SimpleRNNCell描述单步计算单元tf.keras.layers.RNN负责循环展开time_majorTrue表示输入形状为时间步批量特征与num_steps在轴 0 的习惯一致。PaddlePaddlenum_hiddens 256 rnn_layer nn.SimpleRNN(len(vocab), num_hiddens, time_majorTrue)Paddle 的nn.SimpleRNN(input_size, hidden_size, time_majorTrue)与 PyTorch 类似需要输入维度同时用time_majorTrue声明时间步优先。关于多层 RNN本书在 深度循环神经网络sec_deep_rnn中才展开讨论这里只需理解多层就是把上一层 RNN 的输出作为下一层 RNN 的输入。2.2 初始化隐状态MXNet调用rnn_layer.begin_state(batch_sizebatch_size)返回一个列表state列表中包含小批量中每个样本的初始隐状态形状为(隐藏层数, 批量大小, 隐藏单元数)即(1, 32, 256)。对于后续章节要介绍的 LSTM 等模型该列表还会包含额外信息如记忆单元。state rnn_layer.begin_state(batch_sizebatch_size) len(state), state[0].shape # (1, (1, 32, 256))PyTorch直接用零张量初始化形状同样是(隐藏层数, 批量大小, 隐藏单元数)state torch.zeros((1, batch_size, num_hiddens)) state.shape # torch.Size([1, 32, 256])TensorFlow通过rnn_cell.get_initial_state(batch_sizebatch_size, dtypetf.float32)获取。Paddle与 PyTorch 一致paddle.zeros(shape[1, batch_size, num_hiddens])。2.3 前向rnn_layer的输出到底是什么给定隐状态和输入调用rnn_layer(X, state)会返回(Y, state_new)X torch.rand(size(num_steps, batch_size, len(vocab))) Y, state_new rnn_layer(X, state) Y.shape, state_new.shape必须强调这里的“输出”Y不涉及输出层全连接层的计算它指的是每个时间步的隐状态形状为(num_steps, batch_size, num_hiddens)这些隐状态将作为后续输出层的输入。而state_newMXNet 中为列表PyTorch/Paddle 中为张量是小批量最后时间步的隐状态可用于顺序分区中下一个批量的隐状态初始化在 MXNet 中多隐藏层时每一层的隐状态都会存放在state_new中后续的 LSTM 等模型返回的state_new还会携带更多信息如记忆单元状态。2.4 包装完整模型RNNModel类rnn_layer只包含隐藏的循环层因此还需要一个单独的输出层把每个时间步的隐状态映射到词表大小的 logits。本书定义了RNNModel类完成包装。以 PyTorch 版本为例d2l/torch.pyclass RNNModel(nn.Module): 循环神经网络模型 def __init__(self, rnn_layer, vocab_size, **kwargs): super(RNNModel, self).__init__(**kwargs) self.rnn rnn_layer self.vocab_size vocab_size self.num_hiddens self.rnn.hidden_size # 如果RNN是双向的num_directions应该是2否则应该是1 if not self.rnn.bidirectional: self.num_directions 1 self.linear nn.Linear(self.num_hiddens, self.vocab_size) else: self.num_directions 2 self.linear nn.Linear(self.num_hiddens * 2, self.vocab_size) def forward(self, inputs, state): X F.one_hot(inputs.T.long(), self.vocab_size) X X.to(torch.float32) Y, state self.rnn(X, state) # 全连接层首先将Y的形状改为(时间步数*批量大小,隐藏单元数) # 它的输出形状是(时间步数*批量大小,词表大小) output self.linear(Y.reshape((-1, Y.shape[-1]))) return output, state def begin_state(self, device, batch_size1): if not isinstance(self.rnn, nn.LSTM): # nn.GRU以张量作为隐状态 return torch.zeros((self.num_directions * self.rnn.num_layers, batch_size, self.num_hiddens), devicedevice) else: # nn.LSTM以元组作为隐状态 return (torch.zeros((self.num_directions * self.rnn.num_layers, batch_size, self.num_hiddens), devicedevice), torch.zeros((self.num_directions * self.rnn.num_layers, batch_size, self.num_hiddens), devicedevice))要点拆解One-Hot 编码与转置inputs.T把输入的(batch_size, num_steps)转置为(num_steps, batch_size)再经F.one_hot变成(num_steps, batch_size, vocab_size)——这正是time_major约定下 RNN 层期待的输入形状形状重塑Y.reshape((-1, Y.shape[-1]))把(num_steps, batch_size, num_hiddens)展平为(num_steps * batch_size, num_hiddens)送入全连接层后输出(num_steps * batch_size, vocab_size)与训练时的标签y Y.T.reshape(-1)对齐双向预留bidirectional分支把num_directions设为 2并令输出层输入维度翻倍num_hiddens * 2为后续 双向循环神经网络 留好了接口隐状态形态差异nn.LSTM的隐状态是(h, c)元组而普通 RNN/GRU 是单个张量begin_state对此做了类型分支处理——这是 PyTorch/Paddle 版本与 MXNet 版本后者直接透传self.rnn.begin_state(*args, **kwargs)最大的实现差异。MXNetd2l/mxnet.py和 TensorFlowd2l/tensorflow.py版本的RNNModel结构类似其中 TensorFlow 的call里用Y, *state self.rnn(X, state)解包 Keras RNN 返回的多个值。3. 训练与预测3.1 用随机权重先“预演”一次预测正式训练前先基于随机初始化的权重生成 10 个字符直观感受未训练模型的输出device d2l.try_gpu() net RNNModel(rnn_layer, vocab_sizelen(vocab)) net net.to(device) d2l.predict_ch8(time traveller, 10, net, vocab, device)MXNet 版本使用net.initialize(force_reinitTrue, ctxdevice)初始化参数TensorFlow 版本用tf.distribute.OneDeviceStrategy(device_name)包装模型。正如预期随机权重下模型输出是乱码——这证明模型尚未学到任何语言规律。从源码看d2l.predict_ch8d2l/torch.py的执行流程为net.begin_state(batch_size1, devicedevice)初始化单样本隐状态预热期用前缀prefix[1:]逐字符过网络只更新状态、不生成新字符生成期循环num_preds次以y.argmax(dim1)贪婪采样下一个字符并拼接成输出字符串。3.2 正式训练train_ch8复用与从零实现相同的超参数num_epochs500, lr1调用d2l.train_ch8训练num_epochs, lr 500, 1 d2l.train_ch8(net, train_iter, vocab, lr, num_epochs, device)train_ch8d2l/torch.py内部做了四件事选择损失函数nn.CrossEntropyLoss()MXNet 为gluon.loss.SoftmaxCrossEntropyLossTensorFlow 为SparseCategoricalCrossentropy(from_logitsTrue)构造优化器torch.optim.SGD(net.parameters(), lr)每 10 个 epoch 调用predict_ch8(prefix, 50, net, vocab, device)打印生成结果并绘制困惑度曲线训练结束后输出最终困惑度、处理速度词元/秒与设备信息并分别以time traveller和traveller为前缀展示生成文本。在train_epoch_ch8d2l/torch.py中有一个与数据加载方式密切相关的细节当use_random_iterFalse顺序分区时若网络是nn.Module且隐状态是单个张量如nn.GRU会调用state.detach_()截断反向传播——即跨小批量的隐状态延续计算图但不回传梯度当使用随机抽样时则每个批量都通过begin_state重新初始化隐状态。梯度裁剪阈值固定为 1grad_clipping(net, 1)防止循环展开带来的梯度爆炸。3.3 效果与对比训练完成后模型达到与从零实现相当的困惑度perplexity但由于高级 API 对循环计算做了算子级优化训练时间更短、单位时间处理词元更多。这是本节选择高级 API 的核心收益同样的语言模型任务代码更少、更不易出错、运行更快。4. 小结深度学习框架的高级 API 提供了现成的循环神经网络层Gluonrnn.RNN、PyTorchnn.RNN、KerasSimpleRNNCell RNN、Paddlenn.SimpleRNN开发者无需手写循环展开与权重矩阵高级 API 的 RNN 层返回每个时间步的隐状态Y与最后时间步的隐状态state_new前者用于输出层计算后者用于跨小批量传递注意Y并不包含输出层计算还需自行添加一个输出层全连接层把隐状态映射到词表大小RNNModel类就是这一包装的标准模板相比从零实现使用高级 API 可显著加速训练并天然兼容顺序分区 / 随机抽样两种小批量策略以及 LSTM、GRU 等后续模型。5. 练习与拓展尝试用高级 API 让 RNN 模型过拟合例如减少训练数据量或增大隐藏单元数观察困惑度变化如果增加 RNN 的隐藏层数训练会发生什么变化在保持其他超参数不变的情况下多层是否会让模型正常工作提示回顾begin_state中num_layers对隐状态形状的影响以及 深度循环神经网络 的内容尝试用 RNN 实现序列sec_sequence中的自回归模型对比简单线性自回归模型的效果。想对照“从零实现 vs 简洁实现”的完整代码可以直接查看 d2l/torch.py 中的RNNModelScratch与RNNModel两个类以及其余框架的对应实现d2l/mxnet.py、d2l/tensorflow.py、d2l/paddle.py。赞分享人工智能深度学习机器学习教程【免费下载链接】d2l-zh《动手学深度学习》面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。项目地址https://gitcode.com/GitHub_Trending/d2/d2l-zh点击查看免费下载相关推荐ik_llama.cpp 对 Seed Coder 模型 FIM 特殊 Token 的支持从 GGUF 转换到词表加载的完整实现解析ik_llama.cpp 对 Seed Coder 模型 FIM 特殊 Token 的支持从 GGUF 转换到词表加载的完整实现解析 导读 本文以 ik_ll人工智能深度学习机器学习教程动手学深度学习循环神经网络RNN原理与从零到简洁的完整实践指南动手学深度学习循环神经网络RNN原理与从零到简洁的完整实践指南 本章节是《动手学深度学习》d2l zh中 循环神经网络Recurrent Neura人工智能深度学习机器学习教程TRIBE v2入门教程5步快速部署与大脑活动预测实践TRIBE v2入门教程5步快速部署与大脑活动预测实践 TRIBE v2是一个革命性的 多模态大脑编码模型 能够预测人类大脑对视觉、听觉和语言刺激的fMRI上一篇Tails Components 模态框与通知组件提升用户体验的交互设计指南 下一篇音频转视频Audiogram 开源项目推荐创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →