经典方法速通:从理论到实践的桥梁
本节导读
核心内容
- 掌握 DP、MC、TD 如何把价值函数和贝尔曼方程变成可运行的算法。
- 理解 Q-Learning 如何把 TD 更新用于动作价值函数,并学习最优 。
- 认识表格方法的容量瓶颈,以及用函数近似替代表格的必要性。
核心公式
为什么需要这些公式
到这里,我们已经有了 MDP、回报、价值、贝尔曼方程、 和策略目标。现在要问一个更像工程的问题:这些东西怎么变成能跑的算法?DP 像是"规则全知道,坐在桌前算";MC 像是"先完整跑一遍,再看结果修正";TD 像是"边跑边改预测";Q-Learning 把这种边跑边改用到了动作分数上;函数近似则回答"表格太大装不下怎么办"。这一节串起来看会发现:经典方法不是一堆老算法,而是在一步步减少对理想条件的依赖,让智能体从小格子世界走向真实世界。
在前面的几个小节中,我们一步步搭建了 RL 的理论大厦:MDP 五元组是地基,价值函数是框架,贝尔曼方程是计算工具,TD Error 是学习信号。但理论归理论,怎么把这些数学工具变成能跑的算法?这就是本节要回答的问题。
我们将沿着一条清晰的演进路线——从 DP 到 MC 到 TD——看看 RL 的研究者们是如何一步步从"理想但不可用"走向"实用但不够完美"的。然后在旅程的终点,你会发现一个共同的瓶颈——表格装不下——以及一个优雅的解决方案。
第一代:动态规划(DP)——"坐在家里查地图"
如果你完全知道环境的转移概率 和奖励函数 ——比如你在老虎机游戏中自己写的代码——那你根本不需要去"探索"。你可以坐在家里,直接用贝尔曼方程算出每个状态的精确价值。打个比方:你手里有一张完美的城市地图,上面标注了每条路的长度、每个路口的红绿灯时长、甚至每条路的拥堵概率。你不需要出门试走,坐在书桌前就能算出从家到公司的最短路线。
这就是动态规划(Dynamic Programming),由理查德·贝尔曼在 1957 年提出 [1]。它的更新规则就是贝尔曼最优方程的直接实现:
反复对所有状态执行这个更新, 最终会收敛到最优价值 。完美、精确、优雅——理论上。
问题在于:现实中你几乎不可能知道完整的 和 。围棋有 个状态,你不可能穷举所有转移概率;LLM 有天文数字的 token 序列组合,更不可能建出完整的转移矩阵。DP 就像一把只能在理想世界中使用的完美钥匙——理论最优,但现实不可用。
第二代:蒙特卡洛(MC)——"走完一整趟再回头看"
不知道环境模型?那就跑起来看看。
蒙特卡洛方法(Monte Carlo, MC)以摩纳哥的赌城命名——因为它本质上就是"靠运气采样"。这个名字最早由数学家斯塔尼斯拉夫·乌拉姆和约翰·冯·诺伊曼在 1940 年代的曼哈顿计划中使用,后来被系统性地引入 RL 领域。它的核心思想极其朴素:与其坐在家里算,不如实际走一趟,看看到底拿了多少分。采样完整的轨迹(从起点到终点),用实际回报 来估计 :
这里 是学习率,控制"新经验覆盖旧估计的速度"。 就是"实际拿到的分数减去你之前的预测"——差多少就补多少。MC 方法给出的是无偏估计,因为你用的是真实的回报。但方差巨大——同一个状态,不同 episode 的 波动很大。而且必须等到 episode 结束才能更新,不能"边走边学"。
方差大到什么程度?考虑一个具体的例子。在 GridWorld 中,假设你在状态 , 的真值是 -3。如果你跑 10 个 episode 并在 处记录 ,可能得到这样的结果:。这些值的平均是 -3.85,接近真值 -3,但单次的波动范围从 -1.0 到 -7.5。为什么差距这么大?因为 是从当前步一直到 episode 结束的累积折扣回报——后面走的每一步都在影响 。如果这一局碰巧走了一条短路到达终点, 就偏大;如果走了很多弯路, 就偏小。MC 方法拿到的就是这个波动剧烈的 ,用一个波动剧烈的信号去更新 ,自然不稳定。
第 5 章的 REINFORCE 就是 MC 方法在策略空间的直接应用——它需要跑完整个 episode,拿到完整的回报后才能更新策略。
第三代:时序差分(TD)——"每走一步就微调预判"
时序差分(Temporal Difference, TD)由安德鲁·巴托和理查德·萨顿在 1988 年正式提出 [2],被他们称为"RL 核心的、新颖的想法"。TD 是 DP 和 MC 的折中——它既不需要完整的环境模型(像 MC),又能边走边学(不像 MC 那样要等 episode 结束)。用一个日常类比来理解:想象你在预测明天的天气。MC 的做法是等到明天结束,拿全天实际温度来校正你的预测——准确但太慢。TD 的做法是:你预测明天 20°C,今晚看了一眼温度计发现气温已经开始下降,于是立刻把预测调低到 18°C。你不需要等到明天结束,只用"最新的一小步信息"就能修正预测。
你认出来了吗?方括号里的就是我们上一节定义的 TD Error:。TD 方法的更新规则直观地说就是:每走一步,用 TD Error 来微调价值估计——偏高就往下调,偏低就往上调。不需要等 episode 结束,也不需要知道环境模型。一步的反馈就够了。
Q-Learning 是 TD 方法最著名的变体,由克里斯·沃特金斯在 1989 年的博士论文中提出 [3],并在 1992 年与彼得·戴扬正式发表了收敛性证明。它直接学习 值而不是 值:
注意那个 ——它直接使用了贝尔曼最优方程的思想:不看所有动作的平均,只看最好的那个。这意味着 Q-Learning 学的是 (最优动作价值),不管你当前用什么策略在探索——这就是离策略(off-policy)学习。
Sarsa:Q-Learning 的同策略对照
Q-Learning 在更新时用的是 ——不管实际选了什么动作,都按最优的那个来算目标。但如果你实际探索时经常走弯路(比如 ε-greedy 有 10% 概率随机走),Q-Learning 的目标值和实际行为之间就存在差距。
Sarsa 用的是实际选的下一个动作 来构造目标,而不是 max:
这里 就是当前策略在状态 时实际选出的动作——可能是贪心最优,也可能是 ε-greedy 的随机探索。因为评估和改进用的是同一条策略,Sarsa 是**同策略(on-policy)**方法。
| Sarsa(on-policy) | Q-Learning(off-policy) | |
|---|---|---|
| 更新目标 | , 由当前策略选 | |
| 行为策略 = 目标策略? | 是 | 否 |
| 风格 | 保守,考虑了探索噪声 | 激进,假设总能选最优 |
| 后续对应 | PPO、GRPO(第 6、8 章) | DQN、SAC(第 4 章) |
一个直觉对比:如果 GridWorld 旁边有个悬崖,Q-Learning 会学到紧贴悬崖走的最短路径(因为它假设自己不会犯错),但实际走的时候 ε-greedy 的随机探索可能掉下去。Sarsa 会学到离悬崖远一点的安全路径(因为它把随机探索的可能性也考虑进去了)。两种策略各有适用场景——第 4 章的 DQN 走的是 Q-Learning 路线,第 6 章的 PPO 走的是 Sarsa 这一脉的 on-policy 路线。
三代方法的演进可以用一句话概括:DP 需要知道一切但不现实,MC 不需要知道一切但要等很久,TD 不需要知道一切也不需要等——它每走一步就能学一点。
动手:4×4 GridWorld 跑 Q-Learning
让我们用一个具体例子来感受 Q-Learning 的运作过程,亲眼看看 TD Error 是怎么从非零逐渐收敛到零的。
环境设定
┌───┬───┬───┬───┐
│ S │ │ │ │
├───┼───┼───┼───┤
│ │ │ │ │
├───┼───┼───┼───┤
│ │ │ │ │
├───┼───┼───┼───┤
│ │ │ │ G │
└───┴───┴───┴───┘4×4 网格,左上角起点 ,右下角终点 。每步奖励 -1(鼓励尽快到达终点),到达终点奖励 0。动作:上/下/左/右。初始 Q-table:全部为 0。
手算第 1 步:从 S 向右走
智能体从 出发,选择向右走到 。即时奖励 (每步都是 -1)。下一状态的所有 Q 值都是 0(初始化为 0)。所以 TD Target ,TD Error 。新 Q 值 。
TD Error = -1 的含义:之前 Q 值是 0("我什么都不知道,猜测走这步不赚不亏"),实际走了一步却扣了 1 分——预测严重偏高,所以把 Q 值下调了 0.1。
手算训练 100 个 episode 后:Q 值的演变
前几个 episode 的 Q 值变化很小——因为智能体还在随机探索,大多数状态的 Q 值只被更新了几次。但到了第 10 个 episode 左右,一个有趣的现象开始出现:靠近终点 的状态的 Q 值率先变准确。为什么?因为终点附近的 (状态, 动作) 对被访问的次数最多——不管你从起点怎么走,最终都要经过终点附近的格子。
到第 100 个 episode 时,Q 值的分布大概变成这样(只列出几个关键值):
| 状态-动作 | Q(第 1 个 episode) | Q(第 100 个 episode) | Q(收敛后) |
|---|---|---|---|
| 0 | |||
| 0 | |||
| 0 |
你可以看到一个清晰的规律:Q 值从终点向外"扩散"。终点旁边的格子最先学会(因为下一步就是终点,信号最强),然后一层一层向外传播。到第 1000 个 episode 时,所有 Q 值收敛,TD Error 全部趋近于 0。
第 2 步的情况类似:从 继续向右走到 ,TD Error 仍然是 -1,新 Q 值也是 -0.1。因为周围的格子都还没学过,Q 值全是 0。
经过大量训练后,Q 值会收敛。以 为例:从 到 最短路径需要 6 步,每步 -1,考虑 的折扣后 。此时 TD Error ——预判和实际一致了,学习完成。这个过程揭示了 Q-Learning 的本质:TD Error 从一开始的 -1,通过成百上千次的微调,逐渐趋近于 0。每一次微调都是在说"上次猜错了,这次修一点"。
用代码验证
import numpy as np
# 4x4 GridWorld Q-Learning
Q = np.zeros((16, 4)) # 16 个状态, 4 个动作 (上右下左)
alpha, gamma, epsilon = 0.1, 0.9, 0.1
goal = 15 # 右下角的索引
def state_to_idx(row, col):
return row * 4 + col
def step(state, action):
"""执行动作,返回 (下一状态, 奖励, 是否结束)"""
row, col = state // 4, state % 4
if action == 0: row = max(row - 1, 0) # 上
elif action == 1: col = min(col + 1, 3) # 右
elif action == 2: row = min(row + 1, 3) # 下
elif action == 3: col = max(col - 1, 0) # 左
next_state = state_to_idx(row, col)
reward = 0 if next_state == goal else -1
done = next_state == goal
return next_state, reward, done
# 训练 1000 个 episode
for ep in range(1000):
state = 0 # 起点 S
while state != goal:
# ε-贪婪:90% 选最优,10% 随机探索
if np.random.random() < epsilon:
action = np.random.randint(4)
else:
action = np.argmax(Q[state])
next_state, reward, done = step(state, action)
# Q-Learning 更新
td_target = reward + gamma * np.max(Q[next_state])
td_error = td_target - Q[state, action]
Q[state, action] += alpha * td_error
state = next_state
# 打印从起点出发的最优路径
print("收敛后的 Q((0,0), 右) =", Q[0, 1].round(2))
print("最优路径(从 S 出发的动作序列):")
state = 0
actions = ["↑", "→", "↓", "←"]
path = []
while state != goal:
a = np.argmax(Q[state])
path.append(actions[a])
state, _, _ = step(state, a)
print(" → ".join(path))预期输出:
收敛后的 Q((0,0), 右) = -5.69
最优路径(从 S 出发的动作序列):
→ → → ↓ ↓ ↓共同的瓶颈:表格装不下
到目前为止,DP、MC、TD 三代方法都有一个隐含的前提:你能把每个状态的 值或 值存在一张表格里。GridWorld 只有 16 个格子,当然没问题。但真实世界的状态空间有多大?
猜硬币只有 2 个状态,轻松处理。井字棋有 种局面,勉强可以。但国际象棋有 种局面,围棋有 种——而可观测宇宙中的原子总数也不过 个。CartPole 的状态空间是 (无限连续),理论上存在无限多个不同的状态。更不用提大模型了——假设词表大小 50,000、序列长度 1,000,可能的状态数量是 ,这是一个比宇宙中原子总数还大无数倍的数字。
表格方法的问题在于:它对每个状态都是独立学习的——如果你从没见过状态 ,你就完全不知道 是多少。更致命的是,如果状态是图像呢?一帧 84×84 的灰度图有 种可能性——远远超过表格的容量。即使你只考虑"有意义的"游戏画面,数量也是天文数字。表格方法在这种场景下完全失效:你不可能存下每个像素组合对应的价值。
解决方案:函数逼近
既然不可能为每个状态单独存一个值,那换一个思路:能不能学习一个函数,输入状态 ,输出 的近似值?
其中 是函数的参数,通过训练来调整,使得 尽可能接近真实的 。这就是函数逼近(Function Approximation)的核心思想。
神经网络则是最强大的函数逼近器。根据通用逼近定理,一个带有一个隐藏层的前馈神经网络,只要隐藏层足够宽,就可以以任意精度逼近任何连续函数。更重要的是,神经网络能举一反三——假设智能体在训练中见过很多"接近直立"的 CartPole 状态,它们的 值都很高。那么当它遇到一个从未见过但长得很像的状态时,神经网络能通过输入的相似性,推断出"这个状态的价值应该也很高"。这就是泛化能力——不需要穷举每一个状态,只要见过足够多的"代表",就能推断未知状态的值。
还记得第 1 章我们用的 MlpPolicy 吗?它内部就是一个小型神经网络。当我们调用 model.learn(20000) 时,这个网络就在不断调整自己的参数 ,使得它对每个 CartPole 状态输出的价值估计越来越准确。只不过当时 SB3 把这一切都封装在了黑盒里。现在你知道了:那个黑盒里在做的事情,本质上就是用 TD Error 驱动的函数逼近。
理解了"神经网络 = 函数逼近器"这一点,后续所有深度 RL 架构就不再是魔法,而是数学工具的组合:第 5 章的 Critic 网络逼近 ,第 4 章的 Q 网络逼近 ,第 6 章的奖励模型逼近奖励函数 。"Deep Reinforcement Learning"中的"Deep",指的就是用深度神经网络来做函数逼近的 RL。
闭环验证:老虎机的 V 值
在本章开头,我们用老虎机体验了 RL 的基本交互。现在,在学完了所有理论工具之后,让我们回到那里,做一个完整的闭环验证。
在贝尔曼方程一节中,我们用数学推导得到了"永远选 A"策略下的理论 V 值:(A 台出奖率 60%,)。现在用蒙特卡洛仿真来验证——跑大量 episode,看平均回报是否接近 2.0:
import random
def simulate_bandit_value(prob_a=0.6, gamma=0.9, num_episodes=10000):
"""用蒙特卡洛采样验证贝尔曼方程的理论 V 值"""
total_returns = []
for _ in range(num_episodes):
ret = 0
discount = 1.0
while True:
# 永远选 A
reward = 1 if random.random() < prob_a else -1
ret += discount * reward
discount *= gamma
if discount < 1e-10: # 折扣足够小就截断
break
total_returns.append(ret)
return sum(total_returns) / len(total_returns)
empirical_V = simulate_bandit_value()
theoretical_V = 0.2 / (1 - 0.9)
print(f"贝尔曼方程理论 V 值: {theoretical_V:.4f}")
print(f"蒙特卡洛仿真 V 值: {empirical_V:.4f}")
print(f"误差: {abs(empirical_V - theoretical_V):.4f}")预期输出:
贝尔曼方程理论 V 值: 2.0000
蒙特卡洛仿真 V 值: 2.0018
误差: 0.0018理论值和仿真值高度吻合。这验证了贝尔曼方程的正确性——数学推导和实际运行的结果是一致的。从老虎机的直觉体验,到 MDP 的数学形式化,到贝尔曼方程的计算,再到蒙特卡洛的仿真验证,我们走完了一个完整的闭环。
RL 的全景地图:两条路线与一个关键区分
路线分野:Value-Based vs Policy-Based
所有 RL 算法可以分成两大阵营。
Value-Based 路线的思路是:先学 或 ,再从中推导策略。代表算法是 Q-Learning → DQN → Double DQN → Rainbow。它的优势是样本效率高(off-policy 可复用旧数据),但只能处理离散动作,策略是"隐式"的。
Policy-Based 路线的思路是:直接学策略 ,不经过 Q。代表算法是 REINFORCE → Actor-Critic → PPO → DPO → GRPO。它能处理连续动作空间,策略更灵活,但样本效率低(on-policy 需不断采新数据)。
为什么本书选择 Policy-Based?因为大模型对齐任务(RLHF/DPO/GRPO)本质上是在连续的策略空间中优化——模型输出的每一步都是从几万个 token 中采样,这是一个连续概率分布的问题,Value-Based 方法不太擅长。更具体地说,Value-Based 方法的核心操作 要求你遍历所有可能的动作来选最优——当动作空间是 50,000 个 token 时,这还勉强可行;但当动作空间是连续的(比如机器人的关节力矩),穷举根本不可能。Policy-Based 方法直接输出动作的概率分布,从分布中采样即可,天然适配这种场景。但这并不意味着 Value-Based 不重要——第 4 章我们会回来走 DQN 路线,届时你会发现两个世界的底层逻辑惊人地一致。
关键区分:On-policy vs Off-policy
这个区分决定了训练数据能不能复用,在大模型时代尤其重要。
On-policy(同策略)只能用当前策略产生的数据。就像换了跑步姿势后,之前的训练记录就过期了。代表算法有 REINFORCE、PPO、GRPO。训练稳定,但数据用完就扔。
Off-policy(异策略)可以用任何策略产生的旧数据。就像可以用别人的训练笔记改进自己的技术。代表算法有 Q-Learning、DQN、SAC。样本效率高,但训练可能不稳定。
在大模型时代,这个区分有非常具体的体现:PPO 是 on-policy,所以每次 RLHF 训练都要用当前模型重新生成回答,非常吃显存和算力(第 6 章)。DQN 是 off-policy,可以把所有历史经验存进池子反复学习(第 4 章)。DPO 更极端,连在线生成都不需要,直接用固定的离线偏好数据集训练(第 8 章)。GRPO 是 on-policy,但用组内比较省掉了 Critic,降低了采样成本(第 8 章)。
本章小结
至此,全书的理论基石章节已经完成。回顾这段旅程中的关键收获:
我们从两台老虎机出发——亲手体验了 RL 的核心矛盾(探索 vs 利用),理解了期望回报如何量化策略好坏。然后掌握了 MDP 五元组——理解了 如何统一描述所有 RL 问题。接着通过手画宝藏地图"发明"了贝尔曼方程——把"评估一个局面"变成递归的"即时奖励 + 未来价值"。认识了 TD Error——预测与现实的落差,贯穿全书的学习信号。走过了 DP → MC → TD 三代方法的演进,亲手跑了 GridWorld Q-Learning,看到 TD Error 从 -1 收敛到 0。理解了"深度 RL"的本质——神经网络作为函数逼近器,让 RL 从表格走向高维世界。最后建立了方法全景图——Value-Based vs Policy-Based 的分野,以及 On-policy vs Off-policy 的区分。
这些概念将在后续章节反复出现:第 5 章的 Critic 网络是 的实现,第 6 章的 GAE 基于 TD Error,第 4 章的 DQN 直接学习 ,第 8 章的 DPO 和 GRPO 则在策略空间中直接优化。
练习
修改老虎机的概率:把
prob_a改为 0.3(A 台只有 30% 出奖率),重新计算"永远选 A"策略的 值()。最优策略应该是什么?期望回报是多少?GridWorld 实验:在 Q-Learning 代码中,把网格改为 5×5,终点改为右上角。观察 Q 值收敛需要的 episode 数量如何变化。
折扣因子的影响:在老虎机中,分别用 计算"永远选 A"的 值,画出 随 变化的曲线。你能从直觉上解释这条曲线的形状吗?
状态空间思考:假设你要设计一个扫地机器人 RL 系统。状态应该包含哪些信息?动作是什么?奖励怎么设计?(提示:考虑"覆盖率"和"电量"的权衡。)