强化学习基础
强化学习是 RLHF、PPO、GRPO 等对齐方法的数学根基——不理解 RL,就无法真正理解 alignment
在大模型体系中的位置
基础数学 (Math) → 概率论、线性代数、微积分
↓
神经网络基础 (Neural Networks) → MLP、梯度下降、反向传播
↓
强化学习基础 ← 你在这里 → MDP、Bellman、Q-Learning、Policy Gradient
↓
偏好对齐 (Alignment) → RLHF、PPO、DPO、GRPO本章不追求 RL 的大而全,而是 精准覆盖理解 RLHF/PPO 所需的最小知识集:从 MDP 框架出发,经 Bellman 方程、Value-based 方法(Q-Learning、DQN),到 Policy Gradient(REINFORCE),最终搭建通往 PPO 的桥梁。
1. RL 核心概念
1.1 Agent-Environment 交互
强化学习的核心范式:Agent(智能体)在 Environment(环境)中通过试错学习。
action a_t
Agent ──────────────────► Environment
▲ │
│ state s_{t+1} │
│ reward r_{t+1} │
└───────────────────────────┘每一步的交互流程:
- Agent 观察当前 State
- Agent 根据 Policy
选择 Action - Environment 返回 Reward
和新 State - Agent 更新策略,追求长期累积奖励最大化
1.2 核心术语表
| 术语 | 符号 | 含义 |
|---|---|---|
| State(状态) | 环境的完整描述 | |
| Action(动作) | Agent 可执行的操作 | |
| Reward(奖励) | 即时反馈信号 | |
| Policy(策略) | 状态到动作的映射 | |
| State-Value(状态价值) | 从状态 | |
| Action-Value(动作价值) | 在状态 | |
| Discount Factor(折扣因子) | 控制未来奖励的衰减 |
1.3 MDP 框架
马尔可夫决策过程 (Markov Decision Process) 是 RL 的数学框架,定义为五元组
:状态空间 :动作空间 :状态转移概率 :奖励函数 :折扣因子
马尔可夫性质:下一个状态只取决于当前状态和动作,与历史无关:
直觉理解
MDP 就像下棋——你只需要看棋盘当前局面(State),不需要回顾每一步走法(History),就能决定下一步(Action)。当然现实中很多问题不完全满足马尔可夫性,但 MDP 是一个强大的近似框架。
1.4 累积回报 (Return)
Agent 的目标不是最大化即时奖励,而是最大化 折扣累积回报 (Discounted Return):
:完全短视,只关注即时奖励 :长远规划,重视未来奖励
2. Bellman 方程
2.1 Value Function 的递推关系
Bellman 方程是 RL 的基石——它将 Value Function 表达为 当前奖励 + 折扣后续价值 的递推形式。
State-Value Function
展开为求和形式:
Action-Value Function
2.2 Bellman 最优方程
最优策略
关键区别
Bellman 期望方程(给定策略
2.3 代码验证:手算 Bellman 方程
用一个 3 状态的简单 MDP 验证 Bellman 方程:
import numpy as np
# 3 状态 MDP: s0 -> s1 -> s2(terminal)
# 动作空间: {0: left, 1: right}
gamma = 0.9
# 转移概率 P[s][a] = [(prob, next_state, reward)]
P = {
0: {0: [(1.0, 0, -1)], # s0, left -> stay s0, r=-1
1: [(1.0, 1, 0)]}, # s0, right -> go s1, r=0
1: {0: [(1.0, 0, -1)], # s1, left -> go s0, r=-1
1: [(1.0, 2, 1)]}, # s1, right -> go s2, r=+1
2: {0: [(1.0, 2, 0)], # s2 terminal
1: [(1.0, 2, 0)]},
}
# 均匀随机策略 pi(a|s) = 0.5
policy = {s: {0: 0.5, 1: 0.5} for s in range(3)}
# 迭代求解 Bellman 期望方程
V = np.zeros(3)
for _ in range(100):
V_new = np.zeros(3)
for s in range(3):
for a in [0, 1]:
for prob, s_next, r in P[s][a]:
V_new[s] += policy[s][a] * prob * (r + gamma * V[s_next])
V[s] = V_new[s]
V = V_new
print("V(s) under random policy:", V)
# V ≈ [-2.25, -0.26, 0.0] — 越靠近终点价值越高手推验证
对 s1 应用 Bellman 方程:
3. 动态规划
当环境模型
3.1 Policy Evaluation(策略评估)
给定策略
3.2 Policy Iteration(策略迭代)
交替执行两步,直到策略不再变化:
- Policy Evaluation:求当前
的 - Policy Improvement:贪心更新
3.3 Value Iteration(值迭代)
直接迭代 Bellman 最优方程,不显式维护策略:
收敛后提取最优策略:
3.4 代码:4x4 Grid World 的 Value Iteration
import numpy as np
# 4x4 Grid World: 左上角(0)和右下角(15)是终点
# 动作: 0=上, 1=下, 2=左, 3=右
n_states, n_actions = 16, 4
gamma = 1.0
theta = 1e-6 # 收敛阈值
def step(s, a):
"""确定性环境: 返回 (next_state, reward)"""
if s in [0, 15]:
return s, 0 # 终点
row, col = s // 4, s % 4
if a == 0: row = max(row - 1, 0)
elif a == 1: row = min(row + 1, 3)
elif a == 2: col = max(col - 1, 0)
elif a == 3: col = min(col + 1, 3)
return row * 4 + col, -1
# Value Iteration
V = np.zeros(n_states)
while True:
delta = 0
for s in range(n_states):
if s in [0, 15]:
continue
v = V[s]
values = []
for a in range(n_actions):
s_next, r = step(s, a)
values.append(r + gamma * V[s_next])
V[s] = max(values)
delta = max(delta, abs(v - V[s]))
if delta < theta:
break
# 提取最优策略
policy = np.zeros(n_states, dtype=int)
for s in range(n_states):
q_values = [step(s, a)[1] + gamma * V[step(s, a)[0]] for a in range(n_actions)]
policy[s] = np.argmax(q_values)
print("Optimal V:\n", V.reshape(4, 4).round(1))
# 最优策略让每个状态以最短路径到达终点DP 的局限
动态规划需要完整的环境模型
4. Monte Carlo 方法
Monte Carlo (MC) 方法通过 完整 episode 的采样 估计 Value Function,不需要环境模型。
4.1 First-Visit MC 估计
核心思想:运行很多 episode,用每个状态 首次出现后的实际 Return 的均值来估计
import numpy as np
from collections import defaultdict
def first_visit_mc_v(env_step, policy, n_episodes=5000, gamma=0.9):
"""First-Visit MC 估计 V(s)"""
V = defaultdict(float)
returns_count = defaultdict(int)
for _ in range(n_episodes):
# 生成一个完整 episode
episode = []
s = 0 # 起始状态
while True:
a = np.random.choice(list(policy[s].keys()),
p=list(policy[s].values()))
s_next, r, done = env_step(s, a)
episode.append((s, a, r))
s = s_next
if done:
break
# 反向计算 Return
G = 0
visited = set()
for s, a, r in reversed(episode):
G = r + gamma * G
if s not in visited: # First-Visit
visited.add(s)
returns_count[s] += 1
# 增量均值更新
V[s] += (G - V[s]) / returns_count[s]
return dict(V)4.2 MC 估计 + epsilon-Greedy 改进
MC 也可以估计 Q 值,结合
def mc_control_epsilon_greedy(env_step, n_states, n_actions,
n_episodes=10000, gamma=0.9, epsilon=0.1):
"""MC Control with epsilon-greedy"""
Q = defaultdict(lambda: np.zeros(n_actions))
N = defaultdict(lambda: np.zeros(n_actions))
def epsilon_greedy(s):
if np.random.random() < epsilon:
return np.random.randint(n_actions)
return np.argmax(Q[s])
for _ in range(n_episodes):
episode = []
s = 0
while True:
a = epsilon_greedy(s)
s_next, r, done = env_step(s, a)
episode.append((s, a, r))
s = s_next
if done:
break
G = 0
visited_sa = set()
for s, a, r in reversed(episode):
G = r + gamma * G
if (s, a) not in visited_sa:
visited_sa.add((s, a))
N[s][a] += 1
Q[s][a] += (G - Q[s][a]) / N[s][a]
# 最终贪心策略
policy = {s: np.argmax(Q[s]) for s in Q}
return Q, policyMC 的缺点
MC 必须等一个 episode 完全结束才能更新,这对于长 episode(或持续性任务)很低效。TD 方法通过 bootstrapping(用估计值更新估计值)解决了这个问题。
5. 时序差分 (Temporal Difference)
TD 方法结合了 MC 的采样思想和 DP 的 bootstrapping 思想,每一步都可以更新。
5.1 TD(0)
TD(0) 用 一步 TD target
其中
5.2 SARSA(On-Policy TD Control)
SARSA 用
import numpy as np
def sarsa(env_step, n_states, n_actions,
n_episodes=2000, alpha=0.1, gamma=0.99, epsilon=0.1):
"""SARSA: On-Policy TD Control"""
Q = np.zeros((n_states, n_actions))
def epsilon_greedy(s):
if np.random.random() < epsilon:
return np.random.randint(n_actions)
return np.argmax(Q[s])
for ep in range(n_episodes):
s = 0 # 起始状态
a = epsilon_greedy(s)
while True:
s_next, r, done = env_step(s, a)
a_next = epsilon_greedy(s_next)
# SARSA 更新: 用 Q(s', a') — a' 由当前策略选择
Q[s, a] += alpha * (r + gamma * Q[s_next, a_next] - Q[s, a])
s, a = s_next, a_next
if done:
break
return Q5.3 Q-Learning(Off-Policy TD Control)
Q-Learning 和 SARSA 的唯一区别:更新时使用
import numpy as np
def q_learning(env_step, n_states, n_actions,
n_episodes=2000, alpha=0.1, gamma=0.99, epsilon=0.1):
"""Q-Learning: Off-Policy TD Control"""
Q = np.zeros((n_states, n_actions))
for ep in range(n_episodes):
s = 0
while True:
# 行为策略: epsilon-greedy
if np.random.random() < epsilon:
a = np.random.randint(n_actions)
else:
a = np.argmax(Q[s])
s_next, r, done = env_step(s, a)
# Q-Learning 更新: 用 max Q(s', a') — 与行为策略无关
Q[s, a] += alpha * (r + gamma * np.max(Q[s_next]) - Q[s, a])
s = s_next
if done:
break
return Q5.4 SARSA vs Q-Learning:On-Policy vs Off-Policy
| 维度 | SARSA | Q-Learning |
|---|---|---|
| 类型 | On-Policy | Off-Policy |
| 更新目标 | ||
| 行为策略 = 目标策略? | 是 | 否 |
| 安全性 | 更保守(会避开危险动作) | 更激进(总假设未来走最优) |
| 收敛速度 | 较慢 | 较快 |
On-Policy vs Off-Policy — RLHF 中的对应
这个区别在 RLHF 中至关重要:PPO 是 On-Policy(用当前策略生成数据并更新);DPO 则是 Off-Policy(直接从离线偏好数据学习)。理解这个区别有助于理解为什么 PPO 训练更不稳定但上限更高,而 DPO 更稳定但可能偏离最优。
6. DQN:从 Q-Learning 到深度强化学习
6.1 为什么需要 DQN?
Tabular Q-Learning 将 Q 值存在表格中,状态空间大时完全不可行(如 Atari 游戏有
6.2 两个关键技巧
1. Experience Replay(经验回放)
将
- 打破样本的时间相关性
- 提高数据利用率(一条经验可被多次使用)
2. Target Network(目标网络)
使用一个参数滞后的 Target Network
每隔
6.3 DQN 完整实现
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from collections import deque
import random
class QNetwork(nn.Module):
def __init__(self, state_dim, action_dim, hidden=64):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim, hidden),
nn.ReLU(),
nn.Linear(hidden, hidden),
nn.ReLU(),
nn.Linear(hidden, action_dim),
)
def forward(self, x):
return self.net(x)
class DQNAgent:
def __init__(self, state_dim, action_dim, lr=1e-3, gamma=0.99,
buffer_size=10000, batch_size=64, target_update=100):
self.action_dim = action_dim
self.gamma = gamma
self.batch_size = batch_size
self.target_update = target_update
self.step_count = 0
# 主网络 & 目标网络
self.q_net = QNetwork(state_dim, action_dim)
self.target_net = QNetwork(state_dim, action_dim)
self.target_net.load_state_dict(self.q_net.state_dict())
self.optimizer = optim.Adam(self.q_net.parameters(), lr=lr)
self.buffer = deque(maxlen=buffer_size) # Replay Buffer
def select_action(self, state, epsilon=0.1):
if random.random() < epsilon:
return random.randint(0, self.action_dim - 1)
with torch.no_grad():
q = self.q_net(torch.FloatTensor(state))
return q.argmax().item()
def store(self, s, a, r, s_next, done):
self.buffer.append((s, a, r, s_next, done))
def train_step(self):
if len(self.buffer) < self.batch_size:
return
batch = random.sample(self.buffer, self.batch_size)
s, a, r, s_next, done = zip(*batch)
s = torch.FloatTensor(np.array(s))
a = torch.LongTensor(a).unsqueeze(1)
r = torch.FloatTensor(r)
s_next = torch.FloatTensor(np.array(s_next))
done = torch.FloatTensor(done)
# 当前 Q 值
q_values = self.q_net(s).gather(1, a).squeeze()
# Target Q 值(用目标网络)
with torch.no_grad():
q_target = r + self.gamma * (1 - done) * self.target_net(s_next).max(1)[0]
loss = nn.MSELoss()(q_values, q_target)
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
# 定期更新目标网络
self.step_count += 1
if self.step_count % self.target_update == 0:
self.target_net.load_state_dict(self.q_net.state_dict())DQN 的历史意义
2015 年 DeepMind 发表的 DQN 论文 (Human-level control through deep reinforcement learning) 首次证明深度 RL 可以在复杂环境(Atari 游戏)中达到人类水平。这开启了 Deep RL 的黄金时代,最终催生了 PPO、RLHF 等技术。
7. Policy Gradient
7.1 从 Value-Based 到 Policy-Based
前面的方法都是 Value-Based:先估计 Q 值,再从 Q 值推导策略。Policy Gradient 方法直接参数化策略
为什么需要 Policy Gradient?
- Value-Based 方法只能处理离散动作空间
- 策略可以是随机的(stochastic),天然支持探索
- LLM 生成就是一个策略:
是一个在词表上的概率分布
7.2 策略梯度定理
目标:最大化期望累积回报
策略梯度定理 (Policy Gradient Theorem):
7.3 对数导数技巧 (Log-Derivative Trick) 推导
这是整个推导的核心,也是理解 PPO 的关键。逐步推导:
第一步:写出目标函数
第二步:对
第三步:对数导数技巧
利用恒等式
第四步:展开轨迹概率
取对数后,环境转移概率项
为什么这个推导重要?
对数导数技巧让我们 不需要知道环境模型(
7.4 REINFORCE 算法
REINFORCE 是最简单的 Policy Gradient 算法,直接用采样 Return
加入 Baseline 降低方差:用
import torch
import torch.nn as nn
import torch.optim as optim
from torch.distributions import Categorical
class PolicyNet(nn.Module):
def __init__(self, state_dim, action_dim, hidden=64):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim, hidden),
nn.ReLU(),
nn.Linear(hidden, action_dim),
)
def forward(self, x):
logits = self.net(x)
return Categorical(logits=logits)
def reinforce(env, state_dim, action_dim,
n_episodes=1000, gamma=0.99, lr=1e-3):
"""REINFORCE with baseline"""
policy = PolicyNet(state_dim, action_dim)
baseline = nn.Sequential( # Value baseline V(s)
nn.Linear(state_dim, 64), nn.ReLU(), nn.Linear(64, 1)
)
opt_policy = optim.Adam(policy.parameters(), lr=lr)
opt_baseline = optim.Adam(baseline.parameters(), lr=lr)
for ep in range(n_episodes):
states, actions, rewards = [], [], []
s = env.reset()
done = False
while not done:
s_tensor = torch.FloatTensor(s)
dist = policy(s_tensor)
a = dist.sample()
s_next, r, done, _ = env.step(a.item())
states.append(s_tensor)
actions.append(a)
rewards.append(r)
s = s_next
# 计算每步的折扣 Return
returns = []
G = 0
for r in reversed(rewards):
G = r + gamma * G
returns.insert(0, G)
returns = torch.FloatTensor(returns)
states_t = torch.stack(states)
actions_t = torch.stack(actions)
# 更新 baseline (Value Function)
v_pred = baseline(states_t).squeeze()
baseline_loss = nn.MSELoss()(v_pred, returns)
opt_baseline.zero_grad()
baseline_loss.backward()
opt_baseline.step()
# 更新 Policy (REINFORCE + baseline)
advantage = returns - v_pred.detach() # A(s,a) = G - V(s)
dist = policy(states_t)
log_probs = dist.log_prob(actions_t)
policy_loss = -(log_probs * advantage).mean()
opt_policy.zero_grad()
policy_loss.backward()
opt_policy.step()
return policy7.5 从 REINFORCE 到 PPO 的演化
REINFORCE 虽然正确,但在实际训练中有严重问题:
| 问题 | 解决方案 | 结果 |
|---|---|---|
| 高方差 | 加 Baseline | Actor-Critic |
| 采样效率低(On-Policy) | 重要性采样 (Importance Sampling) | Off-Policy PG |
| 更新步长难控制 | 信赖域约束 (Trust Region) | TRPO |
| TRPO 实现复杂 | 用 Clip 近似信赖域 | PPO |
PPO 的核心目标函数(Clipped Surrogate Objective):
其中
PPO 为什么适合 RLHF?
PPO 的三个特性使其成为 RLHF 的首选:
- On-Policy + 多次复用:采集一批数据后可以做多个 epoch 的更新(clip 保证安全)
- 稳定的策略更新:clip 机制避免 LLM 策略突变导致生成质量崩溃
- 可以方便地加 KL 惩罚:
,防止 reward hacking
8. 从 RL 到 RLHF 的桥梁
8.1 LLM 生成 = RL 序贯决策
将 LLM 的文本生成建模为 MDP:
| RL 概念 | LLM 对应 |
|---|---|
| State | prompt + 已生成的 token 序列 |
| Action | 下一个 token |
| Policy | LLM 的 next-token 分布 |
| Reward | Reward Model 对完整生成的打分 |
| Episode | 一次完整的文本生成(从第一个 token 到 EOS) |
8.2 RLHF 的 RL 视角
┌─────────────┐ prompt + 已生成 tokens ┌──────────────────┐
│ LLM (Agent) │ ◄──────────────────────────── │ "Environment" │
│ π_θ(y|x) │ ────────── token y_t ──────► │ Reward Model │
└─────────────┘ │ r = RM(x, y) │
│ + KL penalty │
└──────────────────┘RLHF 的 PPO 目标函数:
其中:
:Reward Model 打分,反映人类偏好 :SFT 模型,作为 KL 正则化的锚点 :KL 惩罚系数,防止 reward hacking(模型找到高分但无意义的回答)
8.3 为什么需要 KL 惩罚?
没有 KL 约束,LLM 会 "hack" Reward Model:
优化过度的例子:
Prompt: "写一首关于春天的诗"
无 KL 约束: "好好好好好好好好好好..." ← RM 给高分但无意义
有 KL 约束: "春风拂过柳梢头,燕子归来筑新巢" ← 既得高分又保持流畅KL 惩罚确保优化后的策略
桥梁已搭建
现在你已经理解了 RL 的核心概念(MDP、Bellman、Q-Learning、Policy Gradient、PPO),也理解了 LLM 生成如何映射为 RL 问题。下一章 偏好对齐 将详细展开 RLHF pipeline、Reward Model 训练、PPO 实现,以及 DPO、GRPO 等替代方案。
9. RL 方法总览
RL 方法分类
│
┌───────────────┼───────────────┐
Model-Based Model-Free Hybrid
(需要环境模型) (不需要)
│ │
DP (§3) ┌───────┼────────┐
Value-Based Policy-Based
│ │
┌──────┤ REINFORCE (§7)
│ │ │
MC (§4) TD (§5) Actor-Critic
│ │
┌──────┤ TRPO
│ │ │
SARSA Q-Learning PPO ← RLHF 的核心
│
DQN (§6)苏格拉底时刻
在继续学习 RLHF 之前,检验你的理解:
- Bellman 方程的递推结构是什么? 为什么说它是 RL 的基石?
- SARSA 和 Q-Learning 的唯一区别是什么? 在悬崖行走(Cliff Walking)问题中,谁学到的路径更安全?为什么?
- DQN 为什么需要 Experience Replay 和 Target Network? 去掉任何一个会怎样?
- 对数导数技巧的关键一步是什么? 它为什么让我们不需要环境模型就能算梯度?
- 把 LLM 生成建模为 MDP,State、Action、Reward 分别是什么? 这个建模有什么不完美之处?(提示:奖励信号的稀疏性)
- PPO 的 Clip 机制在解决什么问题? 如果
设得太大或太小会怎样?
面试考点
Q1: 解释 On-Policy 和 Off-Policy 的区别
On-Policy(如 SARSA、PPO):用当前策略采集数据,用同一策略更新。数据用完即丢,采样效率低但更稳定。
Off-Policy(如 Q-Learning、DQN):数据可以由任意策略采集(行为策略),但更新的是目标策略。可以复用历史数据,采样效率高但需要额外技巧(如 Importance Sampling、Replay Buffer)保证正确性。
RLHF 中的体现:PPO 是 On-Policy(每轮用当前 LLM 生成新数据);DPO 是 Off-Policy(用预先收集的偏好数据训练)。
Q2: 为什么 REINFORCE 方差大?怎么解决?
REINFORCE 用完整 episode 的 Return
- Baseline:
,减去 Value Function 估计值,不改变期望但降低方差 - Actor-Critic:用 TD error
替代 ,进一步降低方差(引入少量偏差) - GAE (Generalized Advantage Estimation):在偏差和方差之间做权衡,PPO 默认使用
Q3: DQN 的 Target Network 为什么能稳定训练?
如果用同一个网络
Target Network
Q4: PPO 的 Clip 机制如何工作?
PPO 限制策略更新幅度,定义 ratio
- 若
(好的动作):ratio 被裁剪到上界 ,防止策略变化过大 - 若
(差的动作):ratio 被裁剪到下界 ,同样防止过度惩罚
效果:在一定的 "信赖域" 内优化策略,超出范围的梯度被截断。实践中
Q5: 解释 RLHF 中的 Reward Hacking 问题
Reward Model 只是人类偏好的近似。如果不加约束地最大化 RM 分数,LLM 会找到 RM 的漏洞——生成一些 RM 给高分但人类不喜欢的内容。这就是 Goodhart 定律 在 AI 中的体现:"当一个指标成为目标时,它就不再是一个好指标"。
解决方案:加 KL 惩罚
推荐资源
| 资源 | 链接 | 说明 |
|---|---|---|
| Sutton & Barto 教材 | incompleteideas.net/book | RL 圣经,免费在线版 |
| Spinning Up in Deep RL | OpenAI | OpenAI 的 Deep RL 入门教程,有 PyTorch 实现 |
| David Silver RL 课程 | UCL Course | 经典 RL 课程视频 + slides |
| DQN 原论文 | Nature 2015 | Human-level control through deep RL |
| PPO 原论文 | arXiv 2017 | Proximal Policy Optimization Algorithms |
| RLHF 原论文 | arXiv 2022 | Training language models to follow instructions with human feedback (InstructGPT) |
| TRL 库 | GitHub | HuggingFace 的 RLHF/DPO/PPO 训练库 |
| 李宏毅 RL 课程 | YouTube | 中文 RL 课程(国立台湾大学) |