卷 II · 记账CH 05深度 5/24

把游戏写成五元组

强化学习的论文开头几乎都有一句「我们把问题建模为一个 MDP,定义为五元组……」。这一章把那五样东西一个个摊开。摊开之后你会发现,最重要的信息不是它们是什么,而是其中有一样你永远拿不到

MDP 五元组转移概率 P马尔可夫性

五样东西

MDP = ( S , A , P , R , γ )
符号叫什么是什么谁给的
S状态集所有可能的处境环境设计者
A动作集所有可以做的选择环境设计者
P转移概率在 s 做 a,落到 s' 的概率世界本身
R奖励函数这一步给多少分
γ折扣因子未来的分打几折

看第四列。五样里有两样是你写的——而这本书认为,那两样才是真正的工作量所在。

把 P 摊开看一眼

下面这台把上一章那个格子世界的转移概率表完整摊开了。点任意一格,选一个动作,它会告诉你「做完之后会落到哪儿、各有多大概率、各拿多少分」。

三个值得停一下的细节

① 80/10/10:这个世界不完全听话

你选 ↑,只有 80% 真的往上走,左右各 10%。这个设定不是为了刁难你,它模拟的是所有真实系统都有的东西:执行误差。机器人的电机有滞后,游戏有帧率抖动,网络有丢包。

它带来一个重要后果:你不能只记一条最优路线。因为你随时会被推到路线之外,所以你必须对每一个可能到达的状态都有对策。这就是为什么策略是一个函数 π(s),而不是一串动作序列。

顺带说,这也是「策略」和「计划」的分野:计划是一串动作,策略是一套应对。在有噪声的世界里,只有后者活得下来。

② 撞墙留在原地——而这居然是有用的

demo 里选一个靠墙的格子,往墙的方向走,你会看到概率里有一项是「回到自己」。

看起来是个无聊的边界处理,但它有个漂亮的用途:靠墙走可以「对冲」滑动。想往上走又怕被滑到右边掉进 −1?那就选一个撞墙的方向——被滑到墙上的那 10% 会原地不动,等于白付 0.04 但至少没出事。

第 7 章那个最优策略里就有这一手。而它不是任何人设计的,是从这张表里算出来的。

③ 奖励挂在「转移」上,不是挂在「状态」上

这是个容易含糊过去、但会实实在在影响数值的地方。这本书统一采用这个口径:

r(s, a, s') = 每步代价 + 终点奖励(s')

也就是「走一步就付 −0.04,落到终点格再另外结算 +1−1

换个口径也行(比如把每步代价记在「到达」上),但所有价值会整体差一个常数。选这一版是因为它能和两本教科书同时对上:AIMA 那个 4×3 世界(每步 −0.04),和 Sutton & Barto 那个悬崖世界(每步 −1,掉崖额外 −99,合起来正好 −100)。第 7 章和第 13 章会逐位核对。

⚠ 一个真的会写错的地方:终止状态的价值是 0

写价值迭代的时候,很多人(包括我写这本书的第一版)会顺手把终止状态的价值初始化成它的奖励,比如 V[goal] = +1

这会把那个 +1 算两遍:一遍在「走进终点」那一步的奖励里,一遍在终点自己的价值里。我第一次跑出来的价值是 1.84、1.89、1.94,而教科书上是 0.81、0.87、0.92——整整高出一大截。

正确的想法很简单:终止状态没有未来,所以它的价值就是 0。+1 不在它身上,在「走进它」那一步的奖励里。

这类 bug 有个共同特征:程序不会报错,曲线照样收敛,只是答案是错的。这就是为什么第 7 章要拿教科书的数字当锚——没有外部基准,你根本不知道自己算错了。

马尔可夫性:那个「M」

MDP 的第一个字母是 Markov。它断言的是这么一件事:

◆ 马尔可夫性

下一步会发生什么,只取决于当前状态和当前动作,与你是怎么走到这儿的无关。

P(s' | s , a , 之前所有历史) = P(s' | s , a)

换句话说:当前状态已经包含了做决策所需的全部信息,历史可以扔掉。

这不是对世界的一个描述,而是对「状态」这个词的一个要求。世界不会自动满足它,是你在设计状态的时候必须让它满足。

