动态规划
第 2 课 · Stage 0 · 动态规划
第二课 · Stage 0

动态规划

承接上一课那两个方程·这一课把方程改写成能跑的更新规则,交出全书的骨架:评估 ⇆ 改进。§11.1 会把它和后面所有内容的关系画出来。

上一课交出的是一组方程——Bellman 期望方程与最优方程,它们说的是「值必须满足什么」。但方程本身算不出任何一个数:未知量在等号两边同时出现,你没法照着它把表填出来。这一课做的就是一件事:把那些方程改写成「更新规则」,反复用,直到表不再变。这就是动态规划——它不是与 MDP 并列的另一套理论,而是把 MDP 的方程变成可执行算法的第一种办法。讲全之后会发现,策略评估、策略改进、策略迭代、值迭代这四个算法都卡在同一样东西上:转移规则

开读之前先摆正一件事:「动态规划」这个词在这一课里跨了两个层级。一个是通用的算法思想,一个是它用在 MDP 上得到的那组具体算法——混着用是这一课最容易卡住的地方。§4 会把两层的边界画清楚,在那之前先照字面读。

这一课的读法:

做什么
§1一张 3×4 得分网格:没有 γ、没有随机、没有策略。只看一件事——为什么能把指数多的路径合并成一张小表
§2–§3把上一课那两个方程摆出来备用:后面每个算法都是把其中一个改写成更新规则
§4画出两个层级的边界,把前面几课的例子逐个归位
§5–§8回到 4×4 仓库,四个算法逐个成形:策略评估、策略改进、策略迭代、值迭代
§9–§10数一数它们各自要做多少次更新,再指出它们共同卡在哪一样东西上

§ 1示例:3×4 得分网格

策略评估、策略改进、值迭代都靠同一件事成立:用 Bellman 递归,把指数多的完整轨迹压成对「状态价值」的反复复用。这件事在 4×4 仓库里和 γ、转移概率、收敛判据混在一起,不容易单独看清。先用一个更小、更硬的例子把它拆出来看一遍,再回仓库。

三个里只有两个在取最大:策略改进和值迭代。策略评估算的是期望——按给定策略把动作加权平均,它没有 max。三者共有的是递归复用那一层,不是取最大那一步。

一张 3×4 的网格,每格写着走进这一格能拿到的分

GRID · 进入该格得分
1−125
03−21
4−1210
左上 (0,0) 是起点,右下 (2,3) 是终点,两格已描边标出

规则四条: 从左上角 (0,0) 出发,出发时先把那格的 1 收下; 每步选一个动作 ↑ ↓ ← →,会出界的动作不能选; 每走进一格就把那格的分再收一次——回头路允许,重复得分也允许 恰好走 K 步,问累计分最大能是多少。

先想清楚该记什么。只记「现在在哪一格」不够,因为还剩几步会改变答案;只记「已经攒了多少分」也不够,因为下一步能去哪只取决于位置。两样都得记,于是记成一张三维表:

dp[k][i][j] = 恰好走了 k 步、此刻站在 (i,j) 时,累计分的最大值。

读法:k已经走过的步数(i,j) 是行、列,都从 0 数起。走不到的格子记 −∞,意思是「k 步到不了这儿」。于是初始只有一格有值:dp[0][0][0] = 1,其余全是 −∞

这个 k 和第 0 课 Vk 里的 k 方向相反。第 0 课的 Vk(s) 是「站在 s、还允许再走 k 步」能拿多少,从终点往回递推;这里的 dp[k][i][j] 是「已经走了 k 步、正站在 (i,j)」攒到了多少,从起点往前推进。两者是同一件事的前向与后向两种写法,本节用前向,是因为「树散开再合并」这个画面在前向下最清楚。别把两个 k 对齐着读。

1.1 路径像树一样散开

从起点开始,每一步、每个位置都往四个方向分叉,树杈的数目按 4k。但格子总共只有 12 个,可达的位置数根本涨不动。把六层全摆出来(· 表示 −∞,即这一步到不了):

dp[0] · 1 格可达
1···
····
····
dp[1] · 2 格可达
·0··
1···
····
dp[2] · 4 格可达
2·2·
·4··
5···
dp[3] · 5 格可达
·3·7
5·2·
·4··
dp[4] · 6 格可达
6·9·
·8·8
9·6·
dp[5] · 6 格可达
·8·14
9·7·
·8·18

逐层数一遍可达格数:1 → 2 → 4 → 5 → 6 → 6,第 4 步就到顶了。到顶的原因有两条:网格一共 12 格;而且每走一步 i+j 的奇偶必翻,所以第 k 步只可能站在奇偶与 k 一致的那 6 格里。等走到第 5 步,树杈已经有 45 = 1024 条,落点却仍然只有 6 个。

最后一层就是答案。终点 (2,3) 到起点的曼哈顿距离正好是 5,所以它第一次可达就是第 5 步——dp[5][2][3] = 18,这就是走满 5 步能拿到的最高分。倒着追一遍每一格是从哪儿来的,路径只有一条:

(0,0) (0,1) (0,2) (0,3) (1,3) (2,3)
逐格得分  1   −1    2    5    1   10   =  18

一步都没浪费:5 步是最短路的长度,而这条最短路又恰好穿过右上角那串高分格(5 和 10)。注意这不是必然的——把 K 放宽到 6,最优解变成 dp[6][2][2] = 20反而不停在终点:规则要求恰好走满 K 步、又不许原地不动,所以第 6 步只能从 (2,3) 走开——往 (1,3) 加 1、往 (2,2) 加 2,于是落在 (2,2)。再放宽到 K = 7,才谈得上折回去把 10 分格再收一次:(2,3)→(2,2)→(2,3),答案跳到 30。

DP 能成立,全部理由就在这个对比里。树杈按 4k 涨,落点被 12 格封死,所以从第 2 步起就必然有大量树杈掉进同一个格子。有多少条路径走到这儿并不重要,重要的只是它们此刻站在哪儿

1.2 同一步到同一格,就地合并

看上面 dp[3] 里高亮的那一格。第 3 步的 (1,0) 上,正好有三条路径撞在一起:

