zh路西法头像
【深入理解 LSTM】从时间反向传播(BPTT)到长时记忆:门控机制与实验验证封面图

【深入理解 LSTM】从时间反向传播(BPTT)到长时记忆:门控机制与实验验证

前言

  • 长短期记忆网络(Long Short-Term Memory,LSTM)作为一种经典的循环神经网络结构,能够通过门控机制对历史信息进行选择性保留与更新,因此被应用于时间序列预测、自然语言处理、语音识别等序列建模任务中
    • 在强化学习领域,LSTM 常被用于构建循环策略网络,通过隐状态保留历史观测信息,以应对部分可观测环境;在模型式强化学习与世界模型中,LSTM 也曾被用于学习环境的时序动态,例如 World Models 通过 MDN-RNN 建模潜在状态的时间演化;在机器人控制、轨迹预测等任务中,LSTM 也具有一定的应用价值
  • 回顾之前的文章 【RNN GRU LSTM】从 Vanilla RNN 到 LSTM:公式推导、手写 cell 与三组不训练实验,当时主要介绍了 Vanilla RNN、门控循环单元(Gated Recurrent Unit,GRU)和 LSTM 的基本结构与工作原理,但对于循环神经网络如何进行训练,以及梯度如何在时间维度上传播,并没有展开详细讨论
  • 因此,本期将从时间反向传播(Backpropagation Through Time,BPTT)出发,分析循环神经网络的训练原理,并结合 LSTM 的门控机制,理解其梯度传播特性,最后通过正弦预测与部分可观测的角速度估计两个实验,进一步认识循环网络的训练过程与 LSTM 的记忆行为请添加图片描述


0 回顾

0-1 Vanilla RNN

循环传递

输入 x_t

输入变换
W_xh x_t + b_xh

上一时刻 h_(t-1)

循环变换
W_hh h_(t-1) + b_hh

相加

tanh 激活

当前隐状态 h_t

线性读出
W_hy h_t + b_y

输出 ŷ_t

下一时刻

  • 朴素循环网络的单步更新写为

h t = tanh ⁡ ( W x h x t + b x h + W h h h t − 1 + b h h ) h_t=\tanh\left(W_{xh}x_t+b_{xh}+W_{hh}h_{t-1}+b_{hh}\right) ht​=tanh(Wxh​xt​+bxh​+Whh​ht−1​+bhh​)

  • 输出由隐状态经线性读出层得到

y ^ t = W h y h t + b y \hat{y}_t=W_{hy}h_t+b_y y^​t​=Why​ht​+by​

  • 符号约定如下:
    • x t ∈ R d x_t\in\mathbb{R}^{d} xt​∈Rd:第 t t t 步的输入
    • h t ∈ R n h_t\in\mathbb{R}^{n} ht​∈Rn:第 t t t 步的隐状态, n n n 为隐层宽度
    • W x h ∈ R n × d W_{xh}\in\mathbb{R}^{n\times d} Wxh​∈Rn×d、 W h h ∈ R n × n W_{hh}\in\mathbb{R}^{n\times n} Whh​∈Rn×n:输入权重与循环权重,在所有时间步共享
    • b x h b_{xh} bxh​、 b h h b_{hh} bhh​:对应的偏置项
  • 初始隐状态取 h 0 = 0 h_0=\mathbf{0} h0​=0,每个时间步使用同一组参数
  • 隐藏状态沿时间递推,使输出能够间接依赖历史输入;但梯度回传需要连续穿过多次相同的线性变换与激活函数,这正是后文讨论 BPTT 的起点
0-2 GRU

输入 x_t

重置门 r_t

上一时刻 h_(t-1)

更新门 z_t

候选状态计算

候选隐状态 h̃_t

旧状态保留
z_t ⊙ h_(t-1)

候选状态加权
(1-z_t) ⊙ h̃_t

相加

当前隐状态 h_t

输出 / 下一时刻

  • GRU 的单步更新由重置门、更新门与候选隐状态组成

r t = σ ( W x r x t + b x r + W h r h t − 1 + b h r ) r_t=\sigma\left(W_{xr}x_t+b_{xr}+W_{hr}h_{t-1}+b_{hr}\right) rt​=σ(Wxr​xt​+bxr​+Whr​ht−1​+bhr​)

z t = σ ( W x z x t + b x z + W h z h t − 1 + b h z ) z_t=\sigma\left(W_{xz}x_t+b_{xz}+W_{hz}h_{t-1}+b_{hz}\right) zt​=σ(Wxz​xt​+bxz​+Whz​ht−1​+bhz​)

h ~ t = tanh ⁡ ( W x n x t + b x n + r t ⊙ ( W h n h t − 1 + b h n ) ) \tilde{h}_t=\tanh\left(W_{xn}x_t+b_{xn}+r_t\odot\left(W_{hn}h_{t-1}+b_{hn}\right)\right) h~t​=tanh(Wxn​xt​+bxn​+rt​⊙(Whn​ht−1​+bhn​))

h t = ( 1 − z t ) ⊙ h ~ t + z t ⊙ h t − 1 h_t=(1-z_t)\odot\tilde{h}_t+z_t\odot h_{t-1} ht​=(1−zt​)⊙h~t​+zt​⊙ht−1​

  • 其中 σ \sigma σ 为 sigmoid 函数, ⊙ \odot ⊙ 为逐元素乘
    • 更新门 z t z_t zt​:控制旧隐状态与候选隐状态的融合比例。按照上述公式, z t z_t zt​ 越接近 1,越倾向于保留旧状态;越接近 0,越倾向于采用候选状态,使历史信息能够通过恒等路径直接传递。
    • 重置门 r t r_t rt​:控制计算候选隐状态时,如何利用上一时刻的隐状态。 r t r_t rt​ 越接近 0,循环变换中来自旧隐状态的对应分量影响越小;越接近 1,则越充分地利用历史状态参与候选状态计算。
  • 需要注意的是,GRU 通过更新门对旧隐状态与候选隐状态进行逐元素凸组合,两者的权重之和恒为 1。这意味着 GRU 更侧重于在旧状态与新状态之间进行替换和融合,而不是将新信息直接累加到旧状态上。

说人话:重置门 r t r_t rt​​ 决定过去的信息有哪些还对这次计算有帮助,更新门 z t z_t zt​
决定旧的记忆要保留多少。

0-3 LSTM

记忆传递

状态传递

输入 x_t

输入门 i_t

上一时刻 h_(t-1)

遗忘门 f_t

候选记忆 g_t

输出门 o_t

旧细胞状态 c_(t-1)

保留旧记忆
f_t ⊙ c_(t-1)

写入新记忆
i_t ⊙ g_t

相加

当前细胞状态 c_t

tanh(c_t)

逐元素乘

当前隐状态 h_t

下一时刻细胞状态

下一时刻隐状态

  • LSTM 将记忆单元与输出隐状态分离,单步更新为

i t = σ ( W x i x t + b x i + W h i h t − 1 + b h i ) i_t=\sigma\left(W_{xi}x_t+b_{xi}+W_{hi}h_{t-1}+b_{hi}\right) it​=σ(Wxi​xt​+bxi​+Whi​ht−1​+bhi​)

f t = σ ( W x f x t + b x f + W h f h t − 1 + b h f ) f_t=\sigma\left(W_{xf}x_t+b_{xf}+W_{hf}h_{t-1}+b_{hf}\right) ft​=σ(Wxf​xt​+bxf​+Whf​ht−1​+bhf​)

g t = tanh ⁡ ( W x g x t + b x g + W h g h t − 1 + b h g ) g_t=\tanh\left(W_{xg}x_t+b_{xg}+W_{hg}h_{t-1}+b_{hg}\right) gt​=tanh(Wxg​xt​+bxg​+Whg​ht−1​+bhg​)

o t = σ ( W x o x t + b x o + W h o h t − 1 + b h o ) o_t=\sigma\left(W_{xo}x_t+b_{xo}+W_{ho}h_{t-1}+b_{ho}\right) ot​=σ(Wxo​xt​+bxo​+Who​ht−1​+bho​)

