DQN与DDNQ
DQN vs DDQN
0. 我们的终极目标:一本“完美”的决策指南
想象一下,你(AI)被困在一个迷宫里。在任何一个“路口”(我们称之为状态 s),你都有几个“选择”(我们称之为动作 a,比如“往东”或“往西”)。
你的目标是找到一条路,能让你未来的“总分”最高(例如,+10 分找到出口,-100 分掉进陷阱)。
你梦想能有一本“完美的决策指南”,我们叫它 。
这本指南会告诉你,在任何路口 s,选择任何动作 a 的“未来总分”是多少。
例如,你在路口 s:
- = +50分 (这条路最终通向出口)
- = -80分 (这条路通向陷阱)
有了这本指南,你就无敌了。你只需要在每个路口,选择那个“分数”最高的动作就行了。
强化学习的唯一目标: 就是想办法“估算”出这本“完美指南” 。
如果我们有这个“神的公式”,AI 就无敌了。它在任何画面 s 前,只需比较一下:
- = 10.5 分
- = 8.2 分
AI 会(贪婪地)选择“不跳”,因为它知道这个动作能带来最高的“未来总分”。
1. “教科书”:我们如何学习?
Q*(s, a)(在“这一帧”执行动作a的“完美分数”)…=(应该等于)…r(你立刻获得的奖励,比如+0.1)…+(加上)…\gamma(一个“折扣因子”,代表 AI 的“耐心”,比如 0.99)…\max_{a'} Q^*(s', a')(在“下一帧”s',AI 再次从所有可能的动作a'中,选择那个能带来“未来最高分”的动作)。
我们的“训练” (optimize_model) 就是为了让 AI 的“神经网络”(policy_net)去强行“模仿”这条法则。
我们通过最小化“损失”(Loss)来做到这一点:
Loss = ( "教科书上的目标值" - "AI大脑的当前值" )²
"AI大脑的当前值" = (即 policy_net 的预测)
"教科书上的目标值" = (我们对 的最佳估算)
DQN 和 DDQN 的唯一区别,就在于它们“估算” 的数学方法不同。
折扣因子
场景 A:如果 (没有耐心)
如果 gamma = 0,我们的“黄金法则”会变成:
数学翻译: “未来总奖励”(Q) 等于“即时奖励”(r)。
行为翻译(“极度短视”):
* AI 变成了“活在当下”的“瘾君子”。它只关心它在下一帧能获得的奖励。
* 它完全不在乎 10 步、50 步或 100 步后会发生什么。
在 Flappy Bird 中的后果:
* 游戏中有两个选择:
1. “摸鱼”: 什么也不做。r = +0.1 (来自 REWARD_SURVIVE)。
2. “冒险”: 尝试穿过 50 帧以外的柱子。
* 的 AI 永远学不会穿过柱子。为什么?
* 因为在它看来,“穿过柱子”的 +5.0 奖励在 50 帧后才发生,太遥远了,它看不见。它只看得到 +0.1(“摸鱼”)和 -5.0(“死亡”)。它会选择“摸鱼”直到撞死。
场景 B:如果 (“圣人”般的无限耐心)
如果 gamma = 1,“黄金法则”会变成:
数学翻译: “未来总奖励”等于所有未来奖励的简单总和。
行为翻译(“无限远见”):
* AI 认为“明天”的 100 块钱和“今天”的 100 块钱价值完全相等。
在 Flappy Bird 中的后果(数学灾难):
* 假设 AI 发现了一个“Bug”,它可以在一个地方“无限摸鱼”,每帧都获得 +0.1 奖励。
* 它的“未来总奖励”会变成:0.1 + 0.1 + 0.1 + ... 直到无穷大 (∞)。
* 数学崩溃了。 神经网络无法计算“无穷大”,Loss 会变成 NaN,训练当场爆炸。
* 这在数学上称为“总和不收敛”。
场景 C:
现在,我们来看看我们选择的 gamma = 0.99 是如何同时解决这两个问题的。
\gamma 的意思是:“未来的奖励是好的,但每晚一帧,它的价值就打 99 折。”
假设 AI 马上就要穿过柱子了(+5.0 奖励),并且它知道自己离柱子还有 3 帧。
AI 会这样计算这个奖励在**“今天”的“净现值” (Present Value)**:
- 第 1 帧后 (存活):
+0.1 - 第 2 帧后 (存活):
+0.1 * (0.99) - 第 3 帧后 (穿过柱子):
+5.0 * (0.99) * (0.99)
如果你把这个链条拉长:
- “现在”的
+1奖励: 价值1.0 - 10 帧后的
+1奖励: 价值 (打了 9 折) - 100 帧后的
+1奖励: 价值 (只剩 3.6 折) - 1000 帧后的
+1奖励: 价值 (几乎一文不值)
2. 问题:迷宫太大了(DQN)
比如在电子游戏中,“路口”(s)就是游戏画面。游戏画面的组合是无限的。
我们不可能用一张“表格”来记录所有 的分数。
DQN (Deep Q-Network) 的解决方案:
我们不“记录”分数,我们用一个**“估算器”来“预测”分数。这个“估算器”就是深度神经网络(Deep Neural Network)**。
我们现在有了一个“AI 大脑”(一个神经网络),我们叫它 。( 代表神经网络中所有的“权重”或“参数”,即“大脑的配置”)。
AI 的“学习”,就是不断调整它的“大脑配置 ”,让它的“估算” 尽可能地接近“教科书”上的“完美分数”。
3. 标准 DQN:一个“过度自信”的估算者
DQN 遇到了一个“鸡生蛋,蛋生鸡”的问题:
- AI 的“估算值” (Current Value) 是:
- AI 的“目标值” (Target Value) 是:
如果 AI 用同一个大脑 既产生“估算值”,又产生“目标值”,那么“目标”就会随着“估算”一起移动。这就像一只试图咬自己尾巴的狗,极其不稳定。
DQN 的第一个创新 (Target Network):
DQN 说:“我们用两个大脑!”
policy_net(): “学生大脑”。它快速学习,我们每一步都更新它。它用来提供“AI 大Mao的当前值”。target_net(): “老师大脑”。它是“学生大脑”的一个**“冷冻”副本**(例如,每 1000 步才复制一次)。它缓慢更新,非常稳定。
DQN 只用“老师大脑” () 来计算那个“教科书上的目标值”:
(这就是 target_net(next_state).max(1)[0])
这解决了“稳定性”问题。但它引入了一个新的、更隐蔽的数学缺陷。
DQN 的致命缺陷:max 运算符的“过度自信” (Overestimation Bias)
target_net () 仍然只是一个“估算器”,它不是“神的公式”。它的估算总是有噪声(Error)。
一个具体的数学例子:
假设 AI 到了“下一个路口 s'”。它有两个选择(“不跳”或“跳”)。
假设**“神的公式”(我们看不见)告诉我们,这两个动作一样好**:
- “完美”的目标值 应该是 。
现在,我们“有噪声”的 target_net()开始“估算”。由于神经网络的随机性,它的估算不是 5.0 和 5.0。
假设它的估算是:
target_net 错误地认为“跳”更好,仅仅是因为它的“随机噪声”碰巧是正的。
现在,DQN 开始计算“目标值”:
看到了吗?
- “完美”的目标是
5.0。 - DQN 计算出的目标是
5.5。
DQN 系统性地高估 (Overestimates) 了未来的奖励。它在用一个被“噪声”污染了的、过于乐观的目标来训练它的 policy_net。
后果: AI 的“大脑”会变得“过度自信”。它会“幻想”某些状态的价值非常高。这导致了你的日志中出现的不稳定行为:它“自信”地执行一个策略(能跑 467 步),但这个策略是基于“幻想”的,所以当它遇到一个“刁钻”的、它没见过的管道时(幻想破灭),它会立即(在 16 步)崩溃。
4. Double DQN (DDQN):一个“谨慎”的估算者
DDQN (Double DQN) 的发明就是为了在数学上修复这个 max 导致的“过度自信”问题。
DDQN 的核心原理:解耦(Decoupling)
DDQN 说:“我不能用同一个(有噪声的)估算器 target_net 去同时 1. 挑选动作 和 2. 评估动作。”
DDQN 将这个过程解耦成两步,它同时使用两个大脑:
- 挑选 (Select): 我们用“正在训练”的
policy_net() 来挑选它认为的“最佳”下一步动作。 - 评估 (Evaluate): 我们用“稳定”的
target_net() 来评估那个被“新大脑”挑出来的动作到底值多少钱。
DDQN 的数学公式
DDQN 的“目标”计算公式因此变得“双重”(Double)了:
让我们用“白痴”的视角,一步步解开这个公式:
-
内部 (挑选):
\argmax_{a} Q_{\theta}(s', a)\argmax(arg-max) 的意思是:“不要给我max(最大值),给我导致最大值的那个参数(a)。”- 步骤 1: AI 让
policy_net()查看s',并**“挑选”出它认为“Q 值最高”的那个动作**a_max(例如action = 0,即“不跳”)。
-
外部 (评估):
Q_{\theta'}(s', a_{\max})- 步骤 2: AI 不再使用
max()。它拿着policy_net选出的动作a_max(“不跳”),转头去问另一个网络target_net():“请你来评估一下,‘不跳’这个动作真正值多少钱?”
- 步骤 2: AI 不再使用
还是那个数学例子:
- “神的公式” (真实值):
- “完美”的目标值 应该是 。
现在,我们有两个独立的、都有噪声的神经网络:
-
policy_net() 的估算 (用于挑选): -
target_net() 的估算 (用于评估):- (注意: 和 是独立的随机噪声,因为它们是两个不同的网络)
现在,DDQN 开始计算“目标值”:
-
挑选 (Select) - (看
policy_net):\argmax(5.2, 4.9)policy_net挑选出的“最佳”动作a_max是:“不跳”。
-
评估 (Evaluate) - (看
target_net):- AI 现在忽略
target_net中那个“被高估”的5.5(“跳”),因为policy_net没有选择它。 - 它只去查找
target_net对“不跳”这个动作的估值。 target_net对“不跳”的估值是:4.5。
- AI 现在忽略
-
DDQN 的“目标值”:
对比一下:
- 完美目标:
5.0 - DQN 目标:
5.5(过度自信!) - DDQN 目标:
4.5(更“保守”,更接近现实!)
结论:
DDQN 通过使用一个“独立”的网络(policy_net)来“挑选”动作,打破了 max() 运算符的“自我高估”的循环。它不再总是选择“被高估”的噪声。
这会导致 AI 的 Q 值估算更低(轻微的“悲观”),但这极大地提高了训练的稳定性和可靠性。你(作为训练师)的日志会显示出更一致的“时长”,而不是在你(Epsilon=0.05)的日志中看到的 467 步 和 16 步 之间的“剧烈摆动”。