卷 II · 记账CH 08深度 8/24

贝尔曼方程:整门学科就这一行

这一行你上一章已经用过了。这一章把它拆开,一个符号一个符号地看,然后指出它藏着的那个问题——那个问题制造了后面十六章的全部内容。

贝尔曼方程backup自指与自举

那一行

V(s) = max Σ P(s'|s,a) · [ r + γ V(s') ]
            a  s'

Richard Bellman 在 1950 年代写下它。这个人还顺手发明了「动态规划」这个词——据他自己说,取这个名字是因为当时他的资助方(美国空军)的领导讨厌「研究」这类字眼,而「动态规划」听起来没人能反对

一行方程,撑起了一整个领域。从左往右念一遍:

符号念作意思
V(s) =「这一格值多少」我们要求的东西
maxa「挑最好的那个动作」我们是有得选的
Σs' P(s'|s,a)「所有可能落到的地方,按概率加权」世界有噪声,只能取期望
r「当场拿到的」眼前
+——← 分界线
γ V(s')「落点本身值多少,打个折」以后

那个加号是这一行的全部内容。左边是眼前,右边是以后。整门学科所有的技术,都是在处理右边那一项。

亲手算一遍

下面这台把一次 backup 完整拆开了:选一格,它把四个动作各自的算式全部列出来,一项一项摆着,最后取 max。

▶ 动手 · 点右上角那个 0.918 的格子

它紧挨着 +1。看「往右」那一行的算式:它是怎么从 +1 和邻居的价值里算出 0.9178 的。

然后点左下角那几格,看看为什么「往左」会赢。

手算一格

拿右上角那格(坐标 (2,0),价值 0.9178)走一遍。它往右走:

  • 80% 真的往右 → 走进 +1 终点。这一步的奖励是 −0.04 + 1 = 0.96,落点价值 0(终点没有未来)。
  • 10% 滑到上边 → 撞墙,留在原地。奖励 −0.04,落点价值就是它自己,0.9178。
  • 10% 滑到下边 → 走进 −1 那格。奖励 −0.04 − 1 = −1.04,落点价值 0。
Q(s, →) = 0.8 × 0.96
        + 0.1 × (-0.04 + 0.9178)
        + 0.1 × (-1.04)
        = 0.768 + 0.0878 - 0.104
        = 0.9178   ✓

你会注意到一件怪事:算 V(s) 的时候用到了 V(s) 自己(那个撞墙留在原地的 10%)。

这不是循环论证,是一个方程——V(s) 出现在等号两边,解出来就是了。价值迭代干的就是这件事,只不过用的是「反复代入直到不动」这种最笨也最稳的解法。

◆ backup 这个词

把上面那次计算叫做对状态 s 做一次 backup——「备份」,或者更贴切地说,把后继状态的信息「往回搬」到当前状态

这个词值得记住,因为整个领域的算法几乎都能按「它怎么做 backup」来分类:

  • 用不用 max:用 → 学的是最优策略;不用(用当前策略的期望)→ 学的是当前策略的价值。这条线区分了「控制」和「预测」。
  • 往回搬多远:一步(TD)?整局(蒙特卡洛)?中间某个长度(n 步 TD、TD(λ))?这条线撑起了第 10、11 章。
  • 搬全部还是搬样本:把所有 s' 按概率加权(要模型)?还是只搬实际走到的那一个(不要模型)?这条线就是卷 II 和卷 III 的分界。

Sutton & Barto 那本书里有一张著名的图,把所有算法按这几个轴摆在一个平面上。看懂那张图,等于看懂了这个领域的地形。

然后是那个致命的问题

再看一眼那一行:

V(s) = max Σ P(s'|s,a) · [ r + γ V(s') ]
            a  s'                    └─┬─┘
                                       └── 这个你还没有

要算 V(s),你得先有 V(s')。要算 V(s'),你得先有 V(s'')。

价值迭代的解法是「那就先瞎猜,反复代入直到自洽」——所有 V 从 0 开始,把方程当赋值语句执行几十遍。这招之所以管用,靠的是终止状态那个锚。