c t = f t ⊙ c t − 1 + i t ⊙ g t c_t=f_t\odot c_{t-1}+i_t\odot g_t ct​=ft​⊙ct−1​+it​⊙gt​

h t = o t ⊙ tanh ⁡ ( c t ) h_t=o_t\odot\tanh(c_t) ht​=ot​⊙tanh(ct​)

  • 输入门 i t i_t it​、遗忘门 f t f_t ft​ 与输出门 o t o_t ot​ 均取 sigmoid,候选记忆 g t g_t gt​ 取 tanh
    • 输入门 i t i_t it​:控制当前候选记忆 g t g_t gt​ 中有多少信息被写入细胞状态 c t c_t ct​。当 i t i_t it​ 越接近 1,候选记忆对当前细胞状态的影响越大;越接近 0,则越少写入新信息。
    • 遗忘门 f t f_t ft​:控制上一时刻细胞状态 c t − 1 c_{t-1} ct−1​ 中有多少信息被保留。当 f t f_t ft​ 越接近 1,历史记忆保留得越多;越接近 0,历史记忆被削弱得越多。
    • 输出门 o t o_t ot​:控制当前细胞状态经过 tanh ⁡ \tanh tanh 变换后,有多少信息用于生成当前隐藏状态 h t h_t ht​。它调节记忆对当前输出的暴露程度,而不是直接删除细胞状态中的记忆。
  • 额外的状态:
    • 隐藏状态 h t h_t ht​:表示当前时刻对外输出的状态,既用于生成预测结果,也会参与下一时刻各个门控和候选记忆的计算。
    • 细胞状态 c t c_t ct​:LSTM 内部的记忆载体,通过遗忘门保留历史信息、通过输入门写入新信息,并沿时间维度持续传递。由于其更新采用加法形式,因此为长期信息与梯度的传播提供了更直接的通路。
  • 为了避免 GRU 仅通过旧隐状态与候选隐状态的逐元素凸组合来更新状态,LSTM 引入了独立的细胞状态 c t c_t ct​,并采用加法形式更新记忆:一方面通过遗忘门控制历史记忆的保留程度,另一方面通过输入门控制新信息的写入程度。
  • 这样,LSTM 不再局限于在旧状态与候选状态之间进行加权替换,而是能够在保留历史信息的同时持续累积新信息,为学习长期依赖关系提供了更灵活的机制。

说人话:LSTM 可以在保留旧记忆的同时,把新信息继续写进记忆里,而不是只能在旧状态和新状态之间做加权取舍。输出门则决定当前需要向外展示多少记忆,遗忘门决定哪些旧记忆需要保留,输入门决定哪些新信息值得写入。


1 RNN 训练流程

1-1 RNN 如何利用历史信息
  • 假设输入三个时间步的数据 x 1 x_1 x1​、 x 2 x_2 x2​、 x 3 x_3 x3​,RNN 依次更新隐藏状态

h t = tanh ⁡ ( W x h x t + W h h h t − 1 + b h ) h_t=\tanh\left(W_{xh}x_t+W_{hh}h_{t-1}+b_h\right) ht​=tanh(Wxh​xt​+Whh​ht−1​+bh​)

x₁

h₁

x₂

h₂

x₃

h₃

共享循环参数 W_hh

  • 这里用虚线表示:每个时间步都使用同一组循环参数 W h h W_{hh} Whh​
  • 因为 h 2 h_2 h2​ 依赖 h 1 h_1 h1​,而 h 3 h_3 h3​ 又依赖 h 2 h_2 h2​,所以最终的输出可以间接受到早期输入 x 1 x_1 x1​ 的影响
  • 由此产生的问题是:模型如何学会让 x 1 x_1 x1​ 对最终输出产生正确的影响
    • 这需要借助 BPTT 完成
1-2 BPTT 基本原理

请添加图片描述

  • 时间反向传播(Backpropagation Through Time,BPTT)是一种用于训练循环神经网络(RNN)的梯度计算方法
    • 其核心思想是沿时间维度展开循环神经网络,将其视为一个具有多个时间步的计算图,再利用反向传播算法和链式法则计算损失函数对网络参数的梯度,进而通过梯度下降类优化算法更新参数
  • BPTT 并不是一种独立的网络结构或优化器,而是循环神经网络进行梯度计算的一种方法
1-2-1 循环神经网络的前向传播
  • 以 Elman RNN 为例,在时间步 t t t,隐藏状态的更新过程可以表示为

h t = ϕ ( W x h x t + W h h h t − 1 + b h ) h_t=\phi\left(W_{xh}x_t+W_{hh}h_{t-1}+b_h\right) ht​=ϕ(Wxh​xt​+Whh​ht−1​+bh​)

  • 输出层计算为

y ^ t = ψ ( W h y h t + b y ) \hat{y}_t=\psi\left(W_{hy}h_t+b_y\right) y^​t​=ψ(Why​ht​+by​)

  • 其中:
    • x t x_t xt​:当前时间步的输入
    • h t h_t ht​:当前隐藏状态,包含当前输入与此前历史信息的递归表示
    • W x h W_{xh} Wxh​:输入到隐藏层的权重矩阵
    • W h h W_{hh} Whh​:隐藏状态之间的循环权重矩阵
    • W h y W_{hy} Why​:隐藏层到输出层的权重矩阵
    • ϕ \phi ϕ、 ψ \psi ψ:相应的激活函数
  • 由于 h t h_t ht​ 依赖 h t − 1 h_{t-1} ht−1​,而 h t − 1 h_{t-1} ht−1​ 又依赖更早的隐藏状态,因此当前输出能够间接受到整个历史输入序列的影响
1-2-2 时间展开
  • 对于长度为 T T T 的输入序列,RNN 可以沿时间维度展开为一系列相互连接的计算单元
  • 前向传播:沿时间方向依次计算隐藏状态,各时间步共享同一组网络参数;隐藏状态沿时间方向传递

时间步 3

时间步 2

时间步 1

输入 x₁

隐藏状态 h₁

输入 x₂

隐藏状态 h₂

输入 x₃

隐藏状态 h₃

共享网络参数 W_hh

  • 反向传播:梯度沿时间展开的计算图反向传递,梯度从损失所在的输出端出发,沿时间反方向传播

时间步 1

时间步 2

时间步 3

隐藏状态 h₃

梯度

隐藏状态 h₂

梯度

隐藏状态 h₁

梯度

损失函数 L

  • 注意:反向传播并不是简单地将同一个梯度原样传回,而是沿展开后的计算图逐步应用链式法则,具体形式见 1-2-3 与 1-2-4 节
1-2-3 状态转换雅可比矩阵推导
  • 为了理解 BPTT 中梯度如何沿时间反方向传播,需要先分析相邻时间步的隐藏状态之间的梯度关系
  • 对于普通 RNN,隐藏状态的更新公式为 h t = tanh ⁡ ( W x h x t + W h h h t − 1 + b h ) h_t=\tanh\left(W_{xh}x_t+W_{hh}h_{t-1}+b_h\right) ht​=tanh(Wxh​xt​+Whh​ht−1​+bh​)其中, x t x_t xt​ 为当前输入, h t − 1 h_{t-1} ht−1​ 为上一时刻的隐藏状态, W x h W_{xh} Wxh​ 和 W h h W_{hh} Whh​ 分别为输入权重矩阵和循环权重矩阵, b h b_h bh​ 为偏置项
  • 将当前输入 x t x_t xt​ 视为常量,对上一时刻的隐藏状态 h t − 1 h_{t-1} ht−1​ 求导,可以得到状态转换的雅可比矩阵

J t = ∂ h t ∂ h t − 1 J_t=\frac{\partial h_t}{\partial h_{t-1}} Jt​=∂ht−1​∂ht​​

  • 根据链式法则,先对线性变换求导,再对 tanh ⁡ \tanh tanh 激活函数求导,可得

