Skip to content

经典方法速览:DP、MC 与 TD

本节导读

核心内容

  • 掌握 DP、MC、TD 三类价值估计方法分别需要什么信息、何时更新、误差来自哪里。
  • 理解从已知模型到采样轨迹,再到一步自举更新的演进逻辑。
  • 学会用 TD Error 连接价值评估、Q-Learning、Critic 训练等后续算法。

核心公式

V(s)aπ(as)[R(s,a)+γsP(ss,a)V(s)](DP 策略评估更新:已知模型时迭代价值)V(s) \leftarrow \sum_a \pi(a\mid s)\left[R(s,a)+\gamma\sum_{s'}P(s'\mid s,a)V(s')\right] \quad \text{(DP 策略评估更新:已知模型时迭代价值)}

DP 策略评估更新 (DP Policy Evaluation Update):

  • \leftarrow:赋值操作,表示用右边的计算结果来更新左边的旧值。
  • aπ(as)\sum_a \pi(a\mid s):对当前状态下所有可能的动作按策略概率进行加权平均。
  • sP(ss,a)\sum_{s'} P(s'\mid s,a):对采取动作后所有可能转移到的下一个状态按转移概率进行加权平均。

V(s)V(s)+α[GtV(s)](MC 价值更新:用完整回报修正估计)V(s) \leftarrow V(s)+\alpha\left[G_t-V(s)\right] \quad \text{(MC 价值更新:用完整回报修正估计)}

MC 价值更新 (MC Value Update):

  • α\alpha:学习率(Learning Rate),控制每次更新时向新目标迈进的步长大小(010 \sim 1)。
  • GtG_t:蒙特卡洛采样的真实完整回报(从当前步一直到这局游戏结束)。

V(s)V(s)+α[r+γV(s)V(s)](TD(0) 价值更新:一步自举更新价值)V(s) \leftarrow V(s)+\alpha\left[r+\gamma V(s')-V(s)\right] \quad \text{(TD(0) 价值更新:一步自举更新价值)}

TD(0) 价值更新 (TD(0) Value Update):

  • rr:走这一步真实拿到的即时奖励。
  • γV(s)\gamma V(s'):对未来收益的猜测(打折后的下一个状态的预估价值)。
  • r+γV(s)r + \gamma V(s'):TD 目标(TD Target),即“眼前的真实奖励 + 对未来的猜测”。

δ=r+γV(s)V(s)(TD Error:提供基础学习信号)\delta = r+\gamma V(s')-V(s) \quad \text{(TD Error:提供基础学习信号)}

TD Error (Temporal Difference Error):

  • δ\delta:时序差分误差(TD Error),表示“现实发生的情况”与“你之前的预测”之间的差距。大于 0 说明有惊喜,小于 0 说明有惊吓。

为什么需要这些公式

上一节告诉我们价值满足贝尔曼方程,但现实里通常没有一本完整的"世界说明书"。如果你真的知道 PPRR,DP 就像拿着答案解析做题,可以直接迭代;如果你不知道规则,但能完整玩完一局,MC 就像考完以后看总分,再回头修正每一步判断;如果一局太长,不想等结束,TD 就像边做题边对答案,走一步改一步。这样看,DP、MC、TD 不是三种互不相关的方法,而是沿着"知道得越来越少,但还想继续学习"这条路发展出来的。读到这里会明白:现代 RL 很多时候靠的不是完美信息,而是不断用新经验修正旧估计。

上一节我们推导出了漂亮的贝尔曼方程,你可能觉得大功告成了:只要套公式,就能算出所有局面的分数。但现实中,我们面临一个巨大的鸿沟:贝尔曼方程需要环境的完整说明书(转移概率 PP 和奖励函数 RR)。

如果你在玩 CartPole,物理公式确实能给你 PPRR;但如果是围棋呢?它的状态多达 1017010^{170} 种。如果是大模型输出文本呢?它的 token 组合是天文数字。在绝大多数现实问题中,环境是一个黑盒,我们根本不知道 PPRR

所以,接下来的问题变成了:在没有说明书的情况下,怎么通过自己动手“试错”,把 V(s)V(s) 估算出来?

强化学习历史上出现了三代经典方法,它们并不是三个平行的备选方案,而是一条逼近现实的演进线——每一代都在试图解决上一代的硬伤:

  • DP(动态规划):拿着答案解析做题,精确但需要知道一切 → 现实中做不到。
  • MC(蒙特卡洛):没有答案解析就自己蒙着眼走到黑,看最后总分再回头修正 → 不用模型,但太慢了。
  • TD(时序差分):走一步就看一眼周围,结合周围的预估分来修正自己 → 既不用模型,也不用等结束,现代 RL 的绝对主力。

理解了这条演进线,你就会恍然大悟:为什么现在大红大紫的 PPO 算法里,它的 Critic 网络要用 TD 而不是 MC?为什么 DQN 需要一个经验回放池?可以说,这三代方法是后续所有算法的基因。我们一代代来看。

第一代:动态规划(Dynamic Programming, DP)

核心思想

如果你完全知道环境的转移概率 PP 和奖励函数 RR,这就好比你在打一个你知道源代码的游戏。既然知道代码怎么写,还需要像瞎子一样在游戏里乱撞试错吗?不需要。你可以直接把 VV 代入贝尔曼方程进行数学运算。

这套不靠盲目试错,直接在已知模型上反复代公式的方法,就是贝尔曼在 1957 年提出的动态规划 [1]

你想想:现实中什么时候会“完全知道” P 和 R?

答案是:当你自己规定了这个世界的物理法则时。比如在一个迷宫小游戏里,规则是你写的:“向右走一步扣 1 分,走到终点得 10 分,而且没有随机性,想往右走就一定能走到右边”。在这种场景下,DP 就是最优解——直接靠数学精确计算,不用去浪费时间跑好几次游戏。这也是为什么运筹学里的物流规划、库存管理到现在还在用 DP——因为仓库规则是确定的。

更新规则

DP 计算价值的核心叫做策略评估(Policy Evaluation)。公式看起来很眼熟,其实就是把贝尔曼期望方程的等号,强行变成了赋值箭头:

V(s)aπ(as)[R(s,a)+γsP(ss,a)V(s)](3.4)V(s) \leftarrow \sum_a \pi(a|s) \left[ R(s,a) + \gamma \sum_{s'} P(s'|s,a) \, V(s') \right] \tag{3.4}

反复对所有状态执行这个更新,VV 最终会收敛到 VπV^\pi 的精确值。

慢慢拆:这个公式从哪来?每一项是什么意思?

1. 先把贝尔曼方程读成一句话

上一节的贝尔曼期望方程说的是:如果 Vπ(s)V^\pi(s) 已经是真值,它应该满足下面这个关系:

Vπ(s)=aπ(as)[R(s,a)+γsP(ss,a)Vπ(s)]V^\pi(s) = \sum_a \pi(a\mid s) \left[ R(s,a) + \gamma \sum_{s'} P(s'\mid s,a) V^\pi(s') \right]

它翻译成人话就是:当前状态的价值,等于所有动作带来的结果,按策略概率加权后的平均分。

麻烦在于:等号左边和右边都有 VπV^\pi。你本来就是想求 VπV^\pi,但公式又要求你已经知道它,所以不能直接一眼算出来。

2. 再把“等号”变成“下一轮更新”

DP 的做法很朴素:

  1. 先给每个状态随便猜一个分数,比如全都设成 0。
  2. 用右边的公式算出下一轮的新分数。
  3. 用新分数替换旧分数,然后继续重复。

所以等式变成了迭代更新:

Vk+1(s)aπ(as)[R(s,a)+γsP(ss,a)Vk(s)]V_{k+1}(s) \leftarrow \sum_a \pi(a\mid s) \left[ R(s,a) + \gamma \sum_{s'} P(s'\mid s,a) V_k(s') \right]

这里的 kk 表示第几轮。VkV_k 是这一轮手里的旧估计,Vk+1V_{k+1} 是用公式算出来的新估计。反复更新之后,VkV_k 会越来越接近真正的 VπV^\pi

3. 最后逐项拆开看

1. 先看最外层:策略怎么选动作。

aπ(as)[]\sum_a \pi(a\mid s)[\cdots]

策略 π\pi 告诉你在状态 ss 下选每个动作的概率。比如策略是“70% 往右,30% 往左”,那状态价值就是:

V(s)=0.7×[往右的价值]+0.3×[往左的价值]V(s) = 0.7 \times [\text{往右的价值}] + 0.3 \times [\text{往左的价值}]

2. 再看中间:选定一个动作后,这一步值多少分。

R(s,a)+γsP(ss,a)Vk(s)R(s,a) + \gamma \sum_{s'} P(s'\mid s,a) V_k(s')

它由两块组成。R(s,a)R(s,a) 是这一步立刻拿到的奖励;后面那一大块是未来的分数,只是未来分数要先乘上折扣因子 γ\gamma

3. 最后看内层:下一步会到哪里。

sP(ss,a)Vk(s)\sum_{s'} P(s'\mid s,a) V_k(s')

这里的 P(ss,a)P(s'\mid s,a) 是环境规则:在状态 ss 做动作 aa 后,有多大概率到达 ss'。如果冰面上“向右走”有 90% 真的向右、10% 滑到下面,那就要把两个下一状态的价值按 0.90.90.10.1 加权平均。

为什么用 γ\gamma(折扣因子)?

γ\gamma 可以先理解成“未来分数的打折系数”。它有两个作用:

  1. 让无限长任务不爆掉。 如果 γ<1\gamma < 1,越远的未来权重越小,价值就更容易收敛。如果 γ=1\gamma = 1,无限期任务可能把奖励一直加下去,最后没有有限答案。
  2. 表达“眼前更重要”。γ=0.9\gamma = 0.9 时,10 步之后的 1 分,在现在只相当于:

0.9100.350.9^{10} \approx 0.35

也就是说,未来不是不重要,而是离得越远,权重越小。

为什么一定会收敛?(压缩映射定理)

把“用右边公式更新一次 VV”这件事叫做贝尔曼算子 TT

TV(s)=aπ(as)[R(s,a)+γsP(ss,a)V(s)]TV(s) = \sum_a \pi(a\mid s) \left[ R(s,a) + \gamma \sum_{s'} P(s'\mid s,a) V(s') \right]

可以证明,如果你拿两份不同的估计 V1V_1V2V_2 去更新,它们之间的差距会被 γ\gamma 缩小:

TV1TV2γV1V2\|TV_1 - TV_2\|_\infty \leq \gamma \|V_1 - V_2\|_\infty

这句话不用一开始就完全证明。你先抓住直觉:每更新一轮,错误会被打一次折。 只要 γ<1\gamma < 1,错误就会越缩越小,最后趋近于 0。

用微型走廊验证公式的每一步

3 格走廊 S → M → G,每步奖励 -1,γ=1\gamma = 1,确定性策略"只往右走"。

转移概率:P(MS,)=1P(\text{M}|S,\text{右}) = 1P(GM,)=1P(\text{G}|M,\text{右}) = 1,其余为 0。

迭代 0→1VV 全为 0。

  • V(S)=1×[(1)+1×V(M)]=1+0=1V(S) = 1 \times [(-1) + 1 \times V(M)] = -1 + 0 = -1
  • V(M)=1×[(1)+1×V(G)]=1+0=1V(M) = 1 \times [(-1) + 1 \times V(G)] = -1 + 0 = -1
  • V(G)=0V(G) = 0(终点)

迭代 1→2

  • V(S)=(1)+V(M)=1+(1)=2V(S) = (-1) + V(M) = -1 + (-1) = -2
  • V(M)=(1)+V(G)=1+0=1V(M) = (-1) + V(G) = -1 + 0 = -1

迭代 2→3V(S)=1+(1)=2V(S) = -1 + (-1) = -2,不再变化。收敛!

你看到关键机制了吗?V(M) 在第 1 轮更新为 -1 之后,立即参与了第 2 轮 V(S) 的计算——这就是"全状态同步扫描"的威力,信息一步就从终点传到了起点。

一个微型例子

假设一个 3 格走廊,S → M → G,每步奖励 -1,γ=1\gamma = 1。初始 VV 全部为 0。

迭代V(S)V(M)V(G)
0000
1-1-10
2-2-10
3-2-10(收敛)

经过 3 次迭代就收敛了——因为 V(M) 更新后立即影响了 V(S) 的下一步更新。这种"全状态扫描"的方式保证了快速收敛。

策略迭代(Policy Iteration)

策略评估只是第一步。知道了 VπV^\pi,就可以用它来改进策略——在状态 ss 选择让 Q(s,a)Q(s,a) 最大的动作:

π(s)=argmaxa[R(s,a)+γsP(ss,a)Vπ(s)]\pi'(s) = \arg\max_a \left[ R(s,a) + \gamma \sum_{s'} P(s'|s,a) V^\pi(s') \right]

策略迭代是"评估策略 → 改进策略 → 再评估"的循环,理论上保证收敛到最优策略 π\pi^*

慢慢拆:策略改进为什么一定有效?

1. 策略迭代只做两件事

策略迭代由两个交替步骤组成:

  1. 策略评估(Policy Evaluation):给定策略 π\pi,用公式 3.4 迭代求解 VπV^\pi
  2. 策略改进(Policy Improvement):用 VπV^\pi 构造一个更好的策略 π\pi'

关键问题是:为什么第二步真的会让策略变好?

2. 先定义“选某个动作后值多少”

定义:

Qπ(s,a)=R(s,a)+γsP(ss,a)Vπ(s)Q^\pi(s,a) = R(s,a) + \gamma \sum_{s'} P(s'\mid s,a) V^\pi(s')

这句话的意思是:先在状态 ss 选动作 aa,之后继续按旧策略 π\pi 行动,最后平均能拿多少分。

3. 新策略只做一件朴素的事:选分最高的动作

新策略 π\pi' 定义为:

π(s)=argmaxaQπ(s,a)\pi'(s) = \arg\max_a Q^\pi(s, a)

也就是说,在每个状态里,先把每个动作的 Qπ(s,a)Q^\pi(s,a) 算出来,然后选最高分那个动作。

4. 为什么这样不会变差?

因为 π(s)\pi'(s) 是最高分动作,所以它至少不会比旧策略原来选的动作差:

Qπ(s,π(s))=maxaQπ(s,a)Qπ(s,π(s))=Vπ(s)Q^\pi(s,\pi'(s)) = \max_a Q^\pi(s,a) \geq Q^\pi(s,\pi(s)) = V^\pi(s)

这行公式只是在说一句很普通的话:从一堆动作里挑最高分,不会比原来那套选择更差。

Qπ(s,π(s))Q^\pi(s,\pi'(s)) 展开,就是:

R(s,π(s))+γsP(ss,π(s))Vπ(s)Vπ(s)R(s,\pi'(s)) + \gamma \sum_{s'} P(s'\mid s,\pi'(s)) V^\pi(s') \geq V^\pi(s)

右边的 Vπ(s)V^\pi(s') 还假设“下一步以后继续按旧策略走”。如果下一步以后也继续用新策略 π\pi',每一步都做同样的“选最高分动作”,价值就不会下降。反复展开这个关系,就得到:

Vπ(s)Vπ(s)V^{\pi'}(s) \geq V^\pi(s)

这就是策略改进定理。

什么时候停止?

π=π\pi' = \pi 时(贪心策略不再改变),意味着对每个状态 ss

π(s)=argmaxaQπ(s,a)\pi(s) = \arg\max_a Q^\pi(s, a)

这正是贝尔曼最优方程的条件——π\pi 已经是最优策略 π\pi^*

为什么不直接用贝尔曼最优方程?

可以直接用,这就是值迭代(Value Iteration)。策略迭代只是把同一件事拆成两步:

  1. 先把当前策略评估清楚。
  2. 再根据评估结果改策略。

这样做的好处是思路更清楚:先问“这套走法到底值多少分”,再问“有没有更好的第一步”。值迭代更像是每一步都直接用 max\max 推进,策略迭代则把“算分”和“选动作”分开。

小实验:看 V 值一步步收敛

值迭代和策略迭代不是抽象的公式——它们是肉眼可见的迭代过程。用一个 4×4 GridWorld 来展示每一步发生了什么。

python
import numpy as np

# 4×4 GridWorld
# S(0,0) → ... → G(3,3)
# 每步奖励 -1,4 个动作(上/下/左/右),γ=0.9
GRID = 4
ACTIONS = [(−1, 0), (1, 0), (0, −1), (0, 1)]  # 上下左右
GAMMA = 0.9

def step(state, action):
    """执行动作,返回 (next_state, reward)"""
    nr = state[0] + action[0]
    nc = state[1] + action[1]
    if 0 <= nr < GRID and 0 <= nc < GRID:
        return (nr, nc), -1
    return state, -1  # 撞墙留在原地,照样扣分

def print_V(V, label=""):
    """打印 V 值矩阵"""
    if label:
        print(f"  {label}")
    for r in range(GRID):
        print("  ", end="")
        for c in range(GRID):
            print(f"{V[r][c]:7.2f}", end=" ")
        print()
    print()

# ========== 值迭代 ==========
V = np.zeros((GRID, GRID))
print("===== 值迭代 =====")
print_V(V, "迭代 0(初始)")

for iteration in range(1, 11):
    V_new = np.zeros((GRID, GRID))
    for r in range(GRID):
        for c in range(GRID):
            if (r, c) == (3, 3):
                continue  # 终点 V=0
            values = []
            for a in ACTIONS:
                (nr, nc), reward = step((r, c), a)
                values.append(reward + GAMMA * V[nr][nc])
            V_new[r][c] = max(values)
    V = V_new.copy()
    if iteration in [1, 2, 3, 5, 10]:
        print_V(V, f"迭代 {iteration}")

print_V(V, "最终结果")

预期输出(节选):

===== 值迭代 =====
  迭代 0(初始)
    0.00    0.00    0.00    0.00
    0.00    0.00    0.00    0.00
    0.00    0.00    0.00    0.00
    0.00    0.00    0.00    0.00

  迭代 1
   -1.00   -1.00   -1.00   -1.00
   -1.00   -1.00   -1.00   -1.00
   -1.00   -1.00   -1.00   -1.00
   -1.00   -1.00   -1.00    0.00

  迭代 2
   -1.90   -1.90   -1.90   -1.90
   -1.90   -1.90   -1.90   -1.90
   -1.90   -1.90   -1.90   -1.00
   -1.90   -1.90   -1.00    0.00

  迭代 5
   -3.44   -3.44   -2.80   -1.90
   -3.44   -2.80   -1.90   -1.00
   -2.80   -1.90   -1.00    0.00
   -1.90   -1.00    0.00    0.00

  最终结果
   -3.56   -2.80   -1.90   -1.00
   -2.80   -1.90   -1.00    0.00
   -1.90   -1.00    0.00    0.00
   -1.00    0.00    0.00    0.00

观察:V 值从终点向外"扩散"——每轮迭代,正确的 V 值向外扩展一格。这是因为值迭代每轮只能"看到"一步远的未来。第 1 轮只有终点旁边的格子被更新(它们距离终点 1 步,V = -1),第 2 轮影响范围扩展到 2 步远的格子(V = -1 - 0.9×1 = -1.9),以此类推。

策略迭代的可视化

策略迭代与值迭代的区别在于:策略迭代交替执行"评估当前策略"和"改进策略"两个步骤,而值迭代直接在每步选最优动作。

python
# ========== 策略迭代 ==========
V = np.zeros((GRID, GRID))
policy = np.zeros((GRID, GRID), dtype=int)  # 每个状态的动作编号

def policy_evaluation(V, policy, iterations=20):
    """用当前策略迭代计算 V"""
    for _ in range(iterations):
        V_new = np.zeros((GRID, GRID))
        for r in range(GRID):
            for c in range(GRID):
                if (r, c) == (3, 3):
                    continue
                a = ACTIONS[policy[r][c]]
                (nr, nc), reward = step((r, c), a)
                V_new[r][c] = reward + GAMMA * V[nr][nc]
        V = V_new.copy()
    return V

def policy_improvement(V, policy):
    """根据 V 改进策略,返回策略是否发生变化"""
    stable = True
    for r in range(GRID):
        for c in range(GRID):
            if (r, c) == (3, 3):
                continue
            values = []
            for a in ACTIONS:
                (nr, nc), reward = step((r, c), a)
                values.append(reward + GAMMA * V[nr][nc])
            best_action = np.argmax(values)
            if best_action != policy[r][c]:
                stable = False
                policy[r][c] = best_action
    return stable

print("===== 策略迭代 =====")
for iteration in range(1, 6):
    V = policy_evaluation(V, policy)
    stable = policy_improvement(V, policy)
    action_names = ["↑", "↓", "←", "→"]
    policy_str = ""
    for r in range(GRID):
        policy_str += "  "
        for c in range(GRID):
            if (r, c) == (3, 3):
                policy_str += "  G "
            else:
                policy_str += f"  {action_names[policy[r][c]]} "
        policy_str += "\n"
    print(f"第 {iteration} 轮:")
    print(policy_str)
    print_V(V)
    if stable:
        print(f"策略已收敛!共 {iteration} 轮")
        break

预期输出:

===== 策略迭代 =====
第 1 轮:
    ↓   ↓   ↓   ↓
    ↓   ↓   ↓   ↓
    ↓   ↓   ↓   ↓
    ↓   ↓   ↓    G

  迭代后 V
   -3.56   -2.80   -1.90   -1.00
   -2.80   -1.90   -1.00    0.00
   -1.90   -1.00    0.00    0.00
   -1.00    0.00    0.00    0.00

第 2 轮:
    →   →   →   ↓
    →   →   →   ↓
    →   →   ↓   ↓
    →   ↓   ↓    G

  策略已收敛!共 2 轮

策略迭代只用了 2 轮就收敛——而值迭代需要约 10 轮。原因:策略迭代每轮内部做了多轮策略评估(把当前策略的 V 算准),然后一步到位地改进策略。值迭代则每轮只做一步更新,收敛更慢但每轮计算更少。

方法收敛轮数每轮计算量特点
值迭代~10 轮每轮扫描所有状态一次简单,每步只做一个 max 操作
策略迭代~2-3 轮每轮需要完整策略评估(内部迭代)收敛快,但每轮更重

在 4×4 的小格子里差别不大,但在大规模问题中,策略评估的内部循环本身就很昂贵——这就是为什么实践中值迭代更常用,而策略迭代的思想则体现在了后续的 Actor-Critic 架构中(第 6 章)。

局限:理论完美,现实骨感

你是不是发现 DP 有点像“上帝视角”?

现实中,我们根本拿不到完整的说明书。你想让机械臂学会抓杯子,难道要先把机械臂和杯子所有微观物理碰撞的概率分布(转移概率 PP)全写进公式吗?如果是围棋,状态数更是高达 1017010^{170},你要怎么算?如果是训练大语言模型,token 组合是天文数字,根本不可能对所有可能状态做一次遍历。

所以,DP 的精确性是建立在“我知道游戏所有规则”和“游戏很简单”这两个前提上的。它更像是一个理论基准,告诉我们“如果在乌托邦里,答案应该是这样的”。在实际中,面对那些不知道规则的环境,我们就得靠瞎蒙和试错来收集数据了。

第二代:蒙特卡洛方法(Monte Carlo, MC)

核心思想:试完算总账

DP 的前提是“我知道游戏源码”。但现实里更多时候,你并不知道源码。你不知道某个动作之后会转到哪里,也不知道奖励函数到底怎么写。那还能不能学?

蒙特卡洛的答案很直接:不知道规则,就亲自进去玩;玩完整局,再看最后总分。

比如你在玩一个陌生迷宫。站在路口 ss,你不知道向左走会不会绕远,也不知道向右走会不会撞墙。MC 不试图提前写出完整地图,而是让你真的从这个路口出发,按当前策略一路走到游戏结束。最后你拿到一个总分:可能是 -8,也可能是 +3,也可能是 +10。

这一局从状态 ss 出发最终拿到的总分,记作 GtG_t。它不是猜出来的,而是你真的跑完这一局后得到的结果。

如果只跑一次,运气成分太大;但如果从同一个状态 ss 出发跑很多次,把这些 GtG_t 取平均,就能估计这个状态大概值多少分:

V(s)很多次 Gt 的平均值V(s) \approx \text{很多次 } G_t \text{ 的平均值}

这就是蒙特卡洛方法:用很多次真实试玩的平均结果,替代你原本不知道的环境公式。 它的名字来自赌城 Monte Carlo,因为它本质上就是靠随机采样来估计平均值 [2]

更新规则

现在的问题是:每跑完一局,难道都要把过去所有总分重新平均一遍吗?

不用。我们只需要保留当前估计 V(s)V(s),然后把它朝新看到的总分 GtG_t 挪一点:

V(s)V(s)+α[GtV(s)](3.5)V(s) \leftarrow V(s) + \alpha \left[ G_t - V(s) \right] \tag{3.5}

这行公式可以这样读:

  1. V(s)V(s):我原来以为状态 ss 值多少分。
  2. GtG_t:这次真的从 ss 出发跑完整局后,实际拿到多少分。
  3. GtV(s)G_t - V(s):现实和预测差了多少。
  4. α\alpha:每次改多少。它通常是一个小数,比如 0.1,意思是“不要一次全信新样本,只往新结果靠近 10%”。

比如你原本觉得这个路口值 0 分,结果这局从这里出发最后拿了 -10 分。如果 α=0.1\alpha = 0.1,新估计就是:

V(s)0+0.1×(100)=1V(s) \leftarrow 0 + 0.1 \times (-10 - 0) = -1

它不会一下子变成 -10,因为一次试玩可能只是运气不好;但它会先往 -10 的方向挪一点。多试很多次以后,V(s)V(s) 就会逐渐靠近真实平均水平。

慢慢拆:MC 更新公式从哪来?

1. 先从最普通的平均数开始

MC 的核心思想就是:用很多次完整回报的平均值来估计 V(s)V(s)

假设状态 ss 被访问了 NN 次,每次得到的回报分别是 G1,G2,,GNG_1, G_2, \ldots, G_N。最直接的做法:

V(s)=1Ni=1NGiV(s) = \frac{1}{N} \sum_{i=1}^{N} G_i

这就是学校里最普通的“求平均分”:把所有分数加起来,再除以次数。

2. 再改成不用保存所有历史的写法

问题是,RL 训练会跑很多很多局。如果每来一个新回报 GN+1G_{N+1},都把过去所有分数重新加一遍,就太麻烦了。

我们希望只保留当前平均值 VNV_N,然后来了一个新样本 GN+1G_{N+1},就直接算出新平均值 VN+1V_{N+1}

VN+1=NVN+GN+1N+1V_{N+1} = \frac{N \cdot V_N + G_{N+1}}{N+1}

这里的 NVNN \cdot V_N 就是“前 NN 次总分加起来”的结果。

继续整理一下:

VN+1=VN+1N+1(GN+1VN)V_{N+1} = V_N + \frac{1}{N+1}(G_{N+1} - V_N)

这行公式很重要。它说明新平均值可以写成:

新估计=旧估计+步长×(新样本旧估计)\text{新估计} = \text{旧估计} + \text{步长} \times (\text{新样本} - \text{旧估计})

这就是公式 3.5 的来源。

3. 最后把步长换成学习率 α\alpha

在严格求平均时,步长是 1N+1\frac{1}{N+1}。样本越多,步长越小,估计越来越稳定。

但在 RL 中,我们经常把它换成固定学习率 α\alpha

V(s)V(s)+α[GtV(s)]V(s) \leftarrow V(s) + \alpha \left[ G_t - V(s) \right]

这样做是因为环境和策略可能一直在变。固定的 α\alpha 让智能体不会“学到一半就完全不改了”。代价是它不会像普通平均数那样完全静止,而是会在真实值附近小幅波动。

实际中常见选择是 α=0.01\alpha = 0.01α=0.1\alpha = 0.1,也可以让 α\alpha 随训练慢慢变小。

GtV(s)G_t - V(s) 的含义:预测误差

GtV(s)G_t - V(s) 就是“实际回报”和“原来预测”之间的差:

  • Gt>V(s)G_t > V(s):实际比预测好 → VV 上调
  • Gt<V(s)G_t < V(s):实际比预测差 → VV 下调
  • Gt=V(s)G_t = V(s):预测准确 → 不动

这与人类学习的直觉完全一致——你预测考试能考 80 分(V(s)V(s)),实际考了 90 分(GtG_t),下次你就会调高对自己的评估。

为什么 MC 是无偏的?

"无偏"的定义:E[V(s)]=Vπ(s)\mathbb{E}[V(s)] = V^\pi(s),即估计值的期望等于真实值。

MC 用的是真实的完整回报 Gt=rt+γrt+1+γ2rt+2+G_t = r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \cdots——这是从状态 ss 出发,实际跑完整个 episode 拿到的总回报。根据 Vπ(s)V^\pi(s) 的定义:

Vπ(s)=E[Gtst=s]V^\pi(s) = \mathbb{E}[G_t | s_t = s]

所以 GtG_t 就是 Vπ(s)V^\pi(s) 的无偏估计(它的期望恰好等于 Vπ(s)V^\pi(s)),用 GtG_t 的平均值来估计 VV 自然也是无偏的。

为什么方差大?

Gt=rt+γrt+1+γ2rt+2+G_t = r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \cdots 把从 tt 时刻到 episode 结束的所有随机奖励加在了一起。每一步的奖励都是随机的,加在一起,不确定性就累加了——就像掷 100 次骰子的总和,比掷 1 次骰子的方差大得多。

形式化地:如果每步奖励 Var(ri)=σ2\text{Var}(r_i) = \sigma^2,则(粗略地)Var(Gt)σ21γ2\text{Var}(G_t) \approx \frac{\sigma^2}{1-\gamma^2},随路径长度指数级增长。

特点:无偏,但运气成分太大(高方差)

MC 给出的估算是**无偏(Unbiased)**的。什么是无偏?就是说你只要试的次数足够多,你算出的平均分 VV 就一定等于真实的平均期望。因为你用的是真金白银跑出来的总分 GtG_t

但这其实也藏着 MC 最大的弱点:方差巨大。因为每一局游戏里,有无数个路口和选择,你这一局赢了,是因为你在这个路口选得对,还是因为后来你在别的地方运气好?

举个你买彩票的例子。你第一天去某家彩票店(状态 A),花了 10 块钱,最后中了 500 万。根据 MC 的想法,这家彩票店的“真实价值” V(A)V(A) 就应该是 500 万减 10 块。 第二天你又去,花了 10 块,什么都没中,价值是 -10 块。 第三天又去,中了 5 块,价值是 -5 块。

如果把这三天的总回报 GtG_t 平均一下,大约是 166 万。MC 会说:“嗯,状态 A 的价值是 166 万”。 但这合理吗?如果你只试了这几次,估计结果简直是被“偶然事件”带着跑的。为了抵消这种偶尔中大奖的极端情况,你可能需要去买一千万次彩票,均值才会落回真实的负数。

这就是高方差(High Variance):只跑几次的估算根本不靠谱,为了得到准确的分数,你需要海量的数据。

局限:必须等一局结束

还有个很致命的问题:用 MC 方法,你必须等游戏彻底结束,才能算账。 如果是打一局 5 分钟的游戏,还能接受。但如果你在训练一个股票交易机器人,难道你要等股市破产或者你老死,才能回过头来给十年前的某一笔交易打分吗?如果是永不停止的任务(没有终点),MC 就彻底失灵了。

第三代:时序差分学习(Temporal Difference, TD)

核心思想:不用等结束,边走边学

MC 每次都要等期末考试成绩出来,才去反思每天的复习计划是不是对的,反应实在太迟钝了。有没有可能边复习边调整呢?

这就轮到强化学习里最重要的创新登场了——由 Sutton 和 Barto 两位祖师爷在 1988 年提出的时序差分(TD) [3]。它被称为“强化学习核心的、新颖的想法”。

TD 是怎么做到的?它把 DP 和 MC 巧妙地缝合在了一起。

  • 像 MC 一样:你不需要懂环境规则,不用知道说明书,直接下场试走。
  • 像 DP 一样:你不需要走到终点,只走一步,然后看看下一站的预估分数是多少,用它来修正你当前的估算。

这其实就像你在迷宫里探索。你原本以为当前位置值 10 分。结果你走了一步,这一步没扣分(奖励是 0),但你一抬头,发现自己竟然走到了一条死胡同旁边(你预估这个死胡同值 -100 分)。 按照 TD 的想法,你立刻就知道:“坏了,我刚刚这步走错地方了,原来这里根本不值 10 分,而是大概 -100 分。” 你立刻就把当前位置的分数下调了。

在这个过程中,你没有走到终点,你用了一个你目前还不那么确定的预估值(下一站的分数),去更新了另一个预估值(当前站的分数)

这种“左脚踩右脚上天”的操作,在数学上叫自举(Bootstrapping)。你可能会怀疑:用瞎猜的数字去修正另一个瞎猜的数字,这靠谱吗? 事实证明,虽然一开始大家都是瞎猜的,但只要环境规则是固定的,你的每一次“走一步看一眼”都会把真实的奖励(眼前这一步拿到的一点点实惠)吸纳进来。只要步数够多,这个网络就会奇迹般地收敛到真实的 V(s)V(s)

更新规则

把上面的“走一步看一眼”写成公式,就是我们非常熟悉的 TD(0) 更新:

V(s)V(s)+α[r+γV(s)V(s)]TD Error δ(3.6)V(s) \leftarrow V(s) + \alpha \underbrace{\left[ r + \gamma V(s') - V(s) \right]}_{\text{TD Error } \delta} \tag{3.6}

方括号里的 r+γV(s)r + \gamma V(s') 叫做 TD Target——"实际结果 + 对未来的估计",也就是你"本该"预测的值。V(s)V(s) 是你之前的预测。两者之差就是 TD Error。

数学推导:TD 更新公式从哪来?为什么比 MC 方差低?

从贝尔曼方程到 TD 更新

回忆贝尔曼期望方程:Vπ(s)=E[r+γVπ(s)s]V^\pi(s) = \mathbb{E}[r + \gamma V^\pi(s') | s]

这个等式说的是:Vπ(s)V^\pi(s) 的真实值等于"r+γV(s)r + \gamma V(s') 的期望"。

DP 的做法:精确计算期望 aπ(as)[sP(ss,a)]\sum_a \pi(a|s) [\cdots \sum_{s'} P(s'|s,a) \cdots]——需要知道 PP

TD 的做法:不计算期望,而是采样一次。实际走一步,观测到 (r,s)(r, s'),用这一个样本来近似期望:

V(s)V(s)+α[r+γV(s)TD TargetV(s)]V(s) \leftarrow V(s) + \alpha \left[ \underbrace{r + \gamma V(s')}_{\text{TD Target}} - V(s) \right]

这就是用单步采样替代了 DP 的全概率求和——不需要知道 PP,只需要跑一步看看发生了什么。

TD Target vs MC 回报

MCTD
目标Gt=rt+γrt+1+γ2rt+2+G_t = r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \cdotsr+γV(s)r + \gamma V(s')
用到多少真实数据?tt 到终止的所有奖励只有一步的 rr
"未来"怎么处理?全部用真实奖励用估计值 V(s)V(s') 代替

TD Target 只用了一步的真实奖励 rr,然后用 V(s)V(s') 来"代替"未来所有的奖励——这就是自举(bootstrapping)

为什么 TD 有偏差?

TD 的目标 r+γV(s)r + \gamma V(s') 中,V(s)V(s') 是一个估计值,不是真实值。所以:

E[r+γV(s)]Vπ(s)\mathbb{E}[r + \gamma V(s')] \neq V^\pi(s)

除非 V(s)=Vπ(s)V(s') = V^\pi(s')(即估计已经完全正确),否则 TD Target 的期望不等于真实值——这就是偏差的来源。

相比之下,MC 的目标 GtG_t 是真实回报,E[Gt]=Vπ(s)\mathbb{E}[G_t] = V^\pi(s)——无偏。

为什么 TD 方差更低?

GtG_t 把所有步骤的随机性都叠在一起了(kk 步的随机奖励求和),而 TD Target 只包含一步的随机性(rr 是随机的,但 V(s)V(s') 在更新时是固定的)。

形式化地:

  • Var(Gt)\text{Var}(G_t) \propto 轨迹长度 × 单步方差(随路径长度增长)
  • Var(r+γV(s))\text{Var}(r + \gamma V(s')) \propto 单步方差(与路径长度无关)

这就是 TD 的核心权衡:牺牲无偏性(引入偏差),换取大幅降低的方差

TD(0) 的收敛性

虽然单步更新有偏差,但可以证明 TD(0) 在满足以下条件时收敛:

  1. t=0αt=\sum_{t=0}^{\infty} \alpha_t = \infty(总学习量足够大)
  2. t=0αt2<\sum_{t=0}^{\infty} \alpha_t^2 < \infty(学习率衰减足够快)

直觉:每次更新虽然不完美(有偏差),但更新方向在期望上是对的(TD Error 的期望趋近于 0)。足够多的更新之后,偏差逐步消除。

一个具体数值例子

3 格走廊 S → M → G,真实价值 V(S)=2,V(M)=1,V(G)=0V(S) = -2, V(M) = -1, V(G) = 0

假设当前估计 V(S)=0,V(M)=0,V(G)=0V(S) = 0, V(M) = 0, V(G) = 0,学习率 α=0.5\alpha = 0.5

MC 方式:从 S 出发跑完一局,得到轨迹 S→M→G,回报 G=1+(1)=2G = -1 + (-1) = -2

V(S)0+0.5×(20)=1V(S) \leftarrow 0 + 0.5 \times (-2 - 0) = -1

只跑了一局,估计从 0 跳到 -1。再跑一局可能得到 -2(估计又大幅变化)——波动大。

TD 方式:从 S 出发走一步,观测到 (r=1,s=M)(r=-1, s'=\text{M})

V(S)0+0.5×(1+1×00)=0.5V(S) \leftarrow 0 + 0.5 \times (-1 + 1 \times 0 - 0) = -0.5

走一步就更新了,变化幅度小。接着从 M 走一步到 G:

V(M)0+0.5×(1+1×00)=0.5V(M) \leftarrow 0 + 0.5 \times (-1 + 1 \times 0 - 0) = -0.5

多跑几轮后,VV 会逐步逼近 [2,1,0][-2, -1, 0]。每步变化幅度小——方差低。

TD Error 的定义

δ=r+γV(s)V(s)(3.7)\delta = r + \gamma V(s') - V(s) \tag{3.7}

数学推导:TD Error 的深层含义——贝尔曼方程的"残差"

TD Error 是什么?

TD Error 是贝尔曼方程在单步上的"违反程度"。如果把贝尔曼方程写成:

Vπ(s)[R(s,a)+γVπ(s)]=0V^\pi(s) - \left[ R(s,a) + \gamma V^\pi(s') \right] = 0

VV 完全正确时,左边恒为 0。但在学习过程中 VV 还不准确,实际采样到 (r,s)(r, s') 后:

δ=r+γV(s)V(s)\delta = r + \gamma V(s') - V(s)

δ\delta 就是"实际发生的事情"(r+γV(s)r + \gamma V(s'))与"你的预测"(V(s)V(s))之间的差距。

TD Error 的期望就是贝尔曼残差

δ\delta 取期望(对所有可能的 (r,s)(r, s')):

E[δ]=E[r+γV(s)s]V(s)=[aπ(as)(R(s,a)+γsP(ss,a)V(s))]V(s)\mathbb{E}[\delta] = \mathbb{E}[r + \gamma V(s') | s] - V(s) = \left[\sum_a \pi(a|s) \left( R(s,a) + \gamma \sum_{s'} P(s'|s,a) V(s') \right)\right] - V(s)

V=VπV = V^\pi 时,右边方括号就是贝尔曼期望方程,等于 Vπ(s)V^\pi(s),所以 E[δ]=0\mathbb{E}[\delta] = 0

E[δ]=0\mathbb{E}[\delta] = 0 等价于 VV 满足贝尔曼方程——这就是为什么 TD Error 是"学习完成"的指标。

TD Error vs 梯度下降的关系

TD 更新可以写成:

V(s)V(s)+αδV(s) \leftarrow V(s) + \alpha \cdot \delta

如果用神经网络参数化 VθV_\theta,更新变为:

θθ+αδθVθ(s)\theta \leftarrow \theta + \alpha \cdot \delta \cdot \nabla_\theta V_\theta(s)

这看起来很像梯度下降,但它不是真正的梯度下降——因为 TD Target r+γV(s)r + \gamma V(s') 中的 V(s)V(s') 也依赖于 θ\theta,而 TD 方法在计算梯度时假装 TD Target 是常数。这种"半梯度"方法在实践中工作得很好,但理论分析更复杂。

三种误差信号对比

方法误差信号无偏?方差
DPaπ(as)[R(s,a)+γsP(ss,a)V(s)]V(s)\sum_a \pi(a\mid s)[R(s,a)+\gamma\sum_{s'}P(s'\mid s,a)V(s')]-V(s)无偏(精确计算期望)0(非随机)
MCGtV(s)G_t - V(s)无偏(真实回报)
TDr+γV(s)V(s)r + \gamma V(s') - V(s)有偏(V(s)V(s') 是估计)

DP 精确但需要模型,MC 无偏但方差大,TD 低方差但有偏差——三种方法分别占据"精确性"和"数据效率"的不同位置。

TD Error 贯穿全书的三重角色

  1. 价值更新的驱动信号V(s)V(s)+αδV(s) \leftarrow V(s) + \alpha \delta(本节)
  2. 优势函数的最简形式A^(s,a)=δ=r+γV(s)V(s)\hat{A}(s,a) = \delta = r + \gamma V(s') - V(s),衡量"这个动作比平均水平好多少"(第 6 章)
  3. GAE 的构建基石AGAE=l=0(γλ)lδt+lA^{\text{GAE}} = \sum_{l=0}^{\infty} (\gamma \lambda)^l \delta_{t+l},多个 TD Error 的指数衰减加权和(第 7 章)
  • δ>0\delta > 0:实际比预期好,价值被低估,应该上调 V(s)V(s)
  • δ<0\delta < 0:实际比预期差,价值被高估,应该下调 V(s)V(s)
  • δ=0\delta = 0:预测完美,学习完成

TD Error 不仅仅是一个误差指标——它在 RL 中承担着三重关键角色,贯穿后续几乎所有算法:

  1. Critic 的训练信号:Critic 网络直接用 δ\delta 来更新价值估计(第 6 章)
  2. 最简优势函数δ\delta 本身就是对"做了这个动作比预期好多少"的估计(第 6 章)
  3. GAE 的基础:GAE 是多个 TD Error 的指数加权和(第 7 章)

TD vs MC:一个直觉对比

想象你预测明天降雨概率是 80%(V(s)=0.8V(s) = 0.8)。第二天实际没下雨(r=0r = 0),而且你观察到后天也不太会下雨(V(s)=0.3V(s') = 0.3)。

  • TD 视角:δ=0+0.9×0.30.8=0.53\delta = 0 + 0.9 \times 0.3 - 0.8 = -0.53。预测偏高,下调。
  • MC 视角:需要等到整个天气周期结束才能回头修正,而且那次周期可能跨越很多天,GtG_t 的波动很大。

TD 的优势在于:走一步就能修正预测,不需要等结束

思考题:TD Error 能不能永远为 0?

在确定性环境中,理论上可以:只要所有状态的 VV 值都精确满足贝尔曼方程,TD Error 就处处为 0。但在随机环境和函数逼近(神经网络)的情况下,TD Error 通常不会精确为 0。因为环境的随机性导致每次采样到的 (r,s)(r, s') 不同,而神经网络的参数有限,无法精确表示所有状态的价值。所以在实际训练中,我们追求的是 TD Error 的期望趋近于 0。

三代方法的演进

DPMCTD
需要环境模型?
需要完整轨迹?
自举
偏差有(但低方差)
方差
代表场景已知规则的小规模问题有终止的采样任务绝大多数无模型在线任务

演进逻辑:DP 精确但不现实 → MC 不需要模型但要等太久 → TD 不需要模型也不需要等。每一代都解决了前一代的核心局限。

本节总结

学完这一节,我们获得了一项关键能力:从数据中估计价值函数。三种方法共享同一个核心——贝尔曼方程——只是"怎么拿到未来信息"的方式不同:

  1. DP(动态规划):需要完美模型。直接用转移概率 PP 和奖励 RR 精确迭代。它是理论基准("如果知道一切会怎样"),无偏差、无方差。
  2. MC(蒙特卡洛):不需要模型,但需要跑完一整局。用完整的真实回报 GtG_t 更新。它是无偏估计("纯靠数据能做到什么"),但方差很大,学习慢。
  3. TD(时序差分):不需要模型,走一步就更新。用"眼前的奖励 + 对未来的猜测(V(s)V(s'))"来修正当前猜测。它有偏差但低方差,是实际应用的主力("数据有限、边做边学时用什么")。
  4. 统一视角(GPI):所有这些算法都可以拆解为"评估当前策略"和"根据价值改进策略"两个交替的过程。

局限性与后续引申

如果你是初学者,最应该掌握的是 TD。不是因为它最简单,而是因为它是现代 RL 的主力——PPO、SAC、TD3 等工业界广泛使用的算法都以 TD 为基础。理解了 TD Error 和自举的思想,你就能直接理解后续高级算法的核心。

此外,这条 MC→TD 的演进线不仅出现在价值估计中。在后续的第 5 章,我们将看到完全相同的演进出现在策略优化中:REINFORCE 就是策略版的 MC(跑完一整局用完整回报),Actor-Critic 就是策略版的 TD(走一步就用 TD Error 更新)。

现在,我们已经解决了"如果不知道环境模型,怎么估计 V(s)V(s)"的问题。但 V(s)V(s) 仍然有一个局限:它只告诉你局面好不好,却不直接告诉你该选哪个动作。为了直接做决策,我们需要把 V(s)V(s) 扩展到具体的动作上。这就是下一节的内容:

下一节:路线一:Q(s,a)——给每个动作打分

参考文献


  1. Bellman, R. (1957). Dynamic Programming. Princeton University Press. ↩︎

  2. Metropolis, N., & Ulam, S. (1949). The Monte Carlo method. Journal of the American Statistical Association, 44(247), 335-341. ↩︎

  3. Sutton, R. S. (1988). Learning to predict by the methods of temporal differences. Machine Learning, 3(1), 9-44. ↩︎

Built for reusable bilingual course delivery