强化学习第四课 —— 深度强化学习:DQN 改进家族详解

2025-12-17 11:46:09
文章摘要
本文深入剖析了DQN算法的三大改进方案:DoubleDQN通过解耦动作选择和评估解决Q值高估问题;DuelingDQN将网络拆分为价值流和优势流,提升状态价值识别能力;Prioritized Experience Replay则通过TD-error优先级采样提高训练效率。文章详细介绍了各算法的数学原理、PyTorch实现及性能对比,指出三者组合能获得最优收敛效果,为后续Rainbow算法奠定基础。

 目录

前言:为什么要改进 DQN?

2. Double DQN (DDQN):解决高估问题

2.1 问题根源:Maximization Bias

2.2 数学原理与改进

2.3 PyTorch 代码实现

3. Dueling DQN:网络结构的革新

3.1 直觉理解

3.2 数学公式

3.3 PyTorch 模型结构

4. Prioritized Experience Replay (PER):让经验更有价值

4.1 核心思想

4.2 数学推导:概率与权重修正

4.3 代码逻辑(简化版)

5. 性能对比图示

6. 总结 (Summary)

前言:为什么要改进 DQN?

在上一篇文章中,我们介绍了 DQN (Deep Q-Network),它成功地将深度学习引入了强化学习。然而,原始的 DQN 并不完美,它存在几个显著的问题:

  1. Q 值高估 (Overestimation Bias):DQN 倾向于高估动作的价值,导致策略不仅不收敛,还可能产生震荡。
  2. 采样效率低:经验回放池 (Replay Buffer) 是随机采样的,但在训练初期,很多样本可能是“无效”或“重复”的,有些关键样本却被忽略了。
  3. 状态价值识别难:在某些状态下,无论采取什么动作,结果都差不多(比如游戏结束前的一瞬间),但 DQN 还是会费劲地去计算每个动作的 Q 值。

为了解决这些问题,DeepMind 后续推出了一系列改进算法。今天我们就来深入剖析其中最经典的三大金刚:Double DQN, Dueling DQN, 和 Prioritized Experience Replay (PER)


2. Double DQN (DDQN):解决高估问题

2.1 问题根源:Maximization Bias

在标准 DQN 中,目标 Q 值 y_t 的计算公式是:

这里有一个严重的问题:我们在选择动作和评估动作时,用的是同一个网络(目标网络 \theta^-)的最大值操作

 如果目标网络存在误差(这在训练初期是必然的),\max操作会倾向于选择那些被高估的动作,导致误差不断累积。

2.2 数学原理与改进

Double DQN 的核心思想是解耦 (Decoupling)

  1. 动作选择 (Selection):使用当前网络 (Main Network, \theta) 来决定哪个动作是最好的。
  2. 动作评估 (Evaluation):使用目标网络 (Target Network, \theta^-) 来计算该动作的价值。

改进后的目标公式如下:

2.3 PyTorch 代码实现

这一改动在代码中非常简单,只需要修改计算 Target Q 值的几行代码:

def compute_loss(self, batch):
    states, actions, rewards, next_states, dones = batch
    
    # 1. 计算当前 Q(s, a)
    current_q = self.q_net(states).gather(1, actions)
    
    with torch.no_grad():
        # === Double DQN 核心修改 ===
        # 步骤 A: 使用 Main Net 选择动作 argmax Q(s', a; theta)
        next_actions = self.q_net(next_states).argmax(1).unsqueeze(1)
        
        # 步骤 B: 使用 Target Net 评估该动作 Q(s', next_action; theta_target)
        next_q = self.target_net(next_states).gather(1, next_actions)
        # =========================
        
        target_q = rewards + self.gamma * next_q * (1 - dones)
        
    loss = nn.MSELoss()(current_q, target_q)
    return loss



3. Dueling DQN:网络结构的革新

3.1 直觉理解

在很多场景下,状态本身的价值 (Value) 比 动作的价值 (Advantage) 更重要。

  1. 例子:在一个赛车游戏中,如果前面是一堵墙(状态 (s)),那么无论你向左转还是向右转(动作 (a)),结果都很糟糕。

原始 DQN 直接输出 Q(s, a),它被迫学习每个动作的每一个细微差别。Dueling DQN 提出将网络拆分为两部分:

  1. 价值函数 (Value Function) (V(s)):评估当前状态的好坏。
  2. 优势函数 (Advantage Function) (A(s, a)):评估在该状态下,采取动作 (a) 比平均情况好多少。

3.2 数学公式

逻辑上,Q 值等于 V 值加上 A 值:

 

但在实际训练中,如果直接相加,神经网络无法区分哪个是 (V) 哪个是 (A)(即不可辨识性问题,Unidentifiability)。为了解决这个问题,我们通常强制让优势函数的平均值为 0。

最终的聚合公式(Aggregation Layer)为:

其中:

  1. \theta 是卷积层共享参数。
  2. \beta 是 (V) 流的参数。
  3. \alpha 是 (A) 流的参数。

3.3 PyTorch 模型结构

Dueling DQN 改变的是网络模型定义:

