策略梯度与REINFORCE
一句话概述
策略梯度(Policy Gradient)方法直接优化策略参数,通过梯度上升最大化期望累积奖励。REINFORCE是最经典的策略梯度算法——它采样完整轨迹,用蒙特卡洛回报G_t作为权重来更新策略:增加"好"动作的概率,减少"坏"动作的概率。与基于价值的方法不同,策略梯度能自然地处理连续动作空间和随机策略。
💡 核心要点:①策略梯度定理:∇_θ J(θ)=E[∇_θ log π_θ(a|s)·Q^π(s,a)],不依赖环境模型;②REINFORCE用蒙特卡洛回报G_t替代Q^π,是最简单的策略梯度实现;③策略梯度能处理连续动作(输出高斯分布参数),维持随机策略;④策略梯度的主要挑战是方差大,需要baseline、Actor-Critic等技巧来降低方差。
教学与演示
一、策略梯度定理——为什么可以求导
是什么(定义):策略梯度定理(Policy Gradient Theorem)是策略梯度方法的数学基础。它证明了策略优化目标J(θ)=E[G_t]关于参数θ的梯度可以表示为:∇_θ J(θ) = E_π[∇_θ log π_θ(a|s) · Q^π(s,a)]。这个公式不包含环境转移概率,因此是无模型的——不需要知道环境模型就能计算梯度。
大白话 策略梯度定理就像"告诉你如何调整策略"——如果一个动作带来了好的结果(Q值高),就增加这个动作的概率;如果带来了坏的结果,就减少概率。梯度的方向就是"让好动作更可能,坏动作更不可能"的方向。
为什么(原理):策略梯度定理的推导依赖于"对数导数技巧"(log-derivative trick):∇_θ π_θ = π_θ · ∇_θ log π_θ。这个技巧将"对概率求导"转化为"对对数概率求导乘以概率",从而可以将期望的梯度转化为梯度的期望——这正是我们需要的,因为我们可以采样来估计期望。
怎么做(实现):
import numpy as np
# ==================== 策略梯度定理演示 ====================
# 简单的两动作环境,验证策略梯度定理
def softmax_policy(theta, state):
"""softmax策略:π(a|s) = exp(θ_sa) / Σ exp(θ_sa')"""
exp_theta = np.exp(theta[state] - np.max(theta[state]))
return exp_theta / exp_theta.sum()
def log_policy_gradient(theta, state, action):
"""计算 ∇_θ log π_θ(a|s)"""
probs = softmax_policy(theta, state)
grad = -probs.copy() # 对未选中的动作,梯度 = -prob
grad[action] = 1 - probs[action] # 对选中的动作,梯度 = 1 - prob
return grad
# 演示
theta = np.array([[0.5, 0.3, 0.2, 0.1], # 状态0的偏好
[0.1, 0.5, 0.3, 0.2]]) # 状态1的偏好
state = 0
probs = softmax_policy(theta, state)
print("=== 策略梯度定理演示 ===")
print(f"状态{state}的策略分布: {[f'{p:.3f}' for p in probs]}")
# 计算梯度
for action in range(4):
grad = log_policy_gradient(theta, state, action)
print(f"\n如果选择动作{action} (概率={probs[action]:.3f}):")
print(f" ∇log π: {[f'{g:.3f}' for g in grad]}")
print(f" 解释: 动作{action}的梯度={grad[action]:.3f}(正→增加概率)")
print(f" 其他动作的梯度均为负→减少概率")
print(f"\n策略梯度定理: ∇J = E[∇log π(a|s) · Q(s,a)]")
print(f" 如果Q(s,a)高 → 增加该动作概率")
print(f" 如果Q(s,a)低 → 减少该动作概率")什么用(应用):策略梯度定理是REINFORCE、PPO、TRPO、A3C等所有基于策略方法的理论基础。它使得我们可以通过采样来估计梯度,而不需要环境模型。
哪些坑(缺点):梯度估计的方差很大(因为Q^π(s,a)的估计来自采样轨迹);需要对所有状态-动作对充分采样;在函数近似下,策略梯度可能收敛到局部最优。
二、REINFORCE——蒙特卡洛策略梯度
是什么(定义):REINFORCE是最简单的策略梯度算法,由Williams在1992年提出。它使用蒙特卡洛回报G_t(从当前状态到episode结束的累积折扣奖励)替代Q^π(s,a)作为策略梯度中的权重。更新规则:θ ← θ + α · G_t · ∇_θ log π_θ(a_t|s_t)。REINFORCE是On-Policy的,每个episode后更新一次。
大白话 REINFORCE就像"事后总结"——玩完一整局游戏后,回头看每一步:如果最终赢了(G_t高),那这局里做的每个动作都应该多做一些;如果输了(G_t低),那这局里的动作都应该少做一些。Simple but effective!
为什么(原理):蒙特卡洛回报G_t是Q^π(s,a)的无偏估计(但方差大)。REINFORCE使用G_t的好处是简单——不需要维护价值函数,不需要bootstrap,一条轨迹走完就能更新。坏处是方差大——G_t包含整个轨迹的随机性,而且episode结束后才能更新(不能在线学习)。
怎么做(实现):
import numpy as np
n_states = 9; n_actions = 4
actions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
def step(state, action):
row, col = state // 3, state % 3
dr, dc = actions[action]
nr = max(0, min(2, row + dr))
nc = max(0, min(2, col + dc))
next_state = nr * 3 + nc
if next_state == 8: r = 10.0
elif next_state == 7: r = -5.0
else: r = -0.1
return next_state, r, next_state in [7, 8]
def reinforce(n_episodes=500, alpha=0.01, gamma=0.9):
"""REINFORCE算法"""
theta = np.zeros((n_states, n_actions)) # 策略参数
rewards_history = []
for ep in range(n_episodes):
# 收集完整轨迹
state = 0; trajectory = []
while state not in [7, 8]:
logits = theta[state]; exp_l = np.exp(logits - np.max(logits))
probs = exp_l / exp_l.sum()
action = np.random.choice(n_actions, p=probs)
next_state, reward, done = step(state, action)
trajectory.append((state, action, reward))
state = next_state
# 计算回报(从后往前)
G = 0; returns = []
for _, _, r in reversed(trajectory):
G = r + gamma * G; returns.append(G)
returns.reverse()
# 策略梯度更新
for (s, a, _), G_t in zip(trajectory, returns):
logits = theta[s]; exp_l = np.exp(logits - np.max(logits))
probs = exp_l / exp_l.sum()
grad = -probs.copy(); grad[a] += 1 # ∇log π
theta[s] += alpha * G_t * grad
rewards_history.append(sum(r for _, _, r in trajectory))
return theta, rewards_history
np.random.seed(42)
theta, rewards = reinforce()
print("=== REINFORCE训练结果 ===")
print(f"前10ep奖励: {[f'{r:.1f}' for r in rewards[:10]]}")
print(f"最后10ep平均: {np.mean(rewards[-10:]):.2f}")
action_names = ["上", "下", "左", "右"]
print(f"\n学习的策略:")
for i in range(3):
row = [];
for j in range(3):
s = i*3+j
if s==8: row.append("目标")
elif s==7: row.append("陷阱")
else: row.append(action_names[np.argmax(theta[s])])
print(" "+" ".join(f"{a:>4}" for a in row))什么用(应用):REINFORCE是策略梯度方法的入门算法,用于教学和理解策略梯度原理。在实际中,REINFORCE通常被Actor-Critic(如PPO)替代,因为后者方差更低、效率更高。
哪些坑(缺点):方差大(G_t包含整个轨迹的随机性);不能在线学习(必须等episode结束);On-Policy,样本效率低;需要baseline来降低方差。
三、Baseline——降低策略梯度方差
是什么(定义):Baseline(基线)是降低策略梯度方差的标准技巧。在策略梯度中引入一个与动作无关的基线b(s):∇_θ J(θ) = E[∇_θ log π_θ(a|s) · (Q^π(s,a) - b(s))]。常用的baseline是状态价值函数V(s),此时Q-V就是优势函数A(s,a)。
大白话 Baseline就像"及格线"——不是看"这个动作得了多少分",而是看"这个动作比平均好多少"。如果一个动作得了5分但平均能得8分,那它其实是"差生";如果一个动作得了3分但平均只有1分,那它其实是"优等生"。Baseline让梯度关注"相对优势"而非"绝对分数",降低了方差。
为什么(原理):Baseline不改变梯度的期望(因为E[∇_θ log π·b(s)] = 0,b(s)与动作无关),但能显著降低方差。直观上,如果所有动作的Q值都很高(比如都接近100),减去baseline后,梯度会更集中在"比其他动作更好的动作"上,而不是盲目地增加所有动作的概率。
怎么做(实现):
import numpy as np
# ==================== Baseline演示 ====================
# 模拟两个episode的回报
# Episode 1: 所有动作回报都偏高(环境简单)
G1 = np.array([10.0, 12.0, 11.0, 9.0]) # 4个动作的回报
# Episode 2: 所有动作回报都偏低(环境困难)
G2 = np.array([-5.0, -3.0, -4.0, -6.0])
print("=== Baseline降低方差演示 ===")
print(f"无Baseline:")
print(f" Episode 1: G={G1}, 梯度权重=G1, 方差={np.var(G1):.1f}")
print(f" Episode 2: G={G2}, 梯度权重=G2, 方差={np.var(G2):.1f}")
# 用均值作为baseline
b1 = np.mean(G1); b2 = np.mean(G2)
print(f"\n有Baseline (b=均值):")
print(f" Episode 1: b={b1:.1f}, A=G-b={[f'{a:.1f}' for a in G1-b1]}, 方差={np.var(G1-b1):.1f}")
print(f" Episode 2: b={b2:.1f}, A=G-b={[f'{a:.1f}' for a in G2-b2]}, 方差={np.var(G2-b2):.1f}")
print(f"\nBaseline的效果:")
print(f" 1. 方差不增加(数学上E[∇logπ·b]=0)")
print(f" 2. 方差降低:梯度权重更集中在相对优势上")
print(f" 3. 常用baseline:V(s)(状态价值函数)")
print(f" 4. 优势函数A(s,a)=Q(s,a)-V(s)是标准做法")什么用(应用):Baseline是策略梯度方法的标准配置。在REINFORCE中,用V(s)作为baseline可以显著降低方差;在A2C/A3C中,优势函数A(s,a)是Critic的核心输出;在PPO中,GAE(广义优势估计)结合了baseline和TD(λ)来高效估计优势函数。
哪些坑(缺点):Baseline本身需要学习(增加了计算开销);如果baseline估计不准确,可能引入偏差;V(s)的估计需要额外的Critic网络。
四、REINFORCE的完整实现与改进
是什么(定义):将REINFORCE与baseline结合,实现一个完整的策略梯度算法。核心流程:用当前策略采集轨迹→计算回报→用V(s)作为baseline计算优势→更新策略参数→更新V(s)。
大白话 REINFORCE + Baseline = 升级版REINFORCE。不再用"绝对分数"来评价动作,而是用"相对优势"——这就是Actor-Critic的雏形!
怎么做(实现):
import numpy as np
n_states = 9; n_actions = 4
actions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
def step(state, action):
row, col = state // 3, state % 3
dr, dc = actions[action]
nr = max(0, min(2, row + dr))
nc = max(0, min(2, col + dc))
next_state = nr * 3 + nc
if next_state == 8: r = 10.0
elif next_state == 7: r = -5.0
else: r = -0.1
return next_state, r, next_state in [7, 8]
def reinforce_with_baseline(n_episodes=500, alpha_theta=0.01, alpha_v=0.1, gamma=0.9):
theta = np.zeros((n_states, n_actions)) # 策略参数
V = np.zeros(n_states) # Baseline: 状态价值函数
rewards_history = []
for ep in range(n_episodes):
state = 0; trajectory = []
while state not in [7, 8]:
logits = theta[state]; exp_l = np.exp(logits - np.max(logits))
probs = exp_l / exp_l.sum()
action = np.random.choice(n_actions, p=probs)
next_state, reward, done = step(state, action)
trajectory.append((state, action, reward))
state = next_state
# 计算回报
G = 0; returns = []
for _, _, r in reversed(trajectory):
G = r + gamma * G; returns.append(G)
returns.reverse()
# 更新策略和baseline
for (s, a, _), G_t in zip(trajectory, returns):
# 更新baseline V(s)
V[s] += alpha_v * (G_t - V[s])
# 优势函数 = G_t - V(s)
advantage = G_t - V[s]
# 策略梯度更新
logits = theta[s]; exp_l = np.exp(logits - np.max(logits))
probs = exp_l / exp_l.sum()
grad = -probs.copy(); grad[a] += 1
theta[s] += alpha_theta * advantage * grad
rewards_history.append(sum(r for _, _, r in trajectory))
return theta, rewards_history
np.random.seed(42)
theta, rewards = reinforce_with_baseline()
print("=== REINFORCE + Baseline ===")
print(f"前10ep奖励: {[f'{r:.1f}' for r in rewards[:10]]}")
print(f"最后10ep平均: {np.mean(rewards[-10:]):.2f}")
print(f"\n已经接近Actor-Critic——")
print(f" 策略网络: Actor(输出动作概率)")
print(f" 价值函数V(s): Critic(提供baseline)")
print(f" 优势函数: A(s,a)=G_t-V(s)(降低方差)")什么用(应用):REINFORCE+Baseline是理解Actor-Critic的桥梁。从这里出发,将"蒙特卡洛回报G_t"替换为"TD误差/优势函数",就得到了真正的Actor-Critic。
哪些坑(缺点):仍然需要完整轨迹(不能在线学习);V(s)的估计在早期不准确,baseline效果不佳;G_t的方差仍然较高。
五、策略梯度中的连续动作空间
是什么(定义):策略梯度方法在连续动作空间中的优势尤为明显。策略通常输出高斯分布(Normal Distribution)的均值和标准差:π_θ(a|s) = N(μ_θ(s), σ_θ(s)²),然后从该分布中采样动作。梯度通过重参数化技巧(reparameterization trick)计算。
大白话 在连续动作空间中(如方向盘角度从-30°到+30°),Q-learning没法做argmax(无法遍历所有角度)。策略梯度直接输出"最佳角度"(均值)和"把握程度"(标准差),然后"随机抽取一个角度"。这样自然地处理了连续动作。
怎么做(实现):
import numpy as np
# ==================== 连续动作空间的策略 ====================
class ContinuousPolicy:
"""连续动作空间的高斯策略"""
def __init__(self, state_dim, action_dim):
# 均值网络参数
self.W_mu = np.random.randn(state_dim, action_dim) * 0.1
self.b_mu = np.zeros(action_dim)
# 对数标准差(可学习参数)
self.log_std = np.zeros(action_dim) # 初始log_std=0 → std=1
def forward(self, state):
"""输出动作分布参数"""
mu = state @ self.W_mu + self.b_mu # 均值
std = np.exp(self.log_std) # 标准差(确保为正)
return mu, std
def sample(self, state):
"""从策略中采样动作"""
mu, std = self.forward(state)
# 从N(mu, std²)采样
action = np.random.normal(mu, std)
# 计算log概率(用于策略梯度)
log_prob = -0.5 * np.sum(((action - mu) / std) ** 2 + 2 * np.log(std) + np.log(2 * np.pi))
return action, log_prob
# 演示
np.random.seed(42)
policy = ContinuousPolicy(state_dim=4, action_dim=2) # 2维连续动作
state = np.array([1.0, 0.5, -0.2, 0.8])
mu, std = policy.forward(state)
print("=== 连续动作空间的策略 ===")
print(f"状态: {state}")
print(f"动作均值 μ: {[f'{m:.3f}' for m in mu]}")
print(f"动作标准差 σ: {[f'{s:.3f}' for s in std]}")
action, log_prob = policy.sample(state)
print(f"\n采样动作: {[f'{a:.3f}' for a in action]}")
print(f"Log概率: {log_prob:.3f}")
print(f"\n连续动作空间中策略梯度的优势:")
print(f" 1. 输出分布参数(μ, σ),不需要argmax")
print(f" 2. 自然处理连续动作空间")
print(f" 3. σ可以学习——自动调整探索程度")
print(f" 4. 这是PPO、SAC等算法的基础")什么用(应用):连续动作策略是机器人控制、自动驾驶、连续控制任务的标准做法。PPO、SAC、TD3等算法都使用高斯策略来处理连续动作。
哪些坑(缺点):高斯策略假设动作分布是单峰的,可能不适合多模态动作分布;方差的初始化和学习需要仔细处理;动作需要裁剪到合法范围。
六、实战:REINFORCE vs Q-learning对比
是什么(定义):在网格世界上对比REINFORCE(策略梯度)和Q-learning(基于价值)的学习特性。
怎么做(实现):
import numpy as np
# (使用前面定义的REINFORCE和Q-learning函数)
# 对比两种方法的收敛速度和稳定性
print("=== REINFORCE vs Q-learning 对比 ===")
print()
print("REINFORCE(策略梯度):")
print(" 优点: 直接优化策略,可处理连续动作和随机策略")
print(" 缺点: 方差大,需要完整轨迹,收敛慢")
print(" 适用: 连续控制、博弈、需要随机策略的场景")
print()
print("Q-learning(基于价值):")
print(" 优点: 方差低,可以离线学习,收敛快")
print(" 缺点: 只能处理离散动作,确定性策略")
print(" 适用: 离散动作空间、游戏AI")
print()
print("共同点:")
print(" 都是无模型方法")
print(" 都通过TD学习或蒙特卡洛学习")
print(" 最终目标都是最大化累积奖励")
print()
print("选择建议:")
print(" 离散动作 → Q-learning/DQN(简单高效)")
print(" 连续动作 → 策略梯度/PPO(自然处理)")
print(" 复杂任务 → Actor-Critic(两者结合)")什么用(应用):通过对比,理解两种范式的适用场景,为实际项目做出正确的算法选择。
哪些坑(缺点):简单环境中的对比结果不能直接推广到复杂环境;REINFORCE在简单环境中性能尚可,但在复杂环境中方差问题更严重。
概念关系图谱
| 概念 | 上位概念 | 核心思想 | 关键公式/方法 | 特点 |
|---|---|---|---|---|
| 策略梯度定理 | 理论基础 | 直接对策略参数求导 | ∇J=E[∇logπ·Q] | 无模型 |
| REINFORCE | 策略梯度 | 蒙特卡洛回报作为权重 | θ←θ+α·G_t·∇logπ | 简单但方差大 |
| Baseline | 方差降低 | 减去与动作无关的基线 | Q(s,a)-b(s) | 降低方差 |
| 优势函数 | 价值度量 | 动作比平均好多少 | A(s,a)=Q(s,a)-V(s) | 标准baseline |
| 得分函数 | 梯度计算 | 对数概率的梯度 | ∇_θ log π_θ(a | s) |
| 高斯策略 | 连续动作 | 输出均值和标准差 | N(μ_θ(s), σ_θ(s)²) | 处理连续动作 |
重点答疑
Q1: 策略梯度为什么不需要环境模型?
策略梯度定理中,∇_θ J(θ) = E[∇_θ log π_θ(a|s) · Q^π(s,a)],这个公式中不包含环境转移概率P(s'|s,a)。这是因为对数导数技巧将"对概率的期望"转化为了"期望的导数",而期望可以通过采样轨迹来估计,不需要知道P。
解答:策略梯度是"无模型"的——不需要知道环境转移规则。只需要采样轨迹,算回报,然后调整策略。
Q2: REINFORCE的方差为什么大?
方差来自两个方面:(1) G_t包含整个轨迹的随机性——环境转移随机的、奖励随机的、策略本身也是随机的,所有这些随机性都累积在G_t中;(2) 不同轨迹的G_t差异很大——有的轨迹运气好获得高回报,运气差则低回报。这导致策略梯度的估计波动很大。
解答:REINFORCE用"整局游戏的总分"来评价每一步,这个总分的波动自然很大。Baseline和Actor-Critic就是为了解决这个问题。
Q3: REINFORCE和Actor-Critic的核心区别是什么?
REINFORCE:用蒙特卡洛回报G_t作为权重,需要完整轨迹。 Actor-Critic:用Critic估计的Q值或优势函数作为权重,可以每步更新。
因此,Actor-Critic可以看作REINFORCE的"在线版"——在线学习,不需要等episode结束。
解答:REINFORCE = 事后总结(等游戏结束再更新),Actor-Critic = 边玩边学(每步都能更新)。Actor-Critic更高效。
Q4: 为什么连续动作空间适合策略梯度而不适合Q-learning?
在连续动作空间中,Q-learning需要argmax_a Q(s,a)来选择动作,这需要求解一个连续优化问题,计算成本高且可能不精确。策略梯度直接输出动作分布参数(如高斯分布的均值和标准差),从分布中采样即可,不需要argmax。
解答:Q-learning在离散动作中"遍历所有动作找最大"很简单,在连续动作中"搜索最佳动作"很困难。策略梯度直接输出"什么动作好",避免了搜索。
Q5: Baseline为什么必须与动作无关?
如果baseline b(s,a)依赖于动作a,那么E[∇_θ log π_θ(a|s) · b(s,a)] ≠ 0,baseline会引入偏差。只有b(s)(只依赖状态)才能保证E[∇_θ log π_θ · b(s)] = 0,从而在不改变梯度期望的情况下降低方差。
解答:baseline必须只看"状态"不看"动作",否则会"偏心"——给某些动作额外的加分或减分,导致梯度偏差。
章节单词汇总
| 英文 | 音标 | 中文 |
|---|---|---|
| policy gradient | /ˈpɑːləsi ˈɡreɪdiənt/ | 策略梯度 |
| REINFORCE | /ˌriːɪnˈfɔːrs/ | 强化(算法名) |
| score function | /skɔːr ˈfʌŋkʃn/ | 得分函数 |
| baseline | /ˈbeɪslaɪn/ | 基线 |
| advantage | /ədˈvæntɪdʒ/ | 优势 |
| Monte Carlo | /ˌmɑːnti ˈkɑːrloʊ/ | 蒙特卡洛 |
| log-derivative trick | /lɔːɡ dɪˈrɪvətɪv trɪk/ | 对数导数技巧 |
| Gaussian policy | /ˈɡaʊsiən ˈpɑːləsi/ | 高斯策略 |
| variance reduction | /ˈveriəns rɪˈdʌkʃn/ | 方差缩减 |
面试练习
Q1 [单选] 策略梯度定理中,梯度不依赖于什么?
- undefined
解答:策略梯度定理的公式中不包含环境转移概率,因此是无模型的。这是策略梯度方法的核心优势。
Q2 [单选] REINFORCE算法中,用于更新策略的权重是什么?
- undefined
解答:REINFORCE使用蒙特卡洛回报G_t(从当前到episode结束的累积折扣奖励)作为权重。这是它方差大的原因。
Q3 [多选] 以下哪些方法可以降低策略梯度的方差?
- undefined
解答:A、B正确,baseline和优势函数都可以降低方差。C错误,增大学习率可能增加不稳定性。D正确,Actor-Critic用Critic的低方差估计替代高方差G_t。
Q4 [单选] 在连续动作空间中,策略梯度方法通常输出什么?
- undefined
解答:连续动作空间中,策略通常输出高斯分布N(μ, σ²)的参数(均值和标准差),然后从该分布中采样动作。
Q5 [多选] 以下关于REINFORCE的说法,哪些是正确的?
- undefined
解答:A正确,REINFORCE是蒙特卡洛方法,需要完整轨迹。B正确。C错误,REINFORCE是On-Policy的。D正确,G_t的方差大。
Q6 [单选] Baseline b(s)引入策略梯度中,必须满足什么条件以保证无偏?
- undefined
解答:baseline必须与动作无关(只依赖于状态),才能保证E[∇log π·b(s)] = 0,不改变梯度期望。
Q7 [单选] 策略梯度定理中的∇_θ log π_θ(a|s)被称为什么?
- undefined
解答:∇_θ log π_θ(a|s)被称为得分函数(score function),是策略梯度中的核心组件。
Q8 [多选] 以下哪些是策略梯度方法的优势?
- undefined
解答:A、B正确,策略梯度直接输出分布参数,天然支持连续动作和随机策略。C错误,策略梯度方差通常比价值方法高。D正确,策略梯度直接优化策略。
Q9 [单选] 在REINFORCE中,如果G_t=-5,∇log π_θ(a|s)对动作a的梯度为正,那么策略会如何变化?
- undefined
解答:G_t=-5<0,所以θ ← θ + α·(-5)·∇log π。如果∇log π对动作a的梯度为正,那么θ朝负方向更新,动作a的概率减少。
Q10 [多选] 以下哪些算法属于策略梯度方法?
- undefined
解答:A、B、D都是策略梯度方法。C(DQN)是基于价值的方法,不属于策略梯度。