J t = diag ⁡ ( 1 − h t 2 ) W h h \boxed{J_t=\operatorname{diag}(1-h_t^2)W_{hh}} Jt​=diag(1−ht2​)Whh​​

  • 其中, diag ⁡ ( 1 − h t 2 ) \operatorname{diag}(1-h_t^2) diag(1−ht2​) 表示将向量 1 − h t 2 1-h_t^2 1−ht2​ 的各个分量放置在对角线上形成的对角矩阵,平方与减法均按元素进行
  • 这个公式可以分为两部分理解:
    • W h h W_{hh} Whh​:描述上一时刻隐藏状态经过线性变换后,对当前隐藏状态产生的影响
    • diag ⁡ ( 1 − h t 2 ) \operatorname{diag}(1-h_t^2) diag(1−ht2​):描述 tanh ⁡ \tanh tanh 激活函数在当前状态处对梯度的缩放作用
  • 当某些隐藏状态的激活值接近 1 1 1 或 − 1 -1 −1 时, tanh ⁡ \tanh tanh 的导数 1 − h t 2 1-h_t^2 1−ht2​ 会接近零,梯度经过对应的神经元时就可能大幅衰减;反之,循环权重矩阵也可能放大某些方向上的梯度
  • 实际实现不会显式构造 J t J_t Jt​:前向只保存 h t h_t ht​,反向时先把下游梯度与 tanh ⁡ \tanh tanh 的导数逐元素相乘,再右乘 W h h W_{hh} Whh​,就得到上一步的梯度
  • 若把梯度写成行向量,则反向一步为

g t − 1 = ( g t ⊙ ( 1 − h t 2 ) ) W h h g_{t-1}=\left(g_t\odot\left(1-h_t^2\right)\right)W_{hh} gt−1​=(gt​⊙(1−ht2​))Whh​

  • 对应的代码如下
g = (1 - h_seq[t] ** 2) * g_next     # [B, H]
g_prev = g @ W_hh                    # [B, H]
  • 显式构造时 J t J_t Jt​ 的形状是 [B, H, H];按上式做的是向量-雅可比积,存储与计算都只到 [B, H],这也是 BPTT 的代码里看不到 H×H 矩阵的原因
1-2-4 损失函数与梯度计算
  • 对于逐时间步预测任务,总损失可以写为

L = ∑ t = 1 T L t ( y ^ t , y t ) L=\sum_{t=1}^{T}L_t\left(\hat{y}_t,y_t\right) L=t=1∑T​Lt​(y^​t​,yt​)

  • 其中 y t y_t yt​ 是真实标签, y ^ t \hat{y}_t y^​t​ 是模型预测

  • 在 BPTT 中,某个时间步的损失不仅会对当前隐藏状态产生梯度,还可能通过循环连接影响更早时间步的隐藏状态

  • 例如,若只考虑最终时间步的损失 L T L_T LT​,则其对早期隐藏状态 h k h_k hk​ 的梯度可以通过链式法则表示为

∂ L T ∂ h k = ∂ L T ∂ h T ∏ j = k + 1 T ∂ h j ∂ h j − 1 \frac{\partial L_T}{\partial h_k}=\frac{\partial L_T}{\partial h_T}\prod_{j=k+1}^{T}\frac{\partial h_j}{\partial h_{j-1}} ∂hk​∂LT​​=∂hT​∂LT​​j=k+1∏T​∂hj−1​∂hj​​

  • 其中:
    • ∂ L T ∂ h T \frac{\partial L_T}{\partial h_T} ∂hT​∂LT​​:表示最后的损失 L T L_T LT​ 对最后一个隐藏状态 h T h_T hT​ 的梯度,也就是最后一步的误差对 h T h_T hT​ 有多敏感
    • ∏ j = k + 1 T ∂ h j ∂ h j − 1 \prod_{j=k+1}^{T} \frac{\partial h_j}{\partial h_{j-1}} ∏j=k+1T​∂hj−1​∂hj​​:表示梯度从 h T h_T hT​ 向前传回 h k h_k hk​ 时,需要经过的每一步状态转换的雅可比矩阵
  • 注意:这里的乘积是矩阵乘法,不是普通数字相乘;单个因子的行向量写法见 1-2-3 节,列向量写法则是转置雅可比按相反顺序作用
  • 共享的第一个后果是:连乘中每一步的因子都是同一个 W h h W_{hh} Whh​ 与不同的对角因子 diag ⁡ ( 1 − h j 2 ) \operatorname{diag}(1-h_j^2) diag(1−hj2​) 相乘,跨时间步并没有引入新的权重,只是把同一个矩阵反复作用了一遍
  • 对于共享参数 W h h W_{hh} Whh​,总梯度需要累积所有相关时间步的贡献

∂ L ∂ W h h = ∑ t = 1 T ∂ L ∂ W h h ∣ t \frac{\partial L}{\partial W_{hh}}=\sum_{t=1}^{T}\left.\frac{\partial L}{\partial W_{hh}}\right|_{t} ∂Whh​∂L​=t=1∑T​∂Whh​∂L​ ​t​

  • 其中每个时间步的参数梯度都需要考虑该参数通过先前隐藏状态对当前损失产生的间接影响
  • 共享的第二个后果是:同一个矩阵要同时承担所有时间尺度的需求,短程拟合需要较强的变换,长程稳定需要接近正交的变换,而更新方向是各时间步梯度之和,没法为某个时间尺度单独定制
  • 最终,优化器根据计算得到的梯度更新模型参数,例如使用梯度下降

W ← W − η ∂ L ∂ W W\leftarrow W-\eta\frac{\partial L}{\partial W} W←W−η∂W∂L​

  • 其中 η \eta η 是学习率
1-3 BPTT 的主要特点与局限
1-3-1 梯度消失与梯度爆炸
  • BPTT 需要通过多个时间步连续计算梯度,雅可比矩阵的连续乘积会让梯度幅值逐渐衰减或快速增大;这里有两个来源在同时起作用,需要分开看
    • 反向路径的长度:梯度沿展开后的计算图往回走,序列越长路径越长,每多走一步就多乘一个雅可比矩阵,衰减或放大的累积也越充分
    • 循环权重共享: W h h W_{hh} Whh​ 在所有时间步上是同一个矩阵, T T T 步连乘相当于同一个线性变换被反复作用,单步的放大或缩小会按同一倍率累积;但共享本身不是梯度消失或爆炸的成因,每一步的雅可比矩阵还包含 tanh ⁡ \tanh tanh 在当前状态处的导数,范数与方向共同决定连乘之后是衰减还是放大。若每一步使用独立的权重矩阵,梯度同样可能消失或爆炸,只是连乘变成一串不同矩阵的乘积,没有单一矩阵的谱可以概括

说人话:RNN 在使用 BPTT 训练时,梯度需要沿时间展开的计算图反向传播。由于各个时间步共享循环权重,梯度会反复经过循环权重矩阵与激活函数导数构成的雅可比矩阵。随着时间跨度增加,这些矩阵的连续相乘可能导致梯度消失或梯度爆炸,使早期时刻的信息难以有效影响模型参数的更新。

  • 由 1-2-4 节,损失对早期隐藏状态的梯度正是这样一串连乘;对两边取范数,并利用矩阵范数的次乘性,可以得到梯度幅值的上界

∥ ∂ L T ∂ h k ∥ ≤ ∥ ∂ L T ∂ h T ∥ ∏ j = k + 1 T ∥ J j ∥ \left\|\frac{\partial L_T}{\partial h_k}\right\|\le\left\|\frac{\partial L_T}{\partial h_T}\right\|\prod_{j=k+1}^{T}\left\|J_j\right\| ​∂hk​∂LT​​ ​≤ ​∂hT​∂LT​​ ​j=k+1∏T​∥Jj​∥

  • 对 Vanilla RNN,状态雅可比矩阵为 J j = diag ⁡ ( 1 − h j 2 ) W h h J_j=\operatorname{diag}(1-h_j^2)W_{hh} Jj​=diag(1−hj2​)Whh​,因此

