强化学习深度实战:从 MDP、Bellman 方程到 DQN、策略梯度与 PPO 的从零实现
0. 为什么强化学习是 LLM 时代的隐藏基石
过去三年里,强化学习(Reinforcement Learning, RL)以一种近乎隐秘的方式重新站到了 AI 舞台中央:RLHF 用人类偏好作为奖励信号把大语言模型对齐到人类意图,推理时计算(test-time compute)本质是在 rollout 轨迹上用验证器引导搜索,智能体把工具调用建模成离散动作、把真实世界当成环境。可绝大多数工程师只见过"对齐"和"智能体"这两层皮,对 RL 的第一性原理反而陌生。
本文回到 RL 的本体:把马尔可夫决策过程(MDP)、Bellman 方程、价值方法与策略方法的对偶、以及支撑现代 RLHF 的 PPO 算法,从数学推导到 PyTorch 从零实现完整走一遍。它不与本站已有的 RLHF / 推理时计算 / 智能体文章重复——那些讲的是"RL 被用来做什么",本文讲的是"RL 本身是什么、怎么训才不会崩"。
三条主线贯穿全文:
- 价值方法(value-based):学一个价值函数,用贪心方式导出策略,代表是 Q-Learning 与 DQN。
- 策略方法(policy-based):直接参数化策略并沿梯度上升,代表是 REINFORCE。
- 演员-评论家(actor-critic):用价值函数给策略梯度当 baseline,折中偏差与方差,代表是 A2C 与 PPO。
1. 数学基础:MDP 与 Bellman 方程
强化学习把问题建模成马尔可夫决策过程,定义为五元组 (S, A, P, R, γ):
- S 是状态空间,A 是动作空间;
- P(s' | s, a) 是状态转移概率,满足马尔可夫性 P(s_{t+1} | s_t, a_t, ...) = P(s_{t+1} | s_t, a_t);
- R(s, a) 是即时奖励;
- γ ∈ [0, 1) 是折扣因子,决定智能体有多"短视"。
智能体遵循策略 π(a | s) 与环境交互,得到一条轨迹 τ = (s_0, a_0, r_0, s_1, ...)。从时刻 t 开始的折扣回报为:
G_t = Σ_{k=0}^{∞} γ^k r_{t+k}
状态价值函数 V^π(s) 衡量"从状态 s 出发、之后一直按 π 行动能拿到的期望回报";动作价值函数 Q^π(s, a) 进一步固定了第一步动作 a。它们满足 Bellman 期望方程:
V^π(s) = E_{a~π, s'~P}[ R(s,a) + γ V^π(s') ]
Q^π(s,a) = E_{s'~P}[ R(s,a) + γ E_{a'~π}[ Q^π(s',a') ] ]
所谓"最优",是找到让每个状态价值最大的策略 π*,它满足最优 Bellman 方程:
V(s) = max_a [ R(s,a) + γ Σ_{s'} P(s'|s,a) V(s') ]
这给了我们一个朴素的值迭代算法:反复把右侧的"一步自举(bootstrap)"结果写回 V,直到收敛。当状态空间离散且不大时,值迭代是理解 RL 的第一把钥匙;一旦状态空间连续或高维,就必须引入函数逼近——这正是后面 DQN 与策略梯度的出发点。
2. Value-Based:从 Q-Learning 到 DQN
表格型 Q-Learning 用一张表存储每个 (s, a) 的 Q 值,按时间差分(TD)方式在线更新:
Q(s,a) ← Q(s,a) + α [ r + γ max_{a'} Q(s',a') − Q(s,a) ]
方括号里就是 TD 误差:目标值(奖励 + 折后下一状态的最大 Q)与当前估计之差。它的离线策略(off-policy)特性允许智能体从别人的经验里学习,这非常高效,但也埋下了 Q 值过估计的隐患。
维度灾难让表格法在连续/高维状态上彻底失效,于是用神经网络 Q_θ(s, a) 去逼近 Q 函数——这就是 DQN。DQN 之所以能在 Atari 上稳定收敛,靠三个工程稳定器:
- 经验回放(replay buffer):把转移 (s, a, r, s', done) 存进一个固定大小的池子,训练时随机采样小批量。它打破样本间的时间相关性,让离线策略学习的数据利用率大幅提升。
- 目标网络(target network):维护一份滞后更新的参数 θ⁻ 来计算 TD 目标,避免"用正在移动的目标训练自己"导致的发散。常用软更新 θ⁻ ← τ θ + (1−τ) θ⁻。
- 奖励/梯度裁剪:把奖励 clip 到 [−1, 1],并对梯度做 global norm clipping,防止偶发的极端奖励把网络推飞。
下面的 PyTorch 片段给出 DQN 的核心骨架:
import torch, torch.nn as nn, random
from collections import deque
class DQN(nn.Module):
def __init__(self, obs_dim, act_dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(obs_dim, 128), nn.ReLU(),
nn.Linear(128, 128), nn.ReLU(),
nn.Linear(128, act_dim))
def forward(self, x):
return self.net(x)
class ReplayBuffer:
def __init__(self, cap=100_000):
self.buf = deque(maxlen=cap)
def push(self, t): self.buf.append(t)
def sample(self, n):
return random.sample(self.buf, n)
def dqn_loss(q_net, tgt_net, batch, gamma=0.99):
s, a, r, s2, done = batch
q = q_net(s).gather(1, a.unsqueeze(1)).squeeze(1)
with torch.no_grad():
q_next = tgt_net(s2).max(1).values
target = r + gamma * q_next * (~done)
return nn.functional.smooth_l1_loss(q, target)
DQN 之后还有一串重要变体,核心都在解决同一件事——更稳、更准、更省样本:
| 变体 | 解决的核心问题 | 关键手段 |
|---|---|---|
| Double DQN | Q 过估计 | 用在线网络选动作、目标网络估价值 |
| Dueling DQN | 状态价值与优势解耦 | 网络输出 V(s) 与 A(s,a) 再合并 |
| Prioritized Replay | 均匀采样效率低 | 按 TD 误差优先级采样 + IS 权重 |
| Rainbow | 综合上述多种技巧 | 把六条改进路线 fused 成一个 agent |
3. Policy-Based:REINFORCE 与策略梯度定理
价值方法在离散、低维动作上很强,但一旦动作空间连续(比如机器人力矩),"max_a Q" 就无从下手。策略方法干脆直接参数化策略 π_θ(a | s),把"期望回报"作为目标去最大化:
J(θ) = E_{τ~π_θ}[ Σ_t γ^t r_t ]
策略梯度定理给出一个极其优雅的结论——你不需要对整条轨迹求导,只需要对单步对数概率乘上该步的回报:
∇_θ J(θ) = E_{τ~π_θ}[ Σ_t ∇_θ log π_θ(a_t | s_t) · G_t ]
这就是 REINFORCE 算法。它的致命问题是高方差:同一条轨迹上所有时间步共享同一个总回报 G_t,导致梯度噪声巨大、训练抖动。标准解法是引入 baseline b(s_t),用优势 A_t = G_t − b(s_t) 替代 G_t。只要 baseline 不依赖动作 a_t,梯度期望不变,但方差大幅下降。最自然的 baseline 就是学习一个状态价值 V_φ(s_t) 来估计 b。
def reinforce_loss(logp, rewards, gamma=0.99):
# logp: 各步 log π(a_t|s_t); rewards: 各步即时奖励
returns, G = [], 0.0
for r in reversed(rewards):
G = r + gamma * G
returns.insert(0, G)
returns = torch.tensor(returns)
returns = (returns - returns.mean()) / (returns.std() + 1e-8) # 优势归一化
return -(logp * returns).mean()
REINFORCE 是 on-policy 的:每更新一次就要重新采样,样本效率极低;而且对学习率极其敏感,步子稍大就会"策略塌缩"。这些缺陷正是 actor-critic 要解决的。
4. Actor-Critic:偏差与方差的折中
actor-critic 把两派优点揉在一起:演员(actor)是策略 π_θ,负责输出动作;评论家(critic)是价值函数 V_φ,负责给演员的更新提供低方差的 advantage 信号。单步更新写成:
∇_θ J ≈ E[ ∇_θ log π_θ(a_t | s_t) · A_t ]
关键在 A_t 怎么算。最朴素用单步 TD:A_t ≈ r_t + γ V_φ(s_{t+1}) − V_φ(s_t)。为了兼顾偏差与方差,GAE(Generalized Advantage Estimation)用 λ 对多步 TD 做指数加权平均:
δ_t = r_t + γ V_φ(s_{t+1}) − V_φ(s_t)
A_t^{GAE(λ)} = Σ_{l=0}^{∞} (γλ)^l δ_{t+l}
λ=0 退化为单步 TD(低方差、高偏差),λ=1 等价于蒙特卡洛回报(高方差、无偏差)。λ≈0.95 是实践中最常用的甜点。
def compute_gae(rewards, values, dones, gamma=0.99, lam=0.95):
adv, gae = [], 0.0
next_v = 0.0
for t in reversed(range(len(rewards))):
mask = 1.0 - float(dones[t])
delta = rewards[t] + gamma * next_v * mask - values[t]
gae = delta + gamma * lam * mask * gae
adv.insert(0, gae)
next_v = values[t]
return adv
A2C(Advantage Actor-Critic)在多份并行环境里异步收集轨迹,用 GAE 算优势后做一次同步更新;它的分布式亲戚 A3C 去掉了全局参数服务器之外的同步屏障。二者共同构成了 PPO 出现之前最实用的深度 RL 基线。
5. PPO:现代 RL 的工作马(也是 RLHF 的引擎)
策略梯度直接做梯度上升很容易"一步跨太大"把策略推到不可逆的坏区域。TRPO 用信赖域(KL 约束)约束更新幅度,但二阶优化太重。PPO(Proximal Policy Optimization)用一阶的裁剪代理目标近似了 TRPO 的约束效果:
令重要性比率 r_t(θ) = π_θ(a_t | s_t) / π_θold(a_t | s_t),则裁剪目标为:
L^{CLIP}(θ) = E[ min( r_t(θ) Â_t, clip(r_t(θ), 1−ε, 1+ε) Â_t ) ]
当 Â_t > 0(动作好于平均),比率超过 1+ε 的部分被截断,防止过度抬高该动作概率;当 Â_t < 0,比率低于 1−ε 的部分被截断,防止过度压低。ε 通常取 0.1~0.2。完整的 PPO 损失还要加上裁剪后的价值损失和熵奖励(鼓励探索),并密切监控新旧策略的 KL 散度作为早停信号。
def ppo_loss(logp, logp_old, adv, value, ret, clip=0.2, c_v=0.5, c_e=0.01, ent=None):
ratio = torch.exp(logp - logp_old)
surr1 = ratio * adv
surr2 = torch.clamp(ratio, 1 - clip, 1 + clip) * adv
policy_loss = -torch.min(surr1, surr2).mean()
value_loss = c_v * ((value - ret) ** 2).mean()
entropy_bonus = c_e * (ent.mean() if ent is not None else 0.0)
return policy_loss + value_loss - entropy_bonus
为什么 PPO 是 RLHF 的引擎?因为在 RLHF 里,奖励模型给出的标量奖励 + 对参考模型的 KL 惩罚,恰好就是要被最大化的目标;PPO 负责在这个目标上做"安全"的策略更新。本文不展开对齐细节(那属于本站 RLHF/DPO 文章的范畴),只想点明:没有 PPO 这种稳定的 on-policy 优化器,就没有今天可用的 RLHF。理解 PPO 的裁剪机制,是读懂一切现代对齐工作的前置条件。
6. 数值与训练陷阱清单
RL 是被公认为"最易训崩"的深度学习方向之一。下面这份清单来自大量工程踩坑,建议在每次训练前对照检查:
- 奖励缩放与裁剪:奖励量纲差异巨大时,先 clip 到合理区间,否则价值网络会被少数大奖励主导。
- 优势归一化:对每个 batch 的 A_t 做 (A − mean)/std,能显著提升稳定性。
- 回放池容量:太小则相关性高、过拟合近期数据;太大则旧数据 irrelevant。DQN 常用 10^5~10^6。
- 目标网络软更新系数 τ:典型 0.005~0.01,太大则失去稳定器意义,太小则学习过慢。
- 熵系数退火:训练初期给高熵鼓励探索,后期逐步衰减以收敛到确定性策略。
- 梯度裁剪:对所有 RL 损失做 global norm clipping(1.0 左右),防止偶发大梯度炸网。
- 折扣因子 γ:episodic 任务常用 0.99;超长程任务可尝试 0.999 或分段时间折扣。
- Q 过估计:off-policy 方法务必上 Double / 优先回放,或监控 Q 值是否系统性偏高。
- PPO 的 clip 与 KL 监控:若 KL 持续爆掉,说明学习率或 clip 范围不当,应触发早停而非硬扛。
- Critic 学习率:critic 通常比 actor 更快收敛,可适当提高价值网络学习率。
| 崩溃现象 | 典型原因 | 修复方向 |
|---|---|---|
| 回报突然归零后不再回升 | 策略塌缩到单动作、熵耗尽 | 提高熵系数、降低学习率 |
| Q 值持续攀升无上限 | 奖励未裁剪、自举正反馈 | 奖励 clip、上 Double DQN |
| 训练剧烈抖动 | 优势未归一化、batch 太小 | 优势归一化、增大 batch |
| PPO 策略几乎不动 | clip 过紧或 KL 早停误触发 | 放宽 clip、检查 KL 基准 |
| 智能体学会"作弊" | 奖励塑形泄漏了捷径 | 重新审视奖励函数设计 |
7. 从 RL 到 LLM:推理时强化与智能体学习
把 RL 的视角套回当下最热的两条主线,很多"新概念"会瞬间祛魅:
- 推理时计算:LLM 在给出最终答案前做多路 rollout(采样多条思维链),再用验证器/投票选优,其本质就是"在策略 π 下展开轨迹、用价值或奖励信号做引导搜索"——标准的 RL 推理思想,只不过环境被换成了 token 生成。
- 智能体:把工具调用、子任务委派建模成离散动作,把真实 API/文件系统当成环境,把任务完成度当成奖励。Agent 的"探索-利用"困境,正是 RL 百年未解的老问题。
- RLHF / DPO:把"人类偏好"转译成奖励信号,用 PPO(或直接偏好优化)把基座模型推到高奖励区域。它站在本文第 5 节的肩膀上。
一句话总结:没有强化学习,就没有现代的对齐,也没有推理时扩展与自主智能体。它是所有这些上层建筑的承重墙。
8. 总结
我们从 MDP 与 Bellman 方程的第一性原理出发,走过了价值方法(Q-Learning → DQN,靠回放池与目标网络稳住函数逼近)、策略方法(REINFORCE,靠 baseline 压低方差)、演员-评论家(GAE 在偏差与方差间找甜点),最终落到支撑整个 RLHF 工业的 PPO 裁剪目标。三条主线——价值、策略、actor-critic——不是互相取代,而是同一枚硬币的三面:它们都在回答"如何用与环境交互得到的稀疏回报,去更新一个会越变越聪明的策略"。
当你下次读到"模型通过强化学习完成对齐"或"推理时计算提升准确率"时,希望脑中浮现的不是黑话,而是 Q 表的自举、策略梯度的对数概率、以及那道被裁剪的重要性比率。那才是 RL 真正的样子。

发表评论 取消回复