class DuelingDQN(nn.Module):
    def __init__(self, state_dim, action_dim):
        super(DuelingDQN, self).__init__()
        
        # 特征提取层 (共享)
        self.feature_layer = nn.Sequential(
            nn.Linear(state_dim, 128),
            nn.ReLU()
        )
        
        # 优势流 (Advantage Stream)
        self.advantage_layer = nn.Sequential(
            nn.Linear(128, 128),
            nn.ReLU(),
            nn.Linear(128, action_dim)
        )
        
        # 价值流 (Value Stream)
        self.value_layer = nn.Sequential(
            nn.Linear(128, 128),
            nn.ReLU(),
            nn.Linear(128, 1) # 输出标量 V(s)
        )
        
    def forward(self, x):
        features = self.feature_layer(x)
        
        advantage = self.advantage_layer(features)
        value = self.value_layer(features)
        
        # === 核心公式:Q = V + (A - mean(A)) ===
        q_values = value + (advantage - advantage.mean(dim=1, keepdim=True))
        
        return q_values



4. Prioritized Experience Replay (PER):让经验更有价值

4.1 核心思想

普通的 Replay Buffer 是均匀采样 (Uniform Sampling)。这就像学生复习功课,不管会的还是不会的,都花同样的时间复习,效率极低。

PER 的思想是:我们要优先复习那些“让我们感到意外”的样本

 在强化学习中,“意外”程度由 TD-Error (\delta) 来衡量:

TD-Error 越大,说明网络对这个样本预测得越不准,越需要学习。

4.2 数学推导:概率与权重修正

1. 采样概率P(i)

 样本 i 被采样的概率定义为:

 

 其中 p_i = |\delta_i| + \epsilon\epsilon 是为了防止概率为 0),\alpha 控制优先级的程度(0 为均匀采样,1 为完全贪婪)。

2. 重要性采样权重 (Importance Sampling Weights)

 因为我们改变了采样分布(不再是均匀分布),这会改变数据的期望值,导致模型有偏。为了修正偏差,我们需要在计算 Loss 时乘以权重 w_i

  1. N 是 Buffer 大小。
  2. \beta 从 0 线性增长到 1,用于抵消偏差。

4.3 代码逻辑(简化版)

由于完整的 PER 需要实现 SumTree 树形结构来保证 O(\log N) 的采样速度,代码较长。这里展示核心的 Loss 计算逻辑:

def update_with_per(self, beta):
    # 1. 从 Buffer 中根据优先级采样
    # indices: 样本索引, weights: IS权重, samples: 数据
    indices, weights, samples = self.buffer.sample(self.batch_size, beta)
    
    states, actions, rewards, next_states, dones = samples
    weights = torch.FloatTensor(weights).to(self.device)
    
    # 2. 计算 Q 值和 Target
    current_q = self.q_net(states).gather(1, actions)
    next_q = self.target_net(next_states).max(1)[0].unsqueeze(1)
    target_q = rewards + self.gamma * next_q * (1 - dones)
    
    # 3. 计算 Element-wise Loss (不求平均)
    loss_element = (current_q - target_q).pow(2)
    
    # 4. 加权 Loss
    loss = (loss_element * weights).mean()
    
    # 5. 反向传播
    self.optimizer.zero_grad()
    loss.backward()
    self.optimizer.step()
    
    # 6. 更新样本优先级 (使用新的 TD-error)
    td_errors = (target_q - current_q).detach().abs().cpu().numpy()
    self.buffer.update_priorities(indices, td_errors + 1e-6)



5. 性能对比图示

为了直观展示这些改进的效果,我们可以对比在同一环境下(如 CartPole 或 Atari)各算法的收敛速度。

(以下代码用于生成对比示意图)

import matplotlib.pyplot as plt
import numpy as np

# 模拟数据
x = np.linspace(0, 500, 100)
y_dqn = 200 * (1 - np.exp(-x/100)) + np.random.normal(0, 5, 100)
y_double = 200 * (1 - np.exp(-x/80)) + np.random.normal(0, 5, 100)
y_dueling = 200 * (1 - np.exp(-x/60)) + np.random.normal(0, 5, 100)
y_per = 200 * (1 - np.exp(-x/50)) + np.random.normal(0, 5, 100) # 收敛最快

plt.figure(figsize=(10, 6))
plt.plot(x, y_dqn, label='DQN', alpha=0.6, linestyle='--')
plt.plot(x, y_double, label='Double DQN', linewidth=2)
plt.plot(x, y_dueling, label='Dueling DQN', linewidth=2)
plt.plot(x, y_per, label='Dueling + PER', linewidth=2, color='red')

plt.title("Performance Comparison: DQN Variants")
plt.xlabel("Episodes")
plt.ylabel("Score")
plt.legend()
plt.grid(True)
plt.show()


结论:通常情况下,Dueling DQN + Double DQN + PER 的组合(通常被称为 Rainbow 的雏形)能获得最快、最稳定的收敛效果。

6. 总结 (Summary)

今天我们深入探讨了 DQN 的家族成员:

算法

核心痛点

解决方案 (一句话)

关键数学/结构

Double DQN

Q 值高估

选动作看当前网,算分看目标网

Q(s', \arg\max Q(s', a; \theta); \theta^-)

Dueling DQN

状态价值不明确

把网络拆成 V(s) 和 A(s, a)

Q = V + (A - \bar{A})

PER

学习效率低

谁误差大,就先学谁

P(i) \propto

最后的一个小彩蛋:如果你把 Double, Dueling, PER, Multi-step, Distributional, Noisy Nets 等所有改进全加在一起,就构成了强化学习界的“六边形战士” —— Rainbow DQN

希望这篇文章能帮你打通 DQN 进阶的任督二脉!动手写代码试试吧!加油!

声明:该内容由作者自行发布,观点内容仅供参考,不代表平台立场;如有侵权,请联系平台删除。
标签:
强化学习(RL)
深度强化学习