强化学习之Q-learning 与 DQN
Q-learning 旨在学习一个最优策略,使得智能体在给定状态下选择最优动作,以获得最大的长期奖励。在 Q-learning 中,智能体维护一个 Q 值表Q(s, a),表示在状态s下执行动作a后所能获得的长期累积奖励。DQN 通过深度神经网络(DNN)近似 Q 值函数,解决了 Q-learning 在高维状态空间中存储 Q 值表的限制问题。在 DQN 中,我们使用神经网络Qsa;θQ(s, a
Q-learning 与 DQN 强化学习算法解析
1. 背景介绍
强化学习(Reinforcement Learning, RL)是一种机器学习方法,广泛应用于游戏、机器人控制、自动驾驶等领域。强化学习的核心思想是智能体(Agent)通过与环境(Environment)交互,不断学习策略(Policy),以最大化累积奖励(Reward)。
在强化学习中,最经典的方法之一是 Q-learning,它是一种无模型(Model-free)的值迭代算法。而 DQN(Deep Q-Network) 则是 Q-learning 在深度学习中的扩展,解决了 Q-learning 在高维状态空间中难以应用的问题。
2. Q-learning 介绍
2.1 Q-learning 的动机
Q-learning 旨在学习一个最优策略,使得智能体在给定状态下选择最优动作,以获得最大的长期奖励。
在 Q-learning 中,智能体维护一个 Q 值表 Q(s, a),表示在状态 s 下执行动作 a 后所能获得的长期累积奖励。
2.2 Q-learning 算法公式
Q-learning 的核心更新公式如下:
Q(s,a)←Q(s,a)+α[r+γmaxa′Q(s′,a′)−Q(s,a)] Q(s, a) \leftarrow Q(s, a) + \alpha [r + \gamma \max_{a'} Q(s', a') - Q(s, a)] Q(s,a)←Q(s,a)+α[r+γa′maxQ(s′,a′)−Q(s,a)]
其中:
- Q(s,a)Q(s, a)Q(s,a) 是 Q 值函数,表示状态 sss 下采取动作 aaa 的价值。
- α\alphaα 是学习率(Learning rate),控制更新步长。
- rrr 是即时奖励(Reward)。
- γ\gammaγ 是折扣因子(Discount Factor),衡量未来奖励的重要性(0 ≤ γ ≤ 1)。
- s′s's′ 是执行动作后的新状态。
- maxa′Q(s′,a′)\max_{a'} Q(s', a')maxa′Q(s′,a′) 是新状态 s′s's′ 下最优动作的 Q 值。
2.3 Q-learning 的使用场景
Q-learning 适用于以下场景:
- 低维离散状态空间的任务(如网格世界、棋类游戏)。
- 需要无模型(Model-free)方法的环境。
- 训练过程中允许大量探索。
2.4 Q-learning 的局限性
- 状态-动作空间较大时,Q 值表会变得难以存储和更新。
- 难以处理连续状态空间,因为 Q 值需要在表中存储所有可能的状态-动作对。
- 在高维任务中收敛速度慢,且难以泛化到未见过的状态。
3. DQN(Deep Q-Network)介绍
3.1 DQN 的动机
DQN 通过 深度神经网络(DNN) 近似 Q 值函数,解决了 Q-learning 在高维状态空间中存储 Q 值表的限制问题。
在 DQN 中,我们使用 神经网络 Q(s,a;θ)Q(s, a; \theta)Q(s,a;θ) 来代替 Q 值表,并通过 梯度下降 进行参数更新。
3.2 DQN 算法核心思想
DQN 主要引入了以下两项技术来提升 Q-learning 的稳定性和收敛速度:
经验回放(Experience Replay)
DQN 训练时不会直接使用最新经验更新 Q 值,而是将过去的经验(状态、动作、奖励、下一个状态)存储在 回放缓冲区(Replay Buffer),并在训练时 随机采样小批量数据 进行更新。这样可以减少样本之间的相关性,提高训练稳定性。
目标网络(Target Network)
为了避免目标值(Target Q 值)剧烈波动,DQN 维护两个神经网络:
- 当前 Q 网络(Q-Network):用于计算当前动作的 Q 值。
- 目标 Q 网络(Target Q-Network):用于计算目标 Q 值,参数每隔一段时间更新一次。
DQN 的目标值计算如下:
Yt=r+γmaxa′Q(s′,a′;θ−) Y_t = r + \gamma \max_{a'} Q(s', a'; \theta^-) Yt=r+γa′maxQ(s′,a′;θ−)
其中 θ−\theta^-θ− 是目标网络的参数。
3.3 DQN 算法流程
- 初始化 Q 网络和目标网络,经验回放缓冲区。
- 采样初始状态 s。
- 采用 ε-贪心策略 选择动作 a(以概率 ε 进行探索,以概率 1-ε 选择当前 Q 值最大的动作)。
- 执行动作 a,观察奖励 r 和新状态 s’,存入回放缓冲区。
- 从回放缓冲区随机采样小批量经验 (s, a, r, s’) 进行训练。
- 计算目标 Q 值 Y_t 并最小化损失函数:
L(θ)=E[(Yt−Q(s,a;θ))2] L(\theta) = \mathbb{E}[(Y_t - Q(s, a; \theta))^2] L(θ)=E[(Yt−Q(s,a;θ))2] - 每隔固定步数,将 Q 网络的参数更新到目标网络。
- 迭代至收敛。
3.4 DQN 的使用场景
DQN 适用于:
- 高维状态空间(如 Atari 游戏、机器人控制)。
- 状态离散但动作空间较大 的任务。
- 无模型(Model-free)强化学习任务。
3.5 DQN 的局限性
- 过估计问题:DQN 可能会高估某些动作的 Q 值,导致策略不稳定。
- 训练不稳定:深度神经网络的训练可能会导致不稳定收敛。
- 样本利用效率低:DQN 在高维任务中仍然需要大量数据才能学习良好的策略。
为了解决这些问题,后续提出了 Double DQN、Dueling DQN、Rainbow DQN 等改进算法。
Double DQN算法
动机:DQN 在计算目标 Q 值时,使用同一个网络既选择动作又评估动作的 Q 值,这容易导致 Q 值偏高(过估计)。
生活上的类别:假设你想买一台新手机,你在网上查找各种测评,每款手机都显示一个“评分”(类似于 Q 值)。如果你只看评分最高的一款,那么可能会遇到评分被夸大的情况(比如某个品牌营销过度,让分数虚高)。
DQN 的问题类似于只相信最高评分,而 Double DQN 的改进就像是:
先用一个神经网络(online q net)选择最好的手机。
再用另一个更客观的神经网络(target q net)去验证这个选择的质量,避免高估。
# 下个状态的最大Q值
# q_net input: state ouput: all action value
if self.dqn_type == 'DoubleDQN': # DQN与Double DQN的区别
max_action = self.q_net(next_states).max(1)[1].view(-1, 1)
max_next_q_values = self.target_q_net(next_states).gather(1, max_action)
else: # DQN的情况
max_next_q_values = self.target_q_net(next_states).max(1)[0].view(-1, 1)
Dueling DQN
传统DQN算法存在的问题(动机)
- 不同动作的 Q 值可能非常接近(例如,站在炸弹旁边时,所有方向的 Q 值都差不多)。
- Q 值的学习效率较低,因为网络要同时学习“状态的好坏” 和 “各个动作的相对优势”。
Dueling DQN 通过将 Q 值拆分为两部分:
Q(s,a)=V(s)+A(s,a) Q(s, a) = V(s) + A(s, a) Q(s,a)=V(s)+A(s,a)
- 状态价值 ( V(s) ):表示在状态 ( s ) 下整体有多好,不考虑具体动作。
- 动作优势 ( A(s, a) ):表示在状态 ( s ) 下,执行动作 ( a ) 相对于其他动作有多大优势。
这种结构能让智能体更容易判断状态的好坏,而不是必须依赖于具体的动作 Q 值。
Dueling DQN 在 DQN 网络的全连接层后,分成两条分支:
- Value Stream(状态价值流):计算 ( V(s) )
- Advantage Stream(动作优势流):计算 ( A(s, a) )
然后,它们通过以下方式合并成 Q 值:
Q(s,a)=V(s)+A(s,a)−1∣A∣∑a′A(s,a′) Q(s, a) = V(s) + A(s, a) - \frac{1}{|\mathcal{A}|} \sum_{a'} A(s, a') Q(s,a)=V(s)+A(s,a)−∣A∣1a′∑A(s,a′)
其中,∑a′A(s,a′)\sum_{a'} A(s, a')∑a′A(s,a′) 表示所有动作的优势均值,这样做的目的是确保 Q 值的唯一性,防止 Value 和 Advantage 之间的相互影响。
import torch
import torch.nn as nn
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(),
)
# 状态价值流
self.value_stream = nn.Sequential(
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, 1) # 只输出 V(s)
)
# 动作优势流
self.advantage_stream = nn.Sequential(
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, action_dim) # 输出 A(s, a)
)
def forward(self, state):
features = self.feature_layer(state)
V = self.value_stream(features)
A = self.advantage_stream(features)
Q = V + (A - A.mean(dim=1, keepdim=True)) # 计算 Q(s, a)
return Q
| 特性 | 传统 DQN | Dueling DQN |
|---|---|---|
| Q 值结构 | 直接输出每个动作的 Q 值 | 拆分为 状态价值 ( V(s) ) 和 动作优势 ( A(s, a) ) |
| 泛化能力 | 需要为每个动作学习 Q 值 | 只需学习状态的价值,泛化更好 |
| 学习效率 | 可能过度关注某些动作 | 更快学会哪些状态重要 |
| 适用场景 | 适用于所有强化学习问题 | 适用于动作影响不明显的情况(如 Atari 游戏) |
Dueling DQN 适用于:
- 状态比动作更重要的环境,比如:
- Atari 游戏:如《太空侵略者》,在屏幕边缘和中间的情况不同,但具体按键可能影响不大。
- Bomberman-like 游戏:智能体可能需要先判断“这个状态危险吗?”而不是“往左还是往右更好?”
- 离散动作空间的强化学习问题,适用于 DQN 改进。
Rainbow DQN
Rainbow DQN 是 DeepMind 提出的综合强化学习算法,它结合了多个 DQN 的改进方法,大幅提升了强化学习的性能和稳定性。
Rainbow DQN 介绍 🌈
Rainbow DQN 是 DeepMind 提出的综合强化学习算法,它结合了多个 DQN 的改进方法,大幅提升了强化学习的性能和稳定性。
1. Rainbow DQN = 多种强化学习技术的结合
Rainbow DQN 之所以叫 “Rainbow”(彩虹),是因为它结合了6 种 DQN 改进方法,就像不同颜色的光融合成了一个强大的算法。
Rainbow DQN 结合了哪些技术?
| 技术 | 作用 |
|---|---|
| Double DQN (DDQN) | 解决 DQN 的 过估计 问题,提高学习稳定性 |
| Dueling DQN | 分解 Q 值为状态价值 (V(s)) 和 动作优势 (A(s, a)),提高学习效率 |
| Prioritized Experience Replay (PER) | 让更重要的经验(例如 TD 误差大的经验)更容易被采样,提高学习效率 |
| Noisy Networks | 在网络中加入可学习的噪声,替代 ε-greedy,提升探索能力 |
| Multi-step Learning | 采用 n 步 Q 学习,增强长期奖励信号 |
| Distributional RL (C51) | 预测 Q 值分布而不是单一值,使学习更稳健 |
| (可选) Categorical DQN (C51) | 进一步优化 Q 值分布估计,提升决策能力 |
1. Double DQN (DDQN)
- 解决 DQN 高估 Q 值 的问题。
- 方法:一个网络选动作,另一个网络评估 Q 值。
- 作用:提高训练稳定性,让智能体避免被误导。
2. Dueling DQN
- 将 Q 值分解为:
Q(s,a)=V(s)+A(s,a) Q(s, a) = V(s) + A(s, a) Q(s,a)=V(s)+A(s,a) - 作用:
- 让智能体更快学习到哪些状态重要,而不是仅关注动作的好坏。
- 在 Bomberman 这种游戏里,AI 先判断“这个位置安全吗?”再决定“往哪走?”。
3. Prioritized Experience Replay (PER)
- 让经验回放(Experience Replay)优先选择重要的样本,而不是均匀随机采样。
- 计算 TD 误差 ∣r+γQ(s′,a′)−Q(s,a)∣|r + \gamma Q(s', a') - Q(s, a)|∣r+γQ(s′,a′)−Q(s,a)∣,误差越大,样本被采样的概率越高。
- 作用:
- 让 AI 更快学习到关键经验(比如“哪种操作最容易赢”)。
- 更高效地利用数据,加快训练速度。
4. Noisy Networks
- 在神经网络的权重中加入可学习的噪声,替代 ε-greedy 进行探索:
W=μ+σ⋅ϵ W = \mu + \sigma \cdot \epsilon W=μ+σ⋅ϵ
其中 ϵ\epsilonϵ 是随机噪声,σ\sigmaσ 是可训练参数。 - 作用:
- 智能体自动调节探索 vs. 开发,无须人为设定 ε-greedy。
- 适用于复杂环境(如 Bomberman),避免 AI 过早收敛到次优策略。
5. Multi-step Learning (n-step TD)
- 采用多步回报:
Gt=rt+γrt+1+γ2rt+2+...+γn−1rt+n−1+γnQ(sn,an) G_t = r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + ... + \gamma^{n-1} r_{t+n-1} + \gamma^n Q(s_n, a_n) Gt=rt+γrt+1+γ2rt+2+...+γn−1rt+n−1+γnQ(sn,an) - 作用:
- 让 AI 关注更长远的奖励,提高学习稳定性。
- 在 Bomberman 里,AI 可以考虑到“放炸弹”后的多个时间步的结果,而不是只关注短期奖励。
6. Distributional RL (C51)
- 预测Q 值的概率分布,而不是单个数值:
Q(s,a)≈∑i=1Npizi Q(s, a) \approx \sum_{i=1}^{N} p_i z_i Q(s,a)≈i=1∑Npizi- 其中 ziz_izi 是离散化的 Q 值可能取值,pip_ipi 是它们的概率。
- 作用:
- 让 AI 不仅知道“哪个动作最好”,还能估计“风险”。
- 在 Bomberman 里,AI 能更好地评估炸弹爆炸的不确定性。
- 离散化 Q 值分布
C51 假设 Q 值的分布是有限的离散值,比如:
zi=minr+i⋅maxr−minrN−1,i=0,1,...,N−1 z_i = \text{min}_r + i \cdot \frac{\text{max}_r - \text{min}_r}{N - 1}, \quad i = 0, 1, ..., N-1 zi=minr+i⋅N−1maxr−minr,i=0,1,...,N−1
- 这里 minr\text{min}_rminr 和 maxr\text{max}_rmaxr 是奖励的最小值和最大值,比如 [-100, 100]。
- 我们将 Q 值分成 ( N = 51 ) 个离散的值(这就是 C51 这个名字的来源)。
- 每个 ziz_izi 代表 Q 值的一个可能取值。
-
预测 Q 值的概率分布
C51 让神经网络输出一个 N 维的概率向量 ( p ),其中:
pi=P(Z(s,a)=zi) p_i = P(Z(s, a) = z_i) pi=P(Z(s,a)=zi)
也就是说,智能体不再只输出一个 Q 值,而是输出 51 个概率值,表示不同 Q 值的可能性。 -
C51 使用 Bellman 更新:
Z(s,a)=R+γZ(s′,a′) Z(s, a) = R + \gamma Z(s', a') Z(s,a)=R+γZ(s′,a′)
但因为 Z(s,a)Z(s, a)Z(s,a) 可能不是在 ziz_izi 里,我们需要把它投影回 C51 的离散支持上,具体方法:
(1) 计算目标分布
(2) 把目标 Q 值映射到 ziz_izi
(3) 计算新的概率分布 p′p'p′
(4) 最小化 KL 散度 DKL(p∣∣p′)D_{KL}(p || p')DKL(p∣∣p′) 进行训练
具体参照下面的代码:
import torch
import torch.nn as nn
import torch.nn.functional as F
class C51DQN(nn.Module):
def __init__(self, state_dim, action_dim, atom_size=51, v_min=-10, v_max=10):
super(C51DQN, self).__init__()
self.action_dim = action_dim
self.atom_size = atom_size # 公式中的N
self.v_min = v_min
self.v_max = v_max
self.support = torch.linspace(v_min, v_max, atom_size)
self.feature_layer = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
)
self.advantage_layer = nn.Linear(128, action_dim * atom_size)
self.value_layer = nn.Linear(128, atom_size)
def forward(self, state):
q_distribution = self.forward(state)
q_values = torch.sum(q_distribution * self.support, dim=2) # 计算期望值
return q_values
def dist(self, state):
feature = self.feature_layer(state)
advantage = self.advantage_layer(feature).view(-1, self.action_dim, self.atom_size)
value = self.value_layer(feature).view(-1, 1, self.atom_size)
q_distribution = value + advantage - advantage.mean(dim=1, keepdim=True)
q_distribution = F.softmax(q_distribution, dim=2) # 归一化成概率分布
return q_distribution
class DQNAgent:
def _compute_dqn_loss(self, samples: Dict[str, np.ndarray]) -> torch.Tensor:
"""Return categorical dqn loss."""
device = self.device # for shortening the following lines
state = torch.FloatTensor(samples["obs"]).to(device)
next_state = torch.FloatTensor(samples["next_obs"]).to(device)
action = torch.LongTensor(samples["acts"]).to(device)
reward = torch.FloatTensor(samples["rews"].reshape(-1, 1)).to(device)
done = torch.FloatTensor(samples["done"].reshape(-1, 1)).to(device)
# Categorical DQN algorithm
delta_z = float(self.v_max - self.v_min) / (self.atom_size - 1)
with torch.no_grad():
next_action = self.dqn_target(next_state).argmax(1)
next_dist = self.dqn_target.dist(next_state)
next_dist = next_dist[range(self.batch_size), next_action]
# self.support : [-1.0, -0.9, ..., 1.0]
# reward : 0.5
# self.gamma : 1.0
t_z = reward + (1 - done) * self.gamma * self.support # [atom_size]
t_z = t_z.clamp(min=self.v_min, max=self.v_max)
# t_z : [-0.5, -0.4, ..., 0.9, 1.0, 1.0, 1.0, 1.0, 1.0]
b = (t_z - self.v_min) / delta_z
# b : [5, 6, 7, ..., 19, 20, 20, 20, 20, 20]
l = b.floor().long()
u = b.ceil().long()
offset = (
torch.linspace(
0, (self.batch_size - 1) * self.atom_size, self.batch_size
).long()
.unsqueeze(1)
.expand(self.batch_size, self.atom_size)
.to(self.device)
)
# [[0, 0, 0, ..], [20, 20, 20, ...], ...]
proj_dist = torch.zeros(next_dist.size(), device=self.device)
proj_dist.view(-1).index_add_(
0, (l + offset).view(-1), (next_dist * (u.float() - b)).view(-1)
)
proj_dist.view(-1).index_add_(
0, (u + offset).view(-1), (next_dist * (b - l.float())).view(-1)
)
dist = self.dqn.dist(state)
log_p = torch.log(dist[range(self.batch_size), action])
loss = -(proj_dist * log_p).sum(1).mean()
return loss
参考网址:https://github.com/Curt-Park/rainbow-is-all-you-need
4. 结论
Q-learning 和 DQN 是强化学习领域的重要算法,各自适用于不同的任务场景。Q-learning 适用于小规模离散状态任务,而 DQN 通过神经网络扩展了 Q-learning,使其能够应用于高维状态空间的任务。尽管 DQN 仍有一些局限性,但后续改进算法进一步提升了其性能,使其在强化学习应用中发挥重要作用。
更多推荐

所有评论(0)