状态在流动,梯度可以停在边界
课堂接续:T10 · T11 · T12 · T13 · T14 · T15 · T16。连续读本:第2章;本主题提供独立定义、例子、代码与来源。
同一九单位给书句从我、昨天开始逐项进入共享状态。状态是当前前向的量,参数是在所有时间步共用的规则;门控与梯度又各自回答运行和学习的问题。下面先解释状态,再用两个四事件块说明截断,保留TBPTT、二维LSTM与GRU公式作为独立扩展。
阅读本主题 · 返回实验 · 与Agent共读全文
读本: CLS 01 · 序列目标与共享状态 ,查找“状态在流动,梯度可以停在边界”;实验中查找“C · 共享参数的梯度到底加了几次?”。
输入、状态和输出接在哪里
源句为“我/昨天/给/了/他/一/本/书/。”,九个单位来自本课的教学切分。
把源句九单位送过encoder,取末隐藏状态形成固定源摘要 $c_{src}=h_n$,再通过 $s_0=g(c_{src})$ 建立decoder的初始状态。第一次预测先用BOS与 $s_0$ 更新出 $s_1$,再由 $s_1$ 形成I的词表分布;选中I后,本步新输入I与上一状态 $s_1$ 一起产生 $s_2$,再预测gave。写到 I gave 时,新送入的只有gave,它与上一状态 $s_2$ 更新出 $s_3$,随后才形成him的分布;him仍是候选,选中后才成为下一步输入。后续同样经过状态更新、词表分布、选择和反馈,依次接入him、a、book、yesterday与句号,最终选中EOS结束。图旁的完整前缀用于记下已生成内容,不表示每一步都把整段前缀重新送入这一个RNN。
I gave him a book yesterday . EOS 是这个教学任务的合适目标。这里展示的是合适目标及生成接口,不是受训模型在这句话上的实测输出。源长9、目标文本7项再加EOS,共8个预测目标;两侧长度不同没有矛盾。句号有自己的下一步预测,不能用它代替EOS。
固定的是源接口,不是每一步decoder状态或输出分布。图中的 $s$ 是decoder递推状态的总记号;采用LSTM时,它可包含hidden与cell,而 $c_{src}$ 只指源摘要。图保留 $c_{src}→s_0$ 作为初始化背景,后续活动连接是“上一状态+本步新输入→当前状态→词表分布”,源条件沿状态传递,不在每一步重新注入。另一种实现可以每步再次注入源条件;具体实验必须声明使用哪一种。原LAB02采用双向源编码、decoder零初始化和逐步注入,固定组对有效源状态取均值,动态组逐次读取。因此不能把本段末状态初始化的示意当成LAB02训练配置。
扩大状态、改进训练和门控都可能改善这个已经可用的接口。一个短例没有证明固定摘要装不下;有限维实向量也不是长句必然失败的数学定理。是否有必要保留源位置供每步读取,要用任务与受控结果继续判断。
| 实际任务 | 输入/输出位置 | 最后一个状态是否足够 |
|---|---|---|
| 给每个词标人名/地名 | 每个词读入后接一个标签头 | 只取末状态会丢掉逐位置输出接口 |
| 判断整封短信是否垃圾 | 全部输入后接句子分类头,也可池化全部状态 | 是否足够是表示与任务的经验问题 |
| 流式门禁/下一词预测 | 每来一个事件就更新状态、产生预测 | 不能偷看未来事件 |
| 翻译一句话 | 源端编码结束后启动变长目标生成 | 固定状态或整列源状态是不同条件接口 |
共享的是更新函数的参数;h_2和h_8是两次不同的状态数值。多层RNN又有层轴:同一时刻第2层读第1层状态;它不是把同一个序列多训练一遍。双向编码器从左右读已知完整源句,能让“bank”使用右侧river;在线生成未知未来词时不能沿反向支路偷读答案。
状态随输入变,参数在时间上共享
九单位源句依次进入同一更新规则 $h_t=\tanh(Ue_t+Wh_{t-1}+b)$。$e_t$ 是当前输入的 embedding,$h_t$ 是这次处理后的状态,$U,W,b$ 是在时间位置共享的参数。$\tanh$逐坐标作非线性变换,把有限实数压到−1与1之间。运行到昨天时参数不会换一套,但状态必须使用已经处理“我”所得的结果。
取未训练的二维教学参数 $U=I,W=0.5I,b=0,h_0=(0,0)$,并给定 $e_{我}=(1,0),e_{昨天}=(0,1)$:
$$h_1=\tanh(1,0)\approx(.761594,0),$$ $$Ue_{昨天}+Wh_1=e_{昨天}+.5h_1=(.380797,1),$$ $$h_2=\tanh(.380797,1)\approx(.363399,.761594).$$
第二行把当前输入与旧状态的贡献相加,可以看到先前“我”的影响仍在。后七个单位继续用同一更新规则;未给其输入向量或实际运行结果时,只画符号状态,不伪造受训数值。
状态保存能力与任务表现需要另行验证。“我昨天给了他一本封面很厚、里面夹着许多便签的书。”仍是给书事件,但序列多了修饰内容;扩展句的位置必须重新编号,不能沿用九单位图的索引。末尾需要的早期关系要经过更多状态更新,这说明依赖路径变长,没有提供一次已测得的失败。下面保留的钥匙配对与梯度检查是不同任务,用来分别检验早期事实是否必要、反向计算是否正确。
以下三步“我/昨天/给了”是旧六片段扩展,第三步不是当前九单位的“给”位置;保留其输入与数值便于复算。
本章用列向量书写单条序列。输入 $x_t\in\mathbb R^d$,状态 $h_t\in\mathbb R^H$,词表 logits $a_t\in\mathbb R^V$。一个基本 tanh RNN 为
$$u_t=W_xx_t+W_hh_{t-1}+b_h,\qquad h_t=\tanh(u_t),$$ $$a_t=W_yh_t+b_y,\qquad p_t=\operatorname{softmax}(a_t).$$
$W_x$ 的形状为 $H\times d$,$W_h$ 为 $H\times H$,$W_y$ 为 $V\times H$。$h_0$ 可设为零、学得参数,或由条件输入产生;必须明确采用哪一种。每读一个 token,$h_t$ 改变,$W_x,W_h,W_y$ 在这次前向计算中保持不变。把网络画成很多时间格,只是在展示同一组参数被使用多次。
状态手算取暂略BOS的二维切片:$W_x=I_2,W_h=0.5I_2,b=0,h_0=0$,我、昨天、给了的输入分别为 $(1,0),(0,1),(1,1)$;得到 $(.761594,0)$、$(.363399,.761594)$、$(.827987,.881130)$。多位置训练图补回BOS后使用符号状态;未指定 BOS 向量,不能假称同一数值前向。
第二步最有意义的观察不是小数位,而是“昨天”到来时没有重新从零开始。$h_2$的输入中包含$0.5h_1$,所以“我”已经退出当前token位置,仍能通过上一状态影响这次结果。另一方面,$W_h$没有因为到了第二步而换一套。运行时变化的是状态,训练时优化的是参数;时间展开只是重复调用的账本。这里还没有保证早先每一项信息都保存得好。
作为课后独立复算,取一个标量例子:$h_0=0$、$W_x=0.5$、$W_h=0.8$、偏置为零,输入为 $(1,0)$。第一步 $h_1=\tanh(0.5)\approx0.462117$;第二步 $h_2=\tanh(0.8h_1)\approx0.353724$。第二个输入虽为零,状态并没有归零,因为它还带着前一步的影响。这个例子只证明计算有状态,不证明它已经学会了长距离任务。
把输出放在每一步,可以做 next-token LM 或逐位置标注;只从最后状态输出,可以做整句分类;用图像特征初始化状态并逐步预测词,可以做字幕。双向、分层或注意力会改变信息流,但不改变“参数与激活要分清”的基本要求。
展开之后,BPTT 就是链式法则
单步更新说明怎样运行,多位置监督说明怎样学习。可以先在展开图上任选两个输出位置:它们各自产生loss,却都依赖同一$W_h$。若只保留某一次调用的梯度,就忽略了这项参数对其他位置的影响。因此需要沿时间依赖追溯,并把各次使用的贡献相加。完整训练图使用符号状态,不应将前面的二维$h_1,h_2$数值直接贴到新图。
设目标 $y_t$ 是与输入错位后的下一 token,总损失 $L=\sum_{t=1}^{T}\ell_t$,其中 $\ell_t=-\log p_t[y_t]$。为了写清反向传播,定义输出误差 $e_t=p_t-\operatorname{onehot}(y_t)$,再令 $\delta_t=\partial L/\partial u_t$。从最后一步反向递推:
$$\delta_t=\left(W_y^\top e_t+W_h^\top\delta_{t+1}\right)\odot(1-h_t\odot h_t),\qquad \delta_{T+1}=0.$$
括号里有两条来源。第一项是本时刻预测错误传回来的梯度;第二项是后续所有损失通过状态链传回来的梯度。漏掉第二项,模型仍可能降低局部预测损失,却没有正确学习当前状态对未来的作用。这就是 backpropagation through time 的核心。
共享参数的梯度汇总为
$$\nabla W_y=\sum_t e_th_t^\top,\qquad \nabla b_y=\sum_t e_t,$$ $$\nabla W_x=\sum_t\delta_tx_t^\top,\qquad \nabla W_h=\sum_t\delta_th_{t-1}^\top,\qquad \nabla b_h=\sum_t\delta_t.$$
若输入来自 embedding,$\partial L/\partial x_t=W_x^\top\delta_t$ 再累加到对应词表行;同一个词出现多次,需要相加,不能覆盖。若 $h_0$ 可训练,还要保留 $W_h^\top\delta_1$。这些式子使用“时间损失求和”的约定;若前向取平均,所有梯度也要除以相同的有效目标数。经典训练困难分析与梯度范数裁剪见 Pascanu、Mikolov 与 Bengio,2013,§2–3。
同一条历史,两种不同的截断
以下只举两个四事件块的关系:第2条写入一个事实,第8条的查询需要它。第1–4条属于第一块,第5–8条属于第二块;这里仅规定位置和依赖,不提供新的训练成绩。第一块处理到 h_4。第二块用 stop_gradient(h_4) 作为起点,数值与 h_4 相同,所以第2条的信息若已被保留,仍可以影响第8条输出;但第8条loss的梯度不会穿过这个边界去改第2条的写入过程。第二块仍会对第5–8条使用的同一组参数产生梯度。若块间做了参数更新,携带状态还是旧参数算出的值,并没有重新编码全部过去。
state = initial_state()
for chunk in stream_chunks:
state = detach(state) # 保留数值,截断本次求导历史
outputs, state = rnn(chunk, state)
loss(outputs, targets).backward()
optimizer.step(); optimizer.zero_grad()
这是解释截断的伪代码,不对应新的训练成绩。若改成每块 state=initial_state(),那是同时清掉前向历史,改变了可用信息。将“detach”和“清零”混同,会误以为截断窗口就是模型能记住的最大长度。更长梯度跨度也不保证学会任务;它只是允许远处写入收到这次误差的信用信号。
长序列为什么不一定是长依赖
一万条日志末尾两条就能判定的任务,不要求利用最早事件。LAB01 C用一对除了早期线索外后缀相同的序列,才迫使判断依赖远处差异;gap改变真实数据长度、距离和显示。保留DN1.1定义:总长gap+2,线索到QUERY距离gap+1。短的固定gap=3有限差分检查另列,不能把其数值叫成gap=32的检查。
观察准确率不升至少有四个解释:到早期状态的梯度太小;状态不再保留标签信息;信息仍在但当前读出头不会用;优化尚未成功。检查逐位置梯度、冻结状态训练简单探针、加易学的短距离版本、检查训练/验证分布,可以分别提供证据。探针读不出也不严格证明信息为零,只否定该探针与训练程序能读出的假设。现有12更新smoke只验证计算链,不足以裁决远距离能力。
为什么远处的信用分配困难
先分清两个需要检验的问题:状态在前向中是否带来早先信息的影响,末尾损失在训练中是否能改变早期的处理。RNN计算图为二者提供了通路,但“箭头连得上”并不等于模型已经学会使用它。一个有判别力的任务要让答案真正随早先事实改变,而不能只靠常见标签或末尾词猜中。
考虑一条受控输入:“蓝扣钥匙放进赭色盒子”,接着给出若干不涉及钥匙的干扰事件,末尾问钥匙在哪里。正确答案必须依赖前面那条事实。我们可以把盒子颜色均匀随机,确保最后几个词不泄露答案,再改变事实与问题的距离。这样测到的才是对远处信息的利用;把 A→B→C 周期从 12 步加长到 18 步,只需要局部转移,并不构成这种证据。
最直接的配对检查,是保持电话、杯子和末尾问题逐字相同,只把开头的赭色盒子改成灰色盒子。正确答案随之变化。任何只依赖共同后缀的确定性系统都无法同时答对这对样本;这给“使用远处信息”一个可以反驳的标准。逐步增加干扰距离,才是在给这条通路增加负担,而不是单纯把输入写得更长。
若末尾答错,loss要影响开头的写入,就必须穿过中间多次更新。基本 RNN 的单步状态 Jacobian 为
$$J_t=\frac{\partial h_t}{\partial h_{t-1}}=\operatorname{diag}(1-h_t^2)W_h,$$
从 $s$ 到 $t$ 的梯度包含 $J_tJ_{t-1}\cdots J_{s+1}$。许多小于 1 的因子连乘,远处损失就难以改变早期写入;许多较大的因子连乘,梯度可能爆炸。矩阵乘积还受方向、奇异值和非线性饱和影响,不能只看一个权重或把所有训练现象归结为单个特征值。tanh 饱和时导数接近零,会进一步削弱某些路径。
梯度范数裁剪处理的是更新不稳定。把所有待更新参数的梯度拼成 $g$,设置阈值 $\tau$:
$$g\leftarrow g\,\min\left(1,\frac{\tau}{\|g\|_2}\right),\qquad \|g\|_2=\sqrt{\sum_i g_i^2}.$$
零范数时保持零即可。超阈值后保持整体方向、压小长度;这不同于逐元素裁剪。裁剪可以防止一次大梯度把参数推得过远,不能恢复已经衰减到几乎零的远程信号。实验里应同时记录裁剪前范数、裁剪触发比例与任务准确率,避免仅凭损失不发散就宣称记忆问题已解决。
完整 BPTT 需要保存用于反向传播的时间激活。截断 BPTT 每隔若干步把状态与之前的计算图断开:前向状态的数值仍可传到下一段,梯度却不能穿过截断边界。这减少内存与计算,也限制了训练时直接分配长期信用的范围。不要把“状态没有清零”误认为“梯度仍能回到整条历史”。
一次完整的保留、写入和输出
LSTM显式区分记忆单元 $c_t$ 与用于对外计算的隐藏状态 $h_t$;它们也不同于翻译的源摘要 $c_{src}$。一次更新把旧内容的保留、候选内容的写入和对外输出分开:$c_t=f_t\odot c_{t-1}+i_t\odot g_t$,$h_t=o_t\odot\tanh(c_t)$。实际门值由当前输入与旧隐藏状态通过可学习参数算出。
为看清动作,先给定一个标量切片:$c_{t-1}=1,f_t=.9,i_t=.1,g_t=.5,o_t=.8$。于是 $c_t=.9\times1+.1\times.5=.95$,$h_t=.8\tanh(.95)\approx.591826$。保留0.9、写入0.05、再输出经过压缩的一部分,是这次给定门值的计算,不是模型已学会了哪一种语言记忆。下列二维完整参数算例继续打开门值怎样算出,保留原来的独立身份。
钥匙任务提示了三种应能分开调节的行为:事实出现时写入,无关事件经过时保留,查询出现时读出。只缩小梯度无法替状态决定哪些信息应当留下。因此,下一项结构选择把保存旧内容和写入新内容放进可区分的通路,并允许当前输出只暴露其中一部分。
LSTM 增加一个 cell 状态 $c_t$,把一条主要状态更新写成加性形式。本节采用常见的无 peephole、带 forget gate 版本;它不是逐字复刻 1997 年最初版本。令 $z_t=[h_{t-1};x_t]$,四个可学习变换为
$$f_t=\sigma(W_fz_t+b_f),\quad i_t=\sigma(W_iz_t+b_i),$$ $$o_t=\sigma(W_oz_t+b_o),\quad g_t=\tanh(W_gz_t+b_g),$$ $$c_t=f_t\odot c_{t-1}+i_t\odot g_t,\qquad h_t=o_t\odot\tanh(c_t).$$
各门、候选与状态均为 $H$ 维。forget gate $f_t$ 决定保留旧 cell 的比例;input gate $i_t$ 决定候选 $g_t$ 写入多少;output gate $o_t$ 决定当前 cell 有多少经 tanh 暴露为 $h_t$。门值来自当前输入、旧 hidden 和已学参数,不是教师手动指定的语义开关。初始概念与后续 forget gate 分别可追到 Hochreiter 与 Schmidhuber,Long Short-Term Memory,1997及 Gers、Schmidhuber 与 Cummins,Learning to Forget,2000。
保留的一次完整门控复算
下面完整展开旧独立二维门控算例使用的参数。取 $h_{t-1}=(0.2,-0.1)$、$e_t=(1,0)$,故 $z_t=(0.2,-0.1,1,0)$。为完整复算,令四个 $W$ 都等于
$$U=\begin{bmatrix}1&0&0.5&0\\0&1&0&0.5\end{bmatrix},\qquad Uz_t=(0.7,-0.1).$$
各偏置分别取 $b_f=(\ln9-0.7,-\ln4+0.1)$、$b_i=(-\ln9-0.7,\ln3+0.1)$、$b_o=(\ln4-0.7,0.1)$、$b_g=(\operatorname{atanh}(0.5)-0.7,-\operatorname{atanh}(0.2)+0.1)$。因此仿射输出及非线性结果为:
| 分支 | 本次仿射输出 | 门/候选向量 |
|---|---|---|
| forget | $(\ln9,-\ln4)$ | $(0.9,0.2)$ |
| input | $(-\ln9,\ln3)$ | $(0.1,0.75)$ |
| output | $(\ln4,0)$ | $(0.8,0.5)$ |
| candidate | $(\operatorname{atanh}(0.5),-\operatorname{atanh}(0.2))$ | $(0.5,-0.2)$ |
参数是为了独立手算指定的,并未训练;改变输入或旧 hidden,门值仍会按上述函数改变。参考解释:V-RNN PDF p107–115,尤其 p112;这里展示通行的 forget-gate 形式,不把它等同于1997年原始论文的每个历史细节。
沿用上表由 $z_t$ 算出的门与候选,给定旧 cell $c_{t-1}=(1,-1)$。先保留旧信息:
$$f_t\odot c_{t-1}=(0.9,0.2)\odot(1,-1)=(0.9,-0.2).$$
再写入候选内容:
$$i_t\odot g_t=(0.1,0.75)\odot(0.5,-0.2)=(0.05,-0.15),$$ $$c_t=(0.9,-0.2)+(0.05,-0.15)=(0.95,-0.35).$$
最后计算本步 hidden:
$$h_t=(0.8,0.5)\odot\tanh(0.95,-0.35)=(0.591826,-0.168188).$$
输入门控制的是“写入多少”,不决定候选的正负。输出门较小也不等于把 cell 擦掉;它限制本步可见的 hidden,cell 仍沿独立通路传下去。
多步干预的定义与边界
旧多步门控扩展另固定一个坐标:c₀=1,每步 i=.01、g=0,因此写入项为0,c₂₀=f²⁰。f=.99时为0.8179069376,f=.8时为0.0115292150。所有门值为教学干预,不是模型学到的参数。先观察保留量怎样变化;本节末尾再将这条状态通路与求导路径对上。
把保留量与求导路径对上
沿cell直接通路、把门激活固定时,一步偏导贡献为 $\operatorname{diag}(f_t)$,多步为这些因子的乘积。上面的20步干预因此既能看保留量,也能看该路径的梯度。跨度改为100步时,$0.99^{100}\approx0.366$,而 $0.9^{100}\approx2.66\times10^{-5}$,更说明乘积对forget gate的敏感性。
实际门依赖旧hidden,完整总导数还含经过hidden和门网络的其他路径;直接cell路径不等于总导数。LSTM提供可学习的保留与改写结构,不保证总梯度恒为1或任意长记忆。是否用好它仍取决于任务、优化、容量与训练分布。V-RNN PDF p113强调直接通路,p115同时交代限制。
回到前面两个四事件块的约定:可分别检查cell中是否保留第2条写入的事实,以及hidden/读出头是否把它用于第8条查询。两处干预对应不同故障;上面的门控复算提供了区分它们的起点。
GRU 符号先统一,才比较实现
本讲把z当写入比例:h=(1-z)⊙h_old+z⊙candidate;Cho2014把z当保留比例:h=z_Cho⊙h_old+(1-z_Cho)⊙candidate。两者换算为 z=1-z_Cho。例如旧值2、候选6,本讲z=.25得到3;原论文要用z_Cho=.75才得到同一数值,不能把.25原样搬过去。
两个门通常由当前输入与旧状态的仿射函数接sigmoid得到:z=σ(W_z x+U_z h_old+b_z)、r=σ(W_r x+U_r h_old+b_r),逐特征取0到1的值。这里z采用上面的写入约定。
Cho候选为 tanh(Wx+U(r⊙h_old))。PyTorch2.9的GRU采用 tanh(W_in x+b_in+r⊙(W_hn h_old+b_hn)),reset在投影后。令h=(1,2)、r=(1,0)、U的两行为(1,1)/(1,-1):投影前reset给(1,1),投影后给(3,0),都是tanh前的值。非对角U使两者一般不同。reset只控制候选中的旧状态通路;即使r=0,最终保留支路仍可能留下旧状态。
门控、双向和堆叠解决不同设计问题。整句命名实体识别可用双向;连续传感流常要求因果;多层把局部模式逐级组合,代价是更多计算与优化难度。选择它们应先问输出何时需要、允许看见哪些输入,再问深度和隐藏宽度。
来源:同版读本第二章;lecture_7物理11–60、91–116连续推演;Cho2014 v3 §2.3、PyTorch2.9 GRU定义。四事件块只规定依赖关系;数值门控与解析梯度是构造核验;LAB01 C的短训练不升级为能力评测。
原课程连续页组:CS224N RNN/LM物理p21–24、p53/56/58;CS231n原文件lecture_7.pdf为RNN与图像条件生成课,页脚April 21, 2025,元数据创建于2026。末页才预告attention;这里不把它登记成Seq2Seq→attention连续页组。
相邻知识与复核资源
全部主题 · 关键结果JSON · 只读诊断源码 · Seq2Seq完整实现 · 视觉机制实现 · TinyDecoder与cache实现