∥ J j ∥ 2 ≤ ∥ diag ⁡ ( 1 − h j 2 ) ∥ 2 ∥ W h h ∥ 2 ≤ σ max ⁡ ( W h h ) \left\|J_j\right\|_2\le\left\|\operatorname{diag}(1-h_j^2)\right\|_2\left\|W_{hh}\right\|_2\le\sigma_{\max}(W_{hh}) ∥Jj​∥2​≤ ​diag(1−hj2​) ​2​∥Whh​∥2​≤σmax​(Whh​)

  • 其中 σ max ⁡ ( W h h ) \sigma_{\max}(W_{hh}) σmax​(Whh​) 是循环权重矩阵的最大奇异值;由于 tanh ⁡ \tanh tanh 的导数不超过 1,对角因子的谱范数也不超过 1,因此 σ max ⁡ ( W h h ) \sigma_{\max}(W_{hh}) σmax​(Whh​) 是 ∥ J j ∥ 2 \|J_j\|_2 ∥Jj​∥2​ 的上界,也就是反向传播一步时梯度范数变化倍率的上界
    • 当 σ max ⁡ ( W h h ) < 1 \sigma_{\max}(W_{hh})<1 σmax​(Whh​)<1 时,反向每经过一个时间步,梯度范数至多保留 σ max ⁡ \sigma_{\max} σmax​ 倍,经过足够多的时间步后按指数速度趋近于零,这就是梯度消失(Vanishing Gradient)
    • 当 σ max ⁡ ( W h h ) > 1 \sigma_{\max}(W_{hh})>1 σmax​(Whh​)>1 时,循环权重具备放大某些方向梯度的可能,但 ∥ J t ∥ 2 > 1 \|J_t\|_2>1 ∥Jt​∥2​>1 并不必然成立:若 tanh ⁡ \tanh tanh 工作在饱和区,对角因子会把放大抵消;若各步雅可比矩阵的方向不断变化,连乘的范数也可能远小于逐项上界的乘积。只有当放大在多个时间步上持续累积时,梯度才会按指数速度增长,这就是梯度爆炸(Exploding Gradient)

说人话:循环权重共享意味着,梯度在每个时间步反向传播时,都会反复经过同一个循环权重矩阵 W h h W_{hh} Whh​。如果这个变换在某些方向上倾向于缩小梯度,经过许多时间步后,梯度就可能越来越小,最终使早期时刻的信息难以影响参数更新;反过来,如果梯度在某些方向上持续被放大,就可能导致梯度爆炸。

  • 但要注意,不能只看 W h h W_{hh} Whh​ 的最大奇异值就断定梯度一定会消失或爆炸。每一步真正影响梯度的,是循环权重矩阵与激活函数导数共同构成的雅可比矩阵。激活函数是否饱和、各步变换的方向如何变化,都会影响最终的梯度。

  • 因此,序列越长,梯度消失或爆炸的风险通常越值得关注,但并不意味着它们必然发生。

  • 以线性递推 h t = w h t − 1 h_t=wh_{t-1} ht​=wht−1​ 为例,梯度中包含因子 w T − k w^{T-k} wT−k;当 ∣ w ∣ < 1 |w|<1 ∣w∣<1 时该因子随步数指数衰减,当 ∣ w ∣ > 1 |w|>1 ∣w∣>1 时随步数指数增长,只有 ∣ w ∣ |w| ∣w∣ 接近 1 时信息才能跨越较长的跨度

  • 数值上可以直接看到这个分界:取正交矩阵 Q Q Q,令 W h h = s Q W_{hh}=sQ Whh​=sQ,则 σ max ⁡ = ∣ s ∣ \sigma_{\max}=|s| σmax​=∣s∣,逐步累乘反向梯度

import torch
H = 64
torch.manual_seed(0)
Q, _ = torch.linalg.qr(torch.randn(H, H))       # 正交阵,谱范数 1
for s in (0.9, 1.0, 1.1):
	W = s * Q
	h, g = torch.zeros(H), torch.ones(H)
	for t in range(50):
		h = torch.tanh(W @ h + 0.1 * torch.randn(H))
		g = (g * (1 - h ** 2)) @ W               # 反向一步
	print(s, round(g.norm().item(), 4))
  • 梯度初值取全 1,范数为 64 = 8 \sqrt{64}=8 64 ​=8;50 步后三个范数分别为 0.0068、0.3054、3.4948:0.9 衰减到初值的千分之一以下,1.0 只剩不到 4%,1.1 保留了四成以上,三者相差超过两个数量级
  • 这里用正交阵而不是一般的随机矩阵,是因为一般随机矩阵的谱范数虽可归一为 1,谱半径却通常小于 1,幂会先短暂增长再衰减,分界不如正交缩放清楚
  • 对 LSTM,记忆单元的更新为 c t = f t ⊙ c t − 1 + i t ⊙ g t c_t=f_t\odot c_{t-1}+i_t\odot g_t ct​=ft​⊙ct−1​+it​⊙gt​,沿记忆单元的直接路径有

∂ c t ∂ c t − 1 = diag ⁡ ( f t ) \frac{\partial c_t}{\partial c_{t-1}}=\operatorname{diag}(f_t) ∂ct−1​∂ct​​=diag(ft​)

  • 因此沿这条路径的跨时间步连乘变为 ∏ j diag ⁡ ( f j ) \prod_j\operatorname{diag}(f_j) ∏j​diag(fj​),梯度是否衰减主要取决于遗忘门的取值;当 f j f_j fj​ 接近 1 时梯度可以保留,当 f j f_j fj​ 接近 0 时梯度迅速衰减
  • 加法结构还提供了一条不经过循环权重连乘的路径,使梯度不必反复穿过 W h h W_{hh} Whh​;但门控本身仍可能饱和,因此 LSTM 只是缓解而不是完全消除梯度消失
  • 当梯度爆炸发生时,参数更新步长会被放大,损失可能出现周期性尖峰甚至数值溢出;梯度裁剪(Gradient Clipping)通过限制梯度范数来抑制这一现象,而学习率衰减则减小后期的更新步长
1-3-2 计算与存储开销
  • 标准 BPTT 通常需要保留前向传播过程中多个时间步的中间激活值,以便反向传播时计算梯度;随着序列长度增长,计算成本和内存开销通常也会增加
  • 在长序列任务中,可以使用截断时间反向传播(Truncated Backpropagation Through Time,TBPTT),将序列划分为较短的片段,并只在片段内进行反向传播;片段之间的循环状态可以保留,也可以重置,取决于具体实现。这能降低计算与存储开销,但也限制了梯度直接跨越片段边界传播的范围

2 如何训练 RNN

2-1 实验任务与数据
2-1-1 介绍
  • 第 1 章讨论的都是公式:状态雅可比矩阵、沿时间的连乘,以及连乘带来的梯度消失与梯度爆炸
    • 这一章把这些公式落到代码上,用一个足够小的任务把训练流程完整走一遍,包括数据如何切分、前向如何组织、反向传播发生在哪一步、训练超参如何设置
  • 任务本身刻意取得很简单,目的是让训练中的每个环节都能单独检查,而不是追求预测精度
    • 三种循环网络(Vanilla RNN、GRU、LSTM)在同一任务、同一配置下训练,为第 3 章讨论 LSTM 的记忆行为提供对照
2-1-2 实验任务
  • 本文采用单步时间序列预测作为训练实验:给定连续 50 个时间步的观测,预测下一个时间步的取值
  • 数据由两个不同频率的正弦分量叠加而成

x t = sin ⁡ ( 0.05 t ) + 0.5 sin ⁡ ( 0.12 t ) x_t=\sin(0.05t)+0.5\sin(0.12t) xt​=sin(0.05t)+0.5sin(0.12t)

  • 该信号包含两个时间尺度,周期分别约为 126 步与 52 步;取值有界且变化平滑,不包含观测噪声,因此适合用来检验训练过程本身,而不会引入数据分布带来的额外干扰