第 3 步到 (1,0) 的三条路逐格得分累计
(0,0) → (1,0) → (0,0) → (1,0)1 + 0 + 1 + 02
(0,0) → (1,0) → (1,1) → (1,0)1 + 0 + 3 + 04
(0,0) → (1,0) → (2,0) → (1,0)1 + 0 + 4 + 05

三条路的走法完全不同,但此刻的处境一模一样:都走了 3 步,都站在 (1,0)。往后能选哪些动作、能走到哪些格、还能拿多少分,三条完全相同——差别只剩已经攒下的那个数。既然如此,攒了 2 和 4 的两条无论后面怎么走都赶不上第三条,当场丢弃:

dp[3][1][0] = max(2, 4, 5) = 5

丢掉的是路径,留下的是一个数。这就是「重复子问题」的实际含义——重复的不是路径,是处境

把这件事写成一层的循环,就得到了这一课所有算法的原型:

for (i,j) in 上一层所有可达位置: # 读 dp[k] for (di,dj) in [↑, ↓, ←, →]: # 试遍所有动作 ni, nj = i+di, j+dj # 落点 s′ if 落点在界内: new = dp[k][i][j] + grid[ni][nj] dp[k+1][ni][nj] = max(dp[k+1][ni][nj], new)

第一行 new = dp[k][i][j] + grid[ni][nj] 是「已经攒的分 + 进新格拿到的分」;第二行的 max 就是合并——谁先算到无所谓,同一个 (k+1, ni, nj) 上只留最大的那个数。一层填完再填下一层,dp[k+1] 只依赖 dp[k],前面的层用完即可丢。

1.3 贪心、暴力搜索、DP

贪心的规则最省事:每一步都挑「进去那格数字最大」的邻居。跑一遍看它去了哪:

(0,0) 1 → ↓(1,0) +0 = 1 → ↓(2,0) +4 = 5 → ↑(1,0) +0 = 5 → ↓(2,0) +4 = 9 → ↑(1,0) +0 = 9

五步拿到 9,而且在 (1,0)(2,0) 之间来回走,离终点一步都没有靠近。失手就在第一步:起点的两个邻居是 (1,0) = 0(0,1) = −1,贪心选 0。可 5 步到终点的最优路是这条:

(0,0) → (0,1) → (0,2) → (0,3) → (1,3) → (2,3) 1 + (−1) + 2 + 5 + 1 + 10 = 18

这条最优路第一步就要先收一个 −1。贪心只看眼前那一格的分,永远不会走这一步,于是也永远走不到右下角那个 10

暴力搜索不会看错。K = 5 时枚举 45 = 1024 条动作序列,其中不出界的有 157 条,逐条算到底再取最大,答案同样是 18。它的问题只有一个:K = 12 时 412 = 16 777 216

做的事K=5 的答案K=5 工作量K=12 工作量
贪心每步只看眼前最大9,而且没到终点5 次比较12 次比较
暴力搜索留下每一条完整路径181 024 条序列16 777 216 条
DP每个「步数 + 位置」只留一个数1872 格156 格

DP 那一行的格子数就是 (K+1) × 12随 K 线性涨,与动作数无关。暴力那一行随 K 指数涨。两者的答案永远相同,因为合并掉的那些路径,本来就不可能是最优的。

DP 的核心不是「枚举所有路径」,是一层层往外传最优值、同一步到同一格就地合并。贪心丢掉的是最优解,暴力留下的是没用的路径,DP 只保留每个「步数 + 位置」上的那一个数。

1.4 前向 dp 与值迭代

值迭代本身就是一种 dp,两者不对立。差别只有两处:表有几张每格装什么

一、表有几张——看「还剩几步」是不是一个变量。本节的 K 是题目规定的,改了 K 答案就变(K=5 最优 18 分停在终点,K=6 最优 20 分反而不停),所以每层是一个独立的答案,得全留着。上一课没有步数上限,γ < 1 让远处的影响按 γk 衰减,逼到某一步「剩几步」就不再影响答案——于是只剩「你在哪一格」,一张表反复覆写就够。现成的证据:仓库里最远的格子离桩 6 步,值迭代 7 遍就精确收敛,再多给步数一个数都不会变。

二、每格装什么——决定你最后拿到什么。

本节的前向 dp dp[k+1][s′] = max( dp[k+1][s′], dp[k][s] + r ) ← 存已经攒到的分,max 在比多条来路 上一课的值迭代 V(s) = maxa [ r + γ V(s′) ] ← 存今后能拿的值,max 在比多个动作
前向 dp值迭代
每格写着走到这儿已经攒了多少从这儿出发今后还能拿多少
max 在比哪条来路攒得多哪个动作值大
最后拿到一条路径一张策略表

前向 dp 每格记的是过去argmax 挑出「我是从哪条路来的、哪条分最高」——倒着追能还原出 §1.1 那条 18 分的路径,但半路上问它下一步往哪,它答不上来。值迭代每格记的是未来argmax 挑出的直接就是「下一步该往哪走」,所以它给的是一张每格都写着往哪走的表:先离线刷到不再变,之后走路只查表。

换个方向递推,dp 也能给出策略。从「还剩 0 步」开始往回推,每格改记「站这儿今后还能拿多少」,argmax 就从来路变成动作——那正是值迭代。而 RL 只能要后者,因为一有随机性,事先排好的那条路就没法照着走了:你规划好 →→→↓↓,第一步就被打滑带偏;而策略表里你飘到的那一格,照样写着该往哪走。

下面几节讲的算法,全是值迭代那一行的变体:§5 策略评估max 换成「照策略加权平均」,§6 策略改进再把它换回 max§7 策略迭代是两者交替做到不动,而 §8 值迭代是把这一交替压到极端。

本节的每个数(各层 dp 表、三条撞在一起的路径、贪心的 9、最优的 18、157 条不出界序列)都由 code/01_dp.ipynb 跑出来,改 gridk 重跑不到 1 秒。


§ 2Bellman 最优方程

§1 用一张 3×4 网格演示了 dp 的两条思想。回到 MDP,先得有方程可写。§2 和 §3 只做一件事:把两个方程摆出来备用。

