当前位置: 首页 > news >正文

一文读懂DQN改进算法(Double DQN+Dueling DQN)—强化学习(7)

目录

1、通俗理解Double DQN和Dueling DQN

1.1、Double DQN:避免 “过度自信” 的选择

1.1.1、传统 DQN 的问题

1.1.2、Double DQN 的改进

1.2、Dueling DQN:拆分 “环境价值” 和 “动作价值”

1.2.1、传统 DQN 的局限性

1.2.2、Dueling DQN 的改进

1.3、两者结合:更聪明的决策系统

1.4、总结

2、Double DQN(双深度 Q 网络)详解

2.1、 传统 DQN 的问题

2.2、 Double DQN 的核心思想

2.3、 目标 Q 值计算

2.4、 代码实现对比

2.5、 优势

3、Dueling DQN(对决网络架构)详解

3.1、 传统 DQN 的局限性

3.2、 Dueling DQN 的核心架构

3.3、网络结构

3.4、 代码实现

3.5、 优势

4、Double DQN 与 Dueling DQN 结合

5、Double DQN实验

5.1、Double DQN实验代码

5.2、Double DQN实验对比结果

5.2.1、DQN实验结果

​编辑 5.2.2、Double DQN实验结果

6、Dueling DQN实验

6.1、Dueling DQN实验代码

6.2、Dueling DQN实验结果


1、通俗理解Double DQN和Dueling DQN

1.1、Double DQN:避免 “过度自信” 的选择

1.1.1、传统 DQN 的问题

想象你是一个餐厅评论家,需要预测每家餐厅的 “最佳菜品”。传统 DQN 的做法是:

  • 尝遍所有菜后,直接选当前认为最好吃的(\max Q)。
  • 但如果某些菜的评分有误差(比如你今天状态不好),你可能会高估某道菜的价值,导致错误选择。

1.1.2、Double DQN 的改进

Double DQN 相当于:

  1. 先选菜:用 “当前的评分表”(主网络)选出你认为最好吃的菜。
  2. 再评估:用 “更稳定的历史评分表”(目标网络)来实际打分。

例子你先用自己最近的评分表选出 “宫保鸡丁”,然后用餐厅的历史平均分(更可靠)来确定这道菜到底多好吃。这样可以避免因为某次偶然的高分而过度推荐某道菜。

1.2、Dueling DQN:拆分 “环境价值” 和 “动作价值”

1.2.1、传统 DQN 的局限性

假设你在玩《王者荣耀》,传统 DQN 会直接告诉你:“在中路遇到敌人时,选貂蝉能赢”。但它没有解释:

  • 是 “中路这个位置本身就容易赢”(环境价值)?
  • 还是 “貂蝉在中路比其他英雄更强”(动作价值)?

1.2.2、Dueling DQN 的改进

Dueling DQN 把评分拆成两部分:

  1. 环境价值 V (s):中路本身的优势(比如兵线好、防御塔多)。
  2. 动作优势 A (s,a):貂蝉在中路比其他英雄强多少。

公式 总价值 = 环境基础分 + (英雄优势分 - 所有英雄平均优势) 比如:中路基础分 80 分,貂蝉优势分 + 15 分,所有英雄平均优势 5 分 → 貂蝉总价值 = 80+(15-5)=90 分。

好处

  • 如果中路优势变化(比如防御塔被推了),所有英雄的评分会同时调整,不用重新训练每个英雄。
  • 如果貂蝉被削弱,只需要更新她的优势分,不影响其他英雄。

1.3、两者结合:更聪明的决策系统

Double DQN + Dueling DQN 就像是:

  1. 用 Dueling DQN 的 “环境分 + 动作分” 系统来更细致地评估选择。
  2. 用 Double DQN 的 “先选后评估” 机制避免过度自信。

生活例子: 你要选一家餐厅吃饭,

Dueling DQN 会告诉你:

  • 这家餐厅的环境基础分(位置好、氛围好)。
  • 每道菜的优势分(比其他餐厅的同类菜好多少)。

而 Double DQN 会帮你:

  1. 先用当前的评分选出 “辣子鸡”。
  2. 再用历史评分(比如朋友的评价)来确认这道菜是否真的好吃。

1.4、总结

  • Double DQN:避免 “过度自信”,通过 “先选后评估” 提高稳定性。
  • Dueling DQN:拆分 “环境价值” 和 “动作价值”,提高学习效率。
  • 结合使用:在复杂环境中更准确、更稳定地学习最优策略。

2、Double DQN(双深度 Q 网络)详解

2.1、 传统 DQN 的问题

传统 DQN 在估计目标 Q 值时使用公式:

y_t = r_t + \gamma \max_{a'} Q_{\theta^-}(s_{t+1}, a')

这种方式存在最大化偏差(Maximization Bias)当 Q 值估计存在误差时,直接取最大值会系统性地高估真实价值,导致训练不稳定

2.2、 Double DQN 的核心思想

Double DQN 通过解耦动作选择和动作评估来减少偏差:

  • 动作选择:使用主网络(当前策略)选择最优动作。
  • 动作评估:使用目标网络评估该动作的 Q 值。

2.3、 目标 Q 值计算

Double DQN 的目标 Q 值公式为:

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

其中:

  • \arg\max_{a'} Q_\theta(s_{t+1}, a'):主网络选择的最优动作。
  • Q_{\theta^-}(s_{t+1}, \cdot):目标网络对该动作的 Q 值评估。

2.4、 代码实现对比

传统 DQN:

# 传统DQN目标Q值计算
next_q_values = target_network(next_state)
target = reward + gamma * torch.max(next_q_values)

Double DQN:

# Double DQN目标Q值计算
next_actions = main_network(next_state).argmax(dim=1)  # 主网络选动作
next_q_values = target_network(next_state)  # 目标网络评估Q值
target = reward + gamma * next_q_values.gather(1, next_actions.unsqueeze(1)).squeeze(1)

2.5、 优势

  • 显著减少最大化偏差,提高 Q 值估计的准确性。
  • 在 Atari 游戏等环境中表现更稳定,收敛性更好。

3、Dueling DQN(对决网络架构)详解

3.1、 传统 DQN 的局限性

传统 DQN 直接输出每个动作的 Q 值,但在许多场景中:

  • 状态价值(State Value)对所有动作相同。
  • 动作优势(Advantage)体现不同动作的相对价值。

3.2、 Dueling DQN 的核心架构

将 Q 值分解为状态价值函数 V (s)动作优势函数 A (s,a)

Q(s,a) = V(s) + A(s,a)

但直接分解会导致参数冗余(V 和 A 不唯一),因此实际使用:

Q(s,a) = V(s) + \left( A(s,a) - \frac{1}{|A|} \sum_{a'} A(s,a') \right)

通过减去平均优势,强制唯一解。

3.3、网络结构

  • 共享层:提取状态特征。
  • 价值流(Value Stream):输出标量 V (s)。
  • 优势流(Advantage Stream):输出每个动作的 A (s,a)。

3.4、 代码实现

import torch
import torch.nn as nnclass DuelingDQN(nn.Module):def __init__(self, state_dim, action_dim):super(DuelingDQN, self).__init__()self.feature = nn.Sequential(nn.Linear(state_dim, 128),nn.ReLU())self.value_stream = nn.Sequential(nn.Linear(128, 128),nn.ReLU(),nn.Linear(128, 1)  # 输出V(s))self.advantage_stream = nn.Sequential(nn.Linear(128, 128),nn.ReLU(),nn.Linear(128, action_dim)  # 输出A(s,a))def forward(self, x):features = self.feature(x)values = self.value_stream(features)advantages = self.advantage_stream(features)q_values = values + (advantages - advantages.mean(dim=1, keepdim=True))return q_values

3.5、 优势

  • 样本效率更高:通过分解,更高效地学习状态价值和动作优势。
  • 泛化能力更强:在状态价值变化但动作优势不变的场景中表现更好(如迷宫导航)。
  • 训练更稳定:减少了对无关动作的估计误差传播。

4、Double DQN 与 Dueling DQN 结合

两者可结合形成更强的算法(如 DDDQN):

  1. 使用 Dueling 架构计算 Q 值。
  2. 在目标 Q 值计算中采用 Double DQN 的解耦策略。

这种结合在许多基准测试中取得了 SOTA 结果,同时解决了高估偏差和样本效率问题。

5、Double DQN实验

5.1、Double DQN实验代码

"""
文件名: 8.1
作者: 墨尘
日期: 2025/7/22
项目名: d2l_learning
备注: 实现DQN和Double DQN算法,解决Pendulum-v1环境(钟摆平衡问题)
"""
# 导入必要的库
import random  # 用于随机选择动作(探索行为)
import gym  # OpenAI Gym库,提供强化学习环境
import numpy as np  # 数值计算库,处理数组和矩阵
import torch  # PyTorch深度学习框架,用于构建神经网络
import torch.nn.functional as F  # 神经网络功能函数(如激活函数、损失函数)
import matplotlib.pyplot as plt  # 绘图库,用于可视化训练结果
import rl_utils  # 自定义强化学习工具库(包含经验回放缓冲区等)
from tqdm import tqdm  # 进度条库,显示训练进度class Qnet(torch.nn.Module):''' 只有一层隐藏层的Q网络(价值网络)'''def __init__(self, state_dim, hidden_dim, action_dim):super(Qnet, self).__init__()  # 继承父类构造函数# 第一层全连接层:输入状态维度 -> 隐藏层维度(提取状态特征)self.fc1 = torch.nn.Linear(state_dim, hidden_dim)# 第二层全连接层:隐藏层维度 -> 动作维度(输出每个动作的Q值)self.fc2 = torch.nn.Linear(hidden_dim, action_dim)def forward(self, x):''' 前向传播:输入状态,输出所有动作的Q值 '''x = F.relu(self.fc1(x))  # 隐藏层用ReLU激活函数(增加非线性)return self.fc2(x)  # 输出层直接输出Q值(无激活函数)class DQN:''' DQN算法实现,支持基础DQN和Double DQN '''def __init__(self,state_dim,  # 状态维度(Pendulum环境为3:角度、角速度等)hidden_dim,  # 神经网络隐藏层维度action_dim,  # 动作维度(离散化后的动作数量)learning_rate,  # 学习率(控制参数更新速度)gamma,  # 折扣因子(权衡当前奖励和未来奖励)epsilon,  # 探索率(ε-贪婪策略中的概率)target_update,  # 目标网络更新频率(每多少步更新一次)device,  # 计算设备(CPU或GPU)dqn_type='VanillaDQN'):  # 算法类型(基础DQN或Double DQN)self.action_dim = action_dim  # 动作数量# 主网络(当前策略网络,实时更新)self.q_net = Qnet(state_dim, hidden_dim, self.action_dim).to(device)# 目标网络(稳定目标值,定期更新)self.target_q_net = Qnet(state_dim, hidden_dim, self.action_dim).to(device)# 优化器(Adam优化器,用于更新主网络参数)self.optimizer = torch.optim.Adam(self.q_net.parameters(), lr=learning_rate)self.gamma = gamma  # 折扣因子self.epsilon = epsilon  # 探索率self.target_update = target_update  # 目标网络更新频率self.count = 0  # 计数器(记录更新步数,用于触发目标网络更新)self.dqn_type = dqn_type  # 算法类型标记self.device = device  # 计算设备def take_action(self, state):''' 根据当前状态选择动作(ε-贪婪策略)'''# 以ε概率随机探索(尝试新动作)if np.random.random() < self.epsilon:action = np.random.randint(self.action_dim)  # 随机选择一个动作# 以1-ε概率利用当前策略(选Q值最高的动作)else:# 将状态转为张量并传入设备state = torch.tensor([state], dtype=torch.float).to(self.device)# 主网络输出所有动作的Q值,选最大Q值对应的动作action = self.q_net(state).argmax().item()return actiondef max_q_value(self, state):''' 计算当前状态的最大Q值(用于跟踪训练效果)'''state = torch.tensor([state], dtype=torch.float).to(self.device)return self.q_net(state).max().item()  # 返回最大Q值def update(self, transition_dict):''' 根据经验回放缓冲区的数据更新主网络参数 '''# 从字典中提取数据并转换为张量,传入计算设备states = torch.tensor(transition_dict['states'], dtype=torch.float).to(self.device)actions = torch.tensor(transition_dict['actions']).view(-1, 1).to(self.device)  # 动作转为列向量rewards = torch.tensor(transition_dict['rewards'], dtype=torch.float).view(-1, 1).to(self.device)  # 奖励转为列向量next_states = torch.tensor(transition_dict['next_states'], dtype=torch.float).to(self.device)dones = torch.tensor(transition_dict['dones'], dtype=torch.float).view(-1, 1).to(self.device)  # 终止标志转为列向量# 计算当前状态-动作对的Q值(从主网络输出中提取对应动作的Q值)q_values = self.q_net(states).gather(1, actions)  # gather函数按动作索引提取Q值# 计算下一状态的最大Q值(基础DQN与Double DQN的核心区别)if self.dqn_type == 'DoubleDQN':# Double DQN:主网络选动作,目标网络评价值(避免高估)# 1. 主网络选出下一状态的最优动作max_action = self.q_net(next_states).max(1)[1].view(-1, 1)  # 取最大值对应的索引(动作)# 2. 目标网络对该动作的Q值进行评估max_next_q_values = self.target_q_net(next_states).gather(1, max_action)else:  # 基础DQN# 目标网络同时负责选动作和评价值(可能高估Q值)max_next_q_values = self.target_q_net(next_states).max(1)[0].view(-1, 1)  # 直接取最大Q值# 计算目标Q值(TD目标:当前奖励 + 折扣后的下一状态最大Q值)# (1 - dones):如果终止,则下一状态无奖励(乘以0)q_targets = rewards + self.gamma * max_next_q_values * (1 - dones)# 计算损失(当前Q值与目标Q值的均方误差)dqn_loss = torch.mean(F.mse_loss(q_values, q_targets))# 梯度清零(避免累积梯度影响更新)self.optimizer.zero_grad()# 反向传播计算梯度dqn_loss.backward()# 应用梯度更新主网络参数self.optimizer.step()# 每更新target_update步,将主网络参数复制到目标网络if self.count % self.target_update == 0:self.target_q_net.load_state_dict(self.q_net.state_dict())self.count += 1  # 计数器加1def dis_to_con(discrete_action, env, action_dim):''' 将离散动作转换为连续动作(因为Pendulum环境需要连续控制力)'''action_lowbound = env.action_space.low[0]  # 连续动作的最小值(Pendulum中为-2)action_upbound = env.action_space.high[0]  # 连续动作的最大值(Pendulum中为2)# 线性映射:离散动作索引 -> 连续动作值return action_lowbound + (discrete_action / (action_dim - 1)) * (action_upbound - action_lowbound)def train_DQN(agent, env, num_episodes, replay_buffer, minimal_size, batch_size):''' 训练DQN智能体 '''return_list = []  # 记录每回合的总奖励(用于评估训练效果)max_q_value_list = []  # 记录每步的最大Q值(用于观察学习进度)max_q_value = 0  # 最大Q值的平滑值# 分10轮训练(每轮训练num_episodes/10回合)for i in range(10):# 进度条显示当前轮次的训练进度with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar:for i_episode in range(int(num_episodes / 10)):episode_return = 0  # 记录当前回合的总奖励state, _ = env.reset()  # 初始化环境,获取初始状态(忽略info)done = False  # 回合是否结束的标志while not done:  # 循环直到回合结束# 智能体根据当前状态选择动作action = agent.take_action(state)# 计算当前状态的最大Q值(平滑处理,避免波动过大)max_q_value = agent.max_q_value(state) * 0.005 + max_q_value * 0.995max_q_value_list.append(max_q_value)  # 记录平滑后的Q值# 将离散动作转换为连续动作(适应环境需求)action_continuous = dis_to_con(action, env, agent.action_dim)# 执行动作,获取下一状态、奖励等信息(Gym v0.26+返回5个值)next_state, reward, terminated, truncated, _ = env.step([action_continuous])# 合并终止条件:达到目标(terminated)或超时(truncated)done = terminated or truncated# 将当前经验(状态、动作、奖励等)存入经验回放缓冲区replay_buffer.add(state, action, reward, next_state, done)# 更新当前状态为下一状态state = next_state# 累积当前回合的奖励episode_return += reward# 当缓冲区数据量超过最小阈值时,开始采样更新网络if replay_buffer.size() > minimal_size:# 从缓冲区随机采样一批数据b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(batch_size)# 构造经验字典transition_dict = {'states': b_s,'actions': b_a,'next_states': b_ns,'rewards': b_r,'dones': b_d}# 调用智能体的update方法更新网络agent.update(transition_dict)# 记录当前回合的总奖励return_list.append(episode_return)# 每10回合显示一次平均奖励(评估训练效果)if (i_episode + 1) % 10 == 0:pbar.set_postfix({'episode': '%d' % (num_episodes / 10 * i + i_episode + 1),'return': '%.3f' % np.mean(return_list[-10:])  # 最近10回合的平均奖励})pbar.update(1)  # 进度条更新return return_list, max_q_value_list  # 返回所有回合的奖励和Q值记录if __name__ == '__main__':# 超参数设置lr = 1e-2  # 学习率num_episodes = 200  # 总训练回合数hidden_dim = 128  # 神经网络隐藏层维度gamma = 0.98  # 折扣因子epsilon = 0.01  # 探索率(ε-贪婪策略)target_update = 50  # 目标网络更新频率(每50步更新一次)buffer_size = 5000  # 经验回放缓冲区大小minimal_size = 1000  # 开始更新网络所需的最小缓冲区数据量batch_size = 64  # 每次更新的样本批次大小# 选择计算设备(优先GPU,无则用CPU)device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")# 环境设置(Pendulum-v1为钟摆平衡环境)env_name = 'Pendulum-v1'# 创建环境,指定渲染模式(可选,用于可视化)env = gym.make(env_name, render_mode="rgb_array")# 初始化环境种子(保证实验可复现)env.reset(seed=0)# 获取状态维度(Pendulum环境为3)state_dim = env.observation_space.shape[0]# 动作离散化数量(将连续动作分为11个离散选项)action_dim = 11# 设置随机种子(保证实验可复现)random.seed(0)np.random.seed(0)torch.manual_seed(0)# 初始化经验回放缓冲区replay_buffer = rl_utils.ReplayBuffer(buffer_size)# 初始化DQN智能体(默认基础DQN,可改为DoubleDQN)agent = DQN(state_dim, hidden_dim, action_dim, lr, gamma, epsilon, target_update, device)#agent = DQN(state_dim, hidden_dim, action_dim, lr, gamma, epsilon, target_update, device, 'DoubleDQN')# 开始训练,获取奖励记录和Q值记录return_list, max_q_value_list = train_DQN(agent, env, num_episodes, replay_buffer, minimal_size, batch_size)# 绘制训练奖励曲线(移动平均,平滑波动)episodes_list = list(range(len(return_list)))mv_return = rl_utils.moving_average(return_list, 5)  # 5回合移动平均plt.plot(episodes_list, mv_return)plt.xlabel('Episodes')  # 横轴:回合数plt.ylabel('Returns')  # 纵轴:总奖励plt.title('DQN on {}'.format(env_name))  # 标题:环境名称plt.show()# 绘制最大Q值曲线(观察学习进度)frames_list = list(range(len(max_q_value_list)))plt.plot(frames_list, max_q_value_list)plt.axhline(0, c='orange', ls='--')  # 参考线:Q值=0plt.axhline(10, c='red', ls='--')   # 参考线:Q值=10plt.xlabel('Frames')  # 横轴:步数plt.ylabel('Q value')  # 纵轴:最大Q值plt.title('DQN on {}'.format(env_name))plt.show()

5.2、Double DQN实验对比结果

5.2.1、DQN实验结果

 5.2.2、Double DQN实验结果

6、Dueling DQN实验

6.1、Dueling DQN实验代码

6.2、Dueling DQN实验结果

http://www.dtcms.com/a/293278.html

相关文章:

  • Docker实战系列:使用Docker部署AI SSH客户端工具IntelliSSH
  • MCP消息协议和传输协议(Java角度)
  • 航班调度优化策略全局概览
  • TCP day39
  • 帆软实现审批流配置
  • C++ 模板库map数据结构的概念和使用案例
  • Rabbit安装
  • vben ruoyi 数据字典解决方案
  • 16.多生成树MSTP
  • Linux文件系统理解1
  • Selenium+Java 自动化测试入门到实践:从环境搭建到元素操作
  • ubuntu22.04 录视屏软件推荐
  • Three.js 实现梦幻星河流光粒子特效原理与实践
  • Redis 5.0中的 Stream是什么?
  • C语言(20250722)
  • 21. `taskSlotTable`和`jobLeaderService`启动
  • 使用空间数据训练机器学习模型的实用工作流程
  • An error occurred at line: 1 in the generated java file问题处理及tomcat指定对应的jdk运行
  • Dify工作流:爬虫文章到AI知识库
  • 【OD机试】数组和最大
  • Java基础环境配置
  • 从零开始学习大模型之文本数据处理
  • BEV-LaneDet
  • 网络编程---网络基础知识
  • 【文本分析】使用LDA模型进行主题建模——李牧南等(2024)《科研管理》、马鸿佳等(2025)《南开管理评论》的复现
  • 24. 两两交换链表中的节点
  • 线程池excutor 和 submit区别 关于异常处理,请详细说明,会吞掉异常吗,需要捕获吗
  • vue3:十八、内容管理-实现行内图片的预览、审核功能
  • Python--numpy基础知识
  • 海洋大地测量基准与水下导航系列之九我国海洋PNT最新技术进展(中)