监督学习有"标准答案",强化学习只有"事后反馈"——你做一连串决策,环境只告诉你结果好不好,不告诉你每一步该怎么做。这正是训练 Agent、对齐 LLM(RLHF)、下棋、机器人控制的底层范式。今天讲四个核心:Q-Learning(学每个动作值多少)→ Policy Gradient(直接学怎么做)→ Actor-Critic(两者合体)→ PPO(让训练稳下来,也是 ChatGPT 对齐的引擎)。它们是层层递进的,不是并列的四个工具。
Q-table 就是一张带 TTL 的记忆缓存(memoization cache):key 是「(状态, 动作)」二元组,value 是「这么做最终能拿多少分」。和你给慢查询加缓存一样——第一次靠探索算出来,之后查表即可。区别是这张表会自我修正:每访问一次就用更靠谱的估计去覆盖旧值,最终收敛。
痛点:在迷宫里走一步并不会立刻告诉你这步好不好——奖励是延迟的(走到终点才加分)。怎么把终点的奖励"传播"回起点的每一步?Q-Learning 的答案是 Bellman 更新(自举 / bootstrapping):
逐符号拆开:Q(s,a) 是当前对"状态 s 做动作 a"的价值估计;r 是这步立即拿到的奖励;max Q(s',a') 是"到了新状态 s' 后,最好的动作能拿多少"——用下一步的估计来改进这一步,这就是自举。中括号整体叫 TD error(时序差分误差):现实(r+γ·未来) 和 旧估计 的差距;α(学习率)控制每次修正多少。直觉就是「拿一个稍微更准的估计去微调当前估计」,像缓存的最终一致性,访问越多越准。
为什么是 off-policy(离策略)?更新里用的是 max(贪心最优动作),但实际探索时用 ε-greedy(大概率走当前最优、小概率随机试)——学的策略和走的策略可以不同。这像生产流量做 A/B:99% 走稳定路由,1% 探索新路由,但你学到的是"最优路由是哪条"。2013 年 DQN(Mnih et al.)把这张表换成神经网络去近似 Q,直接从 Atari 像素学会打游戏——这是深度强化学习的开端。
import gymnasium as gym, numpy as np env = gym.make("FrozenLake-v1", is_slippery=False) Q = np.zeros((env.observation_space.n, env.action_space.n)) # Q 表 α, γ, ε = 0.8, 0.95, 0.1 for ep in range(2000): s, _ = env.reset(); done = False while not done: # ε-greedy:小概率随机探索,否则查表走最优 a = env.action_space.sample() if np.random.rand()<ε else Q[s].argmax() s2, r, term, trunc, _ = env.step(a); done = term or trunc # Bellman 更新:用 r + γ·下一步最优 去修正当前估计 Q[s, a] += α * (r + γ * Q[s2].max() - Q[s, a]) s = s2 print(Q.argmax(axis=1)) # 每个状态学到的最优动作
Q-Learning 是先建价值表、再从表里挑动作(间接);Policy Gradient 直接调一个"策略函数"的参数——像直接调负载均衡器的路由权重:观测到某条路由延迟低(奖励高),就调高它的权重,下次更可能走它。不绕道价值表,直接优化"决策本身"。
痛点:动作连续(方向盘转 17.3°)或状态空间巨大时,Q-table 失效。解法:用神经网络 πθ(a|s) 直接输出"在状态 s 下各动作的概率",θ 是网络参数。目标是最大化期望回报 J(θ)。关键的策略梯度定理(Sutton et al. 2000)给出怎么求导:
直觉拆解:∇log π(a|s) 是"让这个动作更可能发生"的方向;G 是这条轨迹的实际回报(总分)。两者相乘 = 「按回报大小,加权地把好动作的概率往上推、坏动作往下压」。回报高的动作,梯度推得猛;回报为负的,反向压低。这就是经典的 REINFORCE 算法——本质是"奖励加权的最大似然"。
致命弱点:方差极高。G 是一整条轨迹蒙特卡洛采样出来的,运气成分大——同样的好策略,这局走运拿 100 分、下局背运拿 10 分,梯度信号忽大忽小,训练像在噪声里爬山,又慢又不稳。这个"高方差"问题,正是下一张卡 Actor-Critic 要解决的。
import torch, torch.nn as nn policy = nn.Sequential(nn.Linear(4,128), nn.ReLU(), nn.Linear(128,2)) opt = torch.optim.Adam(policy.parameters(), lr=1e-2) # 跑完一整局,拿到 (log_probs, 每步回报 returns) 后: def update(log_probs, returns): returns = torch.tensor(returns) # 标准化回报 = 一个简单的方差削减技巧(baseline 雏形) returns = (returns - returns.mean()) / (returns.std() + 1e-8) # 损失 = -Σ log π(a|s)·G ;最大化回报 = 最小化它的负数 loss = -(torch.stack(log_probs) * returns).sum() opt.zero_grad(); loss.backward(); opt.step() # 把好动作概率推高
纯策略梯度像"等线上指标出来才知道改对没"——慢且噪声大。Actor-Critic 加了个内部 code reviewer:Actor(演员)= 策略网络,负责写代码(出动作);Critic(评论家)= 价值网络,每一步立刻给出"相对基线的好坏"评分。不用等终局,即时反馈,迭代快得多。
它直接治 Policy Gradient 的高方差病。核心招数:把噪声大的原始回报 G 换成 优势函数(Advantage):
逐项看:V(s)(Critic 学的状态价值)= "在状态 s 平均能拿多少分",是个基线(baseline);A = "这个动作比平均好多少"。把策略梯度里的 G 换成 A:
为什么方差骤降?原始 G 可能是"+100 分"(看着都很好,分不出哪个动作真的强);减去基线后变成"+5 / −3"这种居中、有正有负的信号——好坏对比鲜明,梯度方向清晰。数学上减去只依赖状态、不依赖动作的基线不改变梯度期望(无偏),却大幅降低方差。这是 RL 里最优雅的免费午餐之一。Critic 自己怎么学 V?用和 Q-Learning 同款的 TD error 自举更新。A2C / A3C(Mnih et al. 2016)是其经典实现。
# Actor 与 Critic 通常共享底层、各出一个头 class ActorCritic(nn.Module): def __init__(self): super().__init__() self.body = nn.Sequential(nn.Linear(4,128), nn.ReLU()) self.actor = nn.Linear(128, 2) # 输出动作 logits self.critic = nn.Linear(128, 1) # 输出 V(s) 一个标量 def forward(self, s): h = self.body(s) return self.actor(h), self.critic(h) # 关键一步:用 Critic 的 V 算优势,替代高方差的原始回报 G logits, v = model(s); _, v2 = model(s2) advantage = r + γ * v2.detach() - v # detach:不让目标反传到 Critic actor_loss = -(dist.log_prob(a) * advantage.detach()) critic_loss = advantage.pow(2) # 让 V 逼近 r+γV(s')
PPO 就是给策略更新加了灰度发布 / 限流(canary + rate limit):一次只敢改一点点,新策略不准偏离旧策略太远。因为 RL 里一步迈太大就可能策略崩溃(像一次全量上线把线上打挂,还回滚不了——后续数据都是坏策略采的)。PPO = 把"每次最多改 ±20%"硬编码进损失函数。
痛点:Actor-Critic 仍可能一步更新过猛导致崩溃。早先的 TRPO 用复杂的二阶约束解决,但实现繁琐。PPO(Schulman et al. 2017)用一个朴素到惊人的裁剪(clip)目标就搞定。先定义概率比 rt(θ) = πθ(a|s) / πθold(a|s)——新旧策略对同一动作的概率之比(=1 表示没变)。核心目标:
拆开这个 min 和 clip(ε 通常 0.2):clip 把概率比强行夹在 [0.8, 1.2] 区间——想把某动作概率提到旧的 1.5 倍?对不起,收益按 1.2 倍封顶,再激进也没额外好处,于是没动力迈大步。外层 min 取裁剪前后较小值,是个保守的"悲观下界":当优势 A>0(好动作)时限制涨幅,A<0(坏动作)时限制跌幅,双向都不许跨太远。一句话:「在旧策略的信任域(trust region)内小步快走」。
这个简单改动让 PPO 兼具稳定性和易实现性,成了事实标准。最重要的应用:RLHF——用人类偏好训练出奖励模型,再用 PPO 优化 LLM 策略,同时加一个 KL 惩罚防止模型为讨好奖励而偏离原始语言能力太远(机制和 clip 异曲同工:都是"别跑太远")。ChatGPT、Claude 的对齐阶段都建立在这套之上。
# 生产中直接用 stable-baselines3,几行起一个 PPO from stable_baselines3 import PPO model = PPO("MlpPolicy", "CartPole-v1", verbose=1) model.learn(total_timesteps=50_000) # —— clip 目标的核心就这几行(看懂机制用)—— ratio = torch.exp(new_logp - old_logp) # 概率比 = exp(对数差) unclipped = ratio * advantage clipped = torch.clamp(ratio, 1-ε, 1+ε) * advantage # 夹在[0.8,1.2] policy_loss = -torch.min(unclipped, clipped).mean() # 取悲观下界