DQN 深度 Q 网络


文档摘要

DQN 深度 Q 网络 本节摘要:2013 年,Mnih 用一个 Q 学习网络吃下原始像素,在七款 Atari 游戏上击败所有经典 RL 智能体;2015 年扩展到 49 款游戏、登上 Nature,点燃了深度 RL 时代。DQN = Q 学习 + 三个让函数逼近稳定的工程技巧:经验回放(去相关)、目标网络(冻结自举目标)、奖励裁剪(归一化梯度幅度)。本节讲透这三个技巧为何不可或缺——它们要驯服的是「致命三要素」(函数逼近 + 自举 + off-policy,三者合起来没有收敛保证)。你会在一个手写的单隐层 MLP 上跑通完整 DQN,看到它与 Atari 版算法在数学上完全同构,只是规模不同。

DQN 深度 Q 网络

本节摘要:2013 年,Mnih 用一个 Q 学习网络吃下原始像素,在七款 Atari 游戏上击败所有经典 RL 智能体;2015 年扩展到 49 款游戏、登上 Nature,点燃了深度 RL 时代。DQN = Q 学习 + 三个让函数逼近稳定的工程技巧:经验回放(去相关)、目标网络(冻结自举目标)、奖励裁剪(归一化梯度幅度)。本节讲透这三个技巧为何不可或缺——它们要驯服的是「致命三要素」(函数逼近 + 自举 + off-policy,三者合起来没有收敛保证)。你会在一个手写的单隐层 MLP 上跑通完整 DQN,看到它与 Atari 版算法在数学上完全同构,只是规模不同。最后讲清 Double DQN、Dueling、Rainbow 等改进——它们都是叠在这三件套之上的几百分点提升。

对应原课程:Phase 9 · Lesson 05 · dqn(原英文 phases/09-reinforcement-learning/05-dqn/docs/en.md)。

学习目标

阅读完本节,你应当能够:

  1. 解释致命三要素(Deadly Triad)为何让「朴素神经网络 + Q 学习」发散,以及 DQN 的三个技巧如何分别对症。
  2. 写出 DQN 的单步 TD 损失 L(θ) = E[(r + γ max Q(s',·;θ⁻) - Q(s,a;θ))²],区分在线网 θ 与目标网 θ⁻
  3. 实现经验回放目标网络同步(硬同步 vs 软更新)。
  4. 说明 Double DQN 如何修复最大化偏差(在线网选动作、目标网估值)。
  5. 列出 DQN 在 2026 年的适用边界:何时该换成 SAC/PPO/CQL。

一、问题与直觉

表格 Q 学习需要为每个(状态, 动作)对存一个 Q 值。棋盘约 10⁴³ 个状态;一帧 Atari 是 210×160×3 = 100800 个特征。表格 RL 在几千个状态时就死掉,遑论几十亿。

修复办法事后看很显然:用一个神经网络 Q(s, a; θ) 替换 Q 表。但「事后看显然」花了几十年。朴素函数逼近 + Q 学习在致命三要素下发散——函数逼近 + 自举 + off-policy 学习,三者合起来没有收敛保证。Mnih et al. (2013, 2015) 找到了三个工程技巧让训练稳定:

  1. 经验回放(Experience Replay) 去相关转移。
  2. 目标网络(Target Network) 冻结自举目标。
  3. 奖励裁剪(Reward Clipping) 归一化梯度幅度。

DQN 在 Atari 上是第一次:单一架构 + 单一超参集,从原始像素解决几十个控制问题。之后所有的「深度 RL」——DDQN、Rainbow、Dueling、Distributional、R2D2、Agent57——都叠在这个三件套之上。

目标函数:单步 TD 损失

L(θ) = E_{(s,a,r,s')~D} [ (r + γ max_{a'} Q(s', a'; θ⁻) - Q(s, a; θ))² ]
  • θ = 在线网络,每步用梯度下降更新。
  • θ⁻ = 目标网络,周期性从 θ 拷贝(约每 10000 步)。
  • D = 存历史转移的回放池。

三个技巧(按重要性排序)

经验回放:约 10⁶ 转移的环形缓冲。每次训练步均匀随机采样一个 minibatch。这打断了时间相关性(相邻帧几乎相同),让网络能从稀有的高奖励转移里学很多次,并去相关连续的梯度更新。没有它,神经网络 + on-policy TD 在 Atari 上发散。

目标网络:在贝尔曼方程两边用同一个 Q(·; θ),目标每次更新都动——「追自己的尾巴」。修复:留第二份网 Q(·; θ⁻),权重冻结。每 C 步把 θ → θ⁻ 拷一次。这把回归目标稳住数千个梯度步。软更新 θ⁻ ← τ θ + (1-τ) θ⁻(DDPG、SAC 用)是更平滑的变体。

奖励裁剪:Atari 奖励幅度从 1 到 1000+ 不等。裁到 {-1, 0, +1} 阻止单一游戏主导梯度。奖励幅度有意义时它不对;但 Atari 只在乎符号,所以无所谓。

Double DQN 与后续改进

Double DQN(Hasselt 2016)修复最大化偏差:用在线网动作,目标网值。

target = r + γ Q(s', argmax_{a'} Q(s', a'; θ); θ^-)

直接替换,普遍更好。默认就该开。

Rainbow(2017)的其他改进:优先回放(按 TD 误差大小采样)、Dueling 架构(分开 V(s) 与优势头)、Noisy 网(学到的探索)、n 步回报、分布式 Q(C51/QR-DQN)、多步自举。每个加几个百分点,收益大致可加。

💡 「致命三要素」是为什么深度 RL 比监督学习难得多的根源。监督学习里,目标 y 是固定的;DQN 里,目标 r + γ max Q(s',·;θ⁻) 自己也在变——而且跟着你正在训练的网一起变。目标网络的作用就是把「移动的靶」临时钉住几千步,让 SGD 有机会收敛到一个局部解。

二、从零实现

这里的代码是纯标准库、不用 numpy——我们手写一个单隐层 MLP,跑在一个微型连续 GridWorld 上,每次训练步只需微秒级。算法与大规模 Atari DQN 完全一致。

Step 1:经验回放

class ReplayBuffer: def __init__(self, capacity): self.buf = [] self.capacity = capacity def push(self, s, a, r, s_next, done): if len(self.buf) == self.capacity: self.buf.pop(0) self.buf.append((s, a, r, s_next, done)) def sample(self, batch, rng): return rng.sample(self.buf, batch)

Atari 用约 50000 容量;我们的玩具环境 5000 足够。

Step 2:一个微型 Q 网络(手写 MLP)

class QNet: def __init__(self, n_in, n_hidden, n_actions, rng): self.W1 = [[rng.gauss(0, 0.3) for _ in range(n_in)] for _ in range(n_hidden)] self.b1 = [0.0] * n_hidden self.W2 = [[rng.gauss(0, 0.3) for _ in range(n_hidden)] for _ in range(n_actions)] self.b2 = [0.0] * n_actions def forward(self, x): h = [max(0.0, sum(w * xi for w, xi in zip(row, x)) + b) for row, b in zip(self.W1, self.b1)] q = [sum(w * hi for w, hi in zip(row, h)) + b for row, b in zip(self.W2, self.b2)] return q, h

前向传播:线性 → ReLU → 线性。这就是整个网。

Step 3:DQN 更新

def train_step(online, target, batch, gamma, lr): grads = zeros_like(online) for s, a, r, s_next, done in batch: q, h = online.forward(s) if done: y = r else: q_next, _ = target.forward(s_next) y = r + gamma * max(q_next) td_error = q[a] - y accumulate_grads(grads, online, s, h, a, td_error) apply_sgd(online, grads, lr / len(batch))

形状是第 04 节的 Q 学习,只有两处不同:(a) 我们对一个可微的 Q(·; θ) 做反传,而非索引一张表;(b) 目标用的是 Q(·; θ⁻)

Step 4:外循环

for episode in range(N): s = env.reset() while not done: a = epsilon_greedy(online, s, epsilon) s_next, r, done = env.step(s, a) buffer.push(s, a, r, s_next, done) if len(buffer) >= batch: train_step(online, target, buffer.sample(batch), gamma, lr) if steps % sync_every == 0: target = copy(online) s = s_next

在我们的微型 GridWorld(16 维 one-hot 状态)上,约 500 回合学到近最优策略。Atari 上,把它放大到 2 亿帧并加一个 CNN 特征提取器。

三、框架对比

2026 年 DQN 已很少是最先进,但仍是参考性的 off-policy 算法:

任务 首选方法 为什么不用 DQN
离散动作 Atari 类 Rainbow DQN 或 Muesli 同框架、更多技巧
连续控制 SAC / TD3(第 07 节) DQN 没有策略网
on-policy / 高吞吐 PPO(第 08 节) 无回放池;更易扩展
离线 RL CQL / IQL / Decision Transformer 保守 Q 目标,无自举爆炸
大离散动作空间(推荐) 带 action embedding 的 DQN,或 IMPALA 可以,装饰很重要
LLM RL PPO / GRPO 序列级而非步级;损失不同

经验教训仍然流转。回放与目标网络出现在 SAC、TD3、DDPG、SAC-X、AlphaZero 的自博弈缓冲、以及每个离线 RL 方法里。奖励裁剪在 PPO 里以「优势归一化」的形式延续。这套架构就是蓝图。

与 stable-baselines3 / CleanRL 的对照

生产里你不会手写 DQN——stable-baselines3DQN 算法或 CleanRL 的单文件 dqn.py 是标准选择。两者都默认带目标网、回放池、ε 衰减。读 CleanRL 那个单文件是理解 DQN 的最佳途径:它把所有部件压在一个不到 300 行的文件里,与我们的骨架一一对应。值得做的事:把本节的玩具实现与 CleanRL 对照阅读,看每个 trick 在真实代码里长什么样。

四、可复用产物

本节产出一个可复用 skill(位于原课程 outputs/skill-dqn-trainer.md)。骨架:

--- name: dqn-trainer description: 为离散动作 RL 任务产出 DQN 训练配置(缓冲、目标网同步、ε 调度、奖励裁剪)。 version: 1.0.0 phase: 9 lesson: 5 tags: [rl, dqn, deep-rl] --- 给定一个离散动作环境(观测形状、动作数、视野、奖励尺度),输出: 1. 网络。架构(MLP / CNN / Transformer)、特征维、深度。 2. 回放池。容量、minibatch 大小、预热大小。 3. 目标网络。同步策略(每 C 步硬同步,或软更新 τ)。 4. 探索。ε 起 / 止 / 调度长度。 5. 损失。Huber vs MSE、梯度裁剪值、奖励裁剪规则。 6. Double DQN。默认开,除非有明确理由关。 拒绝交付没有目标网络、没有回放池、或 ε 全程=1 的 DQN。 拒绝连续动作任务(改走 SAC / TD3)。 标记任何奖励范围 > 步均值 10 倍的,需裁剪或尺度归一化。

五、练习

  1. 基础。code/main.py,画出每回合回报曲线。多少回合后滚动均值超过 -10?
  2. 进阶。 关掉目标网络(贝尔曼目标两边都用在线网)。测训练不稳定——回报会震荡还是发散?
  3. 挑战。 加上 Double DQN:用在线网挑 argmax a'、目标网估值。在带噪声奖励的 GridWorld 上,1000 回合后,对比有/无 Double DQN 时 Q(s_0, best_a) 相对真值 V*(s_0) 的偏差。

六、常见陷阱

  • 致命三要素。 函数逼近 + off-policy + 自举可能发散。DQN 用目标网 + 回放缓解;任何一个都别拆掉
  • 探索。 ε 必须衰减,典型从前 10% 训练的 1.0 衰到 0.01。早期探索不够,Q 网会收敛到一个局部盆地。
  • 高估。 对有噪 Q 取 max 向上偏。生产里永远用 Double DQN
  • 奖励尺度。 裁剪或归一化奖励;梯度幅度正比于奖励幅度。
  • 回放池冷启动。 缓冲里没有几千转移前别训练。~20 样本上的早期梯度会过拟合。
  • 目标网同步频率。 太频繁 ≈ 没目标网;太稀 ≈ 目标过时。Atari DQN 用 10000 环境步。经验法则:训练视野的 ~1/100。
  • 观测预处理。 Atari DQN 叠 4 帧让状态马尔可夫。任何含速度信息的环境都需要帧堆叠或循环状态。

七、关键术语速查

术语 俗称 实际含义
DQN 「深度 Q 学习」 用神经网络做 Q 函数 + 回放池 + 目标网
经验回放 「打散的转移」 每次梯度步均匀采样的环形缓冲;去相关数据
目标网络 「冻结的自举」 周期性拷贝的 Q,用于贝尔曼目标;稳住训练
致命三要素 「RL 为何发散」 函数逼近 + 自举 + off-policy = 无收敛保证
Double DQN 「最大化偏差的修复」 在线网选动作,目标网估值
Dueling DQN 「V 头与 A 头」 分解 Q = V + A - mean(A);同输出,梯度流更好
Rainbow 「全家桶」 DDQN + PER + Dueling + n 步 + Noisy + 分布式
PER 「优先回放」 按 TD 误差幅度比例采样转移

本节要点回顾

  1. DQN = Q 学习 + 三件套:经验回放、目标网络、奖励裁剪,用来驯服致命三要素。
  2. 致命三要素(函数逼近 + 自举 + off-policy)是朴素深度 Q 学习发散的根因;目标网与回放分别对症「移动靶」与「时间相关」。
  3. 损失是单步 TD:L(θ) = E[(r + γ max Q(s',·;θ⁻) - Q(s,a;θ))²],在线网 θ 更新、目标网 θ⁻ 周期冻结。
  4. 经验回放去相关、目标网络稳靶——两者缺一不可。
  5. Double DQN 默认开:在线网选动作、目标网估值,修复 max 的高估偏差。
  6. Rainbow 的改进可加:PER、Dueling、Noisy、n 步、分布式,每个几百分点。
  7. 2026 年 DQN 是参考而非 SOTA:连续动作用 SAC/TD3,on-policy 高吞吐用 PPO,离线用 CQL/IQL——但回放 + 目标网的范式仍在它们体内。

下一节,我们换个思路——不再估价值,而是直接参数化策略算期望回报的梯度往上爬。REINFORCE 是 PPO、GRPO 与所有 LLM RL 循环的源头。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U