2-1-3 数据采集
  • 记窗口长度为 T w = 50 T_w=50 Tw​=50,第 i i i 个样本的输入与标签分别为

X ( i ) = [ x i , x i + 1 , … , x i + T w − 1 ] , y ( i ) = x i + T w X^{(i)}=\left[x_i,x_{i+1},\dots,x_{i+T_w-1}\right],\qquad y^{(i)}=x_{i+T_w} X(i)=[xi​,xi+1​,…,xi+Tw​−1​],y(i)=xi+Tw​​

  • 本实验生成 2000 步信号,按步长 1 滑窗得到 1950 个样本
    • 按时间顺序划分:前 80% 共 1560 个窗口用于训练,后 20% 共 390 个用于测试,测试段不参与参数更新
  • 完整信号、训练段与测试段的划分,以及单个样本的输入与预测目标如下图所示

请添加图片描述

  • 左图中虚线为两个正弦分量,实线为叠加后的信号,背景区分训练段与测试段;右图给出一个样本的 50 步输入窗口,红色星号为待预测的下一个取值
  • 本实验采用固定长度窗口训练,每个窗口独立初始化循环状态,反向传播仅覆盖窗口内的 50 个时间步,不跨窗口传播梯度;因此模型无法通过循环状态把前一个窗口的信息直接带入下一个窗口
    • 这种按窗口截断梯度的做法属于 TBPTT 的一种实现,截断长度即窗口长度;它与在连续数据流上保留状态、只截断反向传播范围的经典 TBPTT 不同,区别在于状态是否跨片段传递
  • 数据生成与滑窗的代码实现如下
def gen_signal(n_steps: int) -> np.ndarray:
	t = np.arange(n_steps, dtype=np.float64)
	return np.sin(0.05 * t) + 0.5 * np.sin(0.12 * t)


def make_windows(x: np.ndarray, window: int):
	"""x: [T] -> inputs [T-window, window], targets [T-window]"""
	windows = np.lib.stride_tricks.sliding_window_view(x, window)
	inputs = windows[:-1]      # 以 t 结尾的窗口
	targets = x[window:]       # 预测 t+1
	return inputs, targets
  • sliding_window_view 以步长 1 在信号上滑动,取出所有长度为 window 的窗口;inputs 与 targets 逐行对齐,前者作为模型输入,后者是窗口之后的下一个取值
2-2 网络结构与前向传播
  • 三种循环网络共用同一套输入与读出结构,仅循环层不同
    • 每个时间步的循环层接收一维输入,隐层宽度取 64;取窗口最后一个时间步的隐状态,经全连接层映射为标量输出
  • 该结构属于多对一映射:损失只在最后一个时间步产生,窗口内的其余时间步不直接提供监督信号
    • 这与 1-2-4 节的推导相对应:总损失退化为 L = L T L=L_T L=LT​,梯度由 h T h_T hT​ 出发,沿时间反方向逐级传回 h 1 h_1 h1​
  • 模型定义的代码如下
class SeqNet(nn.Module):
	def __init__(self, kind: str, hidden_size: int = 64):
		super().__init__()
		if kind == "rnn":
			self.rnn = nn.RNN(1, hidden_size, num_layers=1,
							  batch_first=True, nonlinearity="tanh")
		elif kind == "gru":
			self.rnn = nn.GRU(1, hidden_size, num_layers=1, batch_first=True)
		elif kind == "lstm":
			self.rnn = nn.LSTM(1, hidden_size, num_layers=1, batch_first=True)
		else:
			raise ValueError(kind)
		self.fc = nn.Linear(hidden_size, 1)

	def forward(self, x):
		# x: [B, 50, 1] -> 取最后时间步的隐状态
		out, _ = self.rnn(x)
		return self.fc(out[:, -1, :])
  • batch_first=True 使输入张量的形状为 [batch, time, feature];out[:, -1, :] 取出每个样本最后一个时间步的隐状态
  • 循环层的初始状态未显式传入,PyTorch 默认将其初始化为零向量,与 2-1 节的截断设定一致
  • 由于三种模型的隐层宽度、读出层与初始化方式相同,后续实验中它们的差异只来自循环单元本身
2-3 训练流程
  • 第 1 章从链式法则出发给出了 BPTT 的梯度形式;在深度学习框架中,这一过程由自动微分完成,训练循环只需要调用 backward()
  • 一个训练批次的核心代码如下
optimizer.zero_grad()
pred = model(xb)
loss = criterion(pred, yb)
loss.backward()
optimizer.step()
  • 需要说明的是,这里不需要显式指定 BPTT。只要 model 的前向传播通过 PyTorch 支持自动微分的张量运算构建计算图,loss.backward() 就会沿计算图反向传播梯度。对于 RNN、GRU、LSTM 等循环网络,计算图包含各个时间步之间的状态依赖关系,因此自动微分会依据链式法则,将梯度沿时间展开的计算图逐步传回早期时间步,这一过程就是 BPTT。
    • 换句话说,BPTT 是循环网络训练时所遵循的梯度计算原理,而 loss.backward() 是 PyTorch 执行这一计算的接口。我们不需要手动推导每一步的梯度,也不需要自己编写时间步之间的反向传播过程。
  • 完整训练循环如下
for epoch in range(1, args.epochs + 1):
	model.train()
	total, n = 0.0, 0
	for xb, yb in train_loader:
		xb, yb = xb.to(device), yb.to(device)
		optimizer.zero_grad()
		loss = criterion(model(xb), yb)
		loss.backward()
		if args.clip_grad > 0:
			nn.utils.clip_grad_norm_(model.parameters(), args.clip_grad)
		optimizer.step()
		total += loss.item() * len(yb)
		n += len(yb)
	train_loss = total / n
	test_mse, _, _ = evaluate(model, test_loader, criterion, device)
	hist["epoch"].append(epoch)
	hist["train_loss"].append(train_loss)
	hist["test_mse"].append(test_mse)
	if scheduler is not None:
		scheduler.step()
  • 每个训练轮先遍历训练集更新参数,再在测试集上计算均方误差,用于观察泛化表现
    • 学习率采用分段衰减:Adam(Adaptive Moment Estimation,自适应矩估计)的初始学习率为 10 − 2 10^{-2} 10−2,在第 100 与第 150 轮各乘以 0.2
  • 采取该策略的原因是,恒定学习率下损失曲线会出现周期性的尖峰,部分随机种子还会在收敛后重新发散;这与 1-3-1 节描述的梯度爆炸现象一致,较大的更新步长会把参数推出当前吸引域
    • 衰减之后后期步长减小,参数得以在极小值附近稳定下来;相比之下,固定阈值的梯度裁剪在本任务中会使三种模型的收敛都变慢,因此最终未启用
2-4 训练配置
  • 三个模型使用完全相同的训练配置,仅循环层类型不同,以保证对比只反映结构差异
  • 主要配置如下
配置项取值
输入窗口50 个时间步
隐层宽度64
批大小64
训练轮数200
损失函数均方误差(Mean Squared Error,MSE)
优化器Adam
初始学习率 10 − 2 10^{-2} 10−2
学习率衰减第 100、150 轮乘以 0.2
随机种子2 个,结果取平均
  • 每个训练轮在测试集上计算一次 MSE,作为泛化误差的估计;训练与测试曲线同时记录
  • 为降低随机初始化带来的波动,所有曲线与指标均对种子取平均
