主题
策略梯度:REINFORCE
前面的 Q-learning 和 DQN 都是基于价值的方法:先学价值函数,再从中导出策略。策略梯度方法换了一种思路:直接把策略参数化为
为什么要直接优化策略
基于价值的方法有几个局限:
- 连续动作难处理:
在连续动作空间中本身就是一个优化问题。 - 只能得到确定性策略:在部分可观测或博弈问题中,最优策略可能是随机的(例如石头剪刀布)。
- 价值的微小变化可能导致策略突变:
不连续,训练可能不稳定。
策略梯度方法直接参数化策略:离散动作常用 softmax 输出各动作概率,连续动作常用高斯分布输出均值和方差。策略随参数平滑变化,收敛性质更好(至少收敛到局部最优)。
策略梯度定理
目标函数
对回合制任务,目标是最大化起始状态的期望回报:
其中轨迹
对数导数技巧
难点在于
而
不需要环境模型就能估计梯度——只要能采样轨迹。
定理推导
再利用因果性(
更一般的策略梯度定理(Sutton et al., 2000)表述为:
其中
扩展阅读:策略梯度定理的完整证明(状态价值展开法)
考虑折扣无限时域、初始状态
第 1 步:对
第 2 步:展开
第 3 步:反复代入。 把右边的
其中
第 4 步:写成期望。 利用
关键点: 证明中没有出现状态分布对
基线不改变梯度。 对任意只依赖状态的
REINFORCE 算法
用单条轨迹的
text
初始化策略参数 θ
重复(对每个回合):
用 π_θ 生成一个回合 S0, A0, R1, ..., S_{T-1}, A_{T-1}, R_T
对 t = 0, 1, ..., T-1:
G_t ← Σ_{k=t+1}^{T} γ^{k-t-1} R_k
θ ← θ + α Σ_t G_t ∇_θ log π_θ(A_t | S_t)1
2
3
4
5
6
2
3
4
5
6
用 PyTorch 实现时,构造“伪损失”
引入基线降低方差
REINFORCE 的梯度估计无偏但方差很大:回报
可以从
这不改变期望,因为
最自然的基线是状态价值
代码:CartPole 上的 REINFORCE
策略网络输出两个动作的 logits;价值网络作为基线;对优势做标准化(常用技巧)。
py
"""REINFORCE(带价值基线):CartPole-v1。"""
import gymnasium as gym
import numpy as np
import torch
import torch.nn as nn
from torch.distributions import Categorical
GAMMA, LR_PI, LR_V, EPISODES = 0.99, 1e-3, 1e-3, 1000
torch.manual_seed(0)
env = gym.make("CartPole-v1")
obs_dim, n_actions = env.observation_space.shape[0], env.action_space.n
policy = nn.Sequential(nn.Linear(obs_dim, 64), nn.Tanh(), nn.Linear(64, n_actions)) # 输出 logits
value = nn.Sequential(nn.Linear(obs_dim, 64), nn.Tanh(), nn.Linear(64, 1)) # 基线 b(s) ≈ v(s)
opt_pi = torch.optim.Adam(policy.parameters(), lr=LR_PI)
opt_v = torch.optim.Adam(value.parameters(), lr=LR_V)
returns_log = []
for ep in range(EPISODES):
obs, _ = env.reset(seed=ep)
states, log_probs, rewards = [], [], []
done = False
while not done:
s = torch.as_tensor(obs, dtype=torch.float32)
dist = Categorical(logits=policy(s))
a = dist.sample()
obs, r, term, trunc, _ = env.step(a.item())
states.append(s); log_probs.append(dist.log_prob(a)); rewards.append(r)
done = term or trunc
# 计算每一步的回报 G_t(从后往前)
G, returns = 0.0, []
for r in reversed(rewards):
G = r + GAMMA * G
returns.append(G)
returns = torch.tensor(returns[::-1], dtype=torch.float32)
states = torch.stack(states)
v = value(states).squeeze(1)
adv = returns - v.detach() # 用基线降低方差
adv = (adv - adv.mean()) / (adv.std() + 1e-8) # 标准化(常用技巧)
loss_pi = -(torch.stack(log_probs) * adv).sum() # 梯度上升 => 最小化负目标
loss_v = ((returns - v) ** 2).mean()
opt_pi.zero_grad(); loss_pi.backward(); opt_pi.step()
opt_v.zero_grad(); loss_v.backward(); opt_v.step()
returns_log.append(sum(rewards))
if (ep + 1) % 100 == 0:
print(f"episode {ep + 1:4d} | 最近100回合平均回报 {np.mean(returns_log[-100:]):6.1f}")
# ---------- 训练结束:用确定性(argmax)策略评估 10 个回合 ----------
eval_env = gym.make("CartPole-v1")
scores = []
for i in range(10):
o, _ = eval_env.reset(seed=10_000 + i)
done, total = False, 0.0
while not done:
with torch.no_grad():
act = int(policy(torch.as_tensor(o, dtype=torch.float32)).argmax())
o, r, te, tr, _ = eval_env.step(act)
total += r; done = te or tr
scores.append(total)
print(f"贪心评估(10 回合)平均回报: {np.mean(scores):.1f}")1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
实际运行输出(单线程 CPU,约 1–2 分钟):
text
episode 100 | 最近100回合平均回报 34.2
episode 200 | 最近100回合平均回报 111.6
episode 300 | 最近100回合平均回报 239.0
episode 400 | 最近100回合平均回报 342.0
episode 500 | 最近100回合平均回报 414.9
episode 600 | 最近100回合平均回报 456.5
episode 700 | 最近100回合平均回报 484.0
episode 800 | 最近100回合平均回报 483.4
episode 900 | 最近100回合平均回报 475.2
episode 1000 | 最近100回合平均回报 467.4
贪心评估(10 回合)平均回报: 289.01
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
训练中(按策略概率随机采样动作)最近 100 回合的平均回报在 700 回合左右达到约 484(满分 500)。值得注意的是,训练结束后改用
优缺点分析
| 优点 | 缺点 |
|---|---|
| 直接优化目标,理论清晰 | 方差大,样本效率低 |
| 支持连续动作和随机策略 | 同策略:每批数据用完即弃 |
| 实现简单,不需要模型 | 必须等回合结束(蒙特卡洛) |
| 收敛到局部最优有保证 | 对学习率敏感,步长过大容易崩溃 |
实践技巧
- 优势标准化:每个回合(或批次)内把优势标准化为零均值单位方差,几乎总能让训练更稳定。
- 学习率:策略网络的学习率通常比监督学习小,
。 - 批量多个回合:每次用多个回合的数据估计梯度可以降低方差。
- 熵正则:在损失中加入
防止策略过早变成确定性的,见 Actor-Critic。
常见误区
- 忘记负号:PyTorch 优化器做的是梯度下降,损失要写成
。 - 基线参与了策略梯度的反向传播:基线
在策略损失中必须detach()。 - 用
而不是 reward-to-go :仍然无偏,但方差更大。 - 基线依赖于动作:基线只能依赖状态,否则会引入偏差。
小结
- 策略梯度定理:
,不需要模型。 - REINFORCE 用蒙特卡洛回报估计梯度,无偏但高方差。
- 减去状态相关的基线不改变期望,能显著降低方差;用
作基线得到优势估计。 - 用学到的价值函数做自举(而不只是基线),就得到下一节的 Actor-Critic。
练习题
- 去掉代码中的价值基线(直接用标准化后的
),比较学习曲线。 - 去掉优势标准化,训练是否还能收敛?
- 推导高斯策略
的 。 - 证明使用 reward-to-go 代替
后,梯度估计仍然无偏。 - 改为每次收集 5 个回合再更新一次,学习曲线的平滑程度如何变化?
参考文献
- Williams, R. J. (1992). Simple statistical gradient-following algorithms for connectionist reinforcement learning. Machine Learning, 8, 229–256.
- Sutton, R. S., McAllester, D., Singh, S., & Mansour, Y. (2000). Policy gradient methods for reinforcement learning with function approximation. NeurIPS.
- Sutton, R. S., & Barto, A. G. (2018). Reinforcement Learning: An Introduction (2nd ed.), 第 13 章.
下一步
上一节:DQN 改进 | 继续阅读:Actor-Critic:A2C / A3C