它们的定义、每个记号的含义、怎么从值函数一步步推出来,上一课已经逐条讲过,这里不重复。之所以要摆出来,是因为后面四个算法各自都是把其中一个方程原样改写成更新规则——不把式子放在眼前,改写那一步看不出改在哪儿。

上一课的结论是这一行,它说的是「最优值必须自洽」:

为了看清后面在改哪儿,把它按功能分成三块——这一课每个算法都只动其中一块:

部件它在管什么
a决策:这一格该选哪个动作。它是唯一让方程非线性的东西,也是这一课后面所有麻烦与技巧的来源
r(s,a)这一步的奖励:当场立刻拿到多少。仓库里恒等于 −1
期望那一项打折后的未来值:落到哪儿、那儿值多少,按概率加权后打 折。确定性时它退化成「查一个数」,打滑时才真的要加权平均

第三块里那个期望,展开就是按转移概率 P(s′|s,a) 加权求和P 就是 MDP 五要素里的「转移」,它保证这个期望能落成一个具体的数)。整行写开就长这样,这是这一课后面所有式子的原型

V(s) = maxa4 个动作里
挑最好的
 { r(s,a)这一步的奖励
这里恒为 −1
 + γ 打折
0.9
Σs′ P(s′ | s, a) · V(s′)打折后的未来值:落点按概率加权 }

这个式子读一遍就够了:站在 s,把 4 个动作各试一遍——每个动作算「这一步当场收多少,加上落点的值按概率加权后打一个 γ 折」——取其中最大的那个数,就是 V⋆(s)

拿一个真数字对一遍。这一课的仓库是确定性的(打滑那版在上一课),所以 P 只取 0 和 1:在格 (2,2) 选「下」,落点 100% 是那一格,其余 13 格的概率全是 0,那个 Σ 里只剩一项活着——

Q((2,2), 下) = −1 + 0.9 × Σs′ P(s′ | (2,2), 下) · V(s′)完整写法:这个 Σ 名义上有 14 项 = −1 + 0.9 × [ 1 × (−1) + 0 × ⋯ + 0 × ⋯ ]只有落点那一项的 P 是 1,其余 13 项被 0 乘没 = −1.9§10 那张 Q 表里「下」那一行,就是这个数

对照上一课打滑版的同一套算术:格 (2,0) 选「下」是 −1 + 0.9 × [ 0.90×(−3.0579) + 0.05×(−3.7536) + 0.05×(−2.9829) ] = −3.7801式子一个字没变,变的只是 P 那一列的数——确定性是「一个 1 加一堆 0」,打滑是「0.90 / 0.05 / 0.05」。这就是为什么那个 Σ 必须一直写在式子里:它不是装饰,是留给 P 的位置

这一课要回答的三个问题,全都藏在这行式子里。
  • 换成「照某个策略办」,方程会变成什么样、还能不能解?—— §3、§5 策略评估
  • 手上有了一张分数表,怎么用它改出更好的策略,凭什么保证不会改坏?—— §6 策略改进
  • 评估与改进交替做,和上一课那个「取最大写回去」是什么关系?—— §7、§8、§9

§ 3Bellman 期望方程

§2 那条式子里策略还没定,外层是取最大。可后面四个算法有三个都要「先给定一个策略再算」——所以还缺一条。

Bellman 期望方程说的是:给定一个策略 π 时,值表必须满足什么。它和 §2 那个最优方程只差外层那一处——那里策略还没定, 在所有动作里挑;这里策略已经给定,外层换成 Σa π(a|s),照它给的概率求平均。对应的算子写作

这个式子读一遍就够了:在状态 s 下,按照策略 π 选择各个动作 a再考虑每个动作可能产生的所有下一状态 s′ 和奖励 r,把「当场的奖励 r + 折扣后的下一状态价值 γVπ(s′)」按概率加权平均

少了一个 ,性质完全变了。右边对 V线性的:一堆加法加一次代入,没有取极值这种「拐弯」的操作。所以策略评估不只是「能迭代求解」,它还能直接解线性方程组——14 个未知数、14 条方程,一次消元就出答案。§5 两条路都会走一遍,看它们对不对得上。

因为带着 是非线性的,你解不出闭式,只能一轮一轮刷。这一个字的差别,是这一课所有算法分岔的源头。

§ 4「DP」的两个含义,以及四个算法的位置

两个方程都摆好了,可它们离「能跑的算法」还差一层。挡在中间的是「DP」这个词跨了两个层级——不先把两层分开,后面四节的关系说不清。

大的那个是通用的算法思想——§1 那张得分网格用的就是它,算法课上做过的 dp 题也在这一层。小的那个是强化学习书里说的 DP:已知模型的 MDP 上的规划,也就是下面四节的四个算法。

4.1 dp 的思想:两条,§1 已经演示过

§1 那张 3×4 网格上做的事,抽出来只有两条——不是新东西,你已经见过了:

① 最优子结构大问题的最优解,可以由子问题的最优解拼出来。
「走满 5 步能拿的最高分」,答案由「走了 4 步、站在某一格的最高分」拼出来——§1.1 那条递推 dp[k+1][s′] = max(dp[k][s] + r) 写的就是这件事。
② 重复子问题同一个子问题会被反复用到,所以只需要算一次、记下来。
第 3 步站在 (1,0) 有三条不同的路,但从这一刻往后,三条路面对的处境完全相同,区别只剩已经攒下的那个数。§1.2 就是这一步:留最高的,其余当场丢弃。
缺了第一条,问题拆不开;缺了第二条,拆开也不省。两条同时成立,45 = 1024 条路径才压得进一张 12 格的小表。

4.2 同一套技术怎么搬到 MDP 上

值函数恰好也满足那两条,所以技术能原样搬过来——不是打比方,是同一个条件在两个问题上都成立:

§1 的网格 dpMDP 上的 dp
子问题是什么走了 k 步、站在 (i,j),最多攒到多少站在状态 s,今后最多还能拿多少
最优子结构写成什么dp[k+1][s′] = max(dp[k][s] + r)Bellman 方程V(s) = maxa[ r + γV(s′) ]
重复在哪儿同一步走到同一格的多条来路经过同一个状态的无数条轨迹——它们此后的处境完全相同
表里存什么每格一个「已经攒了多少」每格一个「今后能拿多少」
递推靠什么停子问题自带「还剩几步」,推完 K 层到头没有天然层次,靠 γ 收住(第 1 课那节收缩性)
Bellman 方程就是「最优子结构」在 MDP 上的写法。最后一行是两边唯一实质的差别,也是上一课为什么要花整节证 γ-压缩的原因。