2-5 训练结果
  • 实验内容:在相同的训练配置下分别训练 Vanilla RNN、GRU 与 LSTM,观察训练损失、测试误差以及测试集上的预测曲线,检验 BPTT 训练流程能否使模型学到信号的时序规律

  • 实验实现:训练集取时间序列的前段,测试集取后段;每个模型以两个随机种子独立训练,曲线取平均;训练过程中每个训练轮记录训练集与测试集的 MSE,训练结束后在测试集上输出预测值与绝对误差

  • 图分别展示如下
    请添加图片描述

  • 左图为训练集 MSE,右图为测试集 MSE,横轴为训练轮数,纵轴为对数坐标

  • 三条曲线在前段快速下降,随后进入带有波动的平台期;在第一次学习率衰减处可以看到明显的二次下降

  • 收敛之后,三个模型的训练与测试误差处于同一数量级,后期曲线彼此接近;Vanilla RNN 在平台期略高,GRU 与 LSTM 的差异很小

请添加图片描述

  • 左图为测试集上的真实信号与三个模型的预测曲线,右图为对应的逐点绝对误差,纵轴取对数坐标
  • 三条预测曲线与真实信号基本重合,只有在绝对误差图上才能分辨出模型之间的差别;误差远小于信号自身的幅度
    • 以持续法作为参照基线,即直接将窗口最后一个时刻的取值作为预测,三个模型的测试误差均比该基线低若干个数量级,说明 BPTT 训练流程能够有效拟合该信号的时序规律
  • 需要指出的是,该任务的依赖跨度仅为 50 个时间步,信号平滑且确定,Vanilla RNN 的梯度衰减尚不足以妨碍学习;因此在该任务上,门控结构没有表现出决定性优势,三者之间的差异与随机初始化带来的波动处于同一量级
    • 这一结果说明,BPTT 的有效性首先取决于训练配置与任务本身的依赖结构;当依赖跨度超出训练窗口时,循环网络在梯度传播上的差异才会被放大,这为第 3 章在部分可观测任务上的对比提供了动机

3 LSTM 与记忆机制详解

  • 第 2 章的正弦预测任务验证了 BPTT 的训练流程,但依赖跨度只有 50 步,信号平滑、确定、无噪声,模型只看窗口最后几帧就能给出预测,还不足以说明门控结构如何支持长距离记忆
  • 本章换一个部分可观测的估计任务:只给摆杆角度,要求估出角速度;把 Vanilla RNN、GRU、LSTM 与两种帧堆叠多层感知机(Multilayer Perceptron,MLP)放在同一配置下比较
  • 这里特别对 LSTM 做详细分析:3-1 交代观测对象与数据,3-2 用公式和代码走一遍 LSTM 的递推与门偏置初始化,3-3 到 3-5 用三组实验回答记忆超过窗口之后谁还可用、记忆存放在哪个状态、以及需要多长的记忆
  • 落点不是证明 LSTM 更好,而是看清楚它的记忆能力从哪里来、在什么条件下才起作用
3-1 观测对象
  • 任务取自 CartPole:状态为 ( x , x ˙ , θ , θ ˙ ) (x,\dot{x},\theta,\dot{\theta}) (x,x˙,θ,θ˙),环境方程与 gym 的实现相同,时间步长 0.02 s,外力限幅 10 N;估计器只拿到摆杆角度 θ t \theta_t θt​,要估的是角速度 θ ˙ t \dot{\theta}_t θ˙t​
  • 单帧角度给不出运动方向,相邻两帧的差分又会被观测噪声放大,只有把历史观测与当前观测合起来,才能得到稳定的估计

请添加图片描述

  • 每回合随机化摆杆半长(0.5 到 0.8 m)、质量(0.08 到 0.15 kg)与四个 PD 增益,并叠加估计器不可见的随机控制脉冲,避免模型记住固定动力学方程
  • 观测与目标为

o t = [ θ t 0.35 m t ,   m t ] , y t = θ ˙ t v s c a l e o_t=\left[\frac{\theta_t}{0.35}m_t,\ m_t\right],\qquad y_t=\frac{\dot{\theta}_t}{v_{\mathrm{scale}}} ot​=[0.35θt​​mt​, mt​],yt​=vscale​θ˙t​​

  • 其中 m t ∈ { 0 , 1 } m_t\in\{0,1\} mt​∈{0,1} 为观测掩码, v s c a l e v_{\mathrm{scale}} vscale​ 约为 0.295 rad/s
  • 同一环境生成 60 万步数据,按回合划分训练、验证与测试集(7:1.5:1.5),测试回合不与训练回合重叠;训练样本为连续 192 步,其中前 48 步只用于状态预热,不计入损失
  • 训练时加入 0.003 rad 角度噪声(约为量程的 1%),测试时另设无噪声条件,用来区分噪声拟合与真实估计误差
  • 设置两种不训练的基线
    • 后向差分: θ ˙ ^ t = ( θ t − θ t − 1 ) / Δ t \hat{\dot{\theta}}_t=(\theta_t-\theta_{t-1})/\Delta t θ˙^t​=(θt​−θt−1​)/Δt,无噪声时接近真值,但会把角度噪声放大约 50 倍
    • 零预测:始终输出 0,RMSE 为目标标准差,约为 0.2887 rad/s,作为未学到有效信息的参照
  • 五类估计器的参数量处于相近量级
模型结构参数量
FS-16帧堆叠 MLP,窗口 1620,865
FS-64帧堆叠 MLP,窗口 6433,153
Vanilla RNN单层循环网络,隐层 644,417
GRU单层门控循环网络,隐层 6413,121
LSTM单层门控循环网络,隐层 6417,473
  • FS-16 与 FS-64 把最近 16 或 64 帧拼接后送入两层、每层 128 维的 ReLU 网络,窗口之外的观测不可见;FS-64 能无损访问窗口内的 63 帧历史,是判断循环结构是否必要的强基线

  • RNN、GRU 与 LSTM 都是单层循环网络,隐层宽度 64,通过线性层把每步隐状态映射为角速度估计

  • 所有模型采用相同训练配置:2 维输入、Adam、学习率从 10 − 3 10^{-3} 10−3 余弦退火至初值的 1%、批大小 64、训练 150 轮、全局梯度范数裁剪至 1.0,结果取 2 个随机种子的平均值

  • 首先测试当前角速度估计:五种模型的误差均在 0.042 到 0.048 之间,彼此差距小于它们与后向差分基线的差距,LSTM 未表现出明显优势
    请添加图片描述

  • 左图为各模型在无噪声与训练噪声下的 RMSE,红色虚线为后向差分基线,黑色点线为恒输出 0 的参照;右图为一段测试回合中五个模型与真值的对比,五条曲线几乎重合

  • 原因是该任务只需利用最近几步的角度,所需记忆没有超出任一模型的有效窗口,循环结构的额外记忆能力尚无用武之地;后续实验将逐步改变记忆需求,观察不同结构的差异

3-2 LSTM 代码详解
  • 这一节配合公式和代码,说明估计器内部的状态如何一步步更新:先给出四组门控的计算,再对照逐时间步的代码,最后用一组消融说明初始化对长记忆的影响
3-2-1 LSTM 的递推公式与 PyTorch 实现
  • LSTM 每个时间步维护两个状态:记忆单元 c t c_t ct​ 与隐状态 h t h_t ht​;输入门、遗忘门、候选状态与输出门依次计算,最后更新记忆并生成隐状态
3-2-1-1 四组门控的计算

