强化学习顿悟记:从 Q 表到 DQN,三个击穿本质的思维跃迁
在学习强化学习(RL)从传统算法迈向深度强化学习(DQN)的过程中,很多人会被密密麻麻的数学公式、神经网络结构、复杂的循环搞得晕头转向。
但当你真正看透代码底层的那一刻,你会发现:复杂的 DQN 本质上只是传统 Q-Learning 的一种“自然演化”。
本文记录我在剖析源码时的三次关键顿悟,带你一眼看穿 DQN 的核心底牌。
顿悟一:反向传播,不过是传统“改表格”的暗度陈仓
很多人把深度强化学习当成一个全新的黑盒,但对比下面两段代码,你会发现它们在物理意义上完全等价:

1. 传统 Q-Learning:显式“硬改表格”
在状态少的小游戏(如走格子)里,Q 值是一张矩阵大表:
# 1. 算出目标与当前预测的差距
update = target - value
# 2. 乘以学习率,直接把对应格子的数值挪过去
Q[row, col, action] += lr * update
逻辑非常朴素:算出来差多少,就直接修改内存矩阵里 (状态, 动作) 那个格子的数字,强行对齐。
2. 深度强化学习 DQN:隐式“反向传播”
当游戏变得像王者荣耀、Atari 画面那样,状态多到几亿种,内存存不下这张大表了。
于是,我们用神经网络充当“查表函数”:输入状态 ,输出每个动作的价值
。
但神经网络没有格子给你直接赋值,该怎么更新?
# 1. 计算均方误差损失 (MSE)
loss = loss_fn(value, target) # 相当于 (value - target)^2
# 2. 反向传播更新网络参数
loss.backward()
optimizer.step()
核心结论
DQN 的反向传播,本质上就是在用梯度下降,去实现传统表格里那句
Q += 步长 * (target - value)!
- 传统方法:状态有限,直接修改格子的绝对数值(显式硬改)。
- 深度学习:状态无限,把 Target 当作临时标签,通过损失函数与反向传播调整全网权重,间接让输出逼近 Target(隐式逼近)。
顿悟二:双模型之谜——动作模型(model)vs 经验模型(prev_model)
在 DQN 训练中,最让人头疼的代码往往是为什么需要两个模型:一个动作模型 model,一个经验模型 prev_model。

1. 贪吃蛇打靶隐喻:移动的靶子
强化学习的 Target 是通过下一步的预估算出来的:
如果你只用一个模型:
- 你射出一箭(预测当前
value)。 - 同时靶子也在跑(
target也由正在更新的模型实时计算)。 - 结果就是:边打靶,靶子边晃,网络参数剧烈震荡,训练彻底崩溃。
2. 角色分工与协作
| 模型角色 | 代码变量名 | 实际输入 | 核心职责 |
|---|---|---|---|
| 动作模型 | model |
(state, action) |
负责做决策、算当前 value,每一步都在反向传播更新(动态射手)
|
| 经验模型 | prev_model |
(next_state) |
负责算未来的最大期望 target,权重暂时冻结(静止靶子)
|
每隔固定步数(例如 10 步),才会把 model 的最新参数同步给 prev_model:
if (i + 1) % 10 == 0:
prev_model.load_state_dict(model.state_dict())
核心本质
经验模型
prev_model,本质上就是“上一个阶段沉淀下来的老模型”。用历史阶段沉淀下的稳定自己(
prev_model,冻结参数定出固定靶心),来考核当前正在努力学习的自己(model,反向传播往前跨一步)。靶子定住,训练自然稳定!
顿悟三:目标同步的艺术——游戏镜头“弹簧缓动(Spring)”式的软更新
在看到每隔 10 步生硬地全盘拷贝权重时(if (i + 1) % 10 == 0:),很多人的第一直觉是:
“能不能根据指标来?比如等 Loss 降低到指定阈值 才同步,是不是更科学?”
1. 强化学习的陷阱:Loss 变小 ≠ 策略变强
在监督学习里,Loss 越低越好;但在强化学习中,直接拿 Loss 当阈值是个巨大陷阱:
- 局部死锁:模型如果陷入原地打转的死胡同,行为极度单一,Loss 也会趋近于 0,容易“假性早熟”,把 Target 锁死在错误目标上。
- 探索剧震:当 Agent 偶然吃到大奖探索到全新领域,Loss 会瞬间暴增。
-
门槛两难:阈值高了震荡,阈值低了可能一辈子也达不到,导致
prev_model永远无法更新。
2. 游戏开发者的灵感:摄像机弹簧缓动(Spring / Lerp)
现代强化学习(如 DDPG、TD3、SAC)采用了一种极其优雅且物理感十足的方案——软更新(Soft Update / Polyak 平均)。
这与游戏引擎里第三人称摄像机的弹簧臂(Spring Arm / Lerp 缓动跟随)完全是同一个数学原理:
# 游戏镜头跟踪角色:避免镜头瞬移生硬,使用线性插值平滑跟随
camera_pos = Lerp(camera_pos, player_pos, tau)
# 强化学习软更新:避免 Target 断崖式跳跃,让经验模型像弹簧阻尼一样丝滑跟随
for prev_param, param in zip(prev_model.parameters(), model.parameters()):
prev_param.data.copy_(0.005 * param.data + 0.995 * prev_param.data)
- 硬更新(% 10):就像摄像机每隔 10 秒“瞬移”一次,画面产生突兀的跳跃。
-
软更新(Spring 跟随):经验模型
prev_model带着惯性和微小阻尼,永远在每一步悄悄向主模型靠拢一点点。既不会让靶子乱晃,又告别了生硬顿挫,训练曲线稳如磐石!
终极总结
-
表格 vs DQN:查表是手动填数,DQN 是借反向传播调权重,两者都在把
value拉向target。 -
动作模型 vs 经验模型:一个是活泼好学的当前射手(
model),一个是沉稳冻结的历史靶子(prev_model),靶子定住,射手才能练出神准枪法。 - 硬同步 vs 软跟随:别用 Loss 阈值防踩坑,用游戏镜头式的“弹簧阻尼缓动(Lerp)”,让经验模型丝滑跟随,稳步迭代!