4.3 从方程到算法:把等号换成赋值

但方程是等式:等号两边同时出现 V,照着它一个数也填不出来。动态规划把等号换成赋值箭头,右边的 V 一律读上一稿——右边就全是已知的数了:

V(s) = ⋯V(s′)⋯  (方程:两边都是未知)  ⟶   Vk+1(s) ← ⋯Vk(s′)⋯  (更新规则:右边已知)

这个式子读一遍就够了:左边下标 k+1、右边下标 k,就是这一课四个算法的共同骨架。剩下的差别只有两处——右边照哪个方程写,以及算完写回哪张表

4.4 为什么一个 dp 变成了四个算法

差别只有一样:待求的量多了一个。§1 的网格只要求一个东西——每格的最高分;MDP 上要求两个值表 V策略 π,而且它们互相定义V 要照 π 才算得出来,π 要照 V 才挑得出来)。两个互相依赖的未知量,就有四种排法

固定谁、求谁右边照哪个方程写回哪张表叫什么
固定 π,求 VBellman 期望方程(外层 Σa π)值表策略评估§5
固定 V,求 π同一条式子的右边,取 argmax策略表策略改进§6
两个轮流固定上面两条交替两张轮流策略迭代§7
不显式存 π,一步同时求Bellman 最优方程(外层 maxa值表值迭代§8
四行算的是同一个量——「这一步的奖励 + 打折后的落点值」,写回值表就叫备份(评估),写回策略表就叫改进。所以值迭代不是和 DP 平级的东西,它是这四个里的一个——这一点最容易搞混。
动态规划(算法思想)── 最优子结构 + 重复子问题 ├─ §1 那张 3×4 得分网格 ── 子问题自带「还剩几步」,推完 K 层到头 └─ MDP 上的动态规划 ──── 把 Bellman 方程当更新规则反复用,靠 γ 收住 └─ 上面那四种排法 ─────────────────── §5–§8

前面几课的例子分别站在哪儿:

在哪一课例子更新式里有没有 max属于哪一个
第 0 课五状态链(每格只有一个动作没有策略评估;候选只有一个,它与值迭代在那条链上完全重合,分不出来
第 1 课4×4 仓库(4 个动作)值迭代——max 到这里才第一次真正起作用
本课 §13×4 得分网格有,但比的是来路在通用 dp 那一支,不属于这四个:它算的是「走到这儿攒了多少」,不是 MDP 的值函数

所以上一课其实是抄了近路:它跳过评估与改进,直接端出「用最优方程、每格更新一次」这一条,因为那是最短、最快能算出一张表的路。这一课把跳过的部分补回来。


§ 5策略评估

4.4 那张表列出了四种排法。从这一节起,一节走一行。先回到 4×4 仓库——画着货架和充电桩那间——把规模摆一遍。

下面每个数字都从这里来16 格去掉 2 个货架 = 14 个状态;右下角的充电桩是终点,进去就不再动、值恒为 0,所以真正要更新的是另外 13 格;每格 4 个动作,合起来 13 × 4 = 52 个状态-动作对。每走一步 −1,γ = 0.9。

这一节走的是 4.4 那张表的第一行:右边照 Bellman 期望方程写,算完写回值表

策略评估:给定一个策略 π,算出它的值表 Vπ——每一格上「照这个策略走下去能拿多少」。它不改策略,策略是别人给的,这一步只负责给它打分。

§3 那条 Bellman 期望方程说的是这张表必须满足什么;把等号换成赋值、右边的 V 读上一稿,就是评估算法:

Vk+1(s) = Σa π(a|s)照策略
平均动作
 Σs P(s′|s,a)照模型
平均落点
 [ r(s,a) + γVk(s′) ]这一步的奖励
+ 打折后的上一稿

这个式子读一遍就够了:站在 s,照策略把每个动作按它的概率过一遍,每个动作再把可能的落点按概率过一遍,各算「这一步的奖励 + 打折后落点在上一稿里的值」,全部加权平均,写成这一格的新值

这一步有两个名字要记住。把全部 14 格挨个更新一遍叫做一次扫描(sweep);而算 Vk+1(s) 时用的是另一个状态的估计值 Vk(s′)、而不是等一整条轨迹跑完的真实回报,这叫自举(bootstrapping)——拿一个估计去更新另一个估计。这两个词后面每一课都会用到。

右下角那个下标是关键:算新表只读旧表Vk),和第 0 课那条备份公式的规矩一模一样。写成算子就是一行:

Vk+1 = TπVk,  收敛到唯一解  Vπ = TπVπ

算子写法读一遍:把上面那一整套动作打包成一个算子 Tπ,输入旧表、输出新表,反复作用到新表和旧表一模一样——那张不再变的表就是 Vπ

而因为 TπV 是线性的,那条 Vπ = TπVπ 也可以不迭代、直接解——把它按矩阵写开:

Vπ = rπ + γPπVπ  ⟹  Vπ = (I − γPπ)−1 rπ

读一遍:14 个未知数、14 条方程,一次矩阵求逆就把整张表解出来,一轮迭代都不用跑。

其中 Pπ 是 14×14 的转移矩阵(第 s 行第 s′ 列 = 照 π 走一步从 s 到 s′ 的概率),rπ 是 14 维的即时奖励向量。γ < 1 保证 I − γPπ 可逆,所以解存在且唯一

5.1 例:给「一律往右」打分

先找个策略来评。挑一个足够简单、能一眼看穿的:不管在哪一格,一律往右走(撞墙就留在原地)。它的分数表长这样:

-10.000-10.000-10.000-10.000-10.000货架-10.000-10.000-10.000-10.000-10.000货架-2.710-1.900-1.0000.000
10 个格子的值正好是 −10,一个不多一个不少。因为「一律往右」在这张图上根本到不了充电桩——除了最底下那一行能一路向右滑进去,其余格子要么撞墙原地打转,要么撞货架卡住。永远走不到终点,就是每一步都亏 1、一直亏下去:
−1 − 0.9 − 0.9² − ⋯ = −1 / (1 − 0.9) = −10
这正是上一课那个 1/(1−γ) 又一次现身:它既是「有效视野」,也是「一直每步 −1 能累计到多少」的下界。折扣把无穷长的失败压成了一个有限的数——这也是为什么 < 1 时,再烂的策略也有一个明确的分数。

5.2 迭代求解与直接求解

路一:反复代入。随便填一张表,然后照着策略一遍遍刷,直到不动。这就是把 反复作用——和上一课一模一样的收缩论证,所以它必然收敛。这次花了 264 轮

路二:直接解方程。既然 是线性的,那 Vπ = TπVπ 就是上面那组线性方程,高斯消元一次出结果。

两条路算出来的 14 个数,逐格最大差 8e-12——就是浮点误差的量级。

既然能一次解出来,为什么还要迭代?因为消元的代价是状态数的三次方(14 格无所谓,百万格就免谈),而且一旦转移规则不在手里,方程根本列不出来——迭代那条路只需要「能采样」,这就是它能一直延续到深度强化学习的原因。

往下会被换掉的地方 这一节的评估靠一样东西成立:Σs P(s′|s,a)。只有把转移概率一个个查出来,那个期望才算得成一个精确的数。可真机上没有人会给你这张表——地面今天多滑、货架有没有被挪走,都不写在任何地方。 于是下一课把期望换成采样:跑完一整幕、用真实拿到的回报 G 当目标(蒙特卡罗),或者只走一步、用 RV(s′) 当目标(时序差分)。P 就此退场。 但采样自己带来一对新麻烦:偏差与方差。蒙特卡罗无偏,可单条轨迹抖得厉害;时序差分抖得小,可它引用的 V(s′) 本身还没算准,是有偏的。 于是又有了在两者之间调的办法:往前看 n 步、TD(λ)、GAE——把「看一步」和「看整幕」之间填满,让你自己选一个折中点。 剩下的限制:不管换成哪一种,评估都还要为每一个状态存一个数。状态一多就存不下——那是 §11.1 里第 ⑤ 支的事(值函数逼近、DQN)。

§ 6策略改进

这一节走的是 4.4 那张表的第二行:右边照的还是同一条式子,但结果写回策略表而不是值表。
策略改进:给定一张值表 Vπ,在每一格重新把 4 个动作试一遍,谁的「这一步的奖励 + 打折后的落点值」最大就改选谁它不改值表,只换动作。

这一步叫贪心改进。它和上一课那次备份算的是同一个量,区别只在结果写去哪里——备份写回值表,改进写回策略。评估与改进恰好互为反向,下一节把它们接成一个循环。写成定义:

π′(s) = argmaxa [ r(s,a) + γ Σs P(s′|s,a) Vπ(s′) ] = argmaxa Qπ(s,a)

这个式子读一遍就够了:在当前状态 s 下,把每一个可能的动作 a 都试着算一遍未来期望回报,然后选出回报最大的那个动作。

方括号里那一项正是 Qπ,所以「改进」就是把 Q 表的每一行取 argmax,没有别的内容。它和 §2 那个 V⋆ 的式子长得几乎一样,差别只在:max 取的是那个最大的,argmax 取的是达到它的那个动作

6.1 例:对「一律往右」改一次

对 5.1 那张分数表做一次改进,10 个格子换了方向

-10.000-10.000-10.000-10.000-10.000货架-10.000-10.000-3.439-2.710-1.900货架-2.710-1.900-1.0000.000
Vπ′(s) ≥ Vπ(s),  对每一个 s 都成立
策略改进定理:贪心改出来的新策略,在每一格上都不会比原来差。不是「平均更好」,是逐格不降——我把 14 个格子逐个比对过,无一例外。

直觉是这样:新策略在第一步上选了个不差于老策略的动作,之后仍旧按老策略走,所以至少不亏;而「之后仍旧按老策略走」这件事可以一路往后推,推到底就得到「一直用新策略也不亏」。这条定理是策略迭代能收敛的全部依据,也是后面 actor-critic 里「actor 朝着 critic 指的方向挪」这件事的原始版本。

注意一个反直觉的细节:这一次改进之后,左上角的值还是 -10.0。10 个格子改了方向,但改完仍然到不了桩——只是从「撞墙」变成了「绕圈」。改进保证不变差,可没保证一步到位。

往下会被换掉的地方 这一节的改进也卡在 P 上,而且卡得更死。看那个 argmax 里的方括号:要比较四个动作,就得先知道每个动作会落到哪、概率各是多少。就算评估那一步已经换成了采样,只要还用 V 挑动作,就仍然绕不开 P 所以第 4 课把 V 换成 Qπ(s) = argmaxa Q(s,a) 只在这一格的四个数里挑最大,不再需要 P代价是表大了 4 倍(52 个数对 14 个数)。 可一旦要自己产生数据,新问题就来了:不试就没有数据。全按当前最优走,没试过的动作永远不会被试,你也就永远不知道它是不是更好。 于是有了 ε-greedy:大部分时候按最优走,留一点概率随便试。但它又带来第三个问题——这样学到的是「含探索在内的自己」(SARSA),不是那个最优策略。 Q-learning 才把这一条解开:把目标里那个采样来的动作换成 max,行为可以随便探索,学到的却是最优策略(Stage 1)。 剩下的限制:argmax 要求动作能一个个枚举。动作一连续就枚举不了——那时只能换成「把策略当参数、沿梯度挪」(Stage 2)。

§ 7策略迭代

这一节走的是 4.4 那张表的第三行:前两节那两步交替做,两张表轮流被改。

写成定义:

π0  —评估→  Vπ0  —改进→  π1  —评估→  Vπ1  —改进→  ⋯  →  π⋆
停机条件:  πk+1 = πk  ⟺  Vπk 已满足 Bellman 最优方程

这条链读一遍就够了:先给一个随便的策略,算出它的值表;照着值表把策略改好;再算新策略的值表,再改……直到某一轮改完策略一个格子都没变,就停。

停机条件那一步值得多看一眼:策略不再变,说明它已经对自己的分数表贪心,即 π(s) = argmaxa Qπ(s,a),于是 Vπ(s) = maxa Qπ(s,a)——这正是 Bellman 最优方程。「策略不动」和「拿到最优解」是同一件事,不是近似。

第几轮评估扫了几遍改了几格改完后左上角的值
126410-10.0
222-10.0
323-10.0
422-4.6856
520 ← 停-4.6856

第一轮为什么要 264 遍,后面几轮只要 2 遍?第一轮评估的是「一律往右」——那张表上有 10 个格子永远到不了桩,值要从 0 一路几何逼近到 −10,刷到 10−12 就得 264 遍。后面几轮接着上一轮那张表往下算,改动只发生在少数几格、而且都是有限步就能走到桩的格子,两遍就到位。

五轮收敛,但前三轮左上角一直停在 −10。信息是一格一格往外传的:每做一次改进,「能到桩」的区域才往外扩一圈,直到第 4 轮才连通到左上角。而第 5 轮改了 0 格——策略已经稳住,这一轮纯粹是确认。

策略迭代有一个重要性质:策略只有有限多种(这里 13 格各 4 选 1),而每轮都严格变好、绝不回头,所以它必然在有限轮内停。值迭代没有这个保证,它是几何逼近,理论上永远差一点点(这间仓库因为是确定性最短路,才恰好在第 7 遍精确收敛)。
往下会被换掉的地方 这一节把评估做到底,代价上面那张表已经写清楚了:光第一轮就要 264 遍扫描。而做到底并没有必要——§8 把它减到一遍,§8.1 再把「每轮评估几遍」抽成一根旋钮 m剩下的限制:这根旋钮在采样的世界里还成立,但两端的代价会重新洗牌。查表时「评估到底」只是多扫几遍;换成采样之后,「到底」意味着要多跑成百上千幕,于是实践中几乎没人再把 m 调到大。

§ 8值迭代

§7 每一轮都要把评估做到底,代价是光第一轮就 264 遍扫描这一节走 4.4 那张表的最后一行:右边改照 Bellman 最优方程写,结果仍旧写回值表——评估被压到只剩一次。

值迭代上一课就用过了,这里再出现一次不是重讲——是它的身份变了。上一课把它当作「解 Bellman 最优方程的一个办法」直接端出来,你只知道它管用;放进这一节的序列里才看得清楚:它是策略迭代把评估减到只剩一次的产物,不是另外冒出来的一个算法。顺带把层级也摆正:dp 是一类做法(把 Bellman 方程当更新规则反复用),值迭代只是其中一个成员,策略评估、策略改进、策略迭代是另外几个。

值迭代就是把评估减到最短:只评估一次,立刻改进;而且因为「评估一次 + 取最大」可以写成同一个动作,它连策略都不用显式存——每一轮直接对值表取 就行。这正是上一课那个

写成定义:把评估那一步的 Σa π(a|s) 直接换成 maxa,评估与改进就合并成了一个动作:

Vk+1(s) = maxa [ r(s,a) + γ Σs P(s′|s,a) Vk(s′) ] = (TVk)(s)
算完之后再取一次 argmax,才得到策略:  π⋆(s) = argmaxa [ r(s,a) + γ Σs P(s′|s,a) V⋆(s′) ]

这个式子读一遍就够了:站在 s,把 4 个动作各算一遍「这一步的奖励 + 打折后落点在上一稿里的值」,直接取最大写回去——不问策略说该走哪,就要最好的那一个。

和评估式逐字对比,差别只有一处Σa π(a|s) 换成了 maxa。这也是为什么值迭代不需要显式存策略——策略藏在那个 max 里,最后才取出来一次。

8.1 广义策略迭代:把「每轮评估几次」当旋钮

把「每轮评估几次」当成一个旋钮 m,前面四节的算法就排在同一条轴上。m 卡在两端之间的那一档有自己的名字——改进策略迭代(modified policy iteration):评估不做到底,只刷 m 遍就去改进

每轮评估次数 m是什么轮数总备份
1(与取最大合并)值迭代7364
2改进策略迭代6468
3改进策略迭代5455
5改进策略迭代5585
10改进策略迭代5910
∞(评估到底)策略迭代53796

这条轴有个名字:广义策略迭代(generalized policy iteration)评估想让值表配得上当前策略,改进想让策略配得上当前值表。两件事互相打断——一方刚做完,另一方的结果就不再准确,于是只能交替往前推。至于每次各做多少,是可以调的。后面的算法只是把这个旋钮调到不同位置:值迭代调到最小,而 Stage 1 之后的方法连「扫全表」都做不到,只能拿采样到的几条转移做很少几次评估。

「备份」在这一课的口径统一成一次动作评估——算一次「r + γ·落点的值」。所以:评估扫一遍 = 13 次(每格只算它选定的那个动作),取最大或改进一遍 = 52 次(每格 4 个动作全试)。这和上一课的说法不同:上一课把「一格上 4 个动作试一遍、取最大写回」整体叫一次备份;这一课改按单个动作计,是为了让没有 max 的评估有 max 的改进能放在同一把尺子上比。换算是固定的:上一课的一次备份 = 这里的 4 次。

往下会被换掉的地方 值迭代把这一课压到了最短,但那个 max 里面仍然坐着 Σs P 把它搬到 Q 上、再把期望换成采样,就是 Q-learning——所以 Q-learning 就是值迭代的采样版,正如 SARSA 是 m = 1 那一档策略迭代的采样版。这两组对应关系值得直接记住。 可 max 一旦作用在带噪的估计上,就会系统性地偏乐观:E[max] ≥ max E(Jensen 不等式)。查表时每个数都是精确的,看不出来;采样之后每个 Q 都带噪,取最大等于专挑那些「运气好、被高估」的动作。 于是有了 Double Q-learning:把「选哪个动作」和「那个动作值多少」拆给两张表,谁也别既当选手又当裁判。后面 Double DQN 就是它的网络版。 再往后,状态一多表存不下,只能把表换成网络(DQN)。而这一换,函数逼近、自举、off-policy 三样凑齐——收敛保证全部失效,就是致命三角。replay buffer 与 target network 这两个补丁是拿来按住它的。 剩下的限制补丁不是定理。这一课那条「必然收敛、还能算出离答案多远」的保证,从换成网络那一刻起就再也没有回来过。

§ 9计算代价:轮数与总更新次数

四个算法都跑完了,可「哪个更快」还没有答案。轮数和更新次数是两回事——这一节把两个数都摆出来。
策略迭代的轮数最少(5 轮),更新次数却多了 10 倍(3796 次备份 vs 值迭代的 364 次)。原因就一句话:「轮」不是一个统一的计量单位——策略迭代光第一轮就要 264 次评估扫描(把「一律往右」那张表从头算准),而值迭代的一轮只有一次。

教科书说「策略迭代收敛轮数少」并没有错,错的是把它读成「策略迭代更快」。真要比,只能比总备份次数。这间仓库里最省的是值迭代(364),中间档的 m 紧随其后(455 / 585),评估到底最贵(3796)。这个排序会随评估有多贵而变:评估便宜时中间档常常反超;评估一贵、每次都要扫全表,把它减到一次的值迭代就最省。

这条轴还有一个更重要的下游:后面 Stage 2 的 actor-critic,骨架就是 m 很小的策略迭代——critic 只刷几步就交给 actor,actor 也只改一点点,交替进行。你在这张表上看到的权衡,会原样出现在那里,只不过评估从「扫全表」换成了「采几条轨迹」。

§ 10三种更新都依赖转移模型 P

§9 比的是代价,这一节比的是前提:把四个算法放在一起,看它们共同依赖什么。

策略迭代是评估与改进的交替,所以真正不同的更新只有三种:评估、改进、值迭代。这三种共同依赖的东西只有一样:转移模型 P——每个动作会把你带到哪儿、概率各是多少。这一节说明为什么三种更新一个都绕不开它,以及绕开它的唯一出路。

回头看改进那一步,把它完整写出来:

要选出最好的动作,你必须知道每个动作会把你带到哪儿、概率各是多少。这一课的三种更新——评估、改进、值迭代——没有一件能绕开这个 P。而真机上没有人会给你 P。

10.1 动作值函数 Q

解法用的不是新东西Q 前面两课都出现过——上一课还在仓库里算过一格。这里只是把它的一个性质拿来用——与其存「这一格值多少」,不如直接存「在这一格做这个动作值多少」

存什么要选动作时怎么办
V(s)
14 个数
把每个动作的落点找出来、按概率加权、加上这一步的奖励,再比大小 —— 需要 P
Q(s,a)
52 个数
在这一格的 4 个数里挑最大的 —— 不需要 P

代价是表变大:值表一格一个数,Q 表一格四个数——13 个非终点格各 4 个动作,52 个数对 14 个数。换来的是选动作这件事不再需要模型。仓库里格 (2,2) 那一行是这样的:

动作 aQ(s,a)优势 A(s,a) = Q − V
-3.439-1.539
-1.90.0 ← 最优
-3.439-1.539
-2.71-0.81
优势 A 把「这个动作比最好的差多少」直接标了出来。最优动作的 A 恰好是 0,其余全是负的——所以「最优」在 Q 表上就是「这一行里 A = 0 的那一列」,一眼可辨,完全不用碰转移规则。

三者的关系也就清楚了:

Stage 1 要做的,就是把 Q 里的数从「算出来」换成「撞出来」:不再查 P,而是真的走一步、看看落到哪、拿到多少,然后把 Q 往那个方向挪一点。这三种更新里唯一被原样保留下来的,是「评估 ↔ 改进交替」这个骨架;而它得以保留靠的正是 Q。

§ 11本章小结

  1. Bellman 最优方程里只有三样东西:取最大(决策)、这一步的奖励、打折后的未来值。取最大是唯一让它非线性的部件。
  2. 把取最大换成「照策略办」,方程就变成线性的——所以策略评估既能迭代,也能一次解出来(这里两条路差 8e-12)。
  3. 永远到不了终点的策略,值是 −1/(1−γ) = −10。折扣把无穷长的失败压成一个有限的数。
  4. 策略改进定理保证逐格不降,但只保证不变差、不保证一步到位——这一课改进一次之后,左上角依然是 −10。
  5. 值迭代 = 只评估一次就改进的策略迭代。轮数少不等于算得少:策略迭代 5 轮、3796 次备份,值迭代 7 轮、364 次备份。
  6. 三种更新都卡在同一个 P 上。把它吸收进 Q,选动作就不再需要模型——这是通往下一课唯一的门。
  7. DP 是后面所有方法的模板,不是一个会被淘汰的老算法。它的两个限制——要有完整的 P每次扫描都要碰遍整个状态空间——决定了后面每一步在改什么:蒙特卡罗去掉模型,改用整条轨迹的真实回报;时序差分留下自举,但只用一条采样的转移;Q-learning 把这里的最优备份原样搬到采样出来的动作价值上换掉的是「怎么拿到那一步的信息」,没换的是这一课的更新骨架。

11.1 这一课在整本书里的位置

这一课交出的不是四个算法,是一副骨架:在一个 MDP 上,用某种数据,反复做「评估 ⇆ 改进」。后面所有的方法——一直到 RLHF——都没有换掉这副骨架,只是把其中某一处换掉。整本书一共只换六处:

图里出现的三个记号,都是 §2 那个 MDP 五元组 (S, A, P, r, γ) 里的成员:P(s′|s,a)转移概率——在状态 s 选了动作 a 之后,下一格恰好是 s′ 的概率;r(s,a)奖励函数——这一步当场拿到多少;π策略——每一格该走哪个动作。 读作「P hat」,表示从数据里估出来的那张转移表,不是真的 P

整本书都在做同一件事在一个 MDP 上,用某种数据,反复做「评估 ⇆ 改进」下面六处,每换掉一处就长出一类新方法,也多一类新麻烦① 转移概率 P 是否已知已知动态规划:策略评估 / 改进 / 策略迭代 / 值迭代第 2 课未知,只能采样把期望换成真跑出来的样本:蒙特卡罗、时序差分、SARSA、Q-learning第 3 课起未知,先估一个 P̂从采样到的转移里数出一张转移表 P̂——这一步叫学模型专题估出 P̂ 之后把 P̂ 当成真的 P,照 §5–§8 跑动态规划:Dyna、PILCO、Dreamer专题② 值函数 V 怎么估:目标看多远、每轮刷几遍目标看多远一步(TD(0))· n 步 · 整幕(MC)· 加权平均(TD(λ)、GAE)第 3 课 / Stage 1做几遍再改进旋钮 m:1 遍是值迭代,∞ 遍是策略迭代第 2 课 §8.1值函数只刷几步就交给改进actor-critic 里的 critic 就是这么用的Stage 2③ 策略 π 怎么改对值表 V 取 argmax策略改进、值迭代——但挑动作要查 P第 2 课对 Q 取 argmax,留探索ε-greedy、SARSA——挑动作不再要 P第 4 课不取 max,把 π 当参数沿梯度挪策略梯度、A2C/A3C——动作连续时只能这样Stage 2限制新旧策略别差太远TRPO、PPO、GRPO——一次改太多会把策略改崩Stage 2④ 更新用的转移数据 (s, a, r, s′) 从哪来用当前这个策略自己采on-policy:SARSA、REINFORCE、PPO——数据用一次就作废第 4 课 / Stage 2用旧策略采的、别人采的off-policy:Q-learning、replay buffer、重要性采样Stage 1只有一份录好的数据,不能再采离线 RL:BCQ、CQL、IQL进阶没试过的动作怎么试到探索:ε-greedy、UCB、内在奖励进阶⑤ 值函数与策略用什么存每个状态存一个数表格法——能印在纸上,收敛有保证第 0–4 课用一个函数近似地存值函数逼近、DQN 家族——状态可以很大甚至连续Stage 1每个状态存回报的整个分布C51、QR-DQN、IQN——不只存它的均值Stage 1策略 π 本身也用网络存策略网络——动作连续时唯一的出路Stage 2代价函数逼近 + 自举 + off-policy 凑齐 = 致命三角,收敛保证全失效Stage 1⑥ 奖励函数 r 从哪来人手写的前面全部——仓库里那个「每走一步 −1」就是手写的第 0–4 课从人的示范里反推行为克隆、逆强化学习进阶从人的偏好里学先用偏好数据训一个奖励模型,再拿它当 r:RLHF、DPO专题把「多探索」写进目标最大熵 RL、SAC——奖励里额外加一项策略的熵Stage 2

源文件 docs/assets/gpi-skeleton.drawio(draw.io 打开可改)。

这六支不是并列的六个话题,是有先后的。前三支(转移概率、值函数怎么估、策略怎么改)是骨架内部的事,这一课把 ① 的「已知」那一行做完了;后三支(数据、表示、奖励函数)是骨架外部的前提每换掉一条,就多一类新方法,也多一类新麻烦

松开哪一处换来什么代价 / 新问题在哪
① 转移概率 P 不再已知不用再要那张转移表,能上真机期望只能靠采样估——偏差与方差第一次登场第 3 课
③ 策略改进改成对 Q 取 argmax连挑动作都不用 P 了必须自己制造数据,于是要探索;学到的是「含探索在内的自己」第 4 课
④ 数据可以来自别的策略旧经验能反复用(replay)、别人的数据也能学行为与目标分家,max 作用在带噪估计上会系统性偏乐观Stage 1
⑤ 值表换成函数状态可以很大甚至连续,见过一个能泛化到附近和 ①④ 凑齐就是致命三角——收敛保证全部失效Stage 1
③ 策略改进换成沿梯度挪动作连续也能做,策略可以是随机的方差极大、on-policy、步子迈大了策略会崩Stage 2
⑥ 奖励函数 r 也不再给定写不出奖励函数的任务也能做(示范、偏好)奖励模型本身会被策略钻空子进阶 / 专题
把这张表当成后面每一课的读法:先问它松开了哪一处,再问它拿什么去补新出来的麻烦。第 3 课不是「又来两个新算法」,是松开①;DQN 不是「加了个神经网络」,是松开⑤之后拿三个补丁去按住致命三角骨架从这一课起就没再变过——这是第 8 条那句「DP 是后面所有方法的模板」的准确含义。
这本书剩下的部分怎么读 到这里为止,全部保证都还在:解唯一、必然收敛、离答案多远算得出来。代价是三个前提——P 已知、状态能枚举、动作能枚举。后面每一课都是在拆其中一条,然后收拾拆完之后的后果。 先拆「P 已知」(第 3 课):期望换成采样,换来能上真机,代价是偏差与方差。 再拆「挑动作也得查 P(第 4 课):V 换成 Q,代价是必须自己探索,而探索又让你学到「含探索的自己」;Q-learning 用一个 max 把这一条解开,代价是最大化偏差。 再拆「状态能枚举」(Stage 1):表换成网络,换来连续状态,代价是致命三角——而这一步同时把前面拆开的两条也牵了进来,三样凑齐才出事。 最后拆「动作能枚举」(Stage 2):argmax 换成沿梯度挪,换来连续控制,代价是方差极大、数据用一次就作废。 更外面还有两层:奖励也写不出来时(模仿学习、逆强化学习、RLHF),以及模型不给、只好先从数据里估一张转移表 P̂ 出来时(Dyna、Dreamer)——后者估完 P̂ 就又回到这一课的四个算法。 所以这一课不是「老方法」,是那把尺子。后面每一个技巧要么在补它被拆掉的某一条,要么在告诉你补不回来。
下一阶段(Stage 1)把转移概率 P 彻底拿走,只留下一条条真跑出来的轨迹。你会看到本课的「评估 ↔ 改进」骨架怎么在只有样本的条件下继续成立,而这一课的 V 与 Q 正好当那把量误差的尺子。
本课所有数字与插图都由随附的 code/01_dp.ipynb(§1 那张 3×4 网格的逐层 dp 表、贪心与暴力的对照)和 code/grid_dp.py 生成(策略评估的两条路、策略改进定理的逐格验证、策略迭代与值迭代的备份计数、Q 表)。整跑不到 1 秒。
↑ 回到顶部