i t = σ ( W i i x t + b i i + W h i h t − 1 + b h i ) f t = σ ( W i f x t + b i f + W h f h t − 1 + b h f ) g t = tanh ⁡ ( W i g x t + b i g + W h g h t − 1 + b h g ) o t = σ ( W i o x t + b i o + W h o h t − 1 + b h o ) \begin{aligned} i_t &= \sigma\left(W_{ii}x_t+b_{ii}+W_{hi}h_{t-1}+b_{hi}\right)\\ f_t &= \sigma\left(W_{if}x_t+b_{if}+W_{hf}h_{t-1}+b_{hf}\right)\\ g_t &= \tanh\left(W_{ig}x_t+b_{ig}+W_{hg}h_{t-1}+b_{hg}\right)\\ o_t &= \sigma\left(W_{io}x_t+b_{io}+W_{ho}h_{t-1}+b_{ho}\right) \end{aligned} it​ft​gt​ot​​=σ(Wii​xt​+bii​+Whi​ht−1​+bhi​)=σ(Wif​xt​+bif​+Whf​ht−1​+bhf​)=tanh(Wig​xt​+big​+Whg​ht−1​+bhg​)=σ(Wio​xt​+bio​+Who​ht−1​+bho​)​

  • 其中 i t i_t it​、 f t f_t ft​、 o t o_t ot​ 分别是输入门、遗忘门与输出门, g t g_t gt​ 是候选记忆;每组计算都同时使用当前输入 x t x_t xt​ 与上一时刻的隐状态 h t − 1 h_{t-1} ht−1​
  • 在 PyTorch 中,四组权重沿第 0 维拼接,排列顺序为输入门、遗忘门、候选状态、输出门;因此 weight_ih_l0 的形状为 [4 * hidden, input_size],weight_hh_l0 的形状为 [4 * hidden, hidden],两段偏置的形状均为 [4 * hidden]
  • 本实验输入维度为 2、隐层宽度为 64,因此 LSTM 的循环层参数量为

4 × 64 × ( 2 + 64 ) + 2 × 4 × 64 = 17,408 4\times64\times(2+64)+2\times4\times64=17{,}408 4×64×(2+64)+2×4×64=17,408

  • 加上读出层的 65 个参数,总计 17,473 个,与 3-1 节的表一致
3-2-1-2 对应的逐时间步代码
lstm = model.rnn
w_ih, w_hh = lstm.weight_ih_l0, lstm.weight_hh_l0
bias = lstm.bias_ih_l0 + lstm.bias_hh_l0
hidden = lstm.hidden_size

h = obs.new_zeros(obs.size(0), hidden)
c = obs.new_zeros(obs.size(0), hidden)
preds = []

for t in range(obs.size(1)):
    # 对应四组门控公式:输入、遗忘、候选、输出
    z = obs[:, t] @ w_ih.T + bias + h @ w_hh.T
    i, f, g, o = z.chunk(4, dim=1)

    i, f, o = torch.sigmoid(i), torch.sigmoid(f), torch.sigmoid(o)
    g = torch.tanh(g)

    # 对应记忆单元更新公式
    c = f * c + i * g

    # 对应隐状态更新公式
    h = o * torch.tanh(c)

    # 对应逐时间步的线性读出
    preds.append(model.fc(h))

pred = torch.stack(preds, dim=1).squeeze(-1)
  • z = ... 把四组门的线性变换合并计算
  • z.chunk(4, dim=1) 按 PyTorch 的门排列顺序拆成 i t i_t it​、 f t f_t ft​、 g t g_t gt​、 o t o_t ot​
  • c = f * c + i * g 中,遗忘门控制旧记忆的保留,输入门控制候选记忆的写入
  • h = o * torch.tanh(c) 中,输出门控制当前记忆对外暴露的程度
  • model.fc(h) 把当前隐状态映射为角速度估计
  • 在训练好的模型上,这段递推与 nn.LSTM 的输出在单精度 CPU 下最大差异约为 2 × 10 − 6 2\times10^{-6} 2×10−6;若在 GPU 上开启 TF32,同样的比较会出现 10 − 3 10^{-3} 10−3 量级的差异,那是矩阵乘法的精度设置不同,不是递推写错了
3-3 预测 k k k 步之前的角速度
3-3-1 实验
  • 当估计任务所需的历史信息较少时,五种估计器的表现较为接近,难以体现不同结构在长时记忆上的差异;因此进一步构造延迟估计任务
    • 保持输入观测和网络结构不变,仅将预测目标改为 k k k 步之前的角速度 θ ˙ t − k \dot{\theta}_{t-k} θ˙t−k​,考察模型保留历史信息的能力

说人话:根据当前及最近几帧的观测估计角速度,任务本身比较简单,模型不需要记住太久以前的信息。因此,我们把任务改成预测 k k k 步之前的角速度,让模型必须从历史信息中找到答案,从而比较不同网络结构的记忆能力。

  • 实验设置 k = 5 k=5 k=5、 25 25 25、 100 100 100 三种延迟,分别用于测试窗口内的短时记忆需求、超出 FS-16 的历史窗口,以及超出 FS-64 的历史窗口

    • 训练时仅对 t ≥ k t\ge k t≥k 的时间步计算损失,确保目标有效
    • 考虑到延迟目标需要更长的预热过程,我们将每段训练序列缩短至 128 步,并将预热长度增加至 96 步
  • 最终的误差随延迟的变化如下

请添加图片描述

  • 随着 k k k 的增加,帧堆叠模型的误差逐渐增大,两条 MLP 曲线在各自窗口处出现明显的台阶,并在历史超出输入窗口后明显失效;三种循环网络则平缓上升
  • 其中 FS-64 在 k = 25 k=25 k=25 时表现最好;到 k = 100 k=100 k=100 时,LSTM 和 GRU 仍能保持较低误差,说明循环网络能够将历史信息保存在内部状态中,并随时间持续传递
  • LSTM 的表现更好,但 GRU 以更少的参数也取得了不错的结果,两者并非谁绝对优于谁

结论:循环结构的价值不在算得更准,而在记忆长度不受窗口限制;只要所需延迟落在窗口内,帧堆叠就能以更简单的方式达到同等甚至更好的精度

3-3-2 门控系数的消融测试
  • 和上一篇文章提到的一样,PyTorch 默认将 LSTM 与 GRU 的门偏置初始化在零附近;对 LSTM,遗忘门预激活接近零时,初始遗忘门输出约为 0.5,若该取值持续不变,记忆通路每步只保留一半,经过 100 步后直接保留的比例降到 0.5 100 0.5^{100} 0.5100,模型在训练初期很难保留长期信息

    • 因此本实验通过 _init_memory_gates 把 LSTM 的遗忘门偏置与 GRU 的更新门偏置同时设为 1;
  • 在 k = 100 k=100 k=100 的延迟任务上,结果如下请添加图片描述

  • LSTM 的误差从 0.2104 降至 0.0338,改善尤为明显;GRU 则从 0.0668 降至 0.0504;Vanilla RNN 没有门控偏置可调,两条柱子等高

  • 这组消融说明,门控初始化会显著影响模型能否学好长记忆,不能把初始化造成的差异直接归因于网络结构;

    • GRU 更新门接近 0.5 时旧状态仍有一半进入下一步,而
    • LSTM 遗忘门接近 0.5 时,细胞状态的直接保留通路会随时间迅速衰减,这与第 1 章关于梯度沿时间传播的分析一致
3-4 c t c_t ct​ 记忆通路与 h t h_t ht​ 隐状态通路的消融实验
  • 3-3 节的实验说明了LSTM 能把 100 步前的信息带到当前,这一节我们来详细分析一下LSTM是如何完成记忆的。
  • 回顾一下,LSTM 每个时间步维护两个状态:记忆单元 c t c_t ct​ 与隐状态 h t h_t ht​
    • c t c_t ct​ 记忆通路:负责累积信息。旧记忆经过遗忘门保留,新信息经过输入门写入。两者是分别控制的,不要求权重之和为 1,因此可以在保留旧信息的同时写入新信息。
    • h t h_t ht​ 隐状态通路:负责读出与交互。它把当前记忆经过输出门处理后暴露给后续计算,包括下一步的门控计算和最终的预测层。
  • 那我们要做的事情很简单:
    • 在长度为 400 步的测试序列中,于第 200 步完成正常状态更新后,将指定的内部状态置零(分别干预单个状态或同时干预两个状态)
    • 随后继续运行模型,并统计干预后 100 步内的预测误差。
    • 将恢复步数定义为:误差经平滑处理后,重新降至无干预基线误差的 1.2 倍以内,并连续保持至少 20 步所需的步数。

