【强化学习】Double DQN(Double Deep Q-Network)算法

【强化学习】Double DQN(Double Deep Q-Network)算法
        📢本篇文章是博主强化学习(RL)领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅解。文章分类在👉强化学习专栏:

       【强化学习】- 【单智能体强化学习】(12)---《Double DQN(Double Deep Q-Network)算法》

Double DQN(Double Deep Q-Network)算法

目录

一、Double DQN算法详解

二、算法背景和提出

2.1 过估计偏差问题

2.2 Double Q-Learning的灵感

2.3 Double DQN的提出

三、Double DQN的核心思想

四、算法流程

五、公式推导

[Python] Double DQN算法实现

[Notice] 代码说明

六、优势与特点

七、总结


一、Double DQN算法详解

        强化学习中的深度Q网络(DQN)是一种将深度学习与Q学习结合的算法,它通过神经网络逼近Q函数以解决复杂的高维状态问题。然而,DQN存在过估计问题(Overestimation Bias),即在更新Q值时,由于同时使用同一个网络选择动作和计算目标Q值,可能导致Q值的估计偏高。

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


二、算法背景和提出

        在强化学习的早期研究中,Q学习是一种经典算法,它通过构建Q值表来描述每个状态-动作对的长期累积奖励。然而,当状态和动作空间变得巨大甚至连续时,Q学习方法难以扩展。为此,深度Q网络(Deep Q-Network, DQN)引入了神经网络来逼近Q函数,并取得了显著的成果,如成功应用于Atari游戏。但DQN算法在实际应用中暴露出了一些问题,其中过估计偏差(Overestimation Bias)尤为突出。

2.1 过估计偏差问题

        在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. 神经网络的逼近误差会放大估计值,从而进一步加剧过估计问题。

这种偏差会导致:

  • 策略变得过于激进;
  • 学习过程变得不稳定;
  • 收敛速度减慢甚至无法收敛。

2.2 Double Q-Learning的灵感

        Double Q-Learning是一种用于减少过估计问题的经典方法。其基本思想是分离动作选择和价值估计。它使用两个独立的Q值表:

  1. 一个表用于选择动作;
  2. 另一个表用于计算目标值。

        Double Q-Learning的目标值公式为:

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

通过这种分离计算,动作选择的误差不会直接影响到目标值计算,从而减少了过估计的风险。


2.3 Double DQN的提出

        Double DQN(DDQN)受Double Q-Learning启发,将其思想扩展到深度强化学习领域。主要区别在于:

  1. 使用在线网络(Online Network)来选择动作;
  2. 使用目标网络(Target Network)来估计动作的价值。

Double DQN的目标值公式为:

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

其中:

Q_{\theta^-}

是目标网络,用于估计目标Q值。

Q_{\theta}

是在线网络,用于选择动作;

        这种方法成功地解决了DQN的过估计问题,并在多个强化学习任务中表现出了更好的性能和稳定性。


三、Double DQN的核心思想

Double DQN通过分离动作选择目标Q值计算来减小过估计问题:

  1. 使用在线网络(Online Network)选择动作。
  2. 使用目标网络(Target Network)计算目标Q值。

这种分离使得目标Q值的计算更加可靠,有助于减少估计偏差。


四、算法流程

1.初始化

        初始化两个神经网络:在线网络

Q_{\theta}

和目标网络

Q_{\theta^-}

       

Q_{\theta^-}

的参数定期从

Q_{\theta}

同步。

2.执行动作

        当前状态

s_t

下,利用

Q_{\theta}

选择动作

a_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.目标值计算(关键点)

        使用在线网络选择下一个状态

s_{i+1}

的最佳动作:

a' = \arg\max_a Q_{\theta}(s_{i+1}, a)

        使用目标网络计算目标Q值:

