尧图精选

《动手学深度学习》循环神经网络(RNN)入门:隐状态、字符级语言模型与困惑度详解

🕒 发布时间:2026/10/2 1:52:28 📁 来源:尧图网络
人工智能深度学习机器学习教程【免费下载链接】d2l-zh《动手学深度学习》面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。项目地址https://gitcode.com/GitHub_Trending/d2/d2l-zh点击查看免费下载本文围绕《动手学深度学习》d2l-zh仓库中 chapter_recurrent-neural-networks/rnn.md 一章展开系统讲解循环神经网络Recurrent Neural NetworkRNN的核心原理从 $n$ 元语法模型的局限出发引出隐状态给出 RNN 的矩阵运算定义、与多层感知机MLP的对比、基于 RNN 的字符级语言模型构建方法以及用于评估语言模型质量的困惑度Perplexity指标。读完本文你将理解 RNN 为何能记住序列历史、为什么参数数量不随时间步增长并掌握使用困惑度量化模型质量的完整方法为后续实现 RNN如 rnn-scratch.md 的从零实现打下坚实基础。从 $n$ 元语法到隐变量模型在语言建模任务中我们希望根据已知的历史词元预测下一个词元。在 语言模型和数据集 一节中介绍的 $n$ 元语法$n$-gram模型假设单词 $x_t$ 在时间步 $t$ 的条件概率仅取决于前面 $n-1$ 个单词即建模 $P(x_t \mid x_{t-1}, \ldots, x_{t-n1})$。这种做法存在一个根本性缺陷如果要让时间步 $t-(n-1)$ 之前的单词也对 $x_t$ 产生影响就必须增大 $n$而模型参数的数量会随之呈指数级增长——对于词表 $\mathcal{V}$需要存储 $|\mathcal{V}|^n$ 个数字。例如即使词表只有 1 万个词二元语法$n2$就需要 $10^8$ 量级的参数三元语法则达到 $10^{12}$ 量级这在实践中几乎不可行。因此与其直接建模 $P(x_t \mid x_{t-1}, \ldots, x_{t-n1})$更可取的做法是使用隐变量模型latent variable model$$P(x_t \mid x_{t-1}, \ldots, x_1) \approx P(x_t \mid h_{t-1}),$$其中 $h_{t-1}$ 称为隐状态hidden state也叫隐藏变量hidden variable它存储了截至时间步 $t-1$ 的全部序列信息。更一般地任意时间步 $t$ 的隐状态都可以由当前输入 $x_t$ 与前一时刻的隐状态 $h_{t-1}$ 共同计算$$h_t f(x_{t}, h_{t-1}).$$从理论上讲只要函数 $f$ 足够强大这个隐变量模型就不是近似——因为 $h_t$ 完全可以存储到目前为止观察到的所有数据。但在实践中记住一切会使计算和存储的代价都变得极其昂贵因此如何设计一个既紧凑又能保留历史信息的 $f$正是循环神经网络要解决的问题。一个重要的概念区分隐藏层 ≠ 隐状态本章特别提醒读者注意两个容易混淆的概念隐藏层hidden layer是在从输入到输出的路径上、从观测角度理解隐藏的层它与普通前馈网络中的隐藏层含义一致隐状态hidden state在技术定义上是给定时间步上所有计算所依赖的输入它只能通过先前时间步的数据来计算。理解这一区分是读懂 RNN 计算图的前提。无隐状态的神经网络MLP 回顾在正式引入 RNN 之前先回顾 多层感知机 一节中只有单隐藏层的 MLP作为对比基线。设隐藏层激活函数为 $\phi$给定一个小批量样本 $\mathbf{X} \in \mathbb{R}^{n \times d}$批量大小为 $n$输入维度为 $d$隐藏层输出 $\mathbf{H} \in \mathbb{R}^{n \times h}$ 的计算式为$$\mathbf{H} \phi(\mathbf{X} \mathbf{W}_{xh} \mathbf{b}_h),$$其中 $\mathbf{W}_{xh} \in \mathbb{R}^{d \times h}$ 为隐藏层权重参数$\mathbf{b}_h \in \mathbb{R}^{1 \times h}$ 为偏置参数$h$ 为隐藏单元数目求和时可借助广播机制参见 线性代数 中的广播讨论。接着隐藏变量 $\mathbf{H}$ 作为输出层的输入$$\mathbf{O} \mathbf{H} \mathbf{W}_{hq} \mathbf{b}_q,$$其中 $\mathbf{O} \in \mathbb{R}^{n \times q}$ 是输出变量$\mathbf{W}_{hq} \in \mathbb{R}^{h \times q}$、$\mathbf{b}_q \in \mathbb{R}^{1 \times q}$ 是输出层的权重与偏置。若为分类问题可用 $\text{softmax}(\mathbf{O})$ 计算各类别的概率分布。这个 MLP 结构与 序列模型 中的回归问题完全类似随机选择特征-标签对通过自动微分和随机梯度下降学习参数即可。关键区别在于这个 MLP 没有隐状态每个样本的处理相互独立不携带任何时间维度上的历史信息。有隐状态的循环神经网络有了隐状态后情况完全不同。假设在时间步 $t$ 有小批量输入 $\mathbf{X}_t \in \mathbb{R}^{n \times d}$——对于 $n$ 个序列样本组成的小批量$\mathbf{X}_t$ 的每一行对应其中一个序列在时间步 $t$ 处的样本。用 $\mathbf{H}_t \in \mathbb{R}^{n \times h}$ 表示时间步 $t$ 的隐藏变量。与 MLP 不同的是这里保存了前一个时间步的隐藏变量 $\mathbf{H}{t-1}$并引入一个新的权重参数 $\mathbf{W}{hh} \in \mathbb{R}^{h \times h}$用来描述如何在当前时间步利用前一时间步的隐状态。当前时间步的隐状态由当前输入与前一时间步隐状态共同计算$$\mathbf{H}t \phi(\mathbf{X}t \mathbf{W}{xh} \mathbf{H}{t-1} \mathbf{W}_{hh} \mathbf{b}_h).$$与无隐状态的 MLP 公式相比上式仅仅多了一项 $\mathbf{H}{t-1} \mathbf{W}{hh}$却实例化了隐变量模型 $h_t f(x_t, h_{t-1})$。从相邻时间步隐状态 $\mathbf{H}t$ 与 $\mathbf{H}{t-1}$ 的关系可以看出这些变量捕获并保留了序列截至当前时间步的历史信息如同神经网络在某个时间步的状态或记忆这正是它被称为隐状态的原因。由于当前时间步隐状态的定义与前一时间步完全相同上式对每个时间步反复应用同一条公式这种计算是循环的recurrent。因此基于这种循环计算隐状态的神经网络被命名为循环神经网络RNN执行上述计算的层被称为循环层recurrent layer。在时间步 $t$输出层的计算与 MLP 类似$$\mathbf{O}_t \mathbf{H}t \mathbf{W}{hq} \mathbf{b}_q.$$参数共享成本不随时间步增长RNN 的全部参数包括参数形状所属层作用$\mathbf{W}_{xh}$$d \times h$隐藏层当前输入 $\mathbf{X}_t$ 的投影$\mathbf{W}_{hh}$$h \times h$隐藏层前一隐状态 $\mathbf{H}_{t-1}$ 的投影$\mathbf{b}_h$$1 \times h$隐藏层隐藏层偏置$\mathbf{W}_{hq}$$h \times q$输出层隐状态到输出的投影$\mathbf{b}_q$$1 \times q$输出层输出层偏置值得强调的是即使在不同时间步RNN 也始终复用这些模型参数。因此RNN 的参数开销不会随着时间步的增加而增长——这是 RNN 与 $n$ 元语法模型参数随 $n$ 指数增长最本质的差异之一。隐状态的计算逻辑下图展示了 RNN 在三个相邻时间步的计算逻辑在任意时间步 $t$隐状态的计算可以分解为两步拼接当前时间步 $t$ 的输入 $\mathbf{X}t$ 与前一时间步 $t-1$ 的隐状态 $\mathbf{H}{t-1}$将拼接结果送入带有激活函数 $\phi$ 的全连接层其输出即为当前时间步的隐状态 $\mathbf{H}_t$。此时模型参数正是 $\mathbf{W}{xh}$ 与 $\mathbf{W}{hh}$ 的拼接以及偏置 $\mathbf{b}_h$。当前隐状态 $\mathbf{H}t$ 一方面参与下一时间步 $\mathbf{H}{t1}$ 的计算另一方面被送入全连接输出层计算当前输出 $\mathbf{O}_t$形成隐状态既向前传递、又向下输出的双路径结构。用代码验证拼接后的矩阵乘法等价性隐状态计算中 $\mathbf{X}t \mathbf{W}{xh} \mathbf{H}{t-1} \mathbf{W}{hh}$ 在数学上等价于将 $\mathbf{X}t$ 与 $\mathbf{H}{t-1}$ 拼接、将 $\mathbf{W}{xh}$ 与 $\mathbf{W}{hh}$ 拼接再做矩阵乘法。虽然可以严格证明原文档仍给出了一段直观的代码验证。首先定义四个矩阵X、W_xh、H、W_hh形状分别为 $(3, 1)$、$(1, 4)$、$(3, 4)$、$(4, 4)$。分别计算X与W_xh的乘积、H与W_hh的乘积并相加得到形状 $(3, 4)$ 的矩阵#tab mxnet, pytorch, paddle X, W_xh d2l.normal(0, 1, (3, 1)), d2l.normal(0, 1, (1, 4)) H, W_hh d2l.normal(0, 1, (3, 4)), d2l.normal(0, 1, (4, 4)) d2l.matmul(X, W_xh) d2l.matmul(H, W_hh)#tab tensorflow X, W_xh d2l.normal((3, 1), 0, 1), d2l.normal((1, 4), 0, 1) H, W_hh d2l.normal((3, 4), 0, 1), d2l.normal((4, 4), 0, 1) d2l.matmul(X, W_xh) d2l.matmul(H, W_hh)接着沿列轴 1拼接X和H得到形状 $(3, 5)$ 的矩阵沿行轴 0拼接W_xh和W_hh得到形状 $(5, 4)$ 的矩阵两者相乘得到与上面完全相同的 $(3, 4)$ 输出#tab all d2l.matmul(d2l.concat((X, H), 1), d2l.concat((W_xh, W_hh), 0))d2l 工具包说明上述代码中的d2l.normal、d2l.matmul、d2l.concat是 d2l 针对不同深度学习框架的统一封装。从 d2l/torch.py 的源码可见normal torch.normal、matmul torch.matmul、concat torch.cat在 d2l/mxnet.py 中则分别映射为np.random.normal、np.dot、np.concatenate。这种统一接口让同一段教学代码可以在 MXNet、PyTorch、TensorFlow、PaddlePaddle 四种后端之间无缝切换。基于 RNN 的字符级语言模型回顾语言模型的目标根据过去的和当前的词元预测下一个词元因此训练时把原始序列向右平移一个词元作为标签。这里以一个具体例子说明如何用 RNN 构建语言模型。设小批量大小为 1批量中的文本序列为 machine。为了简化后续训练将文本词元化为字符而非单词即构建字符级语言模型character-level language model。下图演示了 RNN 如何根据当前及先前的字符预测下一个字符训练过程中对每个时间步输出层的输出做 softmax 操作再用交叉熵损失计算模型输出与标签之间的误差。由于隐藏层中隐状态的循环计算图中第 3 个时间步的输出 $\mathbf{O}_3$ 由文本序列 m、a、c 共同确定而训练数据中该位置的下一个字符是 h因此第 3 个时间步的损失取决于基于特征序列 m、a、c 生成的下一个字符概率分布与该时间步标签 h 的差异。也就是说越靠后的时间步其预测所依赖的历史上下文越长这正是 RNN 相对 $n$ 元语法模型的优势所在。在实践中批量大小通常 $n 1$每个词元由一个 $d$ 维向量表示因此时间步 $t$ 的输入 $\mathbf{X}_t$ 是 $n \times d$ 矩阵与前述有隐状态 RNN 的设定完全一致。在仓库中这一节内容与 rnn-scratch.md 的从零实现直接衔接那里提供了get_params_ch8、init_rnn_state、rnn、predict_ch8、train_epoch_ch8等函数并用本节定义的困惑度作为训练曲线Animator 的 y 轴与最终评估指标。困惑度Perplexity语言模型质量度量如何度量语言模型的质量一个直观思路是好的语言模型能以高准确率预测接下来出现的词元。考虑对 It is raining ... 的三种续写It is raining outside外面下雨了——显然最好用词合理、逻辑连贯虽然它未必精确命中语义上的最优续写in San Francisco、in winter 也都是合理扩展但模型已能捕捉后面应该跟哪类词It is raining banana tree香蕉树下雨了——糟糕得多产生无意义续写但至少模型学会了拼写单词以及单词间一定程度的相关性It is raining piouw;kcj pwepoiut——表明模型训练不足完全无法拟合数据。为什么不能直接用序列似然一个自然的度量方式是计算序列的似然概率但这个数字难以理解、难以比较较短的序列天然比长序列更可能发生。例如评估模型在托尔斯泰《战争与和平》上的似然必然远小于在圣埃克苏佩里《小王子》上的似然——缺少的正是某种平均意义上的归一化。信息论正好提供了工具。如果我们要压缩一段文本就要根据当前词元集合预测下一个词元更好的语言模型能更准确地预测下一个词元从而用更少的比特完成序列压缩。因此可以用整个序列所有 $n$ 个词元上的平均交叉熵损失来衡量$$\frac{1}{n} \sum_{t1}^n -\log P(x_t \mid x_{t-1}, \ldots, x_1),$$其中 $P$ 由语言模型给出$x_t$ 是时间步 $t$ 实际观察到的词元。这个平均值使得不同长度文档的性能可以相互比较。困惑度的定义与直觉由于历史原因自然语言处理领域更偏好使用困惑度perplexity它就是上述平均交叉熵损失的指数$$\exp\left(-\frac{1}{n} \sum_{t1}^n \log P(x_t \mid x_{t-1}, \ldots, x_1)\right).$$困惑度可以理解为下一个词元的实际选择数的调和平均数。看三个边界情形最优情形模型总是以概率 1 完美估计标签词元此时困惑度为1最差情形模型总是以概率 0 预测标签词元此时困惑度为正无穷基线情形模型在所有可用词元上预测均匀分布此时困惑度等于词表中唯一词元的数量。事实上如果不做任何压缩直接存储序列均匀分布编码已经是最优策略因此它提供了一个非平凡的上界任何实用的语言模型都必须优于该上界。这解释了为什么困惑度 10 通常不错而困惑度 35 通常很差词表规模决定了随机猜测的基线水平模型需要把困惑度压到词表大小以下才说明学到了真实语言结构。在 rnn-scratch.md 中训练脚本正是以困惑度作为每轮训练的评价指标如print(f困惑度 {ppl:.1f}, ...)困惑度越低代表模型对下一个字符的预测越准确。小结对隐状态使用循环计算的神经网络称为循环神经网络RNNRNN 的隐状态可以捕获直到当前时间步的序列历史信息RNN 的参数数量不会随时间步的增加而增加参数跨时间步共享可以用 RNN 构建字符级语言模型可以用困惑度平均交叉熵损失的指数评价语言模型的质量其最优值为 1、最差为正无穷、均匀分布基线等于词表大小。练习思考题结合本章内容思考以下问题如果用 RNN 预测文本序列中的下一个字符任意输出所需的维度是多少提示取决于词表大小输出维度应等于候选字符数以便做 softmax 分类。为什么 RNN 能基于文本序列中所有先前的词元在某个时间步表达当前词元的条件概率提示隐状态的循环传递使历史信息逐时间步累积。如果基于一个长序列进行反向传播梯度会发生什么状况提示这与 通过时间反向传播 一节讨论的梯度消失/梯度爆炸问题直接相关。与本节描述的语言模型相关的问题有哪些可从长距离依赖、计算复杂度、训练稳定性等角度展开。本章内容在 chapter_recurrent-neural-networks/rnn.md 中与后续章节构成完整链条理解 RNN 原理后可继续阅读 rnn-scratch.md 从零实现循环神经网络或阅读 rnn-concise.md 使用高级 API 快速搭建 RNN并最终过渡到 LSTM、GRU 等现代循环架构。赞分享人工智能深度学习机器学习教程【免费下载链接】d2l-zh《动手学深度学习》面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。项目地址https://gitcode.com/GitHub_Trending/d2/d2l-zh点击查看免费下载相关推荐Happy monorepo 本地开发指南CLI、Server、Expo App 与 Tauri 桌面的构建、安装、测试与调试Happy monorepo 本地开发指南CLI、Server、Expo App 与 Tauri 桌面的构建、安装、测试与调试 Happy 是一个基于 pnp人工智能深度学习机器学习教程ik_llama.cpp 对 Seed Coder 模型 FIM 特殊 Token 的支持从 GGUF 转换到词表加载的完整实现解析ik_llama.cpp 对 Seed Coder 模型 FIM 特殊 Token 的支持从 GGUF 转换到词表加载的完整实现解析 导读 本文以 ik_ll人工智能深度学习机器学习教程《动手学深度学习》循环神经网络RNN实战指南序列模型、语言建模与 BPTT 全解析《动手学深度学习》循环神经网络RNN实战指南序列模型、语言建模与 BPTT 全解析 导读 本文以《动手学深度学习》d2l zh仓库中 chapter_人工智能深度学习机器学习教程上一篇Twitter Ads API开发实战使用codebird-php创建高效广告管理系统下一篇TDengine 与 Ignition OPC UA Server 集成指南从匿名联调到证书加密生产部署创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →