动作只有“左、右、停”时,网络可以一次输出 3 个 Q 值,直接选最大值。看起来像普通回归,但标签不是固定真值:标签里也有网络自己的估计。

想先看清“回归 loss 降了,为什么 value 还可能跑偏”,从 Fitted Q 的两状态反例开始;这一篇接着讲 DQN 如何缓解这些问题。

把一条 transition 变成 loss

回放缓冲区(replay buffer)存 $(s,a,r,s',d)$。在线网络(online network) $Q_\theta$ 预测当前动作的价值。

慢一些变化的目标网络(target network)$Q_{\bar\theta}$ 构造标签:

$$ y=r+\gamma(1-d)\max_{a'}Q_{\bar\theta}(s',a'),\qquad L(\theta)=\mathbb E[(Q_\theta(s,a)-\mathrm{stopgrad}(y))^2]. $$

$d$ 是真实终止,不是所有 reset。经验回放(experience replay)减弱连续样本的强相关,也重用交互;目标网络减慢目标漂移。它们缓解问题,不保证神经网络一定收敛。

这叫半梯度(semi-gradient):只沿当前 Q 反传,不沿 target 反传。不是对完整 Bellman residual 的所有出现位置同时求导。

DQN 与 Double DQN,差在选择和估值

最大值会更容易挑中被噪声高估的动作。Double DQN 用 online network 选动作,再让 target network 估值:

$$ a^*=\arg\max_a Q_\theta(s',a),\qquad y_{\rm DDQN}=r+\gamma(1-d)Q_{\bar\theta}(s',a^*). $$

取 online Q 为 $[5,4]$,target Q 为 $[2,6]$,$r=1,\gamma=0.9$。DQN 选 target 的 6,得到 6.4;Double DQN 选 online 排第一的动作,再取 target 对它的 2,得到 2.8。

静态对照:DQN target=6.4,Double DQN target=2.8。交互可改变 online 的排序。

这里没有真实 $Q^*$,所以不能凭 target 小就说更准。这个例子只展示解耦机制。两个网络依然相关,也可能低估。

写代码时看 shape 和梯度

下面沿用相同的 Double DQN target,但把公式里的平方误差换成 Smooth L1(这里等价于阈值为 1 的 Huber loss),减弱很大 TD 误差(TD error / residual)对梯度的影响。它没有改变选动作和估值的规则。

import torch

def double_dqn_loss(online, target, states, actions, rewards,
                    next_states, terminated, gamma=0.99):
    if rewards.ndim != 1 or rewards.numel() == 0:
        raise ValueError("rewards must be a nonempty vector")
    if not rewards.is_floating_point():
        raise ValueError("rewards must be floating-point")
    if actions.shape != rewards.shape or terminated.shape != rewards.shape:
        raise ValueError("actions, rewards, and terminated must have shape [batch]")
    if actions.dtype != torch.long or terminated.dtype != torch.bool:
        raise ValueError("actions must be int64 and terminated must be boolean")
    if states.ndim != 2 or states.shape != next_states.shape or states.shape[0] != rewards.numel():
        raise ValueError("states and next_states must have matching [batch, state_dim] shapes")
    if not 0 <= gamma <= 1:
        raise ValueError("gamma must be in [0, 1]")
    prediction = online(states).gather(1, actions[:, None]).squeeze(1)
    with torch.no_grad():
        next_q = torch.zeros_like(rewards)
        continuing = ~terminated
        if gamma > 0 and continuing.any():
            successors = next_states[continuing]
            next_actions = online(successors).argmax(dim=1, keepdim=True)
            next_q[continuing] = target(successors).gather(1, next_actions).squeeze(1)
        expected = rewards + gamma * next_q
    return torch.nn.functional.smooth_l1_loss(prediction, expected)

这里 states 是 [batch, state_dim],actions 是 int64 [batch],rewards 是浮点 [batch],terminated 是布尔 [batch];示例使用同设备、同浮点精度的普通 MLP。形状检查会拒绝 [batch, 1] 的奖励(reward),避免它与 [batch] 的 Q 广播出一张 [batch, batch] 的错误表。

真实终止的后继状态不进入网络:target 就是 reward。若先算出 NaN 再乘零,仍会得到 NaN。外部截断不是终止,必须留下 reset 前的有效 final observation。网络若含 dropout / BatchNorm,还要另定模式;no_grad 不等于 eval(),改变有效 batch 也会影响 BatchNorm 统计。这个小实现不替你管理这些训练状态。

几个名字别混

改动 改了什么 没改什么
Double DQN 下一动作的选择与估值解耦 仍是 Q-learning
Dueling network 以 value 与 action advantage 组合 Q 不等于两个 target network
Prioritized replay 更常抽取某些 transition 不改 target 公式;但采样分布(sampling distribution)变了,要用重要性权重校正偏差

探索通常用 $\epsilon$-greedy。训练时随机动作是为了收集信息,评估时是否关掉要明确记录。把探索噪声造成的低分和 policy 本身的低分混在一起,曲线会不好解释。

为什么 max 会把零均值噪声变成高估?

两个动作的真实价值都是 0,误差各自独立地以相同概率取 +1 或 −1:

两个误差 最大值
−1,−1 −1
−1,+1 +1
+1,−1 +1
+1,+1 +1

每个动作的误差均值为 0,最大值的期望却是 0.5。选择过程留下了看起来最好的噪声。若换一组独立、零均值误差评价被选中的动作,这个例子里的估值期望会回到 0。

Double DQN 的网络并不真正独立,所以这解释的是动机,不是无偏性证明。目标网络太接近在线网络,误差可能高度相关;太久不更新,又可能过时。

Replay 和 target 的“慢”,分别慢在哪里?

Replay 存历史 transition,不是永久固定的 target。抽出 $(s,a,r,s')$ 后,再按当前约定的目标网络构造标签。随机打散减弱连续样本的相关性,却不保证旧数据覆盖新策略(policy)会去的状态。

冻结目标网络不等于所有标签都固定。普通 DQN 对同一 transition 的标签在硬同步之间不变(假设网络输出确定);Double DQN 的 online 网络仍在选动作,排序变化时标签也会变。例如 $r=1,\gamma=0.9$,target 仍为 $[2,6]$,online 从 $[5,4]$ 变成 $[3,4]$,Double DQN 的标签就从 2.8 变为 6.4,无须同步 target。

目标网络可以周期性硬复制,也可以软更新 $\bar\theta\leftarrow(1-\tau)\bar\theta+\tau\theta$。同步周期要说明按环境步还是梯度步计数:每次交互训练 10 次,与训练 1 次时的“1000 步”,不是同样节奏。

增加什么 可能的帮助 额外风险
Replay 容量 历史更丰富 数据变旧,稀有成功被淹没
每次交互的更新次数 更充分利用数据 过拟合(overfitting)有限 replay,放大 Q 误差
同步间隔 目标暂时更稳 目标过时,信息传播变慢

样本效率比较至少同时记录环境交互量和梯度更新量。只说“训练一小时”,分不清多用了数据还是计算。

检查这 4 件事,再调参

先测终止 target 是否等于 reward;再确认 target 无梯度;再核对 replay 里的 action 与 state 是否对齐;最后看 target 同步和环境步数的单位。Loss 降低但 return 不涨,优先排查这些语义,不要先堆更大网络。

参考:Double DQN(arXiv v3,2015)。下一篇:连续动作没法枚举最大值,怎么办?

本页术语 10
回放缓冲区 replay buffer
存储交互样本、供后续更新重新采样的数据结构
在线网络 online network
当前接受梯度更新的网络;这里的 online 不表示已部署为在线服务
目标网络 target network
用于构造学习目标的网络;DQN 中缓慢更新,RND 中则是固定的随机网络
经验回放 experience replay
把过去的交互保存起来重新采样,提高数据复用;并不消除分布偏移
半梯度 semi-gradient
更新当前预测时把自举目标视为常数,不沿目标中的估值继续求导
TD 误差 TD error / residual
一步目标减去当前估值;是学习信号,不是与真实价值之差
奖励 reward
RL 里指导策略更新的标量信号
采样分布 sampling distribution
训练候选被抽中的概率分布,它决定模型经常看到哪类边界
策略 policy
给定状态,规定怎样选动作;可以输出一个动作,也可以输出动作的概率分布
过拟合 overfitting
训练集越来越好,没见过的数据反而变差