y_i = r_i + \gamma Q_{\theta^-}(s_{i+1}, a')

6.更新在线网络

        使用均方误差(MSE)作为损失函数,对

Q_{\theta}

进行梯度下降:

L(\theta) = \frac{1}{N} \sum_i \big(y_i - Q_{\theta}(s_i, a_i)\big)^2

7.更新目标网络

        每隔一定步数,将

Q_{\theta}

的参数复制到

Q_{\theta^-}


五、公式推导

  1. 减小过估计的作用
    • 通过在线网络选择动作,可以更准确地反映当前策略的动作价值。
    • 目标网络仅用来计算Q值,减少了目标计算时的估计偏差。

Q值是由目标网络

Q_{\theta^-}

计算的。

动作  a 是由在线网络

Q_{\theta}

选择的。

Double DQN目标
DDQN通过分离动作选择和目标计算,目标值改为:

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

Q学习目标
传统DQN的目标值是:

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


这里的 max  操作会导致过估计问题。


[Python] Double DQN算法实现

        下面给出是Double DQN算法的完整Python实现代码,它通过PyTorch框架实现,并包含了核心的在线网络和目标网络的更新机制:

项目代码我已经放入GitCode里面,可以通过下面链接跳转:🔥

【强化学习】--- Double DQN算法

后续相关单智能体强化学习算法也会不断在【强化学习】项目里更新,如果该项目对你有所帮助,请帮我点一个星星✨✨✨✨✨,鼓励分享,十分感谢!!!

若是下面代码复现困难或者有问题,也欢迎评论区留言

1. 导入必要库

import numpy as np # 导入NumPy库,用于处理数组和数值计算 import torch # 导入PyTorch库,用于构建和训练深度学习模型 import torch.nn as nn # 导入PyTorch的神经网络模块,用于构建网络结构 import torch.optim as optim # 导入PyTorch的优化器模块,用于优化神经网络参数 import random # 导入Python的随机模块,用于实现随机采样 from collections import deque # 导入deque数据结构,用于存储经验回放池 

 2. 超参数设置

# Hyperparameters GAMMA = 0.99 # 折扣因子,控制奖励的时间衰减 LR = 0.001 # 学习率,用于控制优化器的步长 BATCH_SIZE = 64 # 每次训练的批量大小 MEMORY_CAPACITY = 10000 # 经验回放池的最大容量 TARGET_UPDATE = 10 # 目标网络更新的周期(每10个回合更新一次) 

 3. 定义网络

# Define the neural network class QNetwork(nn.Module): # 定义Q网络,用于逼近Q值函数 def __init__(self, state_dim, action_dim): super(QNetwork, self).__init__() # 初始化父类 self.fc1 = nn.Linear(state_dim, 128) # 第一层全连接层,输入维度为状态维度,输出128维 self.fc2 = nn.Linear(128, 128) # 第二层全连接层,输入和输出均为128维 self.fc3 = nn.Linear(128, action_dim) # 输出层,输出维度为动作维度 def forward(self, x): # 定义前向传播过程 x = torch.relu(self.fc1(x)) # 第一层激活函数为ReLU x = torch.relu(self.fc2(x)) # 第二层激活函数为ReLU x = self.fc3(x) # 输出层不加激活函数,直接输出Q值 return x 

 4. 缓存经验区

# Replay buffer class ReplayBuffer: # 定义经验回放池,用于存储和采样经验数据 def __init__(self, capacity): self.buffer = deque(maxlen=capacity) # 使用deque实现固定长度的经验池 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算法

# Double DQN Agent class DoubleDQNAgent: # 定义Double DQN智能体 def __init__(self, state_dim, action_dim): self.state_dim = state_dim # 状态维度 self.action_dim = action_dim # 动作维度 # Online and target networks 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) # 使用Adam优化器 self.memory = ReplayBuffer(MEMORY_CAPACITY) # 创建经验回放池 self.steps_done = 0 # 记录执行的步数 def select_action(self, state, epsilon): # 动作选择,使用ε-贪婪策略 if random.random() < epsilon: # 以概率epsilon选择随机动作(探索) return random.randint(0, self.action_dim - 1) else: # 否则选择当前Q值最大的动作(利用) state = torch.FloatTensor(state).unsqueeze(0) # 转换为张量并增加批量维度 with torch.no_grad(): # 关闭梯度计算 q_values = self.online_net(state) # 通过在线网络计算Q值 return q_values.argmax().item() # 返回最大Q值对应的动作索引 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 # Sample a batch of transitions 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) # Compute Q values q_values = self.online_net(states).gather(1, actions) # 提取当前Q值 # Compute target Q values using Double DQN 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 # 计算目标Q值 # Compute loss loss = nn.MSELoss()(q_values, target_q_values) # 均方误差损失函数 # Update online network 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. 算法训练

# Environment simulation (example: CartPole-v1) import gym # 导入Gym库,用于创建和交互强化学习环境 env = gym.make('CartPole-v1') # 创建CartPole环境 state_dim = env.observation_space.shape[0] # 获取状态空间的维度 action_dim = env.action_space.n # 获取动作空间的维度 agent = DoubleDQNAgent(state_dim, action_dim) # 创建Double DQN智能体 # Training Loop 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 # 累计奖励初始化为0 while not done: # 每个时间步内 # Epsilon-greedy action selection epsilon = epsilon_end + (epsilon_start - epsilon_end) * np.exp(-1. * agent.steps_done / epsilon_decay) # 动态调整探索率 action = agent.select_action(state, epsilon) # 根据ε-贪婪策略选择动作 # Step in the environment next_state, reward, done, _ = env.step(action) # 执行动作并获取下一状态和奖励 total_reward += reward # 累加奖励 # Store transition and update agent.store_transition(state, action, reward, next_state, done) # 存储经验 agent.update() # 更新在线网络 state = next_state # 更新当前状态 agent.steps_done += 1 # 增加步数计数 # Update target network periodically if episode % TARGET_UPDATE == 0: agent.update_target_network() # 定期更新目标网络 print(f"Episode {episode}, Total Reward: {total_reward}") # 打印当前回合的累计奖励 env.close() # 关闭环境

[Notice] 代码说明

  1. ReplayBuffer:经验回放池,用于存储状态、动作、奖励、下一个状态和是否结束标志。
  2. QNetwork:定义深度Q网络,包含3个全连接层。
  3. DoubleDQNAgent
    • 维护在线网络(Online Network)和目标网络(Target Network)。
    • 使用在线网络选择动作,用目标网络计算目标值。
  4. 训练流程
    • 在每个时间步,使用( \epsilon )-贪婪策略选择动作。
    • 与环境交互,存储数据到经验回放池。
    • 采样小批量数据进行训练,通过Double DQN公式计算目标Q值。
    • 定期更新目标网络。
​# 环境配置 Python 3.11.5 torch 2.1.0 torchvision 0.16.0 gym 0.26.2
        由于博文主要为了介绍相关算法的原理和应用的方法,缺乏对于实际效果的关注,算法可能在上述环境中的效果不佳或者无法运行,一是算法不适配上述环境,二是算法未调参和优化,三是没有呈现完整的代码,四是等等。上述代码用于了解和学习算法足够了,但若是想直接将上面代码应用于实际项目中,还需要进行修改。

六、优势与特点

Double DQN与DQN的对比

特性DQNDouble DQN
目标值计算动作选择和评估使用同一网络分离动作选择和目标评估
过估计偏差明显存在显著减小
训练稳定性容易震荡更加稳定
算法复杂度较低略微增加(多一次网络前向计算)

1.减小过估计偏差

        分离动作选择和目标计算后,Double DQN有效减少了过高估计的风险。

2.更稳定的训练过程

        由于估计值更准确,训练更加平滑,收敛速度更快。

3.简单易实现

        在DQN的基础上,仅需额外引入动作选择的分离逻辑,容易实现。


七、总结

        Double DQN算法的提出,主要是为了解决DQN中的“过估计偏差”问题。通过引入双网络,Double DQN让动作选择和价值评估分离,大大提高了算法的稳定性和准确性。

 更多强化学习文章,请前往:【强化学习(RL)】专栏

        博客都是给自己看的笔记,如有误导深表抱歉。文章若有不当和不正确之处,还望理解与指出。由于部分文字、图片等来源于互联网,无法核实真实出处,如涉及相关争议,请联系博主删除。如有错误、疑问和侵权,欢迎评论留言联系作者,或者添加VX:Rainbook_2,联系作者。✨

Read more

【干货】HNSW算法详解:从基础到进阶,掌握大模型向量搜索核心技术!

【干货】HNSW算法详解:从基础到进阶,掌握大模型向量搜索核心技术!

简介 本文详细介绍了HNSW算法,一种用于高维空间中最近邻搜索的高效索引方法。文章从基础概念开始,逐步解释了可导航图和NSW算法的工作原理、构建与搜索过程,以及其局限性。随后介绍了跳表数据结构,并详细阐述了HNSW的多层级构建和搜索机制。通过比较HNSW与NSW的搜索效率,展示了HNSW在减少跳数和提高搜索准确性方面的优势,为在大模型应用中实现高效的向量相似性搜索提供了技术基础。 HNSW(Hierarchical Navigable Small World)可能是专为高维空间中的最近邻搜索而设计的最有效、最高效的索引方法之一。其核心思想是构建一个图结构,其中每个节点代表一个数据向量,边根据节点的相似性进行连接。HNSW 以这样一种方式组织图表,通过有效地浏览图表来找到近似的最近邻居,从而促进快速搜索操作。但在我们理解 HNSW 之前,必须先理解NSW(可导航小世界算法),它是 HNSW 算法的基础。 什么是可导航图 如果我们将这些向量表示为图的顶点,则彼此靠近的顶点(即相似度高的向量)应该作为邻居连接。也就是说,即使两个节点没有直接连接,也应该可以通过遍历其他顶点到达它们。

By Ne0inhk
《算法题讲解指南:优选算法-前缀和》--29.和为k的子数组,30.和可被k整除的子数组

《算法题讲解指南:优选算法-前缀和》--29.和为k的子数组,30.和可被k整除的子数组

🔥小叶-duck:个人主页 ❄️个人专栏:《Data-Structure-Learning》 《C++入门到进阶&自我学习过程记录》《算法题讲解指南》--从优选到贪心 ✨未择之路,不须回头 已择之路,纵是荆棘遍野,亦作花海遨游 目录 29. 和为k的子数组 题目链接: 题目描述: 题目示例: 解法(前缀和+哈希表): 算法思路: C++算法代码: 算法总结及流程解析: 30. 和可被k整除的子数组 题目链接: 题目描述: 题目示例: 解法(前缀和+哈希表): 前置知识补充: 算法思路: C++算法代码: 算法总结及流程解析: 结束语 29. 和为k的子数组 题目链接: 560. 和为 K 的子数组 -

By Ne0inhk
看一遍就懂:动态规划详解

看一遍就懂:动态规划详解

目录 前言 什么是动态规划? 核心思想 例子1 — 青蛙跳台阶问题 1. 暴力递归解法(超时示范) 2. 带备忘录的递归(自顶向下) 3. 动态规划(自底向上) 动态规划解题套路总结 经典案例:最长递增子序列(LIS) 1. 穷举分析 2. 状态转移方程 3. 代码实现 总结 前言 刷 LeetCode 的时候,经常会遇到动态规划(DP)类型题目。动态规划既经典又有技巧,大厂面试题里常常出现。很多同学第一次接触时会觉得很抽象,今天我们就来一起剖析动态规划的套路,带你从入门到精通。 什么是动态规划? 动态规划(Dynamic Programming,简称 DP)是一种解决复杂问题的算法设计方法,其核心思想是将原问题拆解成相对简单的子问题,逐个解决并保存子问题的结果,避免重复计算,从而高效地求解问题。 动态规划适合具有以下两个性质的问题:

By Ne0inhk
【递归、搜索与回溯算法必刷42题:专题一】从汉诺塔问题到快速幂

【递归、搜索与回溯算法必刷42题:专题一】从汉诺塔问题到快速幂

🎬 个人主页:艾莉丝努力练剑 ❄专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》 《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬 艾莉丝的简介: 🎬艾莉丝的算法专栏简介: 文章目录 * 本文设计专题一算法题链接 * 1 汉诺塔问题 * 题目描述 * 汉诺塔问题(递归解法) * 1. 问题描述 * 2. 递归思想 * 基本情况(递归终止条件) * 递归分解(n ≥ 2) * 3. 递归算法流程(函数设计) * 函数头 * 递归函数流程: * 解题过程 * 算法实现(C++) * 2 合并两个有序链表 * 题目描述 * 解题过程 * 算法实现(

By Ne0inhk