违反它的样子很具体:

  • 状态只有「当前这一帧」→ 看不出球在往哪飞 → 补:堆四帧(DQN 的做法)
  • 状态只有「当前关节角度」→ 看不出腿在往哪甩 → 补:把角速度加进状态
  • 扑克里状态只有「我的牌」→ 看不见对手押了多少 → 补:把下注历史编码进状态

当你补不上的时候(比如对手的底牌你真的看不见),这个问题就叫 POMDP(部分可观测 MDP)。它难得多,标准应对是用一个循环网络(LSTM/GRU)把历史压成一个隐状态,人为造一个「够用的状态」出来。

✎ 「马尔可夫」不等于「没有记忆」

一个常见的误读:马尔可夫性意味着 agent 不能记住任何东西。

不是。它说的是状态里必须包含你需要记住的东西。如果你需要记住「三步之前有没有拿钥匙」,那就把「有没有钥匙」放进状态——放进去之后,这个 MDP 依然是马尔可夫的。

所以马尔可夫性不是一条限制,是一条设计纪律:它逼你把「做决策需要什么」显式地想清楚,然后全部写进状态里。

然后是这一卷真正的转折

上面那张转移概率表,我们叫它模型。有了它,接下来三章的算法都能跑——它们统称动态规划,能算出精确的最优解,而且不需要试错一次。

问题是:

◆ 真实的游戏里,没人会给你这张表

你能拿到 Atari 的转移概率表吗?「在这个画面按左,下一帧变成那个画面的概率是多少」——这张表不存在,游戏的作者也没有它。

QWOP 呢?那是一个物理引擎。理论上确定性的,实际上你不可能把它写成一张 P 表。

你唯一能做的是玩一下试试。那正是卷 III 的全部内容。

那为什么还要花三章讲动态规划?三个理由,都很实在:

  1. 它给出参照答案。第 12 章你会看到 Q-learning 从零学出来的策略,和这一卷算出来的最优解完全一致——如果不知道正确答案长什么样,你怎么判断它学对了?
  2. 贝尔曼方程在这里最清楚。它是整门学科的心脏,而在有模型的情况下,它是一个可以直接执行的赋值语句。后面所有算法都是它的近似版本。
  3. 模型有时候真的有。棋类游戏的规则你完全知道;机器人有物理仿真器。这一支叫基于模型的强化学习,AlphaZero 和 MuZero 都在这一支上,而且它的样本效率高出一大截。
⌗ 换成真机:什么时候你真的有 P

Gymnasium 的 toy_text 那几个环境把 P 表直接暴露出来了,可以拿来验证你自己写的动态规划:

import gymnasium as gym
env = gym.make("FrozenLake-v1", is_slippery=True)

# env.unwrapped.P[state][action] -> [(prob, next_state, reward, done), ...]
print(env.unwrapped.P[0][1])
# [(0.333.., 0, 0.0, False), (0.333.., 4, 0.0, False), (0.333.., 1, 0.0, False)]

注意 is_slippery=True 那一项——那就是本章的 80/10/10,只是 FrozenLake 更狠,是 1/3 均分。把它设成 False,环境就变成确定性的,题目会简单一个量级。

拿这个练手很合适:先自己写一版价值迭代,跑 FrozenLake,再对着别人的实现核对。这一步做过之后,后面所有算法你都会有底。

↩ 回到那个视频

那个学走路的小人,它的 MDP 是这样的:

  • S:十几个连续实数(角度、角速度、躯干倾角……)。无穷多个状态——第 14 章会因为这个把整套方法换掉。
  • A:四个关节的力矩,也是连续的。
  • P:物理引擎。它存在,但你写不出来,也没法枚举。
  • R作者写的那一行。这本书的全部主题。
  • γ:作者设的一个数,通常 0.99。下一章你会看到它有多大威力。

五样里,你实际写的是 R 和 γ,选的是 S 和 A 的表示,而 P 是你完全碰不到的。

这一章的一句话

MDP 就五样东西,其中两样是你写的、一样是你永远拿不到的。而拿不到的那一样,正是接下来两卷的分界线。

下一章:那个 γ。它常被当成一个「为了让数学收敛」的技术性参数,但你会看到,把它从 1.00 拨到 0.90,agent 会当场换一条路走。