监督学习告诉你「这个输入对应什么答案」,强化学习只告诉你「刚才那步做得有多好」。在没有标准答案、只有延迟奖励的场景里——下棋、机器人控制、大模型对齐——强化学习是唯一的选择。
强化学习的问题设定与 MDP
强化学习的数学语言是马尔可夫决策过程(MDP),由五元组 (S, A, P, R, γ) 定义。
MDP 的五个要素
- 状态空间 S:环境的所有可能状态,如棋盘局面。
- 动作空间 A:智能体可执行的动作,如落子位置。离散或连续。
- 转移概率 P(s’|s,a):在状态
s执行动作a后到达s'的概率。 - 奖励函数 R(s,a):即时反馈,是学习唯一的信号来源。
- 折扣因子 γ ∈ [0,1):未来奖励的衰减系数,
γ=0.99表示「近处奖励更重要」。
马尔可夫性是整个框架的基石:下一状态只依赖当前状态与动作,与更早的历史无关。真实问题往往不满足,需要用帧堆叠或 RNN 把历史编码进状态。
回报与价值函数
回报(Return) 是从当前时刻起的折扣累计奖励:
G_t = r_{t+1} + γ r_{t+2} + γ² r_{t+3} + ... = Σ_{k=0}^∞ γ^k r_{t+k+1}
两个核心价值函数:
- 状态价值 V^π(s):从状态
s出发,按策略π行动能拿到的期望回报。 - 动作价值 Q^π(s,a):在状态
s先执行动作a,之后按π行动能拿到的期望回报。
V 回答「这个局面好不好」,Q 回答「在这个局面做这个动作好不好」。几乎所有算法都在估计这两个函数之一。
贝尔曼方程
价值函数满足自洽的贝尔曼方程:
V^π(s) = Σ_a π(a|s) Σ_{s'} P(s'|s,a) [ R(s,a) + γ V^π(s') ]
Q^π(s,a) = Σ_{s'} P(s'|s,a) [ R(s,a) + γ Σ_{a'} π(a'|s') Q^π(s',a') ]
贝尔曼方程的递归结构是所有强化学习算法的计算基础:它把一个无穷和问题变成了一步加未来的迭代。
价值迭代与策略迭代
知道贝尔曼方程后,最朴素的做法是动态规划:反复迭代直到价值收敛。
价值迭代
价值迭代直接对最优价值函数做迭代:
V_{k+1}(s) = max_a Σ_{s'} P(s'|s,a) [ R(s,a) + γ V_k(s') ]
每一步都取「让价值最大的动作」,迭代到 V 不再变化即收敛。收敛后,最优策略就是每步选 argmax_a 的动作。
import numpy as np
def value_iteration(P, R, gamma=0.99, tol=1e-6):
# P: [S, A, S] 转移概率 R: [S, A] 奖励
S, A = R.shape
V = np.zeros(S)
while True:
Q = R + gamma * P.dot(V) # [S, A]
V_new = Q.max(axis=1)
if np.abs(V_new - V).max() < tol:
return V_new, Q.argmax(axis=1)
V = V_new
策略迭代
策略迭代交替做两步:
- 策略评估:在当前策略下,迭代算出
V^π。 - 策略改进:对每个状态取
argmax_a Q^π(s,a),得到新策略。
两者对比:
| 维度 | 价值迭代 | 策略迭代 |
|---|---|---|
| 每轮计算 | 一次扫描 | 完整评估 + 一次改进 |
| 收敛轮数 | 多 | 少 |
| 实现复杂度 | 低 | 中 |
| 适用 | 状态空间小 | 状态空间小 |
两者都要求已知转移概率 P。真实问题中 P 通常未知,这就引出了无模型(Model-Free)方法。
动态规划也有硬伤:它需要遍历所有状态,状态数一多(如围棋的 10^170 种局面)就完全不可行。更致命的是它假设环境模型已知,而现实中机器人不知道执行动作后世界的精确转移概率。
时序差分与 Q-Learning
时序差分(Temporal Difference, TD) 是无模型强化学习的核心思想:不用等回合结束算完整回报,而是用下一时刻的估计来更新当前估计——自举(Bootstrapping)。
TD 误差与更新
TD 误差是「实际观察到的奖励 + 下一状态估计」与「当前估计」之差:
δ_t = r_{t+1} + γ V(s_{t+1}) - V(s_t)
V(s_t) ← V(s_t) + α · δ_t
α 是学习率。这个更新不需要知道 P,只需要一次实际交互得到的 (s, a, r, s')。
Q-Learning 的更新规则
Q-Learning 是离线策略(Off-Policy)的经典算法,更新规则:
Q(s,a) ← Q(s,a) + α [ r + γ max_{a'} Q(s',a') - Q(s,a) ]
注意 max_{a'}——它假设下一步会选最优动作,与当前实际使用的策略无关。这就是「离线策略」的含义:可以用探索性策略收集数据,却学习最优策略。
import numpy as np
def q_learning(env, episodes=5000, alpha=0.1, gamma=0.99, eps=0.1):
Q = np.zeros((env.observation_space.n, env.action_space.n))
for _ in range(episodes):
s, _ = env.reset()
done = False
while not done:
if np.random.rand() < eps:
a = env.action_space.sample() # ε-贪心探索
else:
a = Q[s].argmax()
s2, r, term, trunc, _ = env.step(a)
done = term or trunc
target = r if done else r + gamma * Q[s2].max()
Q[s, a] += alpha * (target - Q[s, a])
s = s2
return Q
探索与利用的权衡
ε-贪心 是最简单的探索策略:以 ε 概率随机动作,1-ε 概率选当前最优。更精细的方案:
- 衰减 ε:训练初期多探索,后期收敛到利用。
- UCB:给不确定性高的动作加分,
a = argmax[Q(a) + c√(ln t / N(a))]。 - 玻尔兹曼探索:按
softmax(Q/τ)采样,温度τ控制随机程度。
DQN 与经验回放
Q-Learning 用表格存 Q,状态一多就爆表。DQN(Deep Q-Network) 用神经网络近似 Q(s,a),并在 2015 年首次让智能体从像素输入直接学会玩 Atari 游戏。
函数近似与损失
DQN 把 Q(s,a) 参数化为网络 Q_θ,损失是最优贝尔曼方程的平方误差:
L(θ) = E_{(s,a,r,s')~D} [ ( r + γ max_{a'} Q_{θ^-}(s',a') - Q_θ(s,a) )² ]
θ^- 是目标网络的参数,定期从 θ 同步。没有目标网络时,目标值 r + γ max Q 也在随参数变动,训练会发散——这是 DQN 最关键的工程技巧之一。
经验回放
经验回放(Experience Replay) 把交互得到的 (s,a,r,s') 存进一个缓冲区,训练时随机采样 mini-batch。它解决两个问题:
- 样本相关性:连续交互的样本高度相关,直接训练会破坏 SGD 的独立同分布假设。随机采样打破相关性。
- 样本效率:一条经验可以被多次复用,而不是用完即弃。
import random
from collections import deque
import torch
class ReplayBuffer:
def __init__(self, capacity=100000):
self.buf = deque(maxlen=capacity)
def push(self, s, a, r, s2, done):
self.buf.append((s, a, r, s2, done))
def sample(self, batch_size):
batch = random.sample(self.buf, batch_size)
s, a, r, s2, d = zip(*batch)
return (torch.as_tensor(s), torch.as_tensor(a),
torch.as_tensor(r, dtype=torch.float32),
torch.as_tensor(s2), torch.as_tensor(d, dtype=torch.float32))
Double DQN 与 Dueling DQN
原始 DQN 有一个系统性偏差:max_{a'} Q(s',a') 会高估真实价值,因为取最大值的动作恰好是被噪声抬高的那个。改进方案:
| 变体 | 核心改进 | 解决的问题 |
|---|---|---|
| Double DQN | 用在线网络选动作、目标网络估值 | Q 值高估 |
| Dueling DQN | 拆成 V(s) 与优势 A(s,a) | 状态价值与动作优势解耦 |
| Prioritized Replay | 按 TD 误差加权采样 | 重要样本利用率 |
| Noisy Net | 网络参数加噪声做探索 | 替代 ε-贪心 |
Double DQN 的改动只有一行:把 max_{a'} Q_{θ^-}(s',a') 改成 Q_{θ^-}(s', argmax_{a'} Q_θ(s',a'))。
策略梯度与 REINFORCE
价值方法在连续动作空间里很别扭——max_a Q(s,a) 需要对动作做优化。策略梯度直接参数化策略 π_θ(a|s),用梯度上升最大化期望回报。
策略梯度定理
策略梯度的核心公式:
∇_θ J(θ) = E_{τ~π_θ} [ Σ_t ∇_θ log π_θ(a_t|s_t) · G_t ]
直觉解释:回报高于平均的动作,提高它的概率;回报低的,降低它。∇log π 指示了「增大该动作概率」的方向,G_t 是权重。
REINFORCE 算法
最简单的策略梯度算法,用完整回合的回报做权重:
import torch
def reinforce(policy, env, optimizer, gamma=0.99, episodes=1000):
for _ in range(episodes):
log_probs, rewards = [], []
s, _ = env.reset()
done = False
while not done:
logits = policy(torch.as_tensor(s, dtype=torch.float32))
dist = torch.distributions.Categorical(logits=logits)
a = dist.sample()
log_probs.append(dist.log_prob(a))
s, r, term, trunc, _ = env.step(a.item())
rewards.append(r)
done = term or trunc
# 从后往前算折扣回报
G, returns = 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)
loss = -torch.stack([lp * G for lp, G in zip(log_probs, returns)]).sum()
optimizer.zero_grad()
loss.backward()
optimizer.step()
returns 的标准化(减均值除标准差)是关键技巧:它把「绝对回报」变成「相对回报」,显著降低梯度方差。
方差与基线
REINFORCE 的致命弱点是方差极大:同一策略跑两次,回报可能天差地别,梯度噪声淹没了信号。引入基线 b(s) 降低方差:
∇_θ J ≈ E [ Σ_t ∇_θ log π_θ(a_t|s_t) · (G_t - b(s_t)) ]
只要基线不依赖动作 a,它就不改变梯度的期望,只降低方差。最常用的基线就是状态价值函数 V(s)——「这个局面平均能拿多少分」。减去它后,权重变成了「比平均好多少」,这正是优势函数。
Actor-Critic 与优势函数
Actor-Critic 把两者结合:Actor 是策略网络(决定做什么),Critic 是价值网络(评价做得好不好)。
优势函数
优势函数定义为:
A(s,a) = Q(s,a) - V(s)
它衡量「在状态 s 下做动作 a,比平均水平好多少」。用优势替代回报作为策略梯度的权重:
∇_θ J = E [ ∇_θ log π_θ(a|s) · A(s,a) ]
优势为正就提高该动作概率,为负就降低。这比原始回报的方差小得多。
GAE 优势估计
GAE(Generalized Advantage Estimation) 用一组 TD 误差的指数加权和估计优势,是 PPO 的标配:
δ_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):
advantages, gae = [], 0
values = values + [0] # 末尾补 0
for t in reversed(range(len(rewards))):
delta = rewards[t] + gamma * values[t + 1] * (1 - dones[t]) - values[t]
gae = delta + gamma * lam * (1 - dones[t]) * gae
advantages.insert(0, gae)
return advantages
顺带一提 A2C(Advantage Actor-Critic) 是同步版的 A3C:并行跑 N 个环境,每步收集所有环境的经验,算 GAE 后一次性更新。它比单环境训练稳定得多,是理解 Actor-Critic 的最佳起点。
PPO 与信赖域方法
策略梯度的根本风险是更新步太大:策略一旦崩坏,收集到的数据全废,很难恢复。TRPO 用 KL 散度约束更新幅度,但二阶优化实现复杂。PPO 用一个巧妙的裁剪目标,把信赖域约束变成一阶可优化。
裁剪目标函数
PPO 定义概率比 r_t(θ) = π_θ(a_t|s_t) / π_{θ_old}(a_t|s_t),目标函数:
L^CLIP(θ) = E [ min( r_t(θ) A_t , clip(r_t(θ), 1-ε, 1+ε) A_t ) ]
直觉:当优势为正(该动作好),提高其概率,但比值最多涨到 1+ε;当优势为负,降低概率,但比值最多降到 1-ε。超出范围后梯度归零,策略不会走太远。
def ppo_loss(new_log_probs, old_log_probs, advantages, clip_eps=0.2):
ratio = torch.exp(new_log_probs - old_log_probs)
surr1 = ratio * advantages
surr2 = torch.clamp(ratio, 1 - clip_eps, 1 + clip_eps) * advantages
return -torch.min(surr1, surr2).mean()
PPO 完整训练循环
for iteration in range(num_iters):
# 1) 用当前策略收集一批轨迹
rollout = collect_rollout(policy, envs, steps=2048)
advantages = compute_gae(rollout.rewards, rollout.values,
rollout.dones, gamma=0.99, lam=0.95)
returns = advantages + rollout.values
# 2) 在同一批数据上多轮更新
for epoch in range(10):
for batch in rollout.minibatches(64):
logp, entropy, value = policy.evaluate(batch.obs, batch.acts)
pg = ppo_loss(logp, batch.old_logp, batch.advantages)
vf = F.mse_loss(value, batch.returns)
loss = pg + 0.5 * vf - 0.01 * entropy.mean()
optimizer.zero_grad()
loss.backward()
nn.utils.clip_grad_norm_(policy.parameters(), 0.5)
optimizer.step()
PPO 的五个关键超参:clip_eps=0.2、gamma=0.99、lam=0.95、entropy_coef=0.01、value_coef=0.5。这套默认值在多数任务上都能跑,是它成为「默认算法」的原因。
与 TRPO 的对比
| 维度 | TRPO | PPO |
|---|---|---|
| 约束方式 | KL 硬约束 | 裁剪软约束 |
| 优化阶数 | 二阶(共轭梯度) | 一阶(SGD) |
| 实现复杂度 | 高 | 低 |
| 样本效率 | 略高 | 略低 |
| 工业采用度 | 低 | 极高 |
连续动作空间与确定性策略
机器人控制、自动驾驶的动作是连续值,argmax_a Q(s,a) 不再可用。
DDPG 与确定性策略
DDPG(Deep Deterministic Policy Gradient) 用一个确定性策略网络 μ_θ(s) 直接输出动作,Critic 评价 Q(s, μ_θ(s))。策略的梯度通过 Critic 反传:
∇_θ J = E [ ∇_a Q(s,a) |_{a=μ_θ(s)} · ∇_θ μ_θ(s) ]
DDPG 借用了 DQN 的两大技巧:经验回放与目标网络(这里用软更新 θ^- ← τθ + (1-τ)θ^-)。
SAC 与最大熵
SAC(Soft Actor-Critic) 在奖励里加入策略熵:
J(π) = Σ_t E [ r(s_t,a_t) + α H(π(·|s_t)) ]
熵项鼓励策略保持随机性,天然解决探索问题,且对超参更鲁棒。SAC 是当前连续控制任务的默认首选。
class GaussianPolicy(nn.Module):
def __init__(self, obs_dim, act_dim, hidden=256):
super().__init__()
self.net = nn.Sequential(nn.Linear(obs_dim, hidden), nn.ReLU(),
nn.Linear(hidden, hidden), nn.ReLU())
self.mu = nn.Linear(hidden, act_dim)
self.log_std = nn.Linear(hidden, act_dim)
def forward(self, obs):
h = self.net(obs)
std = self.log_std(h).clamp(-20, 2).exp()
return self.mu(h), std
def sample(self, obs):
mu, std = self(obs)
dist = torch.distributions.Normal(mu, std)
x = dist.rsample() # 重参数化采样
action = torch.tanh(x) # 压缩到 [-1, 1]
log_prob = dist.log_prob(x) - torch.log(1 - action.pow(2) + 1e-6)
return action, log_prob.sum(-1, keepdim=True)
离散与连续的选型
| 场景 | 动作空间 | 推荐算法 |
|---|---|---|
| 游戏、推荐 | 离散 | DQN / PPO |
| 机器人关节 | 连续 | SAC / TD3 |
| 大模型 token | 离散且巨大 | PPO / GRPO |
| 混合动作 | 混合 | 分层或参数化 |
奖励设计与稀疏奖励
强化学习最大的工程难点不是算法,而是奖励函数。
奖励塑形
奖励塑形(Reward Shaping) 在原始奖励上叠加辅助信号,引导学习:
r'(s,a,s') = r(s,a,s') + γ Φ(s') - Φ(s)
Φ 是势函数。这个形式的塑形(称为势能塑形)在理论上保证不改变最优策略,是最安全的设计。
稀疏奖励的解法
当奖励只在成功时给出(如「机器人把方块放进盒子」),学习信号极其稀少。常用解法:
- HER(Hindsight Experience Replay):把失败轨迹的目标替换成实际达成的状态,从失败中造出成功样本。
- 好奇心驱动:用预测误差作为内在奖励,鼓励探索新状态。
- 课程学习:从简单任务逐步过渡到困难任务。
- 模仿学习预热:先用专家示范做行为克隆,再强化学习微调。
HER 的实现要点是重标记:取轨迹中后续某个时刻实际达成的状态作为新的目标,重算每步奖励,于是失败的轨迹也能贡献正样本。
RLHF 与 RLVR 中的强化学习应用
大模型对齐让强化学习重新成为焦点:用人类的偏好,而不是标注的答案,来优化语言模型。
RLHF 的三阶段流程
- 监督微调(SFT):用人工示范数据微调基座模型。
- 训练奖励模型(RM):收集人类对模型输出的偏好排序,训练一个打分模型。
- PPO 优化:用奖励模型作为奖励信号,PPO 优化语言模型。
PPO 阶段的四个模型:Actor(待优化)、Critic(估计价值)、Reward Model(打分)、Reference Model(算 KL 惩罚)。奖励里加 KL 惩罚是防止模型偏离基座太远:
R_total = R_rm(x, y) - β · KL( π_θ(y|x) || π_ref(y|x) )
实现上,奖励模型只给序列末尾一个标量分,而 KL 是逐 token 计算的,两者相加后按 token 维度广播到整条序列。
RLVR 与可验证奖励
RLVR(Reinforcement Learning with Verifiable Rewards) 用可程序化验证的奖励替代人类偏好模型——数学题对答案、代码跑单元测试、格式用正则校验。它绕开了奖励模型的偏差,是推理模型训练的核心手段。
# RLVR 的奖励:答案正确 +1,格式合规 +0.1,其余 0
def verify_reward(response, gold):
answer = extract_boxed(response) # 提取 \boxed{...} 中的答案
if answer is None:
return 0.0
r = 1.0 if answer.strip() == gold.strip() else 0.0
if has_valid_format(response):
r += 0.1
return r
GRPO(Group Relative Policy Optimization) 是 RLVR 常用的算法变体:对同一问题采样一组回答,用组内平均分作为基线,省掉了 Critic 网络。
A_i = (r_i - mean(r_1..r_G)) / std(r_1..r_G)
组内相对优势天然消除了问题难度的影响——难题组和易题组的基线各自归一化。
奖励黑客与对策
奖励模型只是人类偏好的代理,优化过度就会钻空子:
- 长度膨胀:模型学会「长回答得分高」,输出冗长废话。对策是长度惩罚或长度归一化。
- 谄媚(Sycophancy):迎合用户观点而非说真话。对策是用对抗数据训练奖励模型。
- 格式刷分:只学会输出格式标记而内容空洞。对策是格式分设上限。
训练稳定性与工程实践
强化学习以「难调」著称,以下是踩坑最多的几条。
常见失稳模式
- 价值估计爆炸:
Q值随训练无界增长。对策:梯度裁剪、目标网络、价值裁剪、降低学习率。 - 熵坍塌:策略过早收敛到确定性,失去探索。对策:熵正则系数别调太小。
- 奖励归一化缺失:不同任务奖励尺度差异大,同一套超参失效。对策:用 running mean/std 归一化奖励。
奖励归一化推荐用增量式更新的均值方差估计(Welford 算法),在线更新不保留历史样本,避免内存随训练增长。
并行环境与吞吐
强化学习的瓶颈常在环境交互而非梯度计算。提升吞吐的手段:
- 向量化环境:同时跑 16~64 个环境实例,把交互并行化。
- 异步收集:收集与训练在不同进程,用队列通信。
- 环境步进加速:用 C++ 实现或直接操作状态数组,跳过渲染。
向量化环境(如 gymnasium.vector.SyncVectorEnv)一次 step 返回 N 个环境的转移,把交互吞吐提升一到两个数量级。
复现性与评估
强化学习结果的方差极大,同一个算法换随机种子可能差 30%。评估纪律:
- 多个随机种子:至少 5 个种子,报告均值与标准差。
- 评估用确定性策略:训练用随机策略,评估用均值动作。
- 固定评估环境:单独一组固定种子的评估环境,避免评估方差。
- 报告训练曲线:单个最终数字毫无意义,要看曲线是否稳定。
总结
强化学习的主线是「在不知道环境模型的前提下,用交互数据估计价值或直接优化策略」:MDP 与贝尔曼方程给出问题框架,价值迭代解决模型已知的小问题,Q-Learning 与 DQN 用 TD 误差加经验回放解决无模型离散控制,策略梯度与 Actor-Critic 解决连续与随机策略,PPO 用裁剪目标把不稳定的策略更新变成可用的工程算法。大模型时代,RLHF 与 RLVR 把奖励从「环境给出」变成了「人类偏好」或「程序验证」,但底层仍是同一套 PPO 骨架。落地的第一性原理是:奖励函数设计比算法选择更重要,训练稳定性比收敛速度更重要——绝大多数强化学习项目失败在奖励与评估,而不是网络结构。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。