DQN 与 PPO:深度强化学习
·
摘要:Q-Learning 的表格方法受限于状态空间的规模——游戏画面的像素组合几乎是无穷的,不可能用表格存 Q 值。深度强化学习(Deep RL)用神经网络替代 Q-Table,让 RL 能处理高维输入(图像、连续状态)。2013 年 DeepMind 的 DQN 在 Atari 游戏上超越人类,2017 年 OpenAI 的 PPO 成为策略梯度方法的标杆。这篇文章讲清楚这两个里程碑算法,以及它们如何从 Q-Learning 演进而来。
一、从 Q-Table 到神经网络
Q-Learning 的瓶颈
在上一篇文章的迷宫中,4×4 网格有 16 个状态,Q-Table 只有 64 个格子。但如果是 Atari 游戏呢?
Atari 游戏画面:
分辨率:84×84 像素
颜色:灰度(256 阶)
状态数 = 256^(84×84) ≈ 10^17000
→ 这个数字比宇宙中的原子数(10^80)还要大无数倍
→ 用表格存储绝对不可能
核心思路:用神经网络拟合 Q 函数
Q-Learning: Q(s, a) ← 查 Q-Table[s][a]
↓
DQN: Q(s, a) ← 神经网络(s)[a]
神经网络输入:状态 s(如图像像素)
神经网络输出:每个动作 a 的 Q 值
import torch
import torch.nn as nn
import torch.nn.functional as F
# ===== DQN 的核心:用神经网络替代 Q-Table =====
class DQN(nn.Module):
"""深度 Q 网络——输入状态,输出各动作的 Q 值"""
def __init__(self, state_dim, action_dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
nn.Linear(128, 128),
nn.ReLU(),
nn.Linear(128, action_dim) # 输出每个动作的 Q 值
)
def forward(self, x):
return self.net(x)
# Q-Learning 的做法:
# q_value = q_table[state][action]
#
# DQN 的做法:
# q_values = dqn_network(state) # 一次输出所有动作的 Q 值
# q_value = q_values[action]
二、DQN:Deep Q-Network
DQN 的两个关键创新
DQN(Deep Q-Network)在 2013 年由 DeepMind 提出,在 Atari 游戏上取得突破性成果。它解决了"用神经网络做 Q-Learning"的两个关键问题。
创新 1:经验回放(Experience Replay)
问题:RL 的数据是序列相关的
在迷宫中刚刚走过的路和当前状态高度相关
连续使用相关数据训练 → 神经网络"忘记"了过去 → 不稳定
解决方案:经验回放
把每次交互 (s, a, r, s') 存到一个"经验池"中
训练时从池中随机采样(打乱相关性)
就像人类学习时不只是复习刚学过的一题,而是随机抽题
经验池大小:通常 10000-100000
每次训练采样:32-256 条
from collections import deque
import random
class ReplayBuffer:
"""经验回放缓冲区"""
def __init__(self, capacity=10000):
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=64):
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, dtype=np.float32),
np.array(next_states), np.array(dones))
def __len__(self):
return len(self.buffer)
创新 2:目标网络(Target Network)
问题:Q-Learning 更新中,"目标"也在随训练变化
Q(s, a) ← Q(s, a) + α × [R + γ × max Q(s', a') - Q(s, a)]
↑
这个目标值也在变!
就像追着一个移动的靶子射箭——永远打不中
解决方案:使用两个网络
1. 主网络(Policy Net):不断更新,用于选择动作
2. 目标网络(Target Net):定期从主网络复制,用于计算目标 Q 值
让目标网络"冻结"一段时间,训练更稳定
# ===== DQN 训练的关键部分 =====
def train_dqn_step(main_net, target_net, optimizer, batch, gamma=0.99):
"""DQN 单步训练"""
states, actions, rewards, next_states, dones = batch
# 转为 Tensor
states = torch.FloatTensor(states)
actions = torch.LongTensor(actions)
rewards = torch.FloatTensor(rewards)
next_states = torch.FloatTensor(next_states)
dones = torch.FloatTensor(dones)
# 1. 主网络计算当前 Q 值
current_q = main_net(states) # [batch, n_actions]
current_q = current_q.gather(1, actions.unsqueeze(1)) # [batch, 1]
# 2. 目标网络计算目标 Q 值(固定!不传播梯度)
with torch.no_grad():
next_q = target_net(next_states) # [batch, n_actions]
max_next_q = next_q.max(1)[0] # [batch]
target_q = rewards + gamma * max_next_q * (1 - dones)
# 3. 损失 = MSE(当前 Q, 目标 Q)
loss = F.mse_loss(current_q.squeeze(), target_q)
# 4. 反向传播更新主网络
optimizer.zero_grad()
loss.backward()
optimizer.step()
return loss.item()
# 定期(每 100-1000 步)将主网络参数复制到目标网络
def update_target_network(main_net, target_net):
target_net.load_state_dict(main_net.state_dict())
DQN 完整训练循环
import torch.optim as optim
class DQNAgent:
"""完整的 DQN 智能体"""
def __init__(self, state_dim, action_dim):
self.main_net = DQN(state_dim, action_dim)
self.target_net = DQN(state_dim, action_dim)
self.target_net.load_state_dict(self.main_net.state_dict())
self.optimizer = optim.Adam(self.main_net.parameters(), lr=0.001)
self.replay_buffer = ReplayBuffer(capacity=10000)
self.epsilon = 1.0
self.action_dim = action_dim
self.update_counter = 0
def choose_action(self, state):
"""ε-贪婪选择动作"""
if np.random.random() < self.epsilon:
return np.random.randint(self.action_dim)
state_tensor = torch.FloatTensor(state).unsqueeze(0)
with torch.no_grad():
q_values = self.main_net(state_tensor)
return q_values.argmax().item()
def train_step(self, batch_size=64):
"""用经验回放中的一个 batch 训练"""
if len(self.replay_buffer) < batch_size:
return 0
batch = self.replay_buffer.sample(batch_size)
loss = train_dqn_step(self.main_net, self.target_net,
self.optimizer, batch)
# 定期更新目标网络
self.update_counter += 1
if self.update_counter % 100 == 0:
update_target_network(self.main_net, self.target_net)
return loss
# ===== DQN vs Q-Learning =====
# Q-Learning: Q-Table + 贝尔曼方程更新 + ε-贪婪
# DQN: 神经网络 + 经验回放 + ε-贪婪
# + 目标网络
# + 贝尔曼方程更新
三、从 DQN 到策略梯度
价值方法 vs 策略方法
DQN 是价值方法——先学 Q 值,再根据 Q 值选动作。但价值方法有局限:
价值方法的局限:
1. 只能处理离散动作(从 Q 值中选最大的)
2. 策略是确定性的(同一状态永远选同一个动作)
3. 对连续动作(如机器人关节角度)无能为力
→ 策略梯度方法直接学习策略 π(a|s)
而不是通过 Q 值间接得到策略
REINFORCE 算法
策略梯度中最简单的方法:
REINFORCE 的核心思想:
1. 用神经网络直接输出动作概率 π(a|s)
2. 让"好"的动作概率增大,"坏"的动作概率减小
3. 用整条轨迹的累积奖励作为"好坏"的判断标准
class PolicyNetwork(nn.Module):
"""策略网络——直接输出动作概率"""
def __init__(self, state_dim, action_dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
nn.Linear(128, action_dim),
nn.Softmax(dim=-1) # 输出概率分布
)
def forward(self, x):
return self.net(x)
# REINFORCE 更新
# 目标:最大化 log π(a|s) × G
# 其中 G 是这条轨迹的总奖励
四、PPO:近端策略优化
PPO 要解决的问题
REINFORCE 有一个严重的问题:不稳定。
REINFORCE 的问题:
如果你用旧的交互数据更新了策略,
新策略和旧策略可能差异很大
→ 更新一步,策略"坍塌"了
就像"步子迈太大,直接摔倒了"
PPO(Proximal Policy Optimization)在 2017 年由 OpenAI 提出,核心思想:每次更新不要改变太多。
PPO 的核心创新:Clip 目标
PPO 的目标函数:
L = min( r(θ) × A, clip(r(θ), 1-ε, 1+ε) × A )
其中:
r(θ) = π_新(a|s) / π_旧(a|s) ← 新旧策略的比率
A = 优势函数(这个动作比平均好多少?)
ε = clip 范围(通常 0.2)
关键机制:
- 当 r(θ) 太大时(新策略太激进),clip 把它拉回来
- 当 r(θ) 太小时(新策略太保守),clip 把它拉回来
- 更新被限制在 [1-ε, 1+ε] 的范围内
→ "每次只学一点,不贪多"
为什么 PPO 是 2026 年最主流的 RL 算法?
| 维度 | REINFORCE | DQN | PPO |
|---|---|---|---|
| 训练稳定性 | ❌ 差 | ⚠️ 中等 | ✅ 稳定 |
| 样本效率 | ❌ 低 | ⚠️ 中等 | ✅ 高 |
| 连续动作 | ✅ 支持 | ❌ 不支持 | ✅ 支持 |
| 离散动作 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
| 实现复杂 | ✅ 简单 | ⚠️ 中等 | ⚠️ 中等 |
| 调参难度 | ❌ 难 | ⚠️ 中 | ✅ 易 |
PPO 的"好"体现在:它是 RL 领域的"随机森林"
不像 DQN 那样只有特定场景好用
不像 REINFORCE 那样不稳定
PPO 几乎在所有场景下都能工作
是 2026 年强化学习的第一选择
PPO 的 3 行核心代码
# PPO 最核心的部分只有 3 行伪代码:
# 1. 计算新旧策略比率
ratio = torch.exp(new_log_prob - old_log_prob)
# 2. 裁剪比率(限制更新幅度)
clipped_ratio = torch.clamp(ratio, 1 - clip_eps, 1 + clip_eps)
# 3. 取最小值(不要让激进更新主导)
loss = -torch.min(ratio * advantage, clipped_ratio * advantage).mean()
# 其他都是工程优化
五、三大算法对比总结
强化学习算法演进:
Q-Learning ──→ DQN ──→ PPO
(2013以前) (2013) (2017)
│ │ │
表格方法 深度网络 策略梯度
有限状态 Atari游戏 通用RL基准
核心思想对比
| Q-Learning | DQN | PPO | |
|---|---|---|---|
| 核心概念 | Q-Table | 神经网络 + 经验回放 + 目标网络 | 裁剪策略更新 |
| 学习对象 | Q(s, a) | Q(s, a) | 直接学 π(a|s) |
| 状态空间 | 有限离散 | 连续/高维 | 连续/高维 |
| 动作空间 | 有限离散 | 有限离散 | 连续或离散 |
| 探索方式 | ε-贪婪 | ε-贪婪 | 策略自带随机性 |
2026 年的选型指南
任务类型 → 推荐算法
离散动作 + 小状态空间(迷宫、棋盘)
→ Q-Learning ✅
离散动作 + 高维状态输入(Atari 游戏)
→ DQN ✅(或改进版 Rainbow DQN)
连续动作 + 机器人控制(倒立摆、机械臂)
→ PPO ✅
大规模并行训练(如 DOTA、StarCraft)
→ PPO + 大规模分布式 ✅(如 OpenAI Five)
需要从演示中学习(模仿学习)
→ 先行为克隆 + 再 PPO 微调 ✅
六、强化学习的现实应用
| 领域 | 应用 | 使用的算法 |
|---|---|---|
| 游戏 AI | AlphaGo、OpenAI Five、AlphaStar | 深度 RL + 蒙特卡洛树搜索 |
| 机器人 | 机械臂抓取、四足机器人行走 | PPO / SAC |
| 自动驾驶 | 路径规划、车辆控制 | PPO / DDPG |
| 推荐系统 | 新闻推荐、广告出价 | 多臂老虎机 / 离线 RL |
| 大模型 | RLHF(人类反馈强化学习) | PPO(训练 ChatGPT 的算法) |
| 芯片设计 | 芯片布局优化 | PPO / 图神经网络 + RL |
RLHF:大模型中的强化学习
2024-2026 年最广为人知的 RL 应用是 RLHF(Reinforcement Learning from Human Feedback)——ChatGPT 训练的核心步骤之一。
RLHF 的三步:
1. 监督微调(SFT)
用高质量对话数据微调预训练模型
2. 训练奖励模型
让人类对模型的输出进行排序
训练一个"打分模型"来模拟人类偏好
3. PPO 优化
用 PPO 微调语言模型
→ 目标是最大化奖励模型的分数
→ 同时用 KL 散度约束,防止模型偏离初始分布
→ PPO 在这里起到了"每次不要改变太多"的关键作用!
如果不用 PPO 而用普通的策略梯度,模型可能会"疯掉"
七、总结
| 概念 | 一句话理解 |
|---|---|
| DQN | 用神经网络替代 Q-Table + 经验回放打乱相关性 + 目标网络稳定训练 |
| 经验回放 | 把经历存起来,随机采样训练——打破序列相关性 |
| 目标网络 | 固定目标 Q 值的计算网络——追固定靶而不是移动靶 |
| 策略梯度 | 直接学 π(a|s) 而非 Q(s, a)——天然支持连续动作和随机策略 |
| PPO | 每次更新都限制幅度——"步子大了会扯到蛋" |
核心三句话:
- DQN = Q-Learning + 神经网络 + 经验回放 + 目标网络——它让 RL 能处理图像等高维输入
- PPO = 策略梯度 + "每次不要改太多"的约束——它用 3 行核心代码解决了 RL 训练不稳定的根本问题
- PPO 是 2026 年最通用的 RL 算法——从 Atari 游戏到机器人控制到 ChatGPT 的 RLHF,都是它
更多推荐


所有评论(0)