贝尔曼方程:整门学科就这一行
这一行你上一章已经用过了。这一章把它拆开,一个符号一个符号地看,然后指出它藏着的那个问题——那个问题制造了后面十六章的全部内容。
那一行
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。
它紧挨着 +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) 出现在等号两边,解出来就是了。价值迭代干的就是这件事,只不过用的是「反复代入直到不动」这种最笨也最稳的解法。
把上面那次计算叫做对状态 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) 需要你知道转移概率表。
而真实的游戏里没有这张表。
整个卷 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 轮。这个不对称后面会长成一整个流派。