说人话:我们分别清空 LSTM 中的记忆状态 c t c_t ct​ 和隐藏状态 h t h_t ht​,观察模型受到干扰后,预测误差需要多久才能恢复到正常水平,从而比较这两个状态各自承担的作用。

  • 恢复曲线如下:请添加图片描述

  • 可以很明显的看出:清空 c t c_t ct​ 的破坏最大,误差接近预测零;清空 h t h_t ht​ 只造成中等破坏,且十几步内恢复到基线附近;同时清空 c t c_t ct​和 h t h_t ht​ 造成了毁灭性打击hhh

  • 这与我们对 LSTM 结构的理解一致: c t c_t ct​ 负责保存和传递长期记忆, h t h_t ht​ 则是当前时刻对外暴露的隐藏状态。

    • 清空 c t c_t ct​ 相当于直接抹去累积的历史信息
    • 清空 h t h_t ht​ 后,模型仍保留着 c t c_t ct​,可以在后续时间步中重新生成隐藏状态。
    • 因此,两者受到干预后的恢复表现存在明显差异。

说人话: c t c_t ct​ 负责保存长期记忆, h t h_t ht​ 负责表示当前时刻的状态,并把信息提供给下一步计算和预测。

4 扩展

  • 第 3 章的结论建立在单向、单层的 LSTM 上;实际使用中更常见的是按任务改结构,或者把循环单元放进更复杂的系统里
  • 本章只做三点补充:把第 3 章的结果放回选型语境,看 GRU 在世界模型里的位置,再看 Transformer 如何摊开历史窗口
4-1 LSTM 并非绝对优势
  • 把第 3 章的读数并排看,架构的优劣随任务条件变化,而不是随结构本身固定
    • 短依赖: k = 5 k=5 k=5 时 FS-16 的 0.0182 略低于 LSTM 的 0.0184,窗口能覆盖历史时,帧堆叠用更简单的方式拿到了同等精度
    • 长依赖: k = 100 k=100 k=100 时两个帧堆叠模型失效,LSTM 的 0.0331 与 GRU 的 0.0487 仍然可用;循环结构是必要的,但并非只有 LSTM 一种选择
    • 训练条件:同一结构的两种初始化相差 6 倍(3-2-1 节),初始化带来的差别往往大于结构之间的差别
  • 因此更实用的顺序是先量任务需要多长的记忆:几十步以内用帧堆叠,接近百步才需要循环结构,最后才在 GRU 与 LSTM 之间比较
  • LSTM 的门控结构解决的是信息能否跨时间保留,而不是在所有序列任务上更准;世界模型并不统一使用 LSTM,原因也在这里
4-2 GRU:世界模型中的循环结构
  • 世界模型里的循环部分与 3-1 节同源:从历史观测与动作推断潜在状态,只是观测换成了图像或低维特征;差别在于 GRU 没有独立的记忆单元 c t c_t ct​,更新门直接在旧隐状态与候选状态之间做凸组合

h t = ( 1 − z t ) ⊙ h ~ t + z t ⊙ h t − 1 h_t=(1-z_t)\odot\tilde{h}_t+z_t\odot h_{t-1} ht​=(1−zt​)⊙h~t​+zt​⊙ht−1​

  • 当 z t z_t zt​ 接近 1 时,梯度沿 h t − 1 h_{t-1} ht−1​ 到 h t h_t ht​ 的路径只乘 z t z_t zt​,长记忆不必依赖额外的记忆单元,代价是少了输出门对读出的独立控制
  • 前言提到的 World Models 用 LSTM 作循环部分;PlaNet 与 Dreamer 的循环状态空间模型(Recurrent State Space Model,RSSM)改用 GRU,并把潜在状态拆成确定性与随机两部分

h t = f ϕ ( h t − 1 , z t − 1 , a t − 1 ) , z t ∼ p θ ( z t ∣ h t ) h_t=f_\phi\left(h_{t-1},z_{t-1},a_{t-1}\right),\qquad z_t\sim p_\theta\left(z_t\mid h_t\right) ht​=fϕ​(ht−1​,zt−1​,at−1​),zt​∼pθ​(zt​∣ht​)

  • 确定性部分由 GRU 汇总历史,随机部分表达同一历史下可能出现的多种未来;可见世界模型对循环单元的要求不止记忆,还要学习状态转移、处理随机性、并在多步想象中保持稳定
  • 第 3 章的结果只能说明 LSTM 在长延迟估计上的状态容量更合适,外推成世界模型应当选 LSTM 依据并不充分;反过来,GRU 只落后一小档,也不构成普遍优势
4-3 Transformer:从循环记忆到历史序列建模
  • 同类问题还有另一种解法:不把历史压缩成状态,而是把固定长度的历史序列直接送入注意力网络;这里以 Learning Humanoid Locomotion with Transformers 为例
    • 该工作的策略是一个因果 Transformer:输入为历史观测与历史动作,输出是下一步动作;因果掩码保证第 t t t 步只看到 t t t 及之前的位置,推理时仍按环境步逐个进行
    • 与 LSTM 的差别在于历史如何被汇总:LSTM 把整段历史压进固定维度的 h t h_t ht​,信息一旦被覆盖就找不回来;自注意力让每个时间步直接对窗口内所有位置加权,权重由当前查询决定,不同步之间的关系被显式建模
  • 从第 3 章的角度看,它更接近 FS-64 这类帧堆叠,区别是注意力会学习每一步的权重,并且可以扩展到更长的窗口
  • 论文的观察是:历史里包含环境与机器人自身状态的变化信息,策略可以在推理时据此做上下文适应,不必更新参数;与 4-2 节的 RSSM 不同,它是基于模型无关强化学习训练的控制策略,不学习环境模型,也不做多步想象
    • 代价是注意力的计算与显存随窗口长度平方增长,而 LSTM 的每步开销与窗口长度无关;序列很长时前者需要分块或稀疏化,后者仍是常数状态

总结

  • 本文围绕循环网络的训练过程展开:从状态递推与计算图出发推导 BPTT 的梯度形式,分析状态雅可比矩阵连乘带来的梯度消失与梯度爆炸,并结合 PyTorch 的 nn.LSTM 展开逐门计算与训练、推理代码,最后在正弦叠加信号预测与部分可观测的角速度估计两个任务上考察循环网络的训练过程与 LSTM 的记忆行为
  • 主要结论可以归纳为以下几点
    • BPTT 是沿时间展开的计算图上的反向传播,其梯度由各时间步的局部雅可比矩阵连乘得到
    • 循环权重在所有时间步共享,因此参数梯度是各时间步贡献的累加
    • 连乘因子的范数决定了梯度的衰减或放大,这是普通 RNN 难以学习长期依赖的根本原因
    • 截断 BPTT 以窗口长度限制连乘的深度,在降低开销的同时牺牲了跨窗口的梯度传播
    • 在短依赖的正弦预测任务上,三种循环单元均能收敛到很低的误差,门控结构的优势并不明显,训练配置对收敛稳定性的影响更直接
    • 在当前角速度估计任务中,五种估计器(含帧堆叠 MLP)的表现较为接近,LSTM 并未体现出明显的精度优势;当预测目标延迟到帧堆叠窗口之外时,循环结构在长时记忆上的优势才显现出来
    • 所需记忆超过帧堆叠窗口后,循环结构才显出必要:延迟 100 步时帧堆叠失效,LSTM 与 GRU 仍然可用,且 GRU 紧随其后
    • 状态干预表明记忆单元承担存储、隐状态承担当前输出;记忆预算曲线没有拐点,有效记忆是累积性的
    • 门控初始化会显著改变 LSTM 的长延迟表现(0.2104 对 0.0338),做架构对比时需要先固定这一变量,这与 1-3-1 节关于遗忘门的连乘分析一致
    • 架构选择取决于任务的时间依赖与观测条件:短依赖下帧堆叠已经足够,长依赖下 GRU 与 LSTM 都可用;世界模型中的 RSSM 使用 GRU,说明 LSTM 并非默认选择
  • 如有错误,欢迎指出!
  • 感谢观看!

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/m0_73800387/article/details/169363948

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--