上篇讲了 CLIP 这类图文联合表征,但模型不止能「理解静态」,还能在环境中「决策」,这就进入强化学习。
引言
前面 17 篇都在讲"有监督"——给数据给标签,模型学映射。但很多真实问题是"没有标准答案,只有长期回报":下棋、开车、调度资源。这类问题归强化学习(RL)管。本篇从 MDP 讲起,重点拆解 2013 年引爆深度 RL 的DQN——它第一次让神经网络在 Atari 游戏上超过人类,也是所有深度 RL 的起点。
一、MDP 与 Bellman 方程
强化学习把问题建模成马尔可夫决策过程(MDP):
状态 s、动作 a、奖励 r、转移 P(s'|s,a)、折扣 γ。
智能体在 s 选 a,环境给 r 并转移到 s',目标是最大化期望累积回报
G = Σ γᵗ rₜ。
Q 函数(动作价值)定义为:从状态 s 执行 a、之后按策略行事的期望回报:
Q*(s,a) = E[ r + γ · max_{a'} Q*(s', a') ]这就是Bellman 最优方程——它把"最优 Q"表达成"一步奖励 + 折扣后的下一状态最优 Q"。迭代求解这个方程就能得到最优策略。传统Q-learning用一张表Q[s][a]存值,但状态一多(如图像像素)表就爆了。
二、从 Q-learning 到 DQN
DQN(Deep Q-Network)的关键洞察:用神经网络Q(s,a;θ)去逼近那个存不下的 Q 表。训练目标是让网络预测接近 Bellman 目标:
TD 目标: y = r + γ · max_{a'} Q(s', a'; θ⁻) 损失: L = (Q(s,a;θ) - y)²这里θ⁻是目标网络参数(见下节),训练时冻结,避免目标本身随 θ 移动导致发散。
三、经验回放与目标网络(DQN 两大稳定技巧)
朴素 DQN 极不稳定,两个工程创新救了它:
3.1 经验回放(Experience Replay)
RL 的采样是强相关的(连续帧、连续状态),直接拿来训网络,梯度会被近期相关性带偏。解决:把(s,a,r,s',done)存进一个大缓冲区,训练时随机采样 batch。好处:打破相关性、提高数据复用率。
3.2 目标网络(Target Network)
如果用来算 TD 目标的Q和正在训练的是同一套参数,目标会"自己追自己",容易发散。解决:维护一个周期性同步(如每 C 步拷贝一次)的目标网络θ⁻,让目标在一段时间内稳定。
这两招是 DQN 能在 Atari 上稳定收敛的基石,缺一不可。
四、CartPole 实战实现
下面给出可跑通的 DQN(基于 Gymnasium + PyTorch),核心结构齐全:
import gymnasium as gym import torch, torch.nn as nn from collections import deque import random class DQN(nn.Module): def __init__(self, n_obs, n_act): super().__init__() self.net = nn.Sequential( nn.Linear(n_obs, 128), nn.ReLU(), nn.Linear(128, 128), nn.ReLU(), nn.Linear(128, n_act)) def forward(self, x): return self.net(x) env = gym.make("CartPole-v1") obs_dim, act_dim = env.observation_space.shape[0], env.action_space.n policy_net = DQN(obs_dim, act_dim) target_net = DQN(obs_dim, act_dim) target_net.load_state_dict(policy_net.state_dict()) optimizer = torch.optim.Adam(policy_net.parameters(), lr=1e-3) buffer = deque(maxlen=10000) # 经验回放池 gamma, eps, eps_min, C = 0.99, 1.0, 0.05, 100 for episode in range(500): s, _ = env.reset() done = False while not done: # ε-贪婪探索 if random.random() < eps: a = env.action_space.sample() else: a = policy_net(torch.FloatTensor(s)).argmax().item() s2, r, terminated, truncated, _ = env.step(a) done = terminated or truncated buffer.append((s, a, r, s2, done)) s = s2 if len(buffer) > 200: batch = random.sample(buffer, 64) S, A, R, S2, D = zip(*batch) S, S2 = map(torch.FloatTensor, [S, S2]) A, R, D = map(torch.LongTensor, [A]), torch.FloatTensor(R), torch.BoolTensor(D) q = policy_net(S).gather(1, A.unsqueeze(1)).squeeze() with torch.no_grad(): q_next = target_net(S2).max(1).values y = R + gamma * q_next * (~D) loss = nn.functional.smooth_l1_loss(q, y) optimizer.zero_grad(); loss.backward(); optimizer.step() eps = max(eps_min, eps * 0.995) # 探索率衰减 if episode % C == 0: # 同步目标网络 target_net.load_state_dict(policy_net.state_dict())训练要点:
奖励缩放:CartPole 每步 +1,γ=0.99 让回报有界;若奖励量级很大,建议归一化稳定训练。
探索率 ε 衰减:从 1.0 缓慢降到 0.05,前期多探索、后期多利用。
回报曲线:平均回报应从 ~20 爬到 500(CartPole 单局上限),证明学成了。
五、训练技巧与坑
| 坑 | 表现 | 解法 |
|---|---|---|
| 目标网络不同步 | 损失震荡、不收敛 | 周期性同步target_net |
| 回放池太小 | 相关性未打破 | 增大buffer,充分随机采样 |
| 奖励未裁剪 | 梯度爆炸 | 奖励/回报做归一化 |
| ε 衰减太快 | 还没探索就 exploitation | 放慢衰减,保底 ε_min |
六、算法演进对比
| 算法 | 改进点 | 适用 |
|---|---|---|
| DQN | 经验回放 + 目标网络 | 离散动作 |
| Double DQN | 解耦选动作与算 Q,缓解过估计 | 离散动作 |
| Dueling DQN | 拆分状态值/优势 | 离散动作 |
| PPO | 策略梯度 + 剪辑,稳定且通用 | 连续/离散均可,当前主流 |
DQN 是理解深度 RL 的"Hello World",但工程落地大多已转向PPO(更稳、支持连续控制)。学习顺序建议:先吃透 DQN,再上 PPO。
总结
强化学习让智能体在"只有回报、没有标签"的环境里自学策略。DQN 用神经网络逼近 Q 函数,再用经验回放和目标网络两个巧思把它训稳——这套组合是深度 RL 的奠基之作。本文的 CartPole 代码可直接跑通,建议亲手训出"会立杆"的智能体,比任何理论都直观。
参考资料
Mnih, V., et al. (2013/2015).Playing Atari with Deep Reinforcement Learning/Human-level control through deep RL(DQN).
Van Hasselt, H., et al. (2015).Deep Reinforcement Learning with Double Q-learning.
Schulman, J., et al. (2017).Proximal Policy Optimization Algorithms(PPO).
OpenAI Spinning Up RL 教程:Welcome to Spinning Up in Deep RL! — Spinning Up documentation
往期回顾
- 第一篇:神经网络基础—从感知机到多层网络的数学原理
- 第二篇:反向传播算法详解与梯度消失实战
- 迁移学习与预训练模型应用实践
关注作者,继续《深度学习工程化》专栏:下一篇是大模型时代的重头戏——用 LoRA/QLoRA 在消费级显卡上微调专属大模型。
📚本篇出自专栏《深学之路:从感知机到智能体》—— 从感知机到大模型智能体的完整深度学习连载(基础+工程化+扩散模型 / RAG / Agent 等)。每篇附可运行代码与原创实验,收藏专栏不迷路。