跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
PythonAI算法

Double DQN 算法原理与 PyTorch 实战

Double DQN 针对 DQN 过估计偏差问题,通过分离动作选择与价值评估网络,利用在线网络选动作、目标网络估价值,有效降低 Q 值高估风险。相比 DQN,DDQN 训练更稳定,收敛更快,适合复杂强化学习任务。代码基于 PyTorch 实现,包含双网络更新及经验回放机制。

心动瞬间发布于 2026/3/28更新于 2026/11/14132 浏览
Double DQN 算法原理与 PyTorch 实战

Double DQN 算法详解

强化学习中的深度 Q 网络(DQN)将深度学习与 Q 学习结合,通过神经网络逼近 Q 函数来解决高维状态问题。然而,标准 DQN 存在明显的过估计偏差(Overestimation Bias),即在更新 Q 值时,由于使用同一个网络选择动作并评估价值,容易导致 Q 值估计偏高。

Double DQN(DDQN)引入了双网络机制来缓解这一问题,显著提高了算法的稳定性和收敛性。

算法背景与提出

早期 Q 学习依赖 Q 值表,难以应对巨大或连续的状态空间。DQN 引入神经网络后在 Atari 等任务上取得突破,但实际应用中暴露出过估计偏差问题。

过估计偏差问题

DQN 的 Q 值更新公式如下:

$$y_t^{DQN} = r_t + \gamma \max_a Q_{\theta^-}(s_{t+1}, a)$$

其中 $Q_{\theta^-}$ 是目标网络的 Q 值,$\gamma$ 为折扣因子,$r_t$ 为即时奖励。

DQN 使用 max 操作同时完成动作选择和未来价值估计。这种机制的根本缺陷在于:

  1. 同一网络既负责选动作,又负责算价值。
  2. 神经网络的逼近误差会放大估计值,加剧过估计。

后果包括策略过于激进、训练不稳定、收敛缓慢甚至无法收敛。

Double Q-Learning 的灵感

Double Q-Learning 通过分离动作选择和价值估计来减少过估计。它维护两个独立的 Q 值表:一个用于选动作,另一个用于计算目标值。

其目标值公式为:

$$y_t^{DoubleQ} = r_t + \gamma Q_2(s_{t+1}, \arg\max_a Q_1(s_{t+1}, a))$$

这种分离使得动作选择的误差不直接影响目标值计算。

Double DQN 的提出

受 Double Q-Learning 启发,DDQN 将其思想扩展到深度强化学习领域:

  1. 在线网络(Online Network)负责选择动作。
  2. 目标网络(Target Network)负责估计动作价值。

DDQN 的目标值公式调整为:

$$y_t^{DDQN} = r_t + \gamma Q_{\theta^-}(s_{t+1}, \arg\max_a Q_{\theta}(s_{t+1}, a))$$

这里 $Q_{\theta^-}$ 是目标网络,$Q_{\theta}$ 是在线网络。该机制成功解决了 DQN 的过估计问题,在多个任务中表现出更优的性能。

Double DQN 的核心思想

核心在于分离动作选择和目标 Q 值计算:

  • 利用在线网络选择当前状态下的最优动作。
  • 利用目标网络计算该动作对应的目标 Q 值。

这种解耦让目标值的计算更加可靠,有效减少了估计偏差。

