对应:Stanford CS234 Lecture 4 后半 / Lecture 5 前半(Value Function Approximation, Deep Q-Learning) 配套代码:
code/lecture05/(前 3 个文件只需 numpy;第 4 个文件需要pip install torch gymnasium) 预计阅读时间:4–5 小时
0. 本讲导航
0.1 本讲要回答的问题
状态太多(甚至是连续的),一张表存不下,而且每个状态几乎只会出现一次。怎样学习价值函数和策略?
前四讲的所有算法都是表格型的:每个状态(或状态–动作对)一个格子。这在网格世界里没问题,但真实世界的问题:
- Atari 游戏:状态是 210×160 的彩色画面,可能的画面数是天文数字;
- 机器人:状态是连续的关节角度和速度,同一个状态几乎不可能出现两次;
- 围棋:约 个合法局面。
表格不仅存不下,更致命的是不会泛化:学会了一个画面,对只差一个像素的另一个画面一无所知。回忆第 1 讲 2.4 节,泛化正是 RL 的四个核心特征之一。
本讲的答案:用一个带参数的函数来表示价值, 或 。参数的个数远少于状态数,更新一个状态会影响”相似”的其他状态。
0.2 学完本讲,你应该能够
- 解释为什么需要函数逼近,以及”泛化”带来的偏差–方差权衡。
- 写出梯度下降和随机梯度下降的更新公式,理解学习率的作用。
- 把价值函数逼近写成监督学习问题;推导梯度 MC 和半梯度 TD 的更新公式,解释”半梯度”的含义。
- 说出线性函数逼近下 MC 和 TD 分别收敛到哪里(最小均方误差解 vs TD 不动点)。
- 比较几种常见的特征构造方法(状态聚合、多项式、傅里叶、瓦片编码),理解”能表示”与”好学习”的区别。
- 解释”致命三角”(函数逼近 + 自举 + 异策略)为什么会导致发散。
- 写出 DQN 算法,解释经验回放和目标网络分别解决了什么问题。
- 说出 Double DQN、Dueling DQN、优先经验回放等改进的核心思想。
- 用 PyTorch 实现 DQN,并知道常见的实现陷阱(终止 vs 截断、评估方式、多种子)。
0.3 先修知识
| 知识点 | 用在哪里 | 最低要求 |
|---|---|---|
| 第 3 讲:MC、TD、偏差–方差 | 第 3 节 | 必须 |
| 第 4 讲:SARSA、Q-learning、异策略、Double Q-learning | 第 5–8 节 | 必须 |
| 微积分:导数、偏导数、链式法则 | 第 2、3 节 | 知道”梯度指向函数增长最快的方向” |
| 向量点积 | 全讲 | 会算即可 |
| 神经网络基础 | 第 7 节 | 知道”多层线性变换 + 非线性激活”即可;PyTorch 用法代码注释里会讲 |
0.4 本讲地图
第1节 为什么需要函数逼近?泛化的偏差–方差权衡
│
第2节 预备知识:梯度下降与随机梯度下降
│
第3节 价值函数逼近 = 监督学习(但没有标签)→ 梯度 MC、半梯度 TD
│
第4节 线性函数逼近:收敛到哪里?
│
第5节 特征构造 + 控制:瓦片编码 + 半梯度 SARSA 解决高山小车
│
第6节 致命三角:为什么会发散?(Baird 反例)
│
第7节 DQN:经验回放 + 目标网络
│
第8节 DQN 之后:Double DQN、Dueling、优先经验回放、Rainbow
│
第9节 深度 RL 的实践要点
1. 为什么需要函数逼近?
1.1 表格的两个根本问题
- 存储:状态数太多,存不下。
- 泛化:即使存得下,每个格子只能从”恰好访问到这个状态”的数据中学习。状态越多,每个格子分到的数据越少,估计越不准;而且完全没见过的状态,估计就是初始值——毫无信息。
但现实中,相似的状态往往有相似的价值。车在路上开,位置差 1 厘米,前景几乎一样;游戏画面差一个像素,局势几乎一样。表格方法完全浪费了这种结构。
1.2 函数逼近的思路
用一个带参数 的函数来表示价值:
- 参数个数 远小于状态数 ;
- 调整 去拟合一个状态的价值,会同时改变其他状态的估计——好的函数形式会让”相似”的状态一起变化,这就是泛化。
常见的函数形式:线性函数(特征的加权和)、神经网络、决策树、最近邻……本讲关注可微的函数(线性函数和神经网络),因为可以用梯度下降来调参数。
1.3 泛化的代价:又一次偏差–方差权衡
参数比状态少,就不可能让每个状态都完全准确——必然存在近似误差(偏差)。换来的是:每个参数能利用很多状态的数据,**估计噪声(方差)**大大降低。
代码验证:
01_linear_vfa_random_walk.py实验 4,1000 状态随机游走,同样 5000 个回合的数据:
- 表格型 MC(1000 个参数,没有近似误差):RMS 价值误差 0.108;
- 状态聚合(10 个参数,价值函数被迫是”台阶”形状):RMS 价值误差 0.062。
参数少了 100 倍,误差反而更小。在数据有限时,适度的泛化是有益的。
2. 预备知识:梯度下降与随机梯度下降
(已经熟悉的读者可以跳过。)
2.1 梯度下降
假设我们想找到使某个函数 最小的参数 。梯度 是一个向量,指向 增长最快的方向:
要让 减小,就沿着梯度的反方向走一小步:
是学习率(步长)。直觉:蒙着眼睛下山,每一步都朝脚下最陡的下坡方向迈一小步。步子太大可能越过谷底甚至”飞出去”(发散),太小则走得很慢。
2.2 随机梯度下降(SGD)
如果 是一个期望(对数据分布的平均),比如 ,精确计算梯度需要遍历所有数据。随机梯度下降每次只用一个(或一小批)样本来估计梯度:
单个样本的梯度是真实梯度的无偏估计(期望相等),虽然有噪声,但只要学习率适当地减小,SGD 仍然会收敛。这和第 3 讲”用采样代替期望”是同一个思想。
2.3 条件数:为什么有些问题梯度下降走不动
如果 的等高线是”圆”的,梯度直指谷底,梯度下降很快;如果等高线是”又窄又长的椭圆”,梯度几乎垂直于谷底的方向,梯度下降会在两壁之间来回”之”字形震荡,前进极慢。衡量这种”狭长程度”的指标叫条件数(二次函数的 Hessian 矩阵最大特征值与最小特征值之比)。第 4.4 节会看到这在特征选择中的实际影响。
3. 价值函数逼近:没有标签的监督学习
3.1 如果有”先知”
假设有一个先知,能告诉我们任何状态的真实价值 。那问题就变成了普通的监督学习(回归):让 尽量接近 。目标函数:
这叫均方价值误差(mean squared value error, VE)。
为什么要按 加权? 参数比状态少,不可能让所有状态都准确,必须有所取舍。自然的取舍是:经常访问的状态更重要。 是策略 下各状态被访问的比例(同策略分布,回忆第 1 讲 7.6 节的状态分布)。
对 求梯度(链式法则):
SGD(把常数 2 吸收进 ):
3.2 没有先知:用目标代替
现实中没有先知。第 3 讲已经告诉我们怎样得到 的”替身”——把第 3 讲的更新目标填进去:
| 方法 | 用什么代替 | 更新公式 |
|---|---|---|
| 梯度 MC | 回报 | |
| 半梯度 TD(0) |
表格方法是特例:如果 (每个状态一个参数),则 是只在第 个位置为 1 的向量,上面的公式退化为第 3 讲的 。
3.3 梯度 MC:真正的随机梯度下降
是 的无偏估计(第 3 讲),所以梯度 MC 的更新方向是真实梯度 的无偏估计——它是真正的 SGD。只要学习率满足 Robbins–Monro 条件,就收敛到 的一个局部最小值(线性情况下是全局最小值)。
3.4 半梯度 TD:为什么叫”半”梯度?
TD 目标 本身也依赖 。但在求梯度时,我们假装它是一个常数,只对 求导。如果老老实实地对整个误差 求导,应该还有一项 。我们忽略了它,所以只算了”一半”的梯度。
后果:
- 半梯度 TD 不是任何目标函数的梯度下降,所以不能套用”SGD 收敛到最小值”的结论。
- 线性、同策略时它仍然收敛,但收敛到的是 TD 不动点,而不是 VE 的最小值(4.3 节)。
- 在某些条件下它会发散(第 6 节)。
那为什么还要用它? 因为它保留了 TD 的全部优点:在线更新、方差小、学得快(第 3 讲)。而且”老老实实求全梯度”的方法(叫残差梯度法)在实践中通常学得更慢,而且在随机环境中会收敛到错误的答案。
4. 线性函数逼近
4.1 特征与线性函数
把每个状态表示成一个特征向量 ,价值是特征的加权和:
线性函数的梯度特别简单:。所以:
更新的直觉:误差是正的(实际比预期好),就沿着”当前状态的特征方向”增大参数——下次再遇到具有这些特征的状态,预测就会更高。
4.2 实验:1000 状态随机游走(Sutton & Barto 例 9.1)
- 1000 个状态,从中间出发;每步等概率向左或向右跳 1~100 格;跳出左边界得 −1,右边界得 +1;。
- 真实价值从约 −0.92(最左)到 +0.92(最右)平滑变化,可以用第 1 讲的解析法精确算出。
- 状态聚合:把 1000 个状态按顺序分成 10 组,每组共享一个参数(特征是”属于哪一组”的独热向量)。
梯度 MC(代码 01 实验 1,20000 个回合):
状态 1 101 201 301 401 501 601 701 801 901 1000
真实价值 -0.92 -0.75 -0.57 -0.38 -0.19 0.00 0.19 0.38 0.57 0.75 0.92
梯度 MC -0.84 -0.66 -0.48 -0.24 -0.05 0.15 0.35 0.50 0.63 0.82 0.82
最优聚合解 -0.83 -0.65 -0.47 -0.28 -0.09 0.09 0.28 0.47 0.65 0.83 0.83
价值函数变成了”台阶”。梯度 MC 的收敛点就是这组特征下 VE 最小的解(每组取组内真实价值按 加权的平均)。学到的结果(RMS 误差 0.071)与理论最优(0.054)之间的差距来自常数步长的噪声。
4.3 TD 不动点
半梯度 TD(代码 01 实验 2,20000 个回合):
半梯度 TD(学到的) -0.73 -0.54 -0.38 -0.24 -0.12 -0.00 0.15 0.30 0.48 0.69 0.69
TD 不动点(解析) -0.73 -0.54 -0.39 -0.25 -0.12 0.00 0.15 0.30 0.48 0.69 0.69
最优聚合解 -0.83 -0.65 -0.47 -0.28 -0.09 0.09 0.28 0.47 0.65 0.83 0.83
RMS 误差:半梯度 TD 0.119,TD 不动点 0.117,最优 0.054。
TD 收敛到的解在两端”更平”,误差约为最优的 2 倍。这不是没训练够——学到的结果已经和解析算出的不动点几乎一样。这是”自举 + 函数逼近”的固有偏差。
TD 不动点是什么? 线性半梯度 TD 的期望更新量是
令期望更新为 0,得到 ,即 。
直觉:在 TD 不动点上,“用自己的估计当目标”达到了自洽——估计值与它自举出来的目标在平均意义上一致。但”自洽”不等于”正确”:函数逼近的误差会通过自举传播和累积。在上面的例子中,靠近边界的组的估计被相邻组”拉平”了。
理论保证(Tsitsiklis & Van Roy, 1997):线性函数逼近、同策略分布下,半梯度 TD(0) 以概率 1 收敛到 TD 不动点,且
接近 1 时这个界很松(本例 ,界没有意义),但至少保证了收敛。
4.4 特征的选择:“能表示”与”好学习”
几种常见的特征:
| 特征 | 定义( 为缩放到 的状态) | 特点 |
|---|---|---|
| 状态聚合 | “属于哪一组”的独热向量 | 最简单;价值是台阶形状 |
| 多项式基 | 表示能力强,但高阶项彼此高度相关 | |
| 傅里叶基 | 不同频率的余弦波叠加;彼此近似正交 | |
| 瓦片编码 | 多层错开的网格(5.2 节) | 稀疏、计算快、泛化平滑;连续控制中很常用 |
| 径向基函数(RBF) | 以若干”中心”为基准的平滑特征 |
实验(代码 01 实验 3,梯度 MC,10000 个回合,3 个种子平均):
| 特征 | 参数数 | 理论最优误差 | 学到的误差 | 条件数 |
|---|---|---|---|---|
| 多项式 5 阶 | 6 | 0.0010 | 0.1203 | |
| 多项式 10 阶 | 11 | 0.0004 | 0.1139 | |
| 多项式 20 阶 | 21 | 0.0002 | 0.1094 | |
| 傅里叶 5 阶 | 6 | 0.0079 | 0.0260 | 7.5 |
| 傅里叶 10 阶 | 11 | 0.0028 | 0.0298 | 9.8 |
| 傅里叶 20 阶 | 21 | 0.0008 | 0.0318 | 12 |
“理论最优误差”是用加权最小二乘直接求出的该特征下最好的线性逼近(需要知道真实价值,不是学习算法)。
这张表的教训:
- 表示能力不是问题:两种特征都能把价值函数表示得非常准,多项式基甚至更准。
- 可学习性才是问题:多项式基学到的误差比理论最优差上百倍,而且阶数越高也没改善。原因在最后一栏: 在 上长得非常像,条件数高达 ,梯度下降在这个”又窄又长的山谷”里几乎走不动(2.3 节)。傅里叶特征近似正交,条件数只有 10 左右。
实践启示:选特征时,除了”能不能表示”,还要看”好不好学”——把输入缩放到合适范围、让特征尽量不相关。这些工程细节在神经网络中同样重要(输入归一化、合适的初始化、批归一化等)。
5. 函数逼近下的控制:瓦片编码 + 半梯度 SARSA
5.1 从评估到控制
把 换成 ,把第 4 讲的 SARSA 目标填进去:
这就是半梯度 SARSA。把目标换成 就是带函数逼近的 Q-learning。
5.2 瓦片编码
对于低维连续状态,**瓦片编码(tile coding)**是一种简单有效的特征:
一层网格(= 状态聚合) 多层错开的网格(瓦片编码)
┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐
│ │ │ │ │ │ ┌─┼─┐ │ │ │ ● = 一个状态
├───┼───┼───┼───┤ ├─┼─┼─┼─┼───┼───┤ 它在每一层网格中各落在一个格子里,
│ │ ● │ │ │ │ │ │●│ │ │ │ 激活 n 个特征(每层一个)
├───┼───┼───┼───┤ ├─┼─┴─┼─┘ │ │
│ │ │ │ │ │ │ │ │ │
└───┴───┴───┴───┘ └───┴───┴───┴───┘
- 每层网格把状态空间切成格子,状态落在哪个格子,那个特征就是 1;
- 用 层互相错开的网格,每个状态激活 个特征;
- 两个状态越接近,共同激活的特征越多,价值就越相似——泛化是平滑的;
- 有效分辨率比单层网格细 倍,参数只多 倍;
- 特征是稀疏的(只有 个 1),计算 只需把 个参数相加,非常快。
代码验证:
02_mountain_car_tile_coding.py演示 1:8 层 9×9 网格,共 648 个特征。状态 与 共享全部 8 个特征,与 只共享 1 个。
5.3 高山小车
Sutton & Barto 例 10.1:小车在山谷里,要开到右边山顶。状态是位置 和速度 ,两个连续数;动作是向左加速、不踩油门、向右加速;每步奖励 −1。难点:发动机太弱,直接向右冲不上去,必须先向左倒车爬坡、借助下滑的势能再冲向右边——“为了达到目标,先要远离目标”。
学习曲线(每回合到达山顶所需步数,5 次运行平均,代码 02 演示 2):
| 第 1 回合 | 2–10 | 11–50 | 51–100 | 101–200 | 201–300 | |
|---|---|---|---|---|---|---|
| 0.1 | 2936 | 1226 | 365 | 230 | 183 | 152 |
| 0.5 | 1516 | 487 | 209 | 148 | 137 | 128 |
( 是总步长,分到每个激活特征上是 。参数初始化为 0;因为每步奖励是 −1,0 是一个”乐观”的初始值,驱动智能体去尝试没试过的动作,所以不需要 -贪心——回忆第 4 讲 7.1 节的乐观初始化。)
学到的策略大致按”速度的正负”来决定:速度向右就向右踩油门,速度向左就向左踩油门——荡秋千:顺着当前运动方向加速,一来一回积累能量。用贪心策略跑一个回合:先向左退到约 −0.8,冲向右边但没冲上去;再退到约 −1.17,第二次才借着更大的势能冲上山顶,共 145 步。智能体从没被告诉”要先倒车”,只知道”每步 −1”,它自己发现了这个策略。
6. 致命三角:为什么会发散?
6.1 三个因素
表格型 Q-learning 保证收敛;线性同策略 TD 也保证收敛。但 Sutton & Barto 指出,当下面三个因素同时出现时,可能发散:
- 函数逼近:参数被多个状态共享;
- 自举:用自己的估计当目标(TD、Q-learning);
- 异策略:更新所依据的状态分布,与目标策略自己的状态分布不同。
这三者被称为致命三角(deadly triad)。去掉任意一个,就恢复稳定。
注意:Q-learning + 函数逼近恰好三者俱全——而这正是 DQN 要做的事。
6.2 Baird 反例
Baird(1995)构造了一个小得不能再小的例子:7 个状态、8 个参数、所有奖励都是 0(真实价值全是 0,而且 就能精确表示)。
上面 6 个状态:V(i) = 2 w_i + w_7 ○ ○ ○ ○ ○ ○
╲ ╲ │ │ ╱ ╱ 实线:去下面的状态
下面的状态: V(6) = w_6 + 2 w_7 ● 虚线:去上面某个状态
- 目标策略:永远走实线。行为策略:6/7 概率走虚线、1/7 概率走实线。
- 异策略半梯度 TD,用重要性采样比 修正(实线 ,虚线 ):
结果(代码 03,,,初始 ):
| 步数 | (异策略半梯度 TD,采样) | (半梯度期望更新,无噪声) |
|---|---|---|
| 0 | 10.3 | 10.3 |
| 200 | 52.2 | 29.6 |
| 600 | 166.8 | 140.0 |
| 1000 | 416.3 | 436.9 |
参数一路增长,没有收敛的迹象。即使完全没有采样噪声(右列:每一步都是精确的期望更新),也照样发散——问题不在噪声,而在”更新的分布”与”自举 + 函数逼近”的相互作用。
去掉任意一角:
- 去掉函数逼近(换成表格):各状态的价值先向 靠拢,然后缓慢而稳定地衰减到 0(20 万步后最大值 0.70),始终有界。
- 去掉异策略(按目标策略自己的状态分布更新——它只会访问状态 6): 从 12 平稳下降(1000 步后 7.3),参数不再爆炸。
6.3 发散的直觉
- ,上面每个状态 ,它们共享 。
- 从上面的状态走实线到状态 6 时(),TD 误差 。为了让 追上 ,更新会增大 和 ——而 增大又让 变大,于是下一次的目标 更高……”追目标”的同时把目标推得更远。
- 同策略时,状态 6 自己会被频繁更新,把 拉回来,形成负反馈;但在行为策略的分布下,从状态 6 出发的实线转移太少,这个”刹车”不够用。
- 表格情况下没有参数共享,更新一个状态不会影响别的状态的目标,也就没有这个正反馈。
6.4 收敛性一览
| 算法 | 表格 | 线性 | 非线性(神经网络) |
|---|---|---|---|
| MC 评估(同/异策略) | ✓ | ✓ | ✓(收敛到局部最优) |
| TD 评估(同策略) | ✓ | ✓ | ✗ |
| TD 评估(异策略) | ✓ | ✗ | ✗ |
| MC 控制 / SARSA | ✓ | (✓) | ✗ |
| Q-learning | ✓ | ✗ | ✗ |
✓ = 保证收敛;(✓) = 在接近最优的价值附近震荡;✗ = 没有收敛保证(存在发散的例子)。
Q-learning + 神经网络在最右下角:没有任何收敛保证。 DQN 的贡献就在于:用工程手段让这个”理论上不保证”的组合在实践中稳定下来。
7. DQN:深度 Q 网络
7.1 背景
2013 年(NeurIPS 研讨会论文)和 2015 年(Nature 论文),DeepMind 的 Mnih 等人提出 DQN,第一次让一个智能体直接从原始像素学会玩 Atari 2600 游戏:
- 输入:最近 4 帧游戏画面(缩放为 84×84 灰度图,叠在一起让状态近似马尔可夫——回忆第 1 讲 3.5 节);
- 网络:卷积神经网络,输出每个动作(最多 18 个)的 Q 值;
- 同一套网络结构和超参数,在 49 个游戏上训练,许多游戏达到或超过人类专业测试员的水平。
这被普遍视为深度强化学习时代的开端。它的核心就是 Q-learning + 神经网络 + 两个稳定化技巧。
7.2 技巧一:经验回放
问题:直接用在线 Q-learning 训练神经网络,每一步用刚刚经历的一条转移做一次梯度更新。
- 样本高度相关:连续的游戏画面几乎一样,用相关的数据做 SGD,梯度估计偏差很大,网络会”过拟合”最近的经历、忘掉之前学到的东西。
- 数据只用一次:每条经验用完就扔,样本效率极低。
解决:把经历过的转移 存进一个大的回放缓冲区(replay buffer),训练时从中均匀随机抽取一小批(mini-batch)来学习。
- 打破相关性:一个批次里的样本来自很多不同的时间、不同的回合,近似独立同分布;
- 数据复用:每条转移可以被抽中很多次;
- 平滑分布:训练数据是过去很多个策略的混合,不会因为策略的一点变化而剧烈摆动。
为什么这样做是合法的? 因为 Q-learning 是异策略的(第 4 讲 4.3 节):旧策略收集的数据,同样可以用来学最优策略的 Q 值。同策略算法(如 SARSA)不能直接使用经验回放——旧数据来自旧策略,与当前要评估的策略不匹配。
7.3 技巧二:目标网络
问题:Q-learning 的目标 依赖于正在训练的参数 。每更新一次 ,目标也跟着变——“追着自己的尾巴跑”。用神经网络时尤其糟糕:更新一个状态的 Q 值会影响所有状态(包括 )的 Q 值,这正是 6.3 节发散机制的来源。
解决:维护一个参数为 的目标网络,用它来计算 TD 目标:
在一段时间内冻结不变,每隔 步才从在线网络复制一次:。
这样,在 步之内,学习问题就变成了一个**目标固定的监督学习(回归)**问题,稳定得多。可以把它看成”每隔 步做一次近似的价值迭代”。
7.4 损失函数
在线网络参数 ,目标网络参数 ,从缓冲区抽取的一批转移 :
- 表示 是真正的终止状态(没有未来);
- 常用 Huber 损失:误差小时是平方 ,误差大时是线性 ,对偶尔出现的超大 TD 误差不那么敏感;
- 目标 不参与求导(在 PyTorch 中放在
torch.no_grad()里)——这正是 3.4 节的”半梯度”。
7.5 算法
算法:DQN
初始化:在线网络 Q(·; θ),目标网络 θ⁻ ← θ,回放缓冲区 D(容量 N)
s ← env.reset()
对 step = 1, 2, ..., T:
ε ← 按计划衰减(例如从 1.0 线性降到 0.05)
a ← ε-贪心(Q(s, ·; θ))
执行 a,得到 r, s', terminated, truncated
把 (s, a, r, s', terminated) 存入 D
s ← s';如果回合结束,s ← env.reset()
如果 D 中数据足够:
从 D 中均匀抽取一批 B 条转移
y ← r + γ (1 - terminated) max_a' Q(s', a'; θ⁻) (不求导)
对 L(θ) = mean(Huber(y - Q(s, a; θ))) 做一步梯度下降
每隔 C 步:θ⁻ ← θ
7.6 实现中的陷阱:终止 vs 截断
CartPole 最多 500 步。第 500 步时回合”结束”了,但杆子还立着——未来本来还有奖励,只是因为时间限制被人为截断。
- 真正终止(terminated,杆子倒了):没有未来,;
- 时间截断(truncated):有未来,应当继续自举 。
如果把截断也当成终止,智能体会以为”第 500 步之后一切归零”,价值估计会被系统性地压低。Gymnasium 用 terminated 和 truncated 两个标志区分这两种情况,只把 terminated 存进缓冲区。
一个有用的检查:CartPole 学会之后,杆子永远不倒,每步 +1,,所以 Q 值应当接近 。代码 04 训练结束时平均 Q 值约为 100.6 ✓。如果错误地把截断当终止,Q 值会明显偏低。
7.7 实验:CartPole 上的 DQN 与消融
04_dqn_cartpole.py:两层 128 单元的 MLP,Adam(学习率 5e-4),缓冲区 1 万,批大小 64,目标网络每 250 步同步, 在 1 万步内从 1.0 线性降到 0.05,共 8 万步。每 5000 步用贪心策略评估 10 个回合。4 个随机种子:
| 设置 | 首次评估达到 500 的步数(4 个种子) | 平均 | 最后 3 次评估的平均(4 个种子) | 保存的最好网络重新评估 |
|---|---|---|---|---|
| DQN | 50k, 40k, 55k, 30k | 43.8k | 500, 500, 242, 330 | 全部 500 |
| Double DQN | 45k, 65k, 40k, 40k | 47.5k | 500, 230, 394, 474 | 全部 500 |
| 无经验回放 | 60k, 80k, 80k, 75k | 73.8k | 500, 381, 481, 472 | 全部 500 |
| 无目标网络 | 从未达到 | — | 约 9 | 约 9 |
解读:
- 去掉目标网络:彻底失败。平均回报只有 9 左右(几乎一开始就倒)。打印训练日志会看到平均 Q 值在 3 万步内爆炸到 量级(真实值不超过 100)——这就是第 6 节的致命三角发散,在真实的深度 RL 中活生生地出现了。
- 去掉经验回放(只用最近 64 条连续转移训练):在 CartPole 这个简单问题上仍然能学会,但明显更慢(平均 73.8k 步才首次达到 500,而完整 DQN 是 43.8k)。需要说明的是,在更难的 Atari 游戏上,DQN 原论文的消融实验显示经验回放的作用非常大——去掉它,很多游戏的得分会大幅下降。不同问题上,各个组件的重要性可能不同,不要从一个简单问题上的消融得出普遍结论。
- 性能会波动:即使学到了满分,之后也可能突然下降(DQN 种子 2 最后 3 次评估平均只有 242)。所以实践中要定期评估并保存最好的模型——4 个种子保存的最好网络,在 20 个新回合上全部达到 500。
- Double DQN 在这个问题上与 DQN 相当。它的优势在动作多、奖励噪声大、高估问题严重的任务上更明显(第 4 讲第 6 节)。
8. DQN 之后的改进
DQN 发表后,大量工作对它进行了改进。几个最重要的:
| 改进 | 解决的问题 | 核心思想 | ||
|---|---|---|---|---|
| Double DQN(van Hasselt et al., 2016) | 最大化偏差导致 Q 值高估 | 用在线网络挑动作、目标网络评估:。只需改一行代码 | ||
| Dueling DQN(Wang et al., 2016) | 很多状态下”选哪个动作”不太重要,但网络仍要为每个动作单独学 Q 值 | 网络分两路输出:状态价值 和优势 ,再组合成 $Q(s,a) = V(s) + A(s,a) - \frac{1}{ | A | }\sum_{a’} A(s,a’)$ |
| 优先经验回放(Schaul et al., 2016) | 均匀抽样浪费在”已经学会”的转移上 | 按 TD 误差大小抽样(误差大的更”值得学”),并用重要性权重修正偏差 | ||
| 多步回报 | 1 步 TD 传播信息慢 | 用第 3 讲的 步回报作为目标 | ||
| 分布式 RL(C51 等) | 只学期望丢失了回报分布的信息 | 学习回报的整个分布,而不只是均值 | ||
| Noisy Nets | -贪心的探索很粗糙 | 在网络参数中加入可学习的噪声来驱动探索 | ||
| Rainbow(Hessel et al., 2018) | — | 把以上六种改进组合在一起,在 Atari 上显著超过每一种单独的改进 |
9. 深度 RL 的实践要点
- 一定要用多个随机种子(至少 3~5 个)。本讲 DQN 的实验中,同一个设置在不同种子上”首次达到满分”的时间从 3 万步到 5.5 万步不等。
- 对超参数敏感。代码 04 的超参数是在 3 个种子上试了十几种组合后选出来的;学习率、缓冲区大小、目标网络更新频率稍变,结果就可能差很多。
- 评估要用贪心策略、单独的环境。训练时的回报包含了 探索的噪声。
- 监控 Q 值。Q 值远超理论上限(如 )是发散或高估的信号。
- 保存最好的模型。深度 RL 的性能常常在学好之后又突然下降。
- 区分终止与截断(7.6 节)。
- 从简单问题开始调试。先在 CartPole 这样几十秒就能跑完的问题上验证代码正确,再去跑需要几小时、几天的问题。
10. 本讲总结
10.1 核心概念清单
- 函数逼近用带参数的函数表示价值,实现泛化;参数少于状态,用近似误差(偏差)换来方差的降低。
- 价值函数逼近 = 用 RL 目标代替标签的监督学习:梯度 MC 用 ,半梯度 TD 用 。
- 半梯度:TD 目标也依赖 ,但求导时把它当常数。
- 线性函数逼近:梯度就是特征 ;梯度 MC 收敛到最小 VE 解;半梯度 TD 收敛到 TD 不动点,误差 最优。
- 特征选择:既要”能表示”,也要”好学习”(条件数);瓦片编码适合低维连续状态。
- 致命三角:函数逼近 + 自举 + 异策略可能发散;Q-learning + 神经网络三者俱全。
- DQN:Q-learning + 神经网络 + 经验回放(打破相关性、复用数据)+ 目标网络(冻结目标、避免追尾)。
- 实现细节:Huber 损失、终止 vs 截断、贪心评估、保存最好模型、多种子。
- 改进:Double DQN、Dueling、优先经验回放、多步回报、分布式 RL、Noisy Nets → Rainbow。
10.2 公式速查表
| 名称 | 公式 |
|---|---|
| 梯度下降 | |
| 均方价值误差 | |
| 通用 VFA 更新 | |
| 线性价值 | , |
| 梯度 MC(线性) | |
| 半梯度 TD(线性) | |
| TD 不动点 | ,, |
| TD 误差界 | |
| 半梯度 SARSA | |
| 异策略 TD(重要性采样) | , |
| DQN 目标 | |
| Double DQN 目标 | |
| Dueling |
10.3 中英术语对照表
| 中文 | 英文 | 中文 | 英文 |
|---|---|---|---|
| 函数逼近 | Function approximation | 价值函数逼近 | Value function approximation (VFA) |
| 泛化 | Generalization | 特征向量 | Feature vector |
| 梯度下降 | Gradient descent | 随机梯度下降 | Stochastic gradient descent (SGD) |
| 学习率 | Learning rate | 条件数 | Condition number |
| 均方价值误差 | Mean squared value error (VE) | 同策略分布 | On-policy distribution |
| 梯度 MC | Gradient Monte Carlo | 半梯度 TD | Semi-gradient TD |
| TD 不动点 | TD fixed point | 残差梯度 | Residual gradient |
| 状态聚合 | State aggregation | 多项式基 | Polynomial basis |
| 傅里叶基 | Fourier basis | 瓦片编码 | Tile coding |
| 径向基函数 | Radial basis function (RBF) | 高山小车 | Mountain Car |
| 致命三角 | Deadly triad | Baird 反例 | Baird’s counterexample |
| 重要性采样比 | Importance sampling ratio | 深度 Q 网络 | Deep Q-Network (DQN) |
| 经验回放 | Experience replay | 回放缓冲区 | Replay buffer |
| 目标网络 | Target network | Huber 损失 | Huber loss |
| 终止 / 截断 | Terminated / Truncated | 消融实验 | Ablation study |
| 双 DQN | Double DQN | 对决网络 | Dueling network |
| 优先经验回放 | Prioritized experience replay | 灾难性遗忘 | Catastrophic forgetting |
11. 练习题
练习 1(概念):为什么说”表格方法是线性函数逼近的特例”?写出对应的特征向量,并验证梯度 MC 的更新公式退化为第 3 讲的形式。
参考答案
取 (第 个位置为 1 的独热向量),则 。梯度 MC 更新 只改变第 个分量:,正是第 3 讲的 。代码 01 实验 4 验证了这一点。
练习 2(手算):特征 ,,当前 ,,。观察到转移 。计算半梯度 TD(0) 更新后的 ,以及更新后 和 的值。注意 也变了吗?
参考答案
,。TD 误差 。 。 更新后 ,。
也变了(从 1.0 变成 1.15),虽然我们只更新了 ——因为它们共享参数 。这就是泛化;在 Baird 反例中,同样的机制导致了发散(更新 会改变 的价值,从而改变目标)。
练习 3(半梯度):写出”完整梯度”版本的 TD 更新(对 求全导数)。它和半梯度 TD 差在哪一项?
参考答案
,所以完整梯度更新为 。 与半梯度相比多了 这一项(线性时是 )。这叫残差梯度法。它是真正的梯度下降、收敛性更好,但在随机环境中会收敛到错误的解(因为 包含了转移随机性带来的方差),实践中也学得较慢。
练习 4(瓦片编码):用 4 层网格、每层 10×10 格子的瓦片编码表示一个二维状态空间,一共有多少个特征?每个状态激活几个特征?如果动作有 3 个,用”每个动作一组参数”的方式表示 ,一共需要多少个参数?
参考答案
每层 100 个格子(如果考虑偏移,实现中常用 11×11 = 121),4 层共 400(或 484)个特征。每个状态激活 4 个。 需要 (或 )个参数。每次更新只改变其中的 4 个。
练习 5(致命三角):下面每种情况,致命三角的哪几角存在?是否有发散的风险? (a) 表格型 Q-learning;(b) 线性函数逼近 + 同策略 MC 评估;(c) 线性函数逼近 + SARSA;(d) 神经网络 + Q-learning + 经验回放。
参考答案
(a) 自举 + 异策略,没有函数逼近 → 收敛。 (b) 函数逼近,没有自举、同策略 → 收敛(真正的梯度下降)。 (c) 函数逼近 + 自举,同策略 → 线性时在最优附近震荡但不发散(表 6.4)。 (d) 三者俱全 → 有发散风险。经验回放让更新分布更接近”过去策略的混合”,一定程度上还加重了异策略程度;目标网络是 DQN 用来缓解发散的主要手段(7.7 节:去掉目标网络后 Q 值爆炸到 )。
练习 6(DQN 设计):为什么 SARSA 不能直接使用经验回放,而 Q-learning 可以?
参考答案
SARSA 是同策略的:它的目标 中的 必须来自当前要评估的策略。缓冲区里的 是很久以前的旧策略选的,用它会学成”旧策略的价值”。Q-learning 的目标 不依赖于数据中的下一个动作, 只取决于环境的转移,与收集数据的策略无关,所以旧数据照样可用。(Expected SARSA 如果对当前策略求期望,也可以使用经验回放。)
练习 7(终止 vs 截断):CartPole 中,如果把”到达 500 步”也当作终止(),学到的 Q 值会怎样?对策略有什么影响?
参考答案
智能体会认为”第 500 步之后没有未来”。但状态中并不包含”现在是第几步”,所以对同一个状态,有时目标是 (未到 500 步),有时是 (恰好第 500 步),这些样本互相矛盾,Q 值会被系统性地压低,并且带有额外的噪声。在 CartPole 中通常仍能学会平衡,但学得更不稳定;在其他问题中可能导致明显次优的策略。可以修改代码 04,把 buffer.add(..., terminated) 改成 buffer.add(..., terminated or truncated) 观察平均 Q 值的变化(正确实现时约为 100)。
练习 8(动手)★:运行 python 04_dqn_cartpole.py --no-target,观察”平均 Q 值”一列。它最终大约是多少?CartPole 中 Q 值的理论上限是多少?这说明了什么?
参考答案
平均 Q 值会迅速增长到 量级。理论上限是 。Q 值比上限大了上百万倍,说明价值估计完全发散了——在线网络每更新一次,目标跟着变,“追目标的同时把目标推得更远”(6.3 节)。这是致命三角在深度 RL 中的直接体现,也说明了目标网络的必要性。
12. 延伸阅读
- Sutton & Barto,Reinforcement Learning: An Introduction(第 2 版):第 9 章(同策略预测的函数逼近,1000 状态随机游走、特征构造、瓦片编码)、第 10 章(同策略控制,高山小车)、第 11 章(异策略方法与致命三角,Baird 反例)。
- CS234 Spring 2024 Lecture 4–5 录像与课件。
- Mnih et al. (2015),Human-level control through deep reinforcement learning(Nature):DQN 原始论文,附录中有经验回放和目标网络的消融实验。
- Tsitsiklis & Van Roy (1997),An Analysis of Temporal-Difference Learning with Function Approximation:线性 TD 收敛性与误差界。
- Hessel et al. (2018),Rainbow: Combining Improvements in Deep Reinforcement Learning:DQN 各种改进的系统比较。
- CleanRL(GitHub):单文件、注释清晰的深度 RL 算法实现,适合对照学习。
13. 下一讲预告
到目前为止,所有控制算法都是基于价值的:先学 ,再用 得到策略。这条路有几个局限:
- 连续动作(比如机器人关节的力矩)时, 本身就是一个难以求解的优化问题;
- 的一点点变化可能让 突然跳到另一个动作,策略变化不平滑;
- 有些问题的最优策略是随机的(比如石头剪刀布、部分可观测的环境),而对 贪心只能得到确定性策略。
下一讲换一个思路:直接参数化策略 ,用梯度上升最大化期望回报。你会看到:
- 如何对”期望回报”求梯度——似然比技巧;
- REINFORCE 算法;
- 为什么策略梯度方差很大,以及基线如何在不引入偏差的情况下降低方差。
下一讲之前,建议你:跑通 code/lecture05/ 下的 4 个脚本(第 4 个需要先安装 torch 和 gymnasium),完成练习 2、5、6,并尝试练习 8 的消融实验。