本节摘要:时序差分(TD)学习把更新时机提前到"每走一步":用即时奖励加上下一步价值的估计来修正当前价值,V(s) ← V(s) + α[r + γV(s') − V(s)]。括号里的差叫 TD 误差,它同时是更新量、意外程度与学习信号。本节讲清自举为何"偏差换方差",用 N 步 TD 把 TD(0) 与蒙特卡洛连成一条连续谱,再用资格迹 TD(λ) 把谱上所有点优雅地加权合一。
3.2 节末尾点了 MC 的软肋:必须等回合结束。任务是下围棋,一条轨迹几百步;任务是机器人学走路,一条轨迹几分钟。等一个样本等到地老天荒,学习信号被拖到极稀。TD 的解法一句话:**不等了,走一步就用"r + γV(s')"当作回报的替身来更新。**写成更新式:
V(s) ← V(s) + α · [ r + γ V(s') − V(s) ]
方括号里的量叫 TD 误差,记作 δ。三个视角理解它。作为更新量:当前估计与新目标的差距,乘学习率 α 就是修正步伐。作为意外程度:δ 大说明现实与预期脱节——你以为这一步之后价值很高(γV(s')),结果拿到的 r 小气得很,预期被现实敲打。作为学习信号:整个 TD 学习的原始驱动力就是让 δ 趋近零;δ=0 时更新式变成恒等式,恰好就是贝尔曼期望方程成立。这个"把方程的残差往零压"的解法,数值分析里叫不动点迭代,2.2 节手算的涟漪传播是它的同步版本,TD 是它的在线随机版本。
TD 用 V(s') 这个估计值当目标,而估计值本身在变、可能错——这门"拿估计更新估计"的手艺叫自举(bootstrapping)。它的代价与收益构成全书最重要的一笔交易。
代价是偏差。MC 的目标是真实回报 G,在"期望意义下"无偏;TD 的目标是 r + γV(s'),其中 V(s') 若失真,目标就系统性偏移。极端例子:V 表全零初始化,前期 TD 目标永远只有 r 那一项,学习信号短视——这是冷启动阶段 TD 学得慢的原因之一。收益是方差骤降。MC 的 G 叠加了从 s 到终点的所有随机转移与所有动作选择的抖动;TD 的目标只叠加一步的随机性,后续不确定性被 V(s') 一项"打包吸收"了。方差的量级差异直接决定学习曲线的毛刺程度:MC 曲线大起大落,TD 曲线平滑下探。一句话总结这笔交易:MC 把方差攒到回合结束一次性爆发,TD 把不确定性折价进估计里分期偿还。
数字例子感受一下。设从 s 出发到终点的奖励序列有多条可能,其中两条是 [0, 0, +100] 与 [0, 0, -100]。MC 视角下 s 的回报样本是 +100 与 -100,均值 0 但单样本方差上万;TD 视角下,只要 s 的下一步状态 s' 的 V 已经稳定(比如两条序列在 s' 之前路径相同),TD 目标 r + γV(s') 的抖动只来自第一步的 r——方差小一个量级以上。当然,若 V(s') 本身学错了方向,TD 会稳定地错下去,而且自我强化:这就是自举的双刃剑,第 4 章 DQN 训练崩溃的根源之一。
一步太短视、整条太吵闹,中间的档位是 N 步 TD:目标用真实奖励走 N 步,再接一个估计——
G_t^(n) = R_{t+1} + γR_{t+2} + ... + γ^{n-1} R_{t+n} + γ^n V(s_{t+n})
N=1 退化为 TD(0);N=∞(到回合尾)退化为 MC。N 步目标把"多少随机性打包进估计、多少留给真实采样"变成一个可调旋钮。实践中 N 取 3 到 8 常在偏差与方差之间拿到好平衡(第 5 章 A2C/PPO 用的正是 N 步优势估计)。N 步的思想再推一步就是 **TD(λ)**:不选单一 N,而是把所有 N 步目标按 λ^(n-1) 几何加权平均——λ=0 退回 TD(0),λ=1 逼近 MC。实现上不需要真的算无穷个 N,用**资格迹**(eligibility trace)即可增量完成:每个状态维护一个"最近被访问的 Fresh 程度",访问就加一,每步按 γλ 衰减,TD 误差按资格迹分摊到所有近期访问过的状态。λ 于是成为"信任当前估计的成色"旋钮:λ 小,信估计(偏、快);λ 大,信现实(准、吵)。
| N | 目标构成 | 偏差 | 方差 | 适用 |
|---|---|---|---|---|
| 1 | r + γV(s') | 高 | 低 | 在线高频更新,环境平稳 |
| 5 | 走 5 步真奖励 + 估计 | 中 | 中 | 多数控制任务的甜点区 |
| ∞ | 真实回报 G | 无 | 高 | 回合短且要求无偏评估 |
用一个最小例子把 δ 跑出来。4 格走廊,智能体固定向右走,只有最右格给 +1:
GAMMA, ALPHA = 0.9, 0.1 V = [0.0, 0.0, 0.0, 0.0, 0.0] # 5 个格子(最后一个是终点,V 恒 0) def td0_episode(): s = 0 steps = 0 while s < 4: r = 1.0 if s == 3 else 0.0 # 第 4 格右行拿 +1 并到达终点 s2 = s + 1 delta = r + GAMMA * V[s2] - V[s] # TD 误差:意外程度 V[s] += ALPHA * delta print(f" s={s}: r={r:.1f} delta={delta:+.4f} V[s]={V[s]:.4f}") s = s2 steps += 1 for ep in range(1, 4): print(f"回合 {ep}") td0_episode() # 回合 1(节选): # s=3: r=1.0 delta=+1.0000 V[3]=0.1000 # 回合 2(节选): # s=2: r=0.0 delta=+0.0900 V[2]=0.0090 # s=3: r=1.0 delta=+0.9100 V[3]=0.1900 # 回合 3(节选): # s=1: r=0.0 delta=+0.0081 V[1]=0.0008 # s=2: r=0.0 delta=+0.0819 V[2]=0.0171 # s=3: r=1.0 delta=+0.8281 V[3]=0.2710
读输出看两件事。其一,价值从终点向起点逐回合"倒灌":第一回合只有 V[3] 动,第二回合 V[2] 开始沾光,第三回合 V[1] 醒来——奖励信号在时间上往回传播,每回合一格,与 2.2 节手算的涟漪一模一样,只是这次靠交互而不是模型。其二,δ 每回合整体变小:预期追上了现实,意外在减少。学习,就是让世界越来越不让你惊讶的过程。
💡 检验理解的一个问句:若把 ALPHA 提到 1.0,上面的 V[3] 第一回合会直接跳到 1.0,看起来更快,为什么反而危险?答:单样本噪声被全额吸收,遇到非平稳或随机奖励时会来回振荡——α 是"信这条新样本几分"的表态。