算法流程

  1. 初始化:创建在线网络 $Q_{\theta}$ 和目标网络 $Q_{\theta^-}$,后者参数定期从前者同步。
  2. 执行动作:在当前状态 $s_t$ 下,利用在线网络选择动作 $a_t = \arg\max_a Q_{\theta}(s_t, a)$。
  3. 存储经验:将转移样本 $(s_t, a_t, r_t, s_{t+1})$ 存入经验回放池。
  4. 经验回放:从池中随机采样小批量数据 $(s_i, a_i, r_i, s_{i+1})$。
  5. 目标值计算:
    • 用在线网络选出下一状态的最佳动作:$a' = \arg\max_a Q_{\theta}(s_{i+1}, a)$。
    • 用目标网络计算目标 Q 值:$y_i = r_i + \gamma Q_{\theta^-}(s_{i+1}, a')$。
  • 更新在线网络:使用均方误差(MSE)损失函数对 $Q_{\theta}$ 进行梯度下降。
  • 更新目标网络:每隔固定步数,将在线网络参数复制到目标网络。
  • 公式推导

    DDQN 的关键改进在于目标值的构造。传统 DQN 的 max 操作会导致过估计,而 DDQN 通过分离网络降低了这一风险。

    DDQN 目标: $$y_t^{DDQN} = r_t + \gamma Q_{\theta^-}(s_{t+1}, \arg\max_a Q_{\theta}(s_{t+1}, a))$$

    Q 学习目标: $$L(\theta) = \frac{1}{N} \sum_i \big(y_i - Q_{\theta}(s_i, a_i)\big)^2$$

    动作 $a$ 由在线网络 $Q_{\theta}$ 选择,Q 值由目标网络 $Q_{\theta^-}$ 计算。这种设计确保了目标值的相对无偏性。

    Python 实现

    下面给出基于 PyTorch 的 Double DQN 完整实现,包含核心的双网络更新机制。

    1. 导入必要库

    import numpy as np
    import torch
    import torch.nn as nn
    import torch.optim as optim
    import random
    from collections import deque
    

    2. 超参数设置

    GAMMA = 0.99          # 折扣因子
    LR = 0.001            # 学习率
    BATCH_SIZE = 64       # 批量大小
    MEMORY_CAPACITY = 10000  # 经验池容量
    TARGET_UPDATE = 10    # 目标网络更新周期
    

    3. 定义网络结构

    class QNetwork(nn.Module):
        def __init__(self, state_dim, action_dim):
            super(QNetwork, self).__init__()
            self.fc1 = nn.Linear(state_dim, 128)
            self.fc2 = nn.Linear(128, 128)
            self.fc3 = nn.Linear(128, action_dim)
    
        def forward(self, x):
            x = torch.relu(self.fc1(x))
            x = torch.relu(self.fc2(x))
            x = self.fc3(x)
            return x
    

    4. 经验回放池

    class ReplayBuffer:
        def __init__(self, capacity):
            self.buffer = deque(maxlen=capacity)
    
        def push(self, state, action, reward, next_state, done):
            self.buffer.append((state, action, reward, next_state, done))
    
        def sample(self, batch_size):
            batch = random.sample(self.buffer, batch_size)
            states, actions, rewards, next_states, dones = zip(*batch)
            return (np.array(states), np.array(actions), np.array(rewards), 
                    np.array(next_states), np.array(dones))
    
        def __len__(self):
            return len(self.buffer)
    

    5. Double DQN Agent

    class DoubleDQNAgent:
        def __init__(self, state_dim, action_dim):
            self.state_dim = state_dim
            self.action_dim = action_dim
            self.online_net = QNetwork(state_dim, action_dim)
            self.target_net = QNetwork(state_dim, action_dim)
            self.target_net.load_state_dict(self.online_net.state_dict())
            self.target_net.eval()
            self.optimizer = optim.Adam(self.online_net.parameters(), lr=LR)
            self.memory = ReplayBuffer(MEMORY_CAPACITY)
            self.steps_done = 0
    
        def select_action(self, state, epsilon):
            if random.random() < epsilon:
                return random.randint(0, self.action_dim - 1)
            else:
                state = torch.FloatTensor(state).unsqueeze(0)
                with torch.no_grad():
                    q_values = self.online_net(state)
                    return q_values.argmax().item()
    
        def store_transition(self, state, action, reward, next_state, done):
            self.memory.push(state, action, reward, next_state, done)
    
        def update(self):
            if len(self.memory) < BATCH_SIZE:
                return
            states, actions, rewards, next_states, dones = self.memory.sample(BATCH_SIZE)
            states = torch.FloatTensor(states)
            actions = torch.LongTensor(actions).unsqueeze(1)
            rewards = torch.FloatTensor(rewards).unsqueeze(1)
            next_states = torch.FloatTensor(next_states)
            dones = torch.FloatTensor(dones).unsqueeze(1)
    
            q_values = self.online_net(states).gather(1, actions)
    
            with torch.no_grad():
                next_actions = self.online_net(next_states).argmax(dim=1, keepdim=True)
                next_q_values = self.target_net(next_states).gather(1, next_actions)
                target_q_values = rewards + (1 - dones) * GAMMA * next_q_values
    
            loss = nn.MSELoss()(q_values, target_q_values)
            self.optimizer.zero_grad()
            loss.backward()
            self.optimizer.step()
    
        def update_target_network(self):
            self.target_net.load_state_dict(self.online_net.state_dict())
    

    6. 训练循环

    import gym
    
    env = gym.make('CartPole-v1')
    state_dim = env.observation_space.shape[0]
    action_dim = env.action_space.n
    agent = DoubleDQNAgent(state_dim, action_dim)
    
    num_episodes = 500
    epsilon_start = 1.0
    epsilon_end = 0.01
    epsilon_decay = 500
    
    for episode in range(num_episodes):
        state = env.reset()
        done = False
        total_reward = 0
        while not done:
            epsilon = epsilon_end + (epsilon_start - epsilon_end) * np.exp(-1. * agent.steps_done / epsilon_decay)
            action = agent.select_action(state, epsilon)
            next_state, reward, done, _ = env.step(action)
            total_reward += reward
            agent.store_transition(state, action, reward, next_state, done)
            agent.update()
            state = next_state
            agent.steps_done += 1
    
        if episode % TARGET_UPDATE == 0:
            agent.update_target_network()
        print(f"Episode {episode}, Total Reward: {total_reward}")
    
    env.close()
    

    优势与特点

    特性DQNDouble DQN
    目标值计算动作选择和评估使用同一网络分离动作选择和目标评估
    过估计偏差明显存在显著减小
    训练稳定性容易震荡更加稳定
    算法复杂度较低略微增加(多一次前向计算)
    1. 减小过估计偏差:分离机制有效降低了过高估计的风险。
    2. 更稳定的训练过程:估计值更准确,收敛更快。
    3. 简单易实现:在 DQN 基础上仅需调整动作选择逻辑。

    总结

    Double DQN 针对 DQN 的过估计偏差问题,通过引入双网络机制,让动作选择和价值评估分离,大幅提升了算法的稳定性和准确性。在实际工程中,它是处理复杂强化学习任务时的首选基线方案之一。

    目录

    1. Double DQN 算法详解
    2. 算法背景与提出
    3. 过估计偏差问题
    4. Double Q-Learning 的灵感
    5. Double DQN 的提出
    6. Double DQN 的核心思想
    7. 算法流程
    8. 公式推导
    9. Python 实现
    10. 1. 导入必要库
    11. 2. 超参数设置
    12. 3. 定义网络结构
    13. 4. 经验回放池
    14. 5. Double DQN Agent
    15. 6. 训练循环
    16. 优势与特点
    17. 总结

    更多推荐文章

    查看全部
    • 从零到服装 ERP:低代码打造企业级系统实战
    • Z-Image ComfyUI 网页端部署与文生图实战
    • 企业微信外部群 Webhook 配置与消息推送指南
    • AI 赋能产品全流程:从需求到上线的落地实践
    • 策略模式实战:从硬编码到算法解耦的演进之路
    • uniapp 通用价格组件开发与 DCloud 插件市场发布指南
    • Obsidian 接入 AI 配置指南
    • 基于 Spring AI 与 Ollama 的离线私有化 AI 服务构建
    • 2025 年 AIGC 技术发展的六大核心趋势
    • AI 产品经理入门指南与进阶路线图
    • 学习 Python 的八大理由:数据驱动的职业发展与技术优势
    • Python 爬虫入门:基础类库与实战案例详解
    • AI 变现误区解析:为何掌握工具不等于获得收益
    • 环形链表、两个数组中的交集、随机链表的复制
    • FPGA 实现任意角度图像旋转原理与代码设计
    • 逆向 WebChat 协议:构建 OpenClaw 零侵入通用适配器
    • Ubuntu 虚拟机部署 OpenClaw 个人 AI 助手
    • 前端微前端架构:避免应用变成巨石单体
    • Doubao-Seed-Code 接入 Claude Code 本地部署与实战教程
    • Actix Web框架全面指南与Rust Web开发

    相关免费在线工具

    • 加密/解密文本

      使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

    • RSA密钥对生成器

      生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

    • Mermaid 预览与可视化编辑

      基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

    • 随机西班牙地址生成器

      随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

    • Gemini 图片去水印

      基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

    • curl 转代码

      解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online