卷 II · 记账CH 07深度 7/24

一个格子到底值多少钱

奖励是当场结清的,可你要的是「从此以后总共能拿多少」。这一章造那把尺子,并且当着你的面把它算出来——一轮一轮,看价值像水波一样从终点往外扩散。最后拿教科书上印的数字逐位核对。

价值函数 V价值迭代与 AIMA 逐位核对

为什么需要「价值」这个东西

回到第 2 章那个区分:奖励 r 是当场的,回报 G 是从现在到结束的总和。agent 要最大化的是 G。

问题来了:G 只有等这一局打完才知道。而你要做决定是现在

所以我们需要一个预估:站在这个格子上,按最优方式走下去,期望还能拿多少?这个预估就叫价值

◆ 价值函数 V(s)

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,永远分不清。

✎ V 和 Q:两把尺子,差一个「已经决定了」

这本书后面会大量出现 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。