01 / 从表格到函数
Q 表会随状态数增长
第十五节只有 6 个状态、4 个动作,一张 6×4 的表就够了。如果状态包含地图位置、钥匙有无、障碍开关等组合,每种组合都要一行。改变下面的数量,看看 Q 表需要多少个数。
神经网络用共享参数把状态映射为 Q 值,因此不需要为每种状态组合显式存一行。这不保证它在没见过的状态上一定预测正确;数据覆盖、状态表示和训练稳定性仍然重要。
02 / 输入与输出都要看清形状
一次前向计算给出四个动作的估计
把状态 S/A/B/D/C/G 编成长度 6 的 one-hot 向量。网络 6 → 32 → 32 → 4,末尾四个数按「右、下、上、左」排列。训练时的一个批次是 (32, 6) → (32, 4)。本例输入编码仍与状态数量有关,只是没有再为每个状态—动作对显式存表项;更丰富的环境可改用坐标、图像等输入。
one-hot (6,)→Linear + ReLU
(32,)→Linear + ReLU
(32,)→Q(s,·)
(4,)
输入状态
训练结束后的四个输出
和第十五节一样,进入 C 或 G 后回合终止,不再做动作选择。终止状态的网络输出没有在页面中当作可行动作价值来解释。
03 / DQN 的核心数学
在线网络求当前 Q,目标网络造 TD 目标
一条经验是 (s, a, r, s′, terminal)。在线网络的 Q(s,a) 是要被调整的预测;另一个参数暂时冻结的目标网络给出下一状态的最大 Q。终止时未来项必须归零。
这里使用 SmoothL1(Huber)损失:小误差附近像平方误差,大误差附近增长更平缓。下方用固定的玩具数值演算,和上面的真实训练结果分开。
chosen_q = online(states).gather(1, actions).squeeze(1) # (32,)
with torch.no_grad():
next_best = target(next_states).max(dim=1).values
td_target = rewards + gamma * next_best * (~terminals).float()
loss = torch.nn.functional.smooth_l1_loss(chosen_q, td_target)
optimizer.zero_grad()
loss.backward()
optimizer.step()04 / 两个缓解训练不稳定的装置
把经历存起来;让目标晚一点动
经验回放 Replay buffer
智能体连续走的几步高度相关。先把经历放进容量 1000 的队列,再随机抽 32 条组成训练批次,既能反复利用经历,也打散了相邻样本的顺序。
目标网络 Target network
如果用于计算 y 的网络每次梯度更新后也立刻变化,就像追一个不断移动的答案。本课的目标网络参数保持不变,每 100 次优化才复制一次在线网络参数。
每批更新→复制参数
每 100 次优化→Q_target
批内冻结
看一次实际抽取的前 8 条经验。表中「原动作」是在环境执行前选的动作;随机替换时环境重抽执行动作,也可能抽回原动作。Q-learning / DQN 的更新仍记录原动作,因为它学习的是“选择这个动作后,在随机环境里预期怎样”。
下表只展示第一次优化批次中的 8/32 条。缓存里的经验会继续被后续随机批次复用;这张表不表示所有经验都只来自这些回合。
05 / 真正在 CPU 上训练并评估
用多次运行分清探索成绩与贪心成绩
环境沿用旧网格:S→C 立即得 +2,S↓A→B→D→G 在终点得 +10,γ=0.8。与上一节的确定性网格相比,每一步有 10% 概率把预定动作替换为随机方向,因此同一策略的回报也会变化。训练 600 回合,ε 从 1 降到 0.05;评估关闭 ε 探索,但环境随机扰动仍存在。
各训练种子的贪心评估均值
各检查点使用同一组 40 个环境随机种子;最终结果另外用 100 回合评估。曲线点之间只是连线,没有在中间回合做评估。
当前检查点
最后的对照
随机策略始终在四个方向均匀选择,不学习;用 300 回合估计平均折扣回报。三个 DQN 模型每个用 100 回合,评估环境随机种子相同,所以是配对比较。表里的「标准差」反映每回合回报波动,不是三个训练种子的统计置信区间。
看一条实际评估轨迹
06 / 亲手改一处,解释一个现象
完成本节练习
配套程序在 lesson-16-dqn.py。在项目根目录用 uv 管理的 Python 运行 uv run python lessons/lesson-16-dqn.py,结果写入 lessons/lesson-16-output/results.json。不需要下载环境、数据或预训练权重。
- 找到
chosen_q、td_target、loss三行,分别写出形状,并说明为什么终止时不能加未来 Q。 - 把
TARGET_SYNC从 100 改为 20,重新运行。比较三个种子的检查点评估均值;只描述这个实验的变化,不把一次实验当成普遍结论。 - 恢复参数,再把全局的
SLIP改为 0,预测回报的回合间标准差会怎样变化;实际运行验证。注意这同时改变了训练环境,若只想比较评估环境,需要给评估和训练分设扰动参数。 - 解释为什么本例用 Q 表更省事,换到大量状态时神经网络又可能有用。
07 / 快速自测