但这里还藏着一个更要命的依赖,第 5 章已经点过:那个 Σ P(s'|s,a) 需要你知道转移概率表。

而真实的游戏里没有这张表。

◆ 于是问题变成了:怎么在没有 P 的情况下做 backup

整个卷 III 就是在回答这一个问题,而答案有两个方向,都朴素得让人意外:

方向一:不算期望,直接采样。不知道「所有可能落到哪儿、各多少概率」?那就走一次,看看实际落到了哪儿。走一万次,平均值自然会逼近那个期望。这就是蒙特卡洛(第 10 章)。

方向二:右边那个 V(s') 我也没有?那就用我现在的估计凑合。这叫自举(bootstrapping),是第 11 章的主题。

把这两个方向叠在一起——用采样代替期望,用估计代替真值——你就得到了 Q-learning(第 12 章)。

而这一套的代价是:不再有任何收敛保证。第 16 章那个把权重炸到 5.68×10⁶ 的例子,账要算在「自举」这一项上。

一个副产品:最优性原理

贝尔曼方程还捎带了一个很有用的结论,值得单独说:

✎ 最优子结构

如果一条路径是从 A 到 C 的最优路径,且它经过 B,那么它从 B 到 C 的那一段,一定也是从 B 到 C 的最优路径。

听起来是废话,但它有个很强的推论:最优策略不需要记住你是怎么走到这儿的。不管你走了什么弯路、犯过什么错,站在 B 这一格,最优的下一步是同一个。

这就是为什么策略可以写成 π(s) 这么简单的形式——一个函数,输入当前状态,输出动作,不带任何历史

顺带说,这也是这条原理和 Dijkstra、Floyd、编辑距离那些经典动态规划共享的东西。贝尔曼方程和它们是一家人,区别只在于强化学习的版本里多了两样:一个取期望的 Σ(因为世界有噪声),和一个你拿不到 P 的现实。

四个方程,一张表

你在论文里会遇到四个长得很像的贝尔曼方程。它们的区别只在两个二选一上,摆在一起就不会认错了:

给定策略 π(预测)最优(控制)
V Vπ(s) = Σa π(a|s) Σs' P[r + γVπ(s')] V*(s) = maxa Σs' P[r + γV*(s')]
Q Qπ(s,a) = Σs' P[r + γ Σa' π(a'|s')Qπ(s',a')] Q*(s,a) = Σs' P[r + γ maxa' Q*(s',a')]

看右下角那个 Q* 的式子,特别是里面那个 maxa'那就是 Q-learning 的更新目标,第 12 章直接照抄这一行。

而左下角 Qπ 里那个「按 π 加权」,就是 SARSA。第 13 章你会看到,这个位置上是 max 还是 π,会让两个算法在悬崖边上走出完全不同的两条路

⌗ 换成真机:这一行在代码里长什么样

库里的实现都很短,因为它们就是上面那些式子的直译。DQN 里那一行是:

# 一个 batch 的 TD 目标
with torch.no_grad():
    next_q = target_net(next_states).max(dim=1).values   # ← max_a' Q(s',a')
    target = rewards + gamma * next_q * (1 - dones)      # ← r + γ·max Q

loss = F.smooth_l1_loss(q_net(states).gather(1, actions), target)

三行。贝尔曼最优方程,加一个「让估计去逼近它」的回归损失,DQN 的核心就这么多。

注意 (1 - dones),又是那个「终止状态没有未来」。这个乘法项是初学者最常漏的一处,而且漏了之后不报错、能训练、结果偏高

还有 target_net——为什么要另外一个网络?第 16 章会给你一个把权重炸到 10⁶ 的判例。

↩ 回到那个视频

那个学走路的小人,在每一帧都在做一次 backup:「我现在这个姿势值多少?= 这一帧挪了多远 + 0.99 × 下一个姿势值多少」。

五十赫兹,跑几百万帧。那一行方程被执行了几亿次。

而它学到的「这个倾角很危险」,本质上就是那个 V 在某个区域塌下去了——因为从那儿开始,后面所有 backup 传回来的都是「摔倒」。没有人写过一行「不要摔倒」,是这一行方程把它算出来的。

这一章的一句话

贝尔曼方程只有一个加号:左边是眼前,右边是以后。而右边那两样东西——转移概率和后继的价值——你一样都没有。整门学科剩下的部分,都是在想办法绕过这件事。

下一章:卷 II 收尾。两种解这个方程的做法摆在一起比一比,你会看到一个很有意思的不对称——策略 5 轮就定了,价值要磨 46 轮。这个不对称后面会长成一整个流派。