贝尔曼方程与价值函数,这两个词几乎出现在每一份强化学习入门资料里,也几乎是初学者的第一道分水岭。我第一次读到 V(s) = E[R + γV(s')] 时,第一反应是“这数学看着不难,可它到底在算什么?”后来在完整推导了几轮状态价值函数的迭代之后才彻底明白:它是在用一种递归的方式回答“当前状态到底值多少钱”。这篇笔记面向刚看完马尔可夫决策过程、准备深入强化学习核心公式的人,我会从折扣因子讲到状态价值和动作价值,再把贝尔曼期望方程和最优方程拆开揉碎,最后用一个3×3网格世界手算一遍,把我踩过的坑一并交代清楚。
1. 从回报到价值函数:强化学习真正在“估”什么
1.1 为什么只看单步回报不够?折扣因子的三重含义
初学强化学习时,最容易出现的误区是:把“奖励最大”等同于“单步奖励最大”。如果在某个状态下,往左走能拿+0.5,往右走能拿+0.2,那就无脑选左?这不对。因为强化学习关心的是从当前时刻开始,到终局为止的累积回报,而不是眼前这一口的甜头。
所以我们需要定义“回报”(Return),也就是从 t 时刻开始往后所有折扣奖励的总和:
- G_t = R_{t+1} + γR_{t+2} + γ²R_{t+3} + … = Σ_{k=0}^{∞} γ^k R_{t+k+1}
这个 γ 就是折扣因子,取值在 [0, 1] 之间。它在数学、直觉和工程三个层面各有含义:
- 数学层面:当 γ < 1 且每一步奖励有界时,无穷级数收敛,G_t 是有限值,价值函数才有稳定解。
- 直觉层面:未来的奖励存在不确定性,早拿到的奖励更确定、更有价值;折扣因子天然表达了“眼前的钱比未来的钱更值钱”的偏好。
- 工程层面:γ 控制了价值“往回看多远”。γ 接近 1,模型会关注极其长远的结果;γ 很小,模型会变得短视,只盯着几步内的收益。
我在实际使用中习惯这样理解:折扣因子不仅是数学技巧,它是一种对“远见”的量化。γ = 0 时只在乎当前一步,γ = 0.99 时基本在乎无穷远期的累积收益。设计一个强化学习任务时,如果每一步都耗时较长、不确定性高,我会倾向选 0.9 到 0.95;如果任务是慢速长线的策略博弈,通常会选 0.99 甚至更高。
这能解决什么问题?它解决了“奖励序列长度不一致”的麻烦。有的回合走 3 步就结束,有的走 300 步才结束,直接比较总奖励不公平,但折算成“按折扣因子加权后的累积回报”后,它们有了统一的比较尺度。
1.2 状态价值函数 V(s) 与动作价值函数 Q(s,a):两把不同的尺子
有了回报 G_t,就可以定义价值函数。严格来说,回报是一个随机变量,因为它依赖未来的状态转移和动作选择。价值函数就是回报的期望,用期望消除随机性。
状态价值函数 V^π(s),表示在策略 π 下,从状态 s 出发的期望回报:
- V^π(s) = E_π[G_t | S_t = s]
动作价值函数 Q^π(s, a),表示在策略 π 下,从状态 s 出发、先执行动作 a、之后继续遵循策略 π 的期望回报:
- Q^π(s, a) = E_π[G_t | S_t = s, A_t = a]
两者最核心的关系是:Q 值就是在某个状态执行某个动作的“即时奖励”加上“后继状态的状态价值均值”:
- Q^π(s, a) = Σ_{s', r} p(s', r | s, a) [r + γV^π(s')]
- V^π(s) = Σ_a π(a|s) Q^π(s, a)
为什么需要两把尺子?状态价值 V 解决的是“我现在所处的位置到底好不好”,它不考虑当前动作,只考虑整体局势;动作价值 Q 解决的是“我面前这些具体选项哪个更香”,它是决策时直接比较的对象。我在做实际项目时,V 函数往往用于评估当前局势、计算优势函数,而 Q 函数用于选出具体行为。两个都不能缺。
打一个比方:假期你要决定去海岛还是爬山。V 函数告诉你海岛整体性价比高、风景好、口碑稳定;Q 函数告诉你具体到你这趟出行、结合预算和天气,选海岛更划算。V 是局势评估,Q 是动作选择,方向不同,缺一不可。
2. 贝尔曼方程的本质:把“未来”递归地折叠进“现在”
2.1 从一个关键分解讲起:回报的递归身份
贝尔曼方程之所以漂亮,在于它利用了回报本身的递归结构。按定义,G_t 从 R_{t+1} 开始,而 G_{t+1} 从 R_{t+2} 开始,所以:
- G_t = R_{t+1} + γ(R_{t+2} + γR_{t+3} + …) = R_{t+1} + γG_{t+1}
这个等式看着太简单了,但它是整棵大树的根。把它代进状态价值函数的定义式,就会得到贝尔曼方程:
- V^π(s) = E_π[R_{t+1} + γV^π(S_{t+1}) | S_t = s]
这里的区别在于:原来 V^π(s) 的定义对整个无穷回报求期望,现在只需要对“即时奖励 + 下一步状态的价值”求期望。无穷的前瞻被压缩成了一步,这就是“把未来折叠进现在”。只要知道下一步各个状态的价值,当前价值就立刻被确定了。这个递归结构把“看得很远”和“只需要看一步”统一了起来,是动态规划思想在强化学习里的真正入口。
我初学时的最大卡点在这里:总觉得折叠一步之后,V(S_{t+1}) 本身还是包含未来无穷步的,这不等于又回到原点吗?对,但它不再需要从无穷远处往回推。只要所有状态的 V 都满足这个自洽方程,它们就是“未来一步接一步的标准答案”。与其说它是一个无限递归,不如说它是一个自洽方程组:N 个状态对应 N 个方程、N 个未知数。这从“无穷视角”变成了“有限线性系统”,求解思路完全变了。
2.2 贝尔曼期望方程:在策略 π 下评估世界
把上面的推导细化,就是完整的贝尔曼期望方程。对于给定策略 π,状态价值函数满足:
- V^π(s) = Σ_a π(a|s) Σ_{s', r} p(s', r | s, a) [r + γV^π(s')]
动作价值函数也有自己的期望方程:
- Q^π(s, a) = Σ_{s', r} p(s', r | s, a) [r + γ Σ_{a'} π(a'|s') Q^π(s', a')]
这里有两个数学期望,经常有人混:
- 对动作求期望,权重是策略 π(a|s)。策略说“这个动作有多大概率被选中”,价值就按这个概率做加权平均。
- 对状态转移求期望,权重是环境转移概率 p(s', r | s, a)。环境说“执行 a 之后,有多大概率落到 s' 并拿到 r”,价值就按这个概率做加权平均。
两层期望对齐之后,V^π(s) 就算清楚了。这个方程的作用是“策略评估”:给我一个策略 π,我来回答这个策略到底有多好。它的典型应用场景是:在周四的某公司导航 Demo 里,先把一个规则策略(比如“遇到岔路口右转”)的价值函数算出来,才能知道这个策略比随机走强多少。
实际工程里,策略评估不一定真的解线性方程组,更多是用迭代法反复套公式,直到 V 值变化小于阈值。理论上,γ < 1 时有界奖励可以保证收敛;实践上我们还要关心收敛速度和数值稳定性,这在后面实操部分会展开。
贝尔曼期望方程的“为什么”核心在于:它把“一个策略的好坏”从抽象概念变成可解的数值问题。没有它,你根本没法和别人讨论你的策略到底好在哪里、坏在哪里。
3. 贝尔曼最优方程:价值函数与最优策略的相遇
3.1 期望变最大:一个符号之差
学习到贝尔曼最优方程时,最大的冲击是:好像只是把 Σ_a π(a|s) 换成了 max_a,公式就突然从“评估一个给定策略”变成了“求解最优策略”。这个符号之差,背后是价值逻辑的根本变化。
贝尔曼最优方程长这样:
- V*(s) = max_a Σ_{s', r} p(s', r | s, a) [r + γV*(s')]
- Q*(s, a) = Σ_{s', r} p(s', r | s, a) [r + γ max_{a'} Q*(s', a')]
注意几个细节:
- 期望方程的外层对动作求期望,最优方程的外层对动作求最大值。V*(s) 已经不含策略 π,理论上最优策略是确定性的,所以不再需要加权平均。
- 最优方程里的 V* 是同一个函数出现在等式两边,这意味着“如果我从 s 出发一直按最优方式行动,那当前动作选能让 r + γV*(s') 最大的那个”。
- Q* 方程里的 max 放在了后继值上:执行 a 之后,下一步会从所有动作中选最大的 Q*;这代表你相信未来每一步都会按最优走。
为什么可以这样取 max?这背后是贪心原理与最优子结构的结合。如果存在一个最优策略,那么从任何状态出发,最优策略的后续决策也必然是最优的。强化学习环境满足马尔可夫性,当前状态已经浓缩了历史信息,所以“剩下所有时刻都按最优行动”是唯一合理假设。从数学上,贝尔曼算子是一个压缩映射,反复迭代会收敛到唯一的不动点,这个不动点就是 V*。理论与直觉在这个点是一致的。
3.2 从价值函数到最优策略的落地路径
重点来了:算出了 V* 不等于立刻就能决策。决策时你要比较的是“各个动作谁更能提高累积回报”,而不是只看当前状态好不好。已知 V* 的情况下,最优策略可以写成:
- π*(s) = argmax_a Σ_{s', r} p(s', r | s, a) [r + γV*(s')]
这个式子需要知道转移概率 p,也就是需要“环境模型”。如果能拿到 Q*,决策就更直接:
- π*(s) = argmax_a Q*(s, a)
因为 Q*(s, a) 已经把所有未来最优响应都折进去了,选最大的 Q 就是最优动作,不需要显式知道环境模型。这也是为什么后续各种无模型算法都围绕 Q 值做文章:Q-learning、Double DQN、Dueling DQN,本质都是在逼近 Q*,然后用 argmax 选动作。
在实际项目里,我第一次实现价值迭代时,想当然地以为 V* 收敛了就大功告成,结果发现策略没用对。原因就是 V* 只能告诉“这个状态有多好”,要在已知模型的情况下才能反推动作;如果模型未知,就必须维护 Q* 或者额外学习一个策略。这条路线图想清楚之后,很多模型的代码结构就通透了。
4. 手算小网格世界:3×3环境下的贝尔曼方程实操
4.1 环境参数与转移规则设定
理论讲完必须上手算。我一直认为,贝尔曼方程不亲手算过一遍迭代,永远是纸上的符号体操。下面设计一个最简单的 3×3 网格世界:
- 状态:9 个格点,s1 到 s9,按 3 行 3 列排列。
- 动作:上、下、左、右,每个动作等概率执行。
- 终点:s9(第 3 行第 3 列),进入后回合终止。
- 奖励:进入终点给 +1;其他所有非终点的转移给 -0.1,代表每步的行动代价。
- 边界:越界动作停留在原地。
- 折扣因子:γ = 0.9。
- 演示策略:均匀随机策略,每个动作概率为 0.25。
这里选择 -0.1 的步数惩罚,是为了在价值函数中体现出“距离终点越远,价值越低”,否则所有非终点状态的价值可能区分度不够。这听起来是教学细节,但设计奖励时就决定了后续价值函数长什么样。真实项目里也一样,步数惩罚的数值直接影响最优策略是否倾向于尽快到达目标。
4.2 第一次、第二次迭代的手算与解读
用价值迭代的同步更新公式,从 V^0(s) = 0 开始。
第一次迭代,对非终点状态,因为 V^0 处处为 0,所以只需要看一步奖励的期望。计算后得到 V^1:
- s1 到 s5、s7 这些不直接接触终点的状态:V^1 = -0.1
- s6 和 s8:一个向下到 s9,一个向右到 s9,四个动作里包含一个 +1,所以 V^1 = 0.25 × (+1 - 0.1 - 0.1 - 0.1) = 0.175
- s9 是终止状态:V^1 = 0
这轮结果其实不意外,它只反映“一步内能不能吃到终点奖励”。真正的信息在第二轮开始。
第二次迭代,以 s1(左上角)为例,它的两个动作(上、左)会撞墙留在原地,另外两个动作(右、下)分别进入 s2 和 s4。V^2(s1) 的计算过程是:
- 上:-0.1 + 0.9 × (-0.1) = -0.19
- 左:-0.1 + 0.9 × (-0.1) = -0.19
- 右:-0.1 + 0.9 × (-0.1) = -0.19
- 下:-0.1 + 0.9 × (-0.1) = -0.19
- V^2(s1) = 0.25 × (-0.19 - 0.19 - 0.19 - 0.19) = -0.19
再看中心状态 s5(第 2 行第 2 列),四个方向分别进入 s2、s8、s4、s6。注意 s8 和 s6 在 V^1 中已经是 0.175,所以:
- 上到 s2:-0.1 + 0.9 × (-0.1) = -0.19
- 下到 s8:-0.1 + 0.9 × 0.175 = 0.0575
- 左到 s4:-0.1 + 0.9 × (-0.1) = -0.19
- 右到 s6:-0.1 + 0.9 × 0.175 = 0.0575
- V^2(s5) = 0.25 × (-0.19 + 0.0575 - 0.19 + 0.0575) = -0.06625
第二次迭代后,完整 V^2 可以整理成下表:
| 状态 | V^2 值 |
|---|---|
| s1 | -0.19 |
| s2 | -0.19 |
| s3 | -0.128125 |
| s4 | -0.19 |
| s5 | -0.06625 |
| s6 | 0.169375 |
| s7 | -0.128125 |
| s8 | 0.169375 |
| s9 | 0 |
这组数字非常有意思:离终点近的 s6、s8 已经变成正值,中心 s5 次之,角落 s1、s4 最差。价值函数开始“把距离翻译成数值”,这就是贝尔曼方程递归传播信息的效果。手算的价值不在于算出精确数字,而在于体会“未来价值是如何一步步传回当前的”。我强烈建议初学者至少手算一次这种小网格,比盯着代码看一小时都有用。
4.3 用一个小脚本验证迭代收敛
手算到第二轮已经略微繁琐,继续手写全部收敛过程不现实。工程上我们直接用价值迭代脚本完成后续计算。下面是我在本地跑过的完整脚本,结构刻意保持简单:
import numpy as np rows, cols = 3, 3 gamma = 0.9 actions = [(-1, 0), (1, 0), (0, -1), (0, 1)] # 上、下、左、右 V = np.zeros((rows, cols)) terminal = (2, 2) def step(state, action): r, c = state dr, dc = action nr, nc = r + dr, c + dc # 越界则原地不动 if nr < 0 or nr >= rows or nc < 0 or nc >= cols: nr, nc = r, c next_state = (nr, nc) if next_state == terminal: return next_state, 1.0 return next_state, -0.1 for k in range(30): newV = np.zeros_like(V) for r in range(rows): for c in range(cols): if (r, c) == terminal: continue state = (r, c) values = [] for a in actions: ns, rew = step(state, a) values.append(rew + gamma * V[ns]) newV[r, c] = np.mean(values) # 均匀随机策略 V = newV print(np.round(V, 4))脚本里有两个值得注意的工程点:
- 同步更新:每次迭代用上一轮完整的 V 表计算新表,不边算边改。这样可以保证每一步都在解同一个方程,结果更稳定。
- 用 np.mean(values) 对应均匀随机策略;如果改成 np.max(values),就变成求解贝尔曼最优方程的价值迭代。两套方程在代码上只差一个函数,正是数学中“期望变最大”的直观体现。
运行结果会收敛到一个稳定矩阵,越靠近终点的状态 V 值越高,角落状态 V 值最低。看到这个结果再回头读一遍公式,很多疑问会自然消解。
5. 高频翻车现场:贝尔曼方程学习中的常见问题
5.1 期望方程与最优方程混为一谈
这是我在给组里新人讲强化学习时遇到最有代表性的问题:代码里明明在做价值迭代,更新公式却带着策略π;或者明明在做策略评估,却无脑用了 max。一句话判别方式:公式里如果出现策略 π(a|s),那就是期望方程;如果出现 max_a,那就是最优方程。期望方程告诉你“这个策略有多好”,最优方程告诉你“最好的活法长什么样”。
实际操作里另一个更隐蔽的误用是:用期望方程做策略评估时,没有把终止状态单独处理。终止状态的 V 应该恒为 0,并且不再参与更新。如果漏掉这个条件,终止状态会不断累积假价值,收敛结果完全失真。我在小网格例子里专门让 s9 跳过更新,就是这个原因。
5.2 折扣因子选不对,收敛和策略都受影响
γ 是超参数里最“灵活”但最容易被随意设置的一个。我见过有同学直接照抄别人的 0.99,结果在短任务上收敛奇慢,策略还显得过度远视;也有把 γ 设成 0.7 就跑去跑长线任务,最后策略只看眼前几步,完全不敢大胆探索。
实际经验是:
- 任务步数短(几步到几十步),γ 可以取 0.9 左右。
- 长线任务(几百上千步),γ 取 0.99 或更高。
- 如果每一步都有不低的执行成本,γ 太大不仅收敛慢,还会在价值图中制造“拥挤的假高值区域”,影响策略质量。
另一个容易踩的坑:γ = 1 时,如果环境存在循环、且累积奖励不递减,价值函数可能发散。理论上无折扣有限步问题可以用 γ = 1,但工程上我几乎不用,因为数值稳定性太差。绝大多数强化学习框架默认值在 0.9 到 0.99 之间,是有道理的。
5.3 同步更新与原地更新,数值稳定性的坑
价值迭代有两种常见实现方式:同步更新和原地更新。同步更新需要维护新旧两张表,写的代码稍长,但每次迭代都基于完整旧表,行为更可控。原地更新只维护一张表,边算边覆盖,代码短、占用内存小,但迭代顺序会引入偏差,可能让收敛轨迹更“拧巴”,偶尔还会出现局部震荡的观感。
数学上,压缩映射保证两者最终都收敛到相同不动点,但收敛路径不同。我的建议是:调试阶段一定用同步更新,方便对照公式检查每一步数学是否符合预期;等代码稳定、环境超大、内存吃紧时,再考虑原地更新。亲手试过这两种实现后,你会对“为什么教程里总是强调用两个数组”有体感。
还有一个小技巧:判断是否收敛时,不要只看矩阵是否完全不再变化,而要看最大变化量是否在容差内。我在实现中用过:
delta = np.max(np.abs(newV - V)) if delta < 1e-4: break这个小细节可以在长任务上省下大量无效迭代。
个人体会与下一步建议
我最初学贝尔曼方程时,最大的误区是想把公式背熟再去做题,结果背来背去还是绕不清。后来换了思路:不背公式,而是拿着纸笔在网格上一步一步写“站在这个格子,我的出路是什么,每条出路的即时收益加后续价值是多少”,几轮迭代后,期望方程和最优方程的差异自然而然地刻在脑子里了。如果你想真正突破这个章节,强烈建议你也把教程里的环境换成自己的小网格,设一组手算能处理的参数,务必亲自算到第二轮。这个过程里踩的坑,会变成后面理解 Q-learning 和深度强化学习时最扎实的底层直觉。下一步就可以开始看策略迭代和时序差分学习,到时候你会发现,贝尔曼方程这条根,几乎长在所有后续算法的主干上。