实战指南:序列模型、语言建模与 BPTT 全解析)
人工智能深度学习机器学习教程【免费下载链接】d2l-zh《动手学深度学习》面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。项目地址https://gitcode.com/GitHub_Trending/d2/d2l-zh点击查看免费下载导读本文以《动手学深度学习》d2l-zh仓库中 chapter_recurrent-neural-networks/index.md 一章为骨架系统梳理从为什么需要序列模型到RNN 训练中的梯度问题的完整技术脉络。文章覆盖序列模型的统计基础、文本预处理流水线、语言模型与长序列采样、RNN 的前向计算原理、从零实现与高级 API 简洁实现以及通过时间反向传播BPTT的数学细节同时结合仓库 d2l 包中各框架实现源码进行佐证。读完本文你将理解 RNN 为何能处理序列信息、如何构建字符级语言模型并用困惑度评估以及梯度爆炸/消失的成因与工程化解方案。为什么需要循环神经网络序列数据打破独立同分布假设在之前的章节中表格数据与图像数据都默认样本来自某种分布且独立同分布i.i.d.。然而真实世界的大多数数据并非如此文章中的单词按顺序书写一旦顺序被随机重排原意便难以理解视频中的图像帧、对话中的音频信号、网站的浏览行为天然有序我们不仅能接收一个序列作为输入还期望继续猜测序列的后续例如预测2, 4, 6, 8, 10, ...这在股市波动、患者体温曲线、赛车加速度等时间序列分析中非常常见。简言之如果说卷积神经网络擅长处理空间信息那么本章的循环神经网络recurrent neural networkRNN则擅长处理序列信息——它通过引入状态变量存储过去的信息与当前输入从而确定当前输出。本章共包含七个小节构成一条完整的学习链路小节相对路径核心议题序列模型sequence.md序列数据的统计工具与预测挑战文本预处理text-preprocessing.md词元化与词表构建语言模型和数据集language-models-and-dataset.md概率建模、$n$ 元语法与长序列采样循环神经网络rnn.mdRNN 前向计算原理与困惑度循环神经网络的从零开始实现rnn-scratch.md手写 RNN 字符级语言模型循环神经网络的简洁实现rnn-concise.md用框架高级 API 实现同一模型通过时间反向传播bptt.md梯度计算、截断与稳定性序列模型自回归、马尔可夫与 $k$ 步预测从统计工具看序列预测处理序列数据需要新的统计工具。以股票价格为例用 $x_t$ 表示时间步 $t$ 观察到的价格交易员的目标是估计 $P(x_t \mid x_{t-1}, \ldots, x_1)$。直接回归面临核心矛盾输入数量随 $t$ 增长而增长。为此有两种经典策略自回归模型autoregressive models假设足够长的历史 $x_{t-1}, \ldots, x_1$ 并不必要只取长度为 $\tau$ 的时间跨度 $x_{t-1}, \ldots, x_{t-\tau}$参数数量在 $t \tau$ 时保持恒定从而可以用普通深度网络训练。隐变量自回归模型latent autoregressive models保留对过去观测的总结 $h_t$同时更新预测 $\hat{x}t$ 与总结 $h_t$即 $\hat{x}t P(x_t \mid h_t)$ 且 $h_t g(h{t-1}, x{t-1})$。由于 $h_t$ 从未被观测到故称为隐变量。这正是后续 RNN 的雏形。马尔可夫条件与一阶模型若用 $x_{t-1}, \ldots, x_{t-\tau}$ 代替 $x_{t-1}, \ldots, x_1$ 来估计 $x_t$ 足够精确称序列满足马尔可夫条件Markov condition。当 $\tau1$ 时即一阶马尔可夫模型$$ P(x_1, \ldots, x_T) \prod_{t1}^T P(x_t \mid x_{t-1}) \text{ 当 } P(x_1 \mid x_0) P(x_1). $$当 $x_t$ 为离散值时可用动态规划沿马尔可夫链精确计算例如 $P(x_{t1} \mid x_{t-1}) \sum_{x_t} P(x_{t1} \mid x_t) P(x_t \mid x_{t-1})$只需考虑很短的过去历史。因果关系与静止性在时间上存在自然的前进方向未来事件不能影响过去。因此解释 $P(x_{t1} \mid x_t)$ 比解释 $P(x_t \mid x_{t1})$ 更容易正向估计通常也更可行。同时统计上假设序列的动力学不变静止stationary整个序列的联合概率可分解为条件概率之积$$ P(x_1, \ldots, x_T) \prod_{t1}^T P(x_t \mid x_{t-1}, \ldots, x_1). $$若处理离散对象如单词则用分类器而非回归模型来估计条件概率。动手实验正弦序列的预测仓库文档 sequence.md 用一个可复现的实验演示序列预测的难度生成 $T1000$ 个时间步的正弦加噪数据x d2l.sin(0.01 * time) d2l.normal(0, 0.2, (T,))取嵌入维度tau 4构造特征标签对$y_t x_t$$\mathbf{x}_t [x_{t-\tau}, \ldots, x_{t-1}]$前 600 个样本用于训练batch_size 16。训练模型用两层全连接10 个隐藏单元ReLU 激活加平方损失的 MLPAdam 优化器学习率 0.01训练 5 轮net nn.Sequential(nn.Linear(4, 10), nn.ReLU(), nn.Linear(10, 1)) net.apply(init_weights) loss nn.MSELoss(reductionnone) trainer torch.optim.Adam(net.parameters(), lr)评估两种预测单步预测one-step-ahead prediction直接输入真实观测即使超出训练范围n_train tau 604结果仍可信多步预测$k$-step-ahead-prediction一旦观测止于 $x_{604}$后续预测必须使用自己上一步的预测结果递归外推$$ \hat{x}{605} f(x{601}, x_{602}, x_{603}, x_{604}),\quad \hat{x}{606} f(x{602}, x_{603}, x_{604}, \hat{x}_{605}),\ldots $$实验结果非常直观多步预测经过若干步后迅速衰减为常数。原因是误差累积——步骤 1 引入误差 $\epsilon_1$ 后步骤 2 的输入被扰动误差按 $\epsilon_2 \bar\epsilon c\epsilon_1$ 递归放大。对比 $k1,4,16,64$ 步预测可以发现超过 4 步的预测几乎无价值。这也呼应了天气预报24 小时内较准、再远精度骤降的现象并预告了本章后续RNN 及更复杂模型要解决的核心问题。文本预处理从原始字符串到词元索引文本是最常见的序列数据。预处理流水线通常包含四步加载文本、拆分为词元、构建词表、转换为数字索引序列。该流程的完整实现沉淀在 text-preprocessing.md 与 d2l/torch.py及 mxnet/tensorflow/paddle 各版本中。读取数据集以 H. G. Wells 的《时光机器》The Time Machine为例仅 3 万多个单词足够小规模实验。read_time_machine将文本读成文本行列表并忽略标点与字母大小写d2l/torch.pydef read_time_machine(): #save 将时间机器数据集加载到文本行的列表中 with open(d2l.download(time_machine), r) as f: lines f.readlines() return [re.sub([^A-Za-z], , line).strip().lower() for line in lines]词元化与词表tokenize(lines, tokenword)将每条文本行拆分为词元列表支持word按空白切分与char按字符切分两种粒度d2l/torch.py。词元是字符串而模型需要数字因此构建**词表vocabulary**将词元映射到从 0 开始的索引。Vocab类的核心设计见 text-preprocessing.md按出现频率对词元降序排序min_freq过滤低频词元以降低复杂度索引 0 固定为未知词元unk语料中不存在或已被删除的词元统一映射到它可通过reserved_tokens预留填充词元pad、序列开始词元bos、序列结束词元eos等特殊词元提供__getitem__词元/词元列表 → 索引/索引列表与to_tokens索引 → 词元双向映射。整合为load_corpus_time_machine为简化后续训练文档将所有功能打包进load_corpus_time_machine(max_tokens-1)d2l/torch.py并做了两点调整改用字符级词元化而非单词因文本行不一定是完整句子将corpus展平为单个词元索引列表。max_tokens 0时可截断语料规模默认参数下返回(corpus, vocab)。语言模型与数据集概率建模与长序列采样语言模型的目标给定长度 $T$ 的词元序列 $x_1, x_2, \ldots, x_T$**语言模型language model**的目标是估计联合概率 $P(x_1, x_2, \ldots, x_T)$。它按链式法则展开为$$ P(x_1, x_2, \ldots, x_T) \prod_{t1}^T P(x_t \mid x_{t-1}, \ldots, x_1). $$一个理想的语言模型可基于自身生成自然文本即使达不到理解它也能消除语音识别中的歧义to recognize speech 与 to wreck a nice beach或判断我想吃奶奶与我想吃奶奶的正常程度。$n$ 元语法与拉普拉斯平滑直接统计计数估计条件概率会遇到稀疏性问题二元组 deep learning 的出现频率远低于单词 deep三元及以上组合更是大量存在于合理语言中却在数据集中缺席。经典补救是拉普拉斯平滑Laplace smoothing在计数中加入小常量例如$$ \hat{P}(x) \frac{n(x) \epsilon_1/m}{n \epsilon_1}, $$其中 $n$ 为训练集单词总数$m$ 为唯一单词数$\epsilon_1 0$ 表示不平滑$\epsilon_1 \to \infty$ 时 $\hat{P}(x)$ 趋向均匀分布 $1/m$。但这类模型存在根本缺陷需存储所有计数、完全忽略单词语义、长序列几乎从未出现而表现不佳。马尔可夫假设将依赖截断为固定阶数一元语法unigram、二元语法bigram、三元语法trigram分别只依赖 0、1、2 个前驱词元。齐普夫定律为什么统计平滑不可行在时光机器语料上统计发现最常用词基本是停用词stop words且词频衰减极快——第 10 高频词的频率不足第 1 高频词的 1/5。双对数坐标下词频近似直线即满足齐普夫定律Zipfs law$$ n_i \propto \frac{1}{i^\alpha}, \quad \log n_i -\alpha \log i c. $$一元、二元、三元语法均服从该规律只是指数 $\alpha$ 不同。这带来三个结论计数平滑建模会高估尾部低频词的频率$n$ 元组总量并不巨大说明语言存在大量可利用的结构大量 $n$ 元组极少出现使拉普拉斯平滑不适合语言建模——因此需要基于深度学习的模型如 RNN。读取长序列随机采样与顺序分区序列本质连续需将任意长的语料切分为固定长度如num_steps个时间步的小批量子序列。language-models-and-dataset.md 给出两种策略两者都从随机偏移量开始切分以兼顾覆盖性与随机性随机采样random samplingseq_data_iter_random(corpus, batch_size, num_steps)d2l/torch.py先从random.randint(0, num_steps - 1)偏移处开始把序列切为长度num_steps的子序列并random.shuffle起始索引使相邻小批量的子序列在原始序列上不一定相邻标签Y是特征X移位一个词元的结果。以序列range(35)、batch_size2、num_steps5为例可生成 $\lfloor (35-1)/5 \rfloor 6$ 个特征标签对即 3 个小批量。顺序分区sequential partitioningseq_data_iter_sequentiald2l/torch.py保证相邻两个小批量中的子序列在原始序列上相邻适合训练时需要跨批量延续隐状态的场景。两者再被包装进SeqDataLoader迭代器并由load_data_time_machine(batch_size, num_steps, use_random_iterFalse, max_tokens10000)d2l/torch.py统一返回数据迭代器与词表默认max_tokens10000限制语料规模、默认采用顺序分区。循环神经网络原理隐状态与参数共享从无隐状态到有隐状态回顾单隐藏层 MLP给定小批量 $\mathbf{X} \in \mathbb{R}^{n \times d}$隐藏层输出$$ \mathbf{H} \phi(\mathbf{X} \mathbf{W}_{xh} \mathbf{b}h), \qquad \mathbf{O} \mathbf{H} \mathbf{W}{hq} \mathbf{b}_q. $$RNN 的关键区别在于引入隐状态时间步 $t$ 的隐变量不仅依赖当前输入还依赖前一时间步的隐变量新增权重 $\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), \qquad \mathbf{O}_t \mathbf{H}t \mathbf{W}{hq} \mathbf{b}_q. $$需要特别区分两个易混淆概念隐藏层是从输入到输出路径上以观测角度理解的层隐状态是给定步骤所做任何事情的输入只能通过先前时间步的数据计算。由于当前步隐状态的定义与前一步相同计算是循环的recurrent执行该计算的层称为循环层。两个关键性质参数共享循环神经网络在不同时间步始终复用同一组参数$\mathbf{W}{xh}, \mathbf{W}{hh}, \mathbf{b}h, \mathbf{W}{hq}, \mathbf{b}_q$因此参数开销不随时间步增加而增加——这正是它相对 $n$ 元语法需存储 $|\mathcal{V}|^n$ 个数字的核心优势。拼接等价性$\mathbf{X}t \mathbf{W}{xh} \mathbf{H}{t-1} \mathbf{W}{hh}$ 等价于把输入与隐状态沿列拼接、把两个权重沿行拼接后再做矩阵乘法。文档用随机矩阵X(3,1)、W_xh(1,4)、H(3,4)、W_hh(4,4)验证两种写法得到相同形状 $(3,4)$ 的输出。字符级语言模型与困惑度RNN 的典型应用是字符级语言模型输入序列 machin 预测标签 achine逐时间步输出经 softmax 后与标签计算交叉熵损失第 3 个时间步的输出由 m、a、c 共同决定。实践中批量大小为 $n1$、每个词元用 $d$ 维向量表示时间步 $t$ 的输入 $\mathbf{X}_t$ 为 $n \times d$ 矩阵。评估语言模型质量用困惑度perplexity——平均交叉熵的指数$$ \exp\left(-\frac{1}{n} \sum_{t1}^n \log P(x_t \mid x_{t-1}, \ldots, x_1)\right). $$它可理解为下一个词元实际选择数的调和平均数完美预测时困惑度为 1均匀基线时困惑度等于词表唯一词元数这是无压缩存储的理论上限任何实际模型必须超越预测概率为 0 时困惑度趋于无穷。较短的序列更可能、绝对似然难以跨文档比较而困惑度使不同长度文档可比。从零实现 RNN 字符级语言模型rnn-scratch.md 从零手写完整训练流程训练配置为batch_size32, num_steps35数据经load_data_time_machine读取。独热编码词元索引 $i$ 映射为长度len(vocab)的全 0 向量、第 $i$ 位为 1。小批量批量大小时间步数经one_hot转成三维张量并转置为**时间步数批量大小词表大小**以便沿最外层维度逐步更新隐状态。参数初始化get_params(vocab_size, num_hiddens, device)返回五组参数——隐藏层 $\mathbf{W}{xh} \in \mathbb{R}^{\text{vocab} \times h}$、$\mathbf{W}{hh} \in \mathbb{R}^{h \times h}$、$\mathbf{b}h$输出层 $\mathbf{W}{hq} \in \mathbb{R}^{h \times \text{vocab}}$、$\mathbf{b}_q$输入与输出共享同一词表维度用标准差 0.01 的正态分布初始化。前向计算rnn函数逐时间步执行 $\mathbf{H}t \tanh(\mathbf{X}t \mathbf{W}{xh} \mathbf{H}{t-1} \mathbf{W}_{hh} \mathbf{b}_h)$输出层 $\mathbf{O}_t \mathbf{H}t \mathbf{W}{hq} \mathbf{b}_q$init_rnn_state返回形状为批量大小隐藏单元数的全零初始隐状态。梯度裁剪训练中每批更新前调用d2l.grad_clippingd2l/torch.py将梯度范数裁剪到阈值 $\theta$默认 1防止梯度爆炸。代码中可见param.grad.data被统一缩放这正是后面 bptt.md 要解释的工程化处理。预测与困惑度predict_ch8在给定前缀如 time traveller 后逐字符生成文本先独热编码再迭代隐状态取概率最高的索引作为下一个字符evaluate_metric用困惑度评估训练打印的指标即困惑度值。实验结果在《时光机器》上训练后模型能生成类似 the time machine by h. g. wells 的文本片段当use_random_iterTrue随机采样时因无法跨批量延续隐状态训练稍慢但困惑度仍持续下降。简洁实现高级 API 一行构造 RNNrnn-concise.md 用框架高级 API 复现同一模型代码量大幅缩减num_hiddens 256 rnn_layer nn.RNN(len(vocab), num_hiddens) # PyTorch输入维度为词表大小 state torch.zeros((1, batch_size, num_hiddens)) # (层数, 批量大小, 隐藏单元数) Y, state_new rnn_layer(X, state)要点各框架对应构造分别为 MXNetgluon.rnn.RNN(num_hiddens)、TensorFlowSimpleRNNCellkeras.layers.RNN(time_majorTrue, return_sequencesTrue, return_stateTrue)、Paddlenn.SimpleRNN(len(vocab), num_hiddens, time_majorTrue)隐状态形状统一为隐藏层数批量大小隐藏单元数rnn_layer的输出Y并不含输出层计算——它返回每个时间步的隐状态供后续nn.Dense(vocab_size)输出层使用state_new是最后时间步的隐状态可用于顺序分区下一个小批量的隐状态初始化剩余部分嵌入层、输出层、损失、预测、训练循环与从零实现高度一致最终困惑度同样稳定下降验证了手写实现的正确性。通过时间反向传播BPTT梯度如何流动与截断bptt.md 深入序列模型的梯度计算解释此前反复出现的梯度爆炸梯度消失分离梯度等概念。核心思想BPTT 就是反向传播在 RNN 上的特定应用——把计算图按时间步展开一次基于链式法则反向计算并存储梯度。简化模型下 $h_t f(x_t, h_{t-1}, w_h)$目标 $L \frac{1}{T}\sum_{t1}^T l(y_t, o_t)$。求 $\partial L / \partial w_h$ 时遇到递归项$$ \frac{\partial h_t}{\partial w_h} \frac{\partial f(x_t, h_{t-1}, w_h)}{\partial w_h} \sum_{i1}^{t-1}\left(\prod_{ji1}^{t} \frac{\partial f(x_j, h_{j-1}, w_h)}{\partial h_{j-1}}\right) \frac{\partial f(x_i, h_{i-1}, w_h)}{\partial w_h}. $$当 $t$ 很大时链极长且矩阵高次幂$\mathbf{W}_{hh}^\top$ 的 $T-i$ 次幂导致小于 1 的特征值使梯度消失大于 1 的特征值使梯度发散爆炸。四种梯度计算策略完全计算计算上式全部总和。理论上精确但速度极慢且易梯度爆炸初始条件微小变化引发蝴蝶效应实践中几乎不用截断时间步常规截断在 $\tau$ 步后终止求和即把 $\partial h_{t-\tau}/\partial w_h$ 当作零——这正是从零实现中detach分离梯度的本质。它近似真实梯度、计算可行且将估计偏向更简单更稳定的模型侧重短期影响是实践主流随机截断用期望正确的随机变量 $\xi_t$$P(\xi_t 0) 1-\pi_t$$P(\xi_t \pi_t^{-1}) \pi_t$$E[\xi_t] 1$替换递归项实现不同长度序列的加权和。理论上吸引人但实践中常不比常规截断更好短范围反向传播已足以捕获依赖、方差增加抵消长梯度精确性、短范围交互恰是理想模型性质实际训练中通常以给定数量时间步后分离梯度的方式实现截断。详细的梯度推导在忽略偏置、恒等激活的线性 RNN 中隐状态与输出为 $\mathbf{h}t \mathbf{W}{hx}\mathbf{x}t \mathbf{W}{hh}\mathbf{h}_{t-1}$、$\mathbf{o}t \mathbf{W}{qh}\mathbf{h}_t$。沿计算图反向遍历可得$$ \frac{\partial L}{\partial \mathbf{W}{qh}} \sum{t1}^T \frac{\partial L}{\partial \mathbf{o}_t} \mathbf{h}_t^\top,\quad \frac{\partial L}{\partial \mathbf{h}T} \mathbf{W}{qh}^\top \frac{\partial L}{\partial \mathbf{o}_T}, $$任意 $t T$ 时隐状态梯度递归为$$ \frac{\partial L}{\partial \mathbf{h}t} \mathbf{W}{hh}^\top \frac{\partial L}{\partial \mathbf{h}{t1}} \mathbf{W}{qh}^\top \frac{\partial L}{\partial \mathbf{o}_t}. $$展开后梯度陷入 $\left(\mathbf{W}{hh}^\top\right)^{T-i}$ 的高次幂——特征值小于 1 消失、大于 1 发散数值上即梯度消失/爆炸。BPTT 交替进行前向传播与反向传播并缓存中间值如 $\partial L/\partial \mathbf{h}t$供 $\partial L/\partial \mathbf{W}{hx}$ 与 $\partial L/\partial \mathbf{W}{hh}$ 复用避免重复计算。更复杂的序列模型如 LSTM正是从架构层面进一步缓解这一问题——这将在后续章节chapter_recurrent-modern 下的 lstm、gru 等展开。小结与阅读路径本章在《动手学深度学习》中处于承上启下的位置承上复用此前 MLP、反向传播、softmax 与信息论知识将建模对象从静态数据扩展到序列数据启下RNN 的隐状态与 BPTT 梯度问题是后续 chapter_recurrent-modern 中 GRU、LSTM、深度 RNN、双向 RNN、机器翻译等更复杂模型的理论基石。核心结论可归纳为序列数据打破 i.i.d. 假设RNN 通过循环计算的隐状态捕获直到当前时间步的历史信息且参数数量不随时间步增长文本预处理 加载 → 词元化 → 词表映射 → 数字索引序列仓库提供read_time_machine、tokenize、Vocab、load_corpus_time_machine等可复用 API语言模型估计联合概率$n$ 元语法受齐普夫定律与稀疏性制约长序列读取以随机采样与顺序分区为主RNN 可构建字符级语言模型用困惑度评估质量BPTT 是链式法则在时间维度上的应用矩阵高次幂导致梯度爆炸/消失工程上以梯度裁剪与时间步截断化解并缓存中间值保证效率。相关实现证据可继续在仓库中查阅d2l/torch.pyPyTorch 实现与 d2l/mxnet.py、d2l/tensorflow.py、d2l/paddle.py 对应、本章各小节文档以及各框架下#save标记的复用函数——它们就是本文全部代码示例的权威出处。赞分享人工智能深度学习机器学习教程【免费下载链接】d2l-zh《动手学深度学习》面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。项目地址https://gitcode.com/GitHub_Trending/d2/d2l-zh点击查看免费下载相关推荐基于 PyTorch 的 DDP 分布式训练实战用 torchrun 编排多节点多卡应用基于 PyTorch 的 DDP 分布式训练实战用 torchrun 编排多节点多卡应用 本篇技术指南围绕当前仓库 distributed/ddp 示例系统人工智能深度学习机器学习教程循环神经网络终极指南斯坦福CS229深度学习手册中的序列模型实战解析循环神经网络终极指南斯坦福CS229深度学习手册中的序列模型实战解析 斯坦福CS229机器学习课程的VIP手册为深度学习爱好者提供了全面的理论与实践指导其中文档教程机器学习深度学习500问第六章精读循环神经网络RNN结构图解、BPTT 推导与 LSTM/GRU 变体实战指南深度学习500问第六章精读循环神经网络RNN结构图解、BPTT 推导与 LSTM/GRU 变体实战指南 导读本文以《深度学习500问》第六章为骨架系统深度学习机器学习计算机视觉NLP教程知识库上一篇k-skill 实战韩国高速公路实时路况与 CCTV 查询技能 highway-traffic-status 深度解析下一篇Terraform Provider AWS 权限自检实战深入解析 aws_iam_principal_policy_simulation 数据源创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考