前面所有方法都有一份「标准答案」:要么是标签,要么是「这个划分好不好」的分数。 强化学习把这份答案也拿掉,只留一个迟到的分数——走完一串动作之后,才告诉你总共得了多少。 它的核心问题不是「怎么拟合」,是「现在的选择,怎么跟很久以后的结果挂上钩」。答案是值函数: 把将来的回报打折折回现在,然后每一步都挑「折回来最大」的那个。
这一节是第 8 章的重心,也是全书唯一一节「连训练数据都要自己造」的内容。 监督学习的数据是别人给的;这里的数据是智能体自己走出来的——走法不对,数据就没有。 它跟第 5 章是同一件事的两种说法: 5.2 的 A* 是在已知地图上找最短路,这里的地图未知,只能边走边试。连「探索与利用」的张力, 都跟 5.3 遗传算法里「变异率不能太小」是同一类问题。
一、没有答案,只有一个迟到的分数
先把这一节的五个词对齐。任何强化学习问题都由这五样东西组成:
| 要素 | Demo 里对应什么 | 作用 |
|---|---|---|
| 状态 | 网格上的一个格子 | 此刻在哪 |
| 动作 | 上下左右四个方向 | 此刻能做什么 |
| 奖励 | 到终点 +10、掉陷阱 −5、每步 −0.1 | 做对做错的唯一信号 |
| 策略 | 每个格子该往哪走 | 要学的东西 |
| 折扣因子 γ | 隔一步的奖励还算几成 | 多在乎将来 |
| 「每步 −0.1」这个设计很关键。它让「绕远路」有代价,于是智能体不只要到终点,还要尽快到。 | ||
| 没有这一条,它会站在原地转圈。 | ||
| Demo 的起点先用一个「每个格子随便定一个固定动作」的策略试了试: | ||
| 200 次平均回报 −6.00,到终点 0%。这就是「什么都不学」的基线。 | ||
| 强化学习跟监督学习最本质的差别在这儿:监督学习每一步都有反馈(这个预测对不对), | ||
| 强化学习只有一个迟到的总分,而且你还不知道是哪一步的功劳。 | ||
| 这叫信用分配问题——一万步里拿到一分,这一分该记在哪一步头上? |
二、值函数:把「将来」折回现在
答案是把「将来」折成「现在的一个数」。 定义:一个状态的值 = 从这里出发,将来能拿到的所有奖励的折扣和。 折扣就是 γ 的幂次:隔一步的奖励乘 γ,隔两步乘 γ²……于是「越远的奖励越不值钱」, 而且这个无穷级数一定收敛(γ < 1 时)。 有了值,决策就变得极其简单:看哪个动作走完之后的值最大,就走哪个。这就是贝尔曼那一步: 值(s) = max over 动作 [ 奖励 + γ × 值(下一格) ]。 如果规则已知(知道每个动作会到哪儿),反复套这个式子就能算出全部的值。这叫值迭代。 Demo 的 5 乘 5 网格,γ = 0.95,扫 9 遍就收敛了:
| 格子 | 值 | 怎么读 |
|---|---|---|
| 起点 (0, 0) | 6.38 | 从这里出发,将来能拿 6.38 |
| 终点旁边 (4, 3) | 10.00 | 再走一步就是 +10 |
| 陷阱 (3, 2) | 不参与 | 踩进去就结束,没有「从这里出发」 |
| 整条最优路 | 8 步、总回报 9.30 | 10 − 0.1 × 7 |
| 注意「值 6.38」和「总回报 9.30」不是一回事。9.30 是不打折的实际得分; | ||
| 6.38 是把每一步按 0.95 的幂次折回起点之后的数。+10 隔了 8 步,被乘成 6.63。 | ||
| 值迭代能收敛,是因为它是一个「单调收缩」的过程。每次都用旧值算新值,误差被乘上 γ, | ||
| γ < 1 时误差自己就缩没了。这也是为什么 γ 不能等于 1。 |
三、Q-learning:不知道规则,就走一步改一格
值迭代的前提是「知道每个动作会到哪儿」。 现实里常常不知道,只知道走完拿到多少。于是把「状态的值」换成「状态加动作的值」——这就是 Q。 更新式只有一行:Q(s, a) ← Q(s, a) + α × [ r + γ × max Q(下一格) − Q(s, a) ]。 括号里那一项叫 TD 误差:「本来以为能拿多少」和「实际拿到加下一格最好」的差。 它有个很妙的地方:目标里用的 max Q 是它自己的估计。这叫自举—— 用旧的猜测去改新的猜测,一开始全是 0,慢慢地 0 就长出了真值。 Demo 跑了 500 个回合(每回合最多 100 步):
| 训练回合数 | 最近 50 回合到终点率 | 平均回报 |
|---|---|---|
| 1 | 0.00 | −6.00 |
| 10 | 0.00 | −6.00 |
| 50 | 0.60 | 9.30 |
| 100 | 1.00 | 9.30 |
| 500 | 1.00 | 9.30 |
| 50 回合就到 0.60,100 回合稳在 1.00。学完之后跟值迭代比:21 个格子里 17 个选了同一个动作。 | ||
| 剩下 4 个格子动作不同,可两条路一样长、一样挣钱(都是 8 步、9.30)。 | ||
| 网格里常常有好几条同样好的路,选哪条都不算错。 | ||
| 这也是「怎么评价一个策略」的要点:看值,不是看「每一步有没有跟标准答案一样」。 | ||
| 标准答案本身在强化学习里往往不存在。 |
四、探索率 ε:不试就不知道
ε 是「随机乱选」的概率。它管的是强化学习里最根本的那个矛盾: 不试就不知道那条路好不好,可一直试就一直在浪费步数。 教科书上的说法是:纯贪心(ε = 0)会卡死在一个角落里。这张 5 乘 5 的图上实测一遍, 看它到底卡不卡:
| ε | 平均每回合步数 | 学到的策略到终点 | 走几步 |
|---|---|---|---|
| 0.00 | 9.1 | 是 | 8 |
| 0.02 | 9.2 | 是 | 8 |
| 0.10 | 9.8 | 是 | 8 |
| 0.20 | 10.9 | 是 | 8 |
| 0.50 | 16.1 | 是 | 8 |
| 1.00 | 46.5 | 是 | 8 |
| ε = 0 那一行没有卡死。原因很朴素:第一步朝上撞墙,那个动作的 Q 立刻变负, | |||
| argmax 下一次自然就换了动作。确定性网格里,它总能一路试到终点。 | |||
| 这不是算法聪明,是「地图小、规则确定」把这个毛病盖住了。真正拉开差距的是「学会它花了多少步」: | |||
| ε = 0 平均 9.1 步一回合,ε = 1.0 要 46.5 步,同样的本事花了五倍多的步数。 | |||
| 地图一大、状态一多,开局那一整片 0 会让它一直沿同一个方向试, | |||
| 碰不到奖励就永远没有正反馈——那时候「必须探索」才真的成立。 | |||
| 常规做法是让 ε 从大变小(先探索、后利用)。 | |||
| 这跟 5.3 遗传算法里「变异率不能太小」是同一件事:都是「怎么在保持多样性和收敛之间找平衡」。 |
五、折扣因子 γ:你到底多在乎将来
γ 决定「隔一步的奖励还算几成」。 它是这一节里最容易被当成技术细节、实际上最影响结果的那个旋钮。扫一遍:
| γ | 起点的值 | 照着它走能到终点吗 | 用了几步 |
|---|---|---|---|
| 0.00 | −0.1000 | 否 | 60(撞上限) |
| 0.50 | −0.1203 | 是 | 8 |
| 0.90 | 4.2613 | 是 | 8 |
| 0.95 | 6.3800 | 是 | 8 |
| 0.99 | 8.6413 | 是 | 8 |
| γ = 0 那一行最能说明问题:起点的值只有 −0.1000,跟「撞墙」的值一模一样。 | |||
| 因为远处的 +10 被乘了 0,根本没传回来——每个方向的数都一样,策略等于瞎指。 | |||
| γ 越大,远处的奖励越值钱,决策越看长远。但γ 太接近 1 有两个麻烦:值会变得很大、收敛变慢, | |||
| 而且它会开始把「远处的噪声」也当成信号。 | |||
| 实践里 γ 常常取 0.9 到 0.99。这个范围不是拍脑袋:它大约等于「十几步之内的奖励还算钱」, | |||
| 超过这个视野的信号就被压得听不见了。 | |||
| 最后把这一节收一下。一条主线: | |||
| 没有答案 → 用值函数把将来折回现在 → 不知道规则就拿步子去试 → 试多少由 ε 和 γ 定。 | |||
| 第 9 章的深度网络,常常就是用来当这里的「值函数」的——状态太多、表格存不下的时候, | |||
| 就让网络去猜那个数。 |
六、动手 Demo:没有人告诉你答案,只有分数
五段:网格环境、值迭代、Q-learning、探索率、折扣因子。
第 2 段和第 3 段是同一个问题的两条路:规则已知怎么算,规则未知怎么试。
注意 step 那个函数只有十几行,它就是全部的「世界」。强化学习的所有难度,
都来自「这个函数是黑箱」。
def step(s, a): # 环境:只有「走一步、给个分」
dr, dc = ACT[a]
t = (s[0] + dr, s[1] + dc)
if t[0] < 0 or t[0] >= H or t[1] < 0 or t[1] >= W or t in WALL:
t = s # 撞墙就留在原地
if t in PIT:
return t, -5.0, True
if t == GOAL:
return t, 10.0, True
return t, -0.1, False
def value_iter(gamma): # 规则已知:反复用「下一步的值」更新这一步
V = dict((s, 0.0) for s in FREE)
for it in range(500):
new = {}
for s in FREE:
best = None
for a in range(4):
t, r, done = step(s, a)
q = r if done else r + gamma * V[t] # 贝尔曼那一步
if best is None or q > best:
best = q
new[s] = best
V = new
if max(abs(new[s] - V[s]) for s in FREE) < 1e-9:
break
return V
# Q-learning:规则未知,走一步改一格
a = eps_greedy(s, 0.2)
t, r, done = step(s, a)
tgt = r if done else r + GAMMA * Q[t].max() # 用「下一格最好的值」当目标
Q[s][a] += ALPHA * (tgt - Q[s][a]) # 朝目标挪一小步
s = t
实跑输出:
--------------------------------------------------------------------
没有人告诉你答案,只有分数
--------------------------------------------------------------------
==============================================================
[1] 先看环境:一张 5 乘 5 的网格
符号:S 起点、G 终点(+10)、X 陷阱(−5)、# 墙、每走一步 −0.1。
S . . . .
. # . # .
. . . # .
# . X . .
. . . . G
能站的状态 21 个(去掉 4 面墙),每个状态 4 个动作,一共 84 个格子要填。
**跟前面几节最大的不同:这里没有「正确答案」。**没人告诉你哪一步对,
**只有走完之后拿到的一个分数。这叫延迟回报——现在的选择,结果要过几步才知道。**
先拿「每个状态随便选个固定动作」的策略跑 200 次:
平均回报 -6.00,到终点的 0%
**这个数就是「什么都不学」的水平。下面的所有努力,都是把它抬上去。**
[2] 规则已知时:值迭代,把「将来」折回现在
γ = 0.95,扫描 9 遍之后每个格子的值不再变化(差值小于 1e-9):
6.38 6.82 7.29 7.77 8.29
6.82 #### 6.82 #### 8.83
7.29 7.77 7.29 #### 9.40
#### 8.29 陷阱 9.40 10.00
8.29 8.83 9.40 10.00 目标
从起点照着最优动作走:8 步到终点,总回报 9.30。
**那个 +10 被打了折扣:隔 8 步,就乘 γ 的 8 次方 = 0.6634,值 6.6342。**
每一步还要付 0.1,所以整条路的值 = 奖励的折扣和 − 步数 × 0.1。
**这就是值迭代做的事:把「将来能拿多少」一层层折回现在,直到每个格子的数不再变。**
[3] 规则未知时:Q-learning,一边走一边改主意
值迭代要「知道每个动作会到哪儿」,现实里常常不知道。
**Q-learning 只用「走一步、看结果」:**
**Q(s,a) ← Q(s,a) + α × [r + γ × max Q(s',·) − Q(s,a)]**
训练回合数 最近 50 回合到终点率 平均回报
------------------------------------------------------------------
1 0.00 -6.00
10 0.00 -6.00
50 0.60 9.30
100 1.00 9.30
200 1.00 9.30
500 1.00 9.30
学完之后跟值迭代的最优策略比:21 个格子里有 17 个选了同一个动作(81.0%)。
除掉终点,还有 2 个格子的动作不一样,可两条路一样长、一样挣钱:
照 Q 表走 8 步到终点、总回报 9.30;值迭代那条路 8 步、9.30。
**网格里常常有好几条同样好的路,选哪条都不算错。**
**一个算法「学会没学会」,看的是值,不是「每一步有没有跟标准答案一样」。**
[4] 探索率 ε:这张小地图,盖住了教科书上的那个坑
ε 是「随机乱选」的概率。**教科书说:纯贪心(ε=0)会卡在一个角落里出不来。**
这张 5 乘 5 的图上实测一遍,看它到底卡不卡:
ε 平均每回合步数 学到的策略到终点 走几步
----------------------------------------------------------------------
0.00 9.1 是 8
0.02 9.2 是 8
0.10 9.8 是 8
0.20 10.9 是 8
0.50 16.1 是 8
1.00 46.5 是 8
**ε=0 那一行没有卡死。**原因很朴素:第一步朝上撞墙,那个动作的 Q 立刻变负,
**argmax 下一次自然就换了动作。**确定性网格里,它总能一路试到终点。
**这不是算法聪明,是「地图小、规则确定」把这个毛病盖住了。**
真正拉开差距的是「学会它花了多少步」:ε=0 平均每回合 9.1 步,
ε=1.0 要 46.5 步——**同样的本事,花了 5 倍的步数。**
地图一大、状态一多,开局那一整片 0 会让它一直沿同一个方向试,
**碰不到奖励就永远没有正反馈——那时候「必须探索」才真的成立。**
常规做法是让 ε 从大变小(先探索、后利用),
这跟第 5 章模拟退火里「温度慢慢降」是同一个思路。
[5] 折扣因子 γ:你到底多在乎「将来」
γ 决定「隔一步的奖励还算几成」。扫一遍,每个 γ 都跑值迭代,再看走出来的路:
γ 起点的值 走到终点 用了多少步 总回报
------------------------------------------------------------------
0.00 -0.1000 否 60 -6.00
0.50 -0.1203 是 8 9.30
0.90 4.2613 是 8 9.30
0.95 6.3800 是 8 9.30
0.99 8.6413 是 8 9.30
**γ=0 那一行:起点的值只有 -0.1000,跟「撞墙」的值一模一样。**
照着它走,一直走到上限 60 步也没到终点。
**因为 γ=0 等于「只在乎眼前这一步」,远处的 +10 被乘了 0,根本没传回来。**
每个方向的数都一样,**策略等于瞎指。**
**γ 越大,远处的奖励越值钱,决策越看长远。**
**但 γ 太接近 1,值会变得很大、收敛变慢**,而且容易把远处的噪声也当信号。
四段连起来:**有奖励没答案 → 用值函数把将来折回来 → 不知道规则就拿步子去试。**
**试多少由 ε 和 γ 定。**
**跟监督学习最大的差别就在这:它没有「这一步对不对」的标签,只有一个迟到很久的分数。**
第 2 段那张 V 表值得逐格看一遍。越靠近终点的格子值越大(10.00 就在终点旁边), 离得远的就小(起点 6.38)——这正好是「折扣」的视觉化:距离就是折价。 第 3 段那个「50 回合 0.60、100 回合 1.00」不是平滑曲线。强化学习的学过程经常是这样: 很久没动静,然后在某一次偶然撞到终点之后,信息突然沿着那条路往回传,成绩就跳上去。 第 4 段那个「ε = 0 没卡死」是这次实测最有价值的发现。 教科书没错,但它描述的是「大状态空间 + 稀疏奖励」的情形; 这张 5 乘 5 的图上,撞一次墙就足以让它换动作。 所以看任何「某某方法会失败」的结论,都得问一句:在什么条件下失败。 第 5 段 γ = 0 那一行是个干净的失败案例。值全是 −0.1、策略乱指, 说明「折扣」不是可选的修饰,是把远处的信息拉回眼前的那根绳子。把 γ 设成 0,等于把绳子剪断。 最后一句提醒:这段代码没用神经网络,Q 就是一张 21 乘 4 的表。真实问题的状态是连续的、 成千上万维,表存不下——那才需要第 9 章的深度网络来当近似函数。
30 秒实验
- 把陷阱从 1 个加到 3 个(再加 (3, 3)、(1, 2)),重跑第 2 段。 最优路会绕开它们,起点那个值会掉下来——因为绕路要付更多的 −0.1。
- 把每步的 −0.1 改成 0,重跑第 2 段。 所有路的值得一样,智能体不再「赶时间」, 最优策略会变得不唯一——这一改就说明「步数惩罚」是在给「快」定价。
- 把 ε 从固定值改成随回合线性衰减(从 1.0 降到 0.05),跟固定 0.2 比。 通常前 50 回合学得更快——因为它一开始就敢乱走,后面又不再浪费步子。
常见误区
- 以为强化学习有「标准答案」。它只有奖励。 策略对不对,看的是「照这么走值多少」, 不是「有没有跟某条标准路径一样」——Demo 里 Q-learning 和值迭代走的就是两条不同的路。
- 以为奖励设计是小事。每步 −0.1 这一条,决定了智能体会不会绕远; 把奖励写错,学出来的行为就会荒腔走板(这是强化学习落地最常见的坑)。
- 以为 ε 越小越「好」。ε = 0 在这张图上碰巧没事,但它的平均步数是最少的恰恰因为环境小; 在没有正反馈的大环境里,它就是死路。
- 以为 Q-learning 只需要「多跑几轮」。它要的是探索过的状态。 没走到过的格子,Q 永远是 0,永远学不出来。
自测三问(点开对答案)
1. 值迭代和 Q-learning 都要「把将来的奖励折回现在」,它们差在哪? —— **差在「知不知道规则」。**值迭代用 `step(s, a)` 的返回值直接算「下一格的值」, **所以它要求环境是已知的(基于模型)。**Q-learning 只从实际走过的那一步里学, **连「这个动作会到哪儿」都是走出来的。**Demo 里两条路都收敛到 8 步、9.30, **但一个用 9 遍扫描算出来,另一个用 500 个回合试出来。** 2. 为什么 γ = 0 的时候,智能体连终点都走不到? —— 因为**值迭代的传递全靠 γ 这个乘数**。 γ = 0 时,远处的 +10 被乘成 0,**信息传不回起点**——每个格子的值都变成「这一步的奖励」, 全是 −0.1,**所有动作一样好,策略等于瞎指。**Demo 里起点的值正好是 −0.1000, **跟撞墙的值一模一样,这就是「看不见将来」的样子。** 3. 为什么说「信用分配」是强化学习最难的地方? —— 因为**只有一个迟到的总分,不知道是哪一步挣来的。**Demo 里走到终点只给一个 +10, **前面那 7 步每步都是 −0.1、看上去都在扣分**, **可它们其实是必要的。**值函数和 TD 误差就是用来解决这个的: **把「这一步之后还能拿多少」一点点往回传,让每一步都分到它该得的那份。**集成学习管「把一堆弱模型合起来」,强化学习管「没有答案时怎么试」。 下一节换一个更实际的问题:已经在一个任务上学好的东西,能不能搬到另一个任务上? 那就是迁移学习——它省的不是训练时间,是标注数据。
- ☐ (本节完,下一节 8.3 迁移学习)