一个格子到底值多少钱
奖励是当场结清的,可你要的是「从此以后总共能拿多少」。这一章造那把尺子,并且当着你的面把它算出来——一轮一轮,看价值像水波一样从终点往外扩散。最后拿教科书上印的数字逐位核对。
为什么需要「价值」这个东西
回到第 2 章那个区分:奖励 r 是当场的,回报 G 是从现在到结束的总和。agent 要最大化的是 G。
问题来了:G 只有等这一局打完才知道。而你要做决定是现在。
所以我们需要一个预估:站在这个格子上,按最优方式走下去,期望还能拿多少?这个预估就叫价值:
V(s) = 从状态 s 出发,按最优策略走下去,能拿到的期望回报。
注意三个词:
- 期望——因为世界有噪声(那 20% 的滑动),同一个策略每次结果不同,我们要的是平均。
- 回报不是奖励——它包含了后面所有步。
- 最优策略——V 有很多个版本,每个策略一个。这里说的是最优那个,记作 V*。
为什么它有用?因为一旦你有了 V,最优策略就是免费的:站在任何一格,看看四个方向各会落到哪儿,挑期望价值最高的那个走。把「一整局怎么走」这个问题,压缩成了「这一步往哪走」。
怎么算它
价值有个自指的性质:一个格子的价值,取决于它邻居的价值。
这看起来像个死循环——要算 A 得先知道 B,要算 B 得先知道 A。但它其实是有解的,而且解法朴素得让人意外:
V(s) ← max Σ P(s'|s,a) · [ r + γ V(s') ]
a s'
把这一行当成一个赋值语句,对所有格子反复执行,直到不再变化。
这就是价值迭代。没有别的了。
为什么它会收敛?因为终止状态是锚——它的价值确定是 0(没有未来),而走进它的那一步奖励是确定的 +1 或 −1。于是紧挨着终点的格子第一轮就能算准,然后是它们的邻居,然后是邻居的邻居……信息从锚点往外传。
看它跑
第 0 轮全是 0.000——它对这个世界一无所知。看价值从哪里先亮起来,往哪个方向扩散。
跑到底之后,最下面会出现一张与教科书的对照表。
三件值得停下来看的事
① 价值是从终点往回长的
第 1 轮,只有紧挨着 +1 和 −1 的那几格有了非零值。第 2 轮,它们的邻居也有了。像水波,或者像一滴墨在纸上洇开。
这个画面值得记住,因为它解释了后面很多事:
- 为什么稀疏奖励那么难(第 21 章):奖励只有一个点,波要传很久才到得了起点。如果起点离终点两百步,价值信号得传两百轮。
- 为什么 Q-learning 收敛慢(第 12 章):无模型的时候,波每传一格都需要 agent真的走过那一格。
- 为什么「先给个中间目标」有用:多插几个锚点,波就不用传那么远了。
② 收敛用了 46 轮,但策略早就不动了
这个格子世界只有 11 个非终止状态,价值迭代跑了 46 轮才把最大变化压到 10⁻¹²。
但如果你在第 15 轮左右停下来看策略——那些箭头早就定型了。后面三十轮全是在磨小数点后面第八位、第九位。
这个观察很重要,下一章和下下一章都要用:你要的是策略,不是价值。价值只是拿来推策略的中间品,磨得再精细也不会让箭头改变方向。
③ 最优策略里那个反直觉的细节
看收敛后左下角那一行的箭头。起点右边那两格指的是 ←——往左,也就是背对终点。
这不是 bug。它们在绕开那个 −1:往上绕一大圈虽然多走五六步(多付 −0.24 左右),但完全避开了「被滑进 −1」的风险。
上一章你已经看到了:把 γ 调到 0.9,这个箭头就翻过来了。耐心决定路线。
和教科书逐位核对
这里是这本书最看重的一件事。
上面那个 4×3 的世界不是我编的,它是 Russell & Norvig《人工智能:一种现代方法》第 17 章的标准例子,它的价值在书上是印出来的(图 17.3)。
这就给了我们一件宝贵的东西:一个外部基准。没有它,你根本不知道自己的实现对不对——因为一个算错的价值迭代照样会收敛,照样给出一张好看的图(第 5 章那个「终止状态价值算两遍」的 bug 就是这样:它给出 1.84、1.89、1.94,看起来完全正常)。
这台引擎算出来的,和书上印的:
# 这台引擎(γ=1,每步 -0.04,噪声 0.2) 0.8116 0.8678 0.9178 +1 0.7616 ▓▓▓▓ 0.6603 -1 0.7053 0.6553 0.6114 0.3879 # AIMA 图 17.3 印的 0.812 0.868 0.918 +1 0.762 ▓▓▓▓ 0.660 -1 0.705 0.655 0.611 0.388
九格全中,误差小于 0.001。起点那一格是 0.705,左上角 0.812,右上角 0.918,右下角 0.388。
策略也和图 17.2(a) 一致,包括上面说的那个「绕远路」的细节。
因为这本书后面还有二十几章的数字,全都建立在这台引擎上——第 12 章说「Q-learning 学到了最优解」,靠的就是拿它当参照;第 13 章说「最优回报是 −13」,也是它算的。
如果这台引擎是错的,后面每一个数字都不能信。
所以这本书的验证脚本里,第一条断言就是这九个数。它挂了,整本书就该被打回去重写。
顺带说:这条纪律在你自己做 RL 的时候同样管用。先在一个有已知答案的小问题上验证你的实现,再去跑真问题。否则你调的是超参还是 bug,永远分不清。
这本书后面会大量出现 Q,先把它和 V 的关系钉死:
- V(s):站在 s,还没决定做什么,期望能拿多少。
- Q(s,a):站在 s,已经决定做 a 了,期望能拿多少。
V(s) = max Q(s,a) Q(s,a) = Σ P(s'|s,a)[ r + γV(s') ]
a s'
它们装的是同一份信息,但 Q 更好用,原因只有一个,而且很实际:
有了 V,你还得知道 P 才能选动作(要算「往这边走会落到哪儿」);有了 Q,你直接挑最大的那个就行。
而 P 正是第 5 章说过的、你永远拿不到的那个东西。所以下一卷所有的无模型算法都学 Q,不学 V。Q-learning 的名字就是这么来的。
这是整本书里唯一一个你真的应该自己敲一遍的算法——它短,而且敲完之后卷 II 就通了:
import numpy as np
def value_iteration(P, n_states, n_actions, gamma=0.99, theta=1e-8):
"""P[s][a] -> [(prob, next_state, reward, done), ...] Gymnasium 的格式"""
V = np.zeros(n_states)
while True:
delta = 0
for s in range(n_states):
v_old = V[s]
V[s] = max(
sum(p * (r + gamma * V[s2] * (not done))
for p, s2, r, done in P[s][a])
for a in range(n_actions)
)
delta = max(delta, abs(V[s] - v_old))
if delta < theta:
break
# 从 V 里读出策略
policy = [
np.argmax([sum(p * (r + gamma * V[s2] * (not done))
for p, s2, r, done in P[s][a])
for a in range(n_actions)])
for s in range(n_states)
]
return V, policy
注意那个 * (not done)——那就是第 5 章说的「终止状态价值是 0」。少了它,你会得到一组整体偏高的价值,而且程序不会报任何错。
拿 gym.make("FrozenLake-v1") 跑一下,env.unwrapped.P 就是上面那个 P。
价值函数在那个学走路的小人身上长什么样?
它是一个函数:输入十几个实数(关节角度、角速度、躯干倾角),输出一个数。这个数说的是「以这个姿势站着,接下来大概还能拿多少分」。
你其实可以想象出它的形状:躯干接近直立、重心在支撑脚上方、速度适中 → 价值高;躯干倾角超过某个角度 → 价值断崖式下跌,因为从那儿开始基本救不回来了。
那道断崖,就是这个 agent 学到的「危险」的定义。而它是从几万次摔倒里算出来的,没有人写过一行「不要摔倒」。
这一章的一句话
价值是「从这儿开始还能拿多少」的预估,它从终点往回一圈圈长出来。有了它,「一整局怎么走」就退化成了「这一步往哪走」。
下一章:把上面那个赋值语句拆开算一遍。它有个名字,叫贝尔曼方程,而整门学科剩下的部分,都是在想办法绕过它右边那个你还没有的 V。