ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

d2l-en 深度循环神经网络(Deep RNN)详解:从堆叠架构、数学公式到 MXNet/PyTorch/TensorFlow/JAX 四框架实战

2026/10/3 1:54:52 拓冰建站 浏览量
d2l-en 深度循环神经网络(Deep RNN)详解:从堆叠架构、数学公式到 MXNet/PyTorch/TensorFlow/JAX 四框架实战 文档教程人工智能深度学习NLP计算机视觉强化学习【免费下载链接】d2l-enInteractive deep learning book with multi-framework code, math, and discussions. Adopted at 500 universities from 70 countries including Stanford, MIT, Harvard, and Cambridge.项目地址https://gitcode.com/gh_mirrors/d2/d2l-en点击查看免费下载深度循环神经网络Deep RNN是在时间方向之外沿着输入到输出方向即网络深度方向堆叠多个循环层以增强建模能力的标准方案。本文以 chapter_recurrent-modern/deep-rnn.md 为主线完整推导多层 RNN 的数学定义并结合 d2l 模块中RNNScratch、RNNLMScratch、RNNLM、GRU等类的真实源码d2l/torch.py、d2l/mxnet.py、d2l/tensorflow.py、d2l/jax.py给出从零实现与高层 API 简洁实现两种路径的完整代码与训练配置。读完本文你将掌握深度 RNN 的架构原理、隐藏层与隐藏单元的超参数选取规律并能用多种深度学习框架独立搭建并训练一个多层 GRU 语言模型。为什么需要深度RNN沿时间轴 vs 沿输入输出轴在 chapter_recurrent-neural-networks 各章节介绍的普通 RNN 中网络只包含输入 → 单个循环隐藏层 → 输出层这一条路径。严格说这类网络在时间方向上已经是深的第 1 个时间步的输入需要经过 $T$ 次循环层运算$T$ 常达数百甚至上千才能影响最终时刻 $T$ 的输出。但仅靠时间方向的深度不足以刻画所有复杂关系。许多任务要求网络同时表达同一时间步内、输入到输出之间的复杂映射例如语言建模中同时出现的语义与语法约束。这就需要沿着输入到输出方向与 MLP、深度 CNN 中深度的含义一致再叠加若干循环层。标准做法极其简洁将 RNN 逐层堆叠stack。对长度为 $T$ 的输入序列第一层 RNN 输出一个同样长度为 $T$ 的输出序列这个序列再作为第二层 RNN 的输入依此类推。由此得到包含 $L$ 个隐藏层的深度 RNN如 img/deep-rnn.svg 所示任意时刻 $t$ 的每个循环单元白色方框同时依赖同一层上一时刻的隐藏状态与上一层同一时刻的隐藏状态这正是循环层堆叠时信息传递的两条通道。深度 RNN 的数学形式从输入到输出的逐层递推设时间步 $t$ 的批量输入为 $\mathbf{X}_t \in \mathbb{R}^{n \times d}$$n$ 为样本数$d$ 为每个样本的输入维数第 $l$ 层$l1,\ldots,L$的隐藏状态为 $\mathbf{H}_t^{(l)} \in \mathbb{R}^{n \times h}$$h$ 为隐藏单元数输出层变量为 $\mathbf{O}_t \in \mathbb{R}^{n \times q}$$q$ 为输出数。约定 $\mathbf{H}_t^{(0)} \mathbf{X}_t$则采用激活函数 $\phi_l$ 的第 $l$ 层隐藏状态为$$\mathbf{H}t^{(l)} \phi_l(\mathbf{H}t^{(l-1)} \mathbf{W}{\textrm{xh}}^{(l)} \mathbf{H}{t-1}^{(l)} \mathbf{W}{\textrm{hh}}^{(l)} \mathbf{b}\textrm{h}^{(l)})$$其中权重 $\mathbf{W}{\textrm{xh}}^{(l)} \in \mathbb{R}^{h \times h}$、$\mathbf{W}{\textrm{hh}}^{(l)} \in \mathbb{R}^{h \times h}$ 与偏置 $\mathbf{b}_\textrm{h}^{(l)} \in \mathbb{R}^{1 \times h}$ 是第 $l$ 层隐藏层的模型参数。注意两点关键区别每个隐藏层拥有独立的参数 $\mathbf{W}{\textrm{xh}}^{(l)}、\mathbf{W}{\textrm{hh}}^{(l)}、\mathbf{b}_\textrm{h}^{(l)}$层间参数不共享中间层的输入来自上一层同一时刻的输出 $\mathbf{H}_t^{(l-1)}$而非常规 RNN 中当前时刻原始输入 上一时刻隐藏状态的组合。因此首层用 $\mathbf{H}_t^{(0)} \mathbf{X}_t$ 衔接输入中间各层天然以 $\mathbf{H}_t^{(l-1)}$ 作为输入特征这正是每层输入维数都取 $h$ 的原因。最终输出层只依赖最后一层第 $L$ 层的隐藏状态$$\mathbf{O}t \mathbf{H}t^{(L)} \mathbf{W}{\textrm{hq}} \mathbf{b}\textrm{q}$$其中输出层参数为 $\mathbf{W}{\textrm{hq}} \in \mathbb{R}^{h \times q}$ 与 $\mathbf{b}\textrm{q} \in \mathbb{R}^{1 \times q}$。与 MLP 类似隐藏层数 $L$ 与隐藏单元数 $h$ 都是可调超参数。原文档给出的实践范围是常见 RNN 层宽度 $h$ 落在 $(64, 2056)$常见深度 $L$ 落在 $(1, 8)$。此外只要把上式中的隐藏状态计算替换为 LSTM 或 GRU 的隐藏状态计算就能轻松得到深度门控 RNN——这也是下文实战部分选择 GRU 的原因。从零实现StackedRNNScratch 逐层堆叠模块结构每层一个 RNNScratch 实例从零实现多层 RNN 的思路是把每一层当作一个拥有独立可学习参数的RNNScratch实例。RNNScratch的基础实现定义在 d2l/torch.py以及 d2l/mxnet.py、d2l/tensorflow.py、d2l/jax.py其核心是三个参数——输入到隐藏权重W_xh形状num_inputs × num_hiddens、隐藏到隐藏权重W_hh形状num_hiddens × num_hiddens与偏置b_h初始化时用标准差sigma0.01的高斯分布缩放前向传播则按state tanh(X W_xh state W_hh b_h)逐时间步推进。StackedRNNScratch 正是在此基础上堆叠第 0 层接收num_inputs维输入其余层接收上一层的num_hiddens维输出。# %%tab mxnet, tensorflow class StackedRNNScratch(d2l.Module): def __init__(self, num_inputs, num_hiddens, num_layers, sigma0.01): super().__init__() self.save_hyperparameters() self.rnns [d2l.RNNScratch(num_inputs if i0 else num_hiddens, num_hiddens, sigma) for i in range(num_layers)]# %%tab pytorch class StackedRNNScratch(d2l.Module): def __init__(self, num_inputs, num_hiddens, num_layers, sigma0.01): super().__init__() self.save_hyperparameters() self.rnns nn.Sequential(*[d2l.RNNScratch( num_inputs if i0 else num_hiddens, num_hiddens, sigma) for i in range(num_layers)])# %%tab jax class StackedRNNScratch(d2l.Module): num_inputs: int num_hiddens: int num_layers: int sigma: float 0.01 def setup(self): self.rnns [d2l.RNNScratch(self.num_inputs if i0 else self.num_hiddens, self.num_hiddens, self.sigma) for i in range(self.num_layers)]num_inputs首层输入维数此处取len(data.vocab)即词表大小num_hiddens每层隐藏单元数num_layers堆叠的循环层层数即公式中的 $L$sigma参数初始化缩放系数默认0.01与 d2l/torch.py 中RNNScratch的d2l.randn(...) * sigma保持一致。前向计算逐层串行推进多层前向计算只需按层顺序依次执行每层以上一层的输出序列作为输入并维护自己独立的状态列表Hs。# %%tab all d2l.add_to_class(StackedRNNScratch) def forward(self, inputs, HsNone): outputs inputs if Hs is None: Hs [None] * self.num_layers for i in range(self.num_layers): outputs, Hs[i] self.rnnsi outputs d2l.stack(outputs, 0) return outputs, Hs实现细节与公式一一对应状态Hs是长度为num_layers的列表每层一份初始状态第 $i$ 层的输入即上一层输出outputsd2l.stack(outputs, 0)把该层按时间步收集的输出列表沿时间轴堆叠成(num_steps, batch_size, num_hiddens)张量作为下一层的序列输入。RNNScratch.forward的源码注释d2l/torch.py印证了这一数据布局输入形状为(num_steps, batch_size, num_inputs)初始状态形状为(batch_size, num_hiddens)。在 Time Machine 数据集上训练深度 GRU以 chapter_recurrent-neural-networks/rnn-scratch.md 中的Time Machine数据集为例训练一个 2 层深度 GRU 模型。数据集类TimeMachine定义在 d2l/torch.py其预处理会把文本规整为仅含字母、小写化的序列并按num_steps切分样本。# %%tab all data d2l.TimeMachine(batch_size1024, num_steps32) if tab.selected(mxnet, pytorch, jax): rnn_block StackedRNNScratch(num_inputslen(data.vocab), num_hiddens32, num_layers2) model d2l.RNNLMScratch(rnn_block, vocab_sizelen(data.vocab), lr2) trainer d2l.Trainer(max_epochs100, gradient_clip_val1, num_gpus1) if tab.selected(tensorflow): with d2l.try_gpu(): rnn_block StackedRNNScratch(num_inputslen(data.vocab), num_hiddens32, num_layers2) model d2l.RNNLMScratch(rnn_block, vocab_sizelen(data.vocab), lr2) trainer d2l.Trainer(max_epochs100, gradient_clip_val1) trainer.fit(model, data)关键配置说明num_layers2为控制篇幅与训练成本只堆叠 2 层完整公式支持任意 $L$num_hiddens32每层隐藏单元数远小于工程上常用的 $(64, 2056)$ 区间足以验证堆叠逻辑lr2学习率RNNLMScratch的默认学习率为0.01见 d2l/torch.py此处显式调大以适配语言建模任务gradient_clip_val1梯度裁剪阈值。Trainer.fit_epochd2l/torch.py在每步反向传播后执行clip_gradients当所有参数梯度的 L2 范数超过阈值时按比例缩放梯度。深度 RNN 沿时间展开后梯度极易爆炸裁剪是保证收敛的关键手段num_gpus1使用 1 块 GPUTrainer的 GPU 分支见 d2l/torch.py。语言模型封装RNNLMScratchd2l/torch.py负责将 RNN 块输出的每个时刻隐藏状态经output_layerW_hq H b_q映射到词表维度并以困惑度perplexity即exp(loss)作为训练/验证指标预测时采用自回归方式逐个生成 token。简洁实现复用高层 API 的多层 GRU高层 API 已经封装了多层 RNN 的大量实现细节。以下GRU类继承自d2l.RNN相比 chapter_recurrent-modern/gru.md 的单层版本新增了显式的num_layers与dropout参数。MXNetGluon# %%tab mxnet class GRU(d2l.RNN): #save The multilayer GRU model. def __init__(self, num_hiddens, num_layers, dropout0): d2l.Module.__init__(self) self.save_hyperparameters() self.rnn rnn.GRU(num_hiddens, num_layers, dropoutdropout)rnn.GRU直接接收层数参数dropout控制层间 Dropout。源码对应 d2l/mxnet.pyMXNet 版RNNLM的输出层为nn.Dense(vocab_size, flattenFalse)d2l/mxnet.py。PyTorch# %%tab pytorch class GRU(d2l.RNN): #save The multilayer GRU model. def __init__(self, num_inputs, num_hiddens, num_layers, dropout0): d2l.Module.__init__(self) self.save_hyperparameters() self.rnn nn.GRU(num_inputs, num_hiddens, num_layers, dropoutdropout)PyTorch 版需额外传入num_inputsnn.GRU内部自动完成多层堆叠。源码对应 d2l/torch.py其中RNNLM用nn.LazyLinear惰性初始化输出层d2l/torch.py因此GRU构造时无需预先知道vocab_size。TensorFlowKeras# %%tab tensorflow class GRU(d2l.RNN): #save The multilayer GRU model. def __init__(self, num_hiddens, num_layers, dropout0): d2l.Module.__init__(self) self.save_hyperparameters() gru_cells [tf.keras.layers.GRUCell(num_hiddens, dropoutdropout) for _ in range(num_layers)] self.rnn tf.keras.layers.RNN(gru_cells, return_sequencesTrue, return_stateTrue, time_majorTrue) def forward(self, X, stateNone): outputs, *state self.rnn(X, state) return outputs, stateKeras 的做法是为每一层创建一个GRUCell并组成列表再整体包进tf.keras.layers.RNN。return_sequencesTrue保留每个时间步的输出序列return_stateTrue返回各层最终状态time_majorTrue与 d2l 全程使用的(num_steps, batch_size, features)时间主序数据布局一致RNNScratch注释同样标明该形状。源码对应 d2l/tensorflow.py。JAXFlaxFlax 对 RNN 采用极简主义原生不提供层数参数也默认不带 Dropout。因此 JAX 版需要手工叠加用nn.scan对nn.GRUCell做时间维扫描并在除最后一层外的每个 GRU 层之后插入nn.Dropout。# %%tab jax class GRU(d2l.RNN): #save The multilayer GRU model. num_hiddens: int num_layers: int dropout: float 0 nn.compact def __call__(self, X, stateNone, trainingFalse): outputs X new_state [] if state is None: batch_size X.shape[1] state [nn.GRUCell.initialize_carry(jax.random.PRNGKey(0), (batch_size,), self.num_hiddens)] * self.num_layers GRU nn.scan(nn.GRUCell, variable_broadcastparams, in_axes0, out_axes0, split_rngs{params: False}) # Introduce a dropout layer after every GRU layer except last for i in range(self.num_layers - 1): layer_i_state, X GRU()(state[i], outputs) new_state.append(layer_i_state) X nn.Dropout(self.dropout, deterministicnot training)(X) # Final GRU layer without dropout out_state, X GRU()(state[-1], X) new_state.append(out_state) return X, jnp.array(new_state)nn.GRUCell.initialize_carry为每层初始化(batch_size, num_hiddens)的零状态共num_layers份nn.scan沿时间轴in_axes0, out_axes0展开循环参数在各时间步间共享variable_broadcastparamsDropout 只加在前num_layers - 1层之后最后一层不设 Dropouttraining标志控制随机失活是否生效推理时deterministicTrue。该实现完整对应 d2l/jax.py 中RNN/RNNLM的接口约定RNNLM.forward调用self.rnn(embs, state, self.training)将训练标志传入循环层。简洁实现的超参数选择与训练简洁实现的架构决策与单层 GRU 基本一致输入输出维数等于词表大小vocab_size隐藏单元数仍取 32唯一区别是显式指定num_layers2。# %%tab mxnet gru GRU(num_hiddens32, num_layers2) model d2l.RNNLM(gru, vocab_sizelen(data.vocab), lr2) # Running takes 1h (pending fix from MXNet) # trainer.fit(model, data) # model.predict(it has, 20, data.vocab, d2l.try_gpu())MXNet 分支因耗时超过 1 小时而注释掉训练代码原文档注明待 MXNet 修复。# %%tab pytorch, tensorflow, jax if tab.selected(tensorflow, jax): gru GRU(num_hiddens32, num_layers2) if tab.selected(pytorch): gru GRU(num_inputslen(data.vocab), num_hiddens32, num_layers2) if tab.selected(pytorch, jax): model d2l.RNNLM(gru, vocab_sizelen(data.vocab), lr2) if tab.selected(tensorflow): with d2l.try_gpu(): model d2l.RNNLM(gru, vocab_sizelen(data.vocab), lr2) trainer.fit(model, data)训练完成后即可用前缀生成文本PyTorch 为model.predict(it has, 20, data.vocab, d2l.try_gpu())TensorFlow 为model.predict(it has, 20, data.vocab)JAX 需显式传入训练得到的参数model.predict(it has, 20, data.vocab, trainer.state.params)——这是因为 JAX 的函数式风格要求参数显式传递见 d2l/jax.py 的predict实现。训练深度 RNN 的关键注意事项信息传递路径深度 RNN 中隐藏状态信息同时流向当前层的下一时间步与下一层的当前时间步。层间堆叠带来更强的特征抽象能力但参数总量随 $L$ 线性增长梯度传播与裁剪沿时间轴展开后梯度需回传 $T \times L$ 次范数可能爆炸。Trainer的gradient_clip_val参数d2l/torch.py在每步更新前把梯度范数截断到阈值内是深度 RNN 收敛的基本保障学习率与初始化RNNLMScratch参数由init_params以sigma0.01的缩放初始化d2l/torch.py配合较大学习率此处lr2才能较快收敛总体而言深度 RNN 需要付出可观的工作量学习率调节、梯度裁剪等才能保证正确收敛门控变体将公式中的隐藏状态计算替换为 LSTM 或 GRU 即可得到深度门控 RNN各框架高层 API 均已内置这类多层变体无需手工堆叠模型初始化需谨慎深层堆叠放大了初始化质量对训练稳定性的影响这也是各框架版本统一采用小方差初始化sigma0.01的动机。小结深度 RNN 通过在输入到输出方向上堆叠 $L$ 个循环层实现任意时刻每个单元依赖同层上一时刻与上一层同一时刻的隐藏状态逐层递推公式 $\mathbf{H}t^{(l)} \phi_l(\mathbf{H}t^{(l-1)} \mathbf{W}{\textrm{xh}}^{(l)} \mathbf{H}{t-1}^{(l)} \mathbf{W}{\textrm{hh}}^{(l)} \mathbf{b}\textrm{h}^{(l)})$ 是深度 RNN 的通用骨架输出层只作用于第 $L$ 层隐藏状态常见层宽度 $h \in (64, 2056)$、层数 $L \in (1, 8)$$L$ 与 $h$ 都是可调超参数从零实现只需逐层实例化RNNScratch简洁实现可复用各框架高层 APIGluon/PyTorch 原生多层 GRU、Keras GRUCell 列表、Flax 手写扫描 Dropout深度 RNN 有 vanilla RNN、LSTM、GRU 等多种形态均可在高层 API 中获得训练时需特别注意学习率与梯度裁剪。练习将本节示例中的 GRU 替换为 LSTM比较准确率与训练速度的差异RNNScratch/RNNLMScratch的替换只需调整隐藏状态计算公式。增大训练数据将多本书籍合并作为语料观察困惑度perplexity最低能做到多少。建模文本时是否应该合并不同作者的语料思考这样做为何有利以及可能带来哪些问题提示写作风格与词汇分布差异带来的混合效应。延伸阅读本文依赖的前置章节包括 chapter_recurrent-neural-networks/rnn-scratch.md从零实现 RNN、chapter_recurrent-neural-networks/rnn-concise.md高层 API 实现与 chapter_recurrent-modern/gru.md单层 GRU。赞分享文档教程人工智能深度学习NLP计算机视觉强化学习【免费下载链接】d2l-enInteractive deep learning book with multi-framework code, math, and discussions. Adopted at 500 universities from 70 countries including Stanford, MIT, Harvard, and Cambridge.项目地址https://gitcode.com/gh_mirrors/d2/d2l-en点击查看免费下载相关推荐d2l-en 卷积神经网络实战LeNet-5 架构详解与多框架PyTorch/MXNet/TensorFlow/JAX实现指南d2l en 卷积神经网络实战LeNet 5 架构详解与多框架PyTorch/MXNet/TensorFlow/JAX实现指南 LeNet 5 是最早一批文档教程人工智能深度学习NLP计算机视觉强化学习d2l-en 双向循环神经网络Bidirectional RNN完全指南架构、数学原理与多框架实现d2l en 双向循环神经网络Bidirectional RNN完全指南架构、数学原理与多框架实现 本文是《动手学深度学习》d2l en中 双向循环神文档教程人工智能深度学习NLP计算机视觉强化学习深度循环神经网络Deep RNN多层隐状态堆叠的原理与实战深度循环神经网络Deep RNN多层隐状态堆叠的原理与实战 《动手学深度学习》从零到一实现多层循环神经网络Deep RNN理解为什么单一隐藏层难以建人工智能深度学习机器学习教程上一篇PaddleOCR.js 浏览器端部署在 Web 前端直接运行 PP-OCR 检测与识别下一篇如何快速上手PhpWebStudy10分钟搭建完整开发环境创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考