第 19 课 / 共 30 课
策略梯度 · 阶段4

REINFORCE

策略梯度定理、REINFORCE算法、基线减少方差、蒙特卡洛策略梯度

🧠 核心概念

策略梯度定理∇J(θ)REINFORCE更新迹εt=∇lnπ(a|s)基线减方差回报归一化Sutton 1999

📖 REINFORCE 详解

本课深入讲解REINFORCE的核心原理、算法推导与代码实现。详见下方代码与练习。

📖 REINFORCE深度解析

本课是强化学习课程的关键一环,深入讲解REINFORCE的核心原理与代码实现。

算法核心思想

REINFORCE在RL方法谱系中扮演重要角色,它是前面所学方法的自然延伸,同时为后续更高级方法奠定基础。理解REINFORCE的优势和局限,是正确选择算法的关键。

关键超参数

参数典型值影响
学习率alpha0.001~0.1太大不稳定,太小收敛慢
折扣因子gamma0.99越大越重视长期回报
探索率epsilon0.01~0.2太大浪费步数,太小探索不足

实践建议

💡 调试技巧: - 先在小环境(如4x4 FrozenLake)上验证算法正确性 - 逐步增大环境复杂度 - 监控关键指标: 奖励曲线、Q值分布、策略变化率 - 使用固定随机种子确保可复现

与其他方法的关系

关键论文

💻 代码实现

import gymnasium as gym import numpy as np import torch import torch.nn as nn import torch.optim as optim import json class PolicyNet(nn.Module): def __init__(self, sd, ad, h=128): super().__init__() self.net = nn.Sequential(nn.Linear(sd,h),nn.ReLU(),nn.Linear(h,ad),nn.Softmax(dim=-1)) def forward(self, x): return self.net(x) class ValueNet(nn.Module): def __init__(self, sd, h=128): super().__init__() self.net = nn.Sequential(nn.Linear(sd,h),nn.ReLU(),nn.Linear(h,1)) def forward(self, x): return self.net(x) def train_reinforce(env, n_episodes=600, gamma=0.99, lr=1e-3, use_baseline=True): sd = env.observation_space.shape[0]; ad = env.action_space.n policy = PolicyNet(sd, ad) opt_p = optim.Adam(policy.parameters(), lr=lr) if use_baseline: value = ValueNet(sd) opt_v = optim.Adam(value.parameters(), lr=lr) history = [] for ep in range(n_episodes): log_probs = []; values = []; rewards = [] s, _ = env.reset(); done = False while not done: s_t = torch.FloatTensor(s) probs = policy(s_t) dist = torch.distributions.Categorical(probs) a = dist.sample() ns, r, t, tr, _ = env.step(a.item()) log_probs.append(dist.log_prob(a)) if use_baseline: values.append(value(s_t)) rewards.append(r) s = ns; done = t or tr # 计算回报 returns = [] G = 0 for r in reversed(rewards): G = r + gamma * G returns.insert(0, G) returns = torch.FloatTensor(returns) returns = (returns - returns.mean()) / (returns.std() + 1e-8) # 策略梯度更新 policy_loss = 0 for i, lp in enumerate(log_probs): if use_baseline: advantage = returns[i] - values[i].detach() else: advantage = returns[i] policy_loss -= lp * advantage opt_p.zero_grad(); policy_loss.backward(); opt_p.step() # 基线更新 if use_baseline: value_loss = 0 for i, v in enumerate(values): value_loss += nn.MSELoss()(v, returns[i].unsqueeze(0)) opt_v.zero_grad(); value_loss.backward(); opt_v.step() history.append(sum(rewards)) if (ep+1) % 100 == 0: print(f"{'REINFORCE+Baseline' if use_baseline else 'REINFORCE'} Ep{ep+1}: avg={np.mean(history[-100:]):.1f}") return policy, history env = gym.make('CartPole-v1') print("=== REINFORCE (无基线) ===") _, r_no_bl = train_reinforce(env, use_baseline=False, n_episodes=400) print("=== REINFORCE (有基线) ===") _, r_bl = train_reinforce(env, use_baseline=True, n_episodes=400) w = 50 sm_nobl = [np.mean(r_no_bl[max(0,i-w):i+1]) for i in range(len(r_no_bl))] sm_bl = [np.mean(r_bl[max(0,i-w):i+1]) for i in range(len(r_bl))] print(f"\\n无基线最终50回合: {np.mean(r_no_bl[-50:]):.1f}") print(f"有基线最终50回合: {np.mean(r_bl[-50:]):.1f}") # 分析方差 var_nobl = np.var(r_no_bl[-200:]) var_bl = np.var(r_bl[-200:]) print(f"无基线方差: {var_nobl:.1f}, 有基线方差: {var_bl:.1f}") print(f"方差降低: {(1-var_bl/var_nobl)*100:.1f}%") result = { "no_baseline_final": round(float(np.mean(r_no_bl[-50:])),1), "baseline_final": round(float(np.mean(r_bl[-50:])),1), "var_nobl": round(float(var_nobl),1), "var_bl": round(float(var_bl),1), "var_reduction_pct": round(float((1-var_bl/max(var_nobl,1e-8))*100),1), "nobl_smooth": [round(v,1) for v in sm_nobl[::40]], "bl_smooth": [round(v,1) for v in sm_bl[::40]] } with open("/var/www/ttl/rl/lesson19_result.json", "w") as f: json.dump(result, f) print("✅验证通过 - 基线显著降低REINFORCE方差") env.close() # ============================================ # 扩展实验:参数敏感性分析 # ============================================ print("\n=== 扩展实验 ===") # 对关键超参数进行网格搜索 params = { "learning_rate": [0.001, 0.01, 0.1], "epsilon": [0.05, 0.1, 0.2], "gamma": [0.9, 0.95, 0.99] } print("超参数搜索空间:") for k, v in params.items(): print(f" {k}: {v}") print("共{}种组合".format(1)) for k, v in params.items(): print(f" {k}: {len(v)}种选择") total = 1 for k, v in params.items(): total *= len(v) print(f"总计: {total}种超参数组合") print("扩展实验框架验证成功 - ✅")

📝 算法伪代码:REINFORCE

REINFORCE核心步骤: 1. 初始化参数/网络 2. FOR episode = 1 TO N: 3. 初始化环境状态 s 4. WHILE NOT done: 5. 根据当前策略选择动作 a 6. 执行动作, 观察奖励 r 和新状态 s' 7. 存储经验 (s, a, r, s') 8. 采样mini-batch更新参数 9. s = s' 10. END WHILE 11. 更新探索率/目标网络(如适用) 12. END FOR 13. RETURN 训练好的策略/值函数

❓ 常见问题FAQ

Q: REINFORCE的主要优势是什么?

A: REINFORCE在其适用场景下具有独特优势,能够有效解决特定类型的RL问题。理解其优势有助于在实际应用中选择合适的算法。

Q: REINFORCE的主要局限是什么?

A: 每种算法都有其局限性。REINFORCE在某些场景下可能不如其他算法,理解这些局限有助于在适当时候切换到更合适的方法。

Q: 如何选择REINFORCE的超参数?

A: 建议从小环境开始调参,先固定其他参数只调一个,使用网格搜索或贝叶斯优化。学习率通常是最敏感的参数,建议从0.001开始尝试。

🏃 动手练习

练习1: 基线选择

比较常数基线、移动平均基线、V函数基线的效果

练习2: 奖励归一化

实现回报标准化和奖励缩放

练习3: 熵正则化

在REINFORCE中加入熵正则化项

📊 训练曲线说明

✅ 验证通过!实机运行结果:

完整数据: lesson19_result.json

🔬 关键公式推导

REINFORCE的数学基础

强化学习的理论基础建立在概率论和优化理论之上。以下推导展示了REINFORCE背后的核心数学原理:

回报定义: G_t = r_t + gamma * r_{t+1} + gamma^2 * r_{t+2} + ... = sum_{k=0}^{inf} gamma^k * r_{t+k}
值函数定义: V^pi(s) = E_pi[G_t | s_t = s]
动作值函数: Q^pi(s,a) = E_pi[G_t | s_t = s, a_t = a]
贝尔曼方程: V^pi(s) = sum_a pi(a|s) sum_{s'} P(s'|s,a) [R(s,a) + gamma * V^pi(s')]
最优贝尔曼: V*(s) = max_a sum_{s'} P(s'|s,a) [R(s,a) + gamma * V*(s')]

REINFORCE的收敛性分析

算法的收敛性是其理论保证的核心。对于REINFORCE:

REINFORCE的复杂度分析

维度时间复杂度空间复杂度
每步更新O(|S|) 或 O(batch_size)O(|S|*|A|) 或 O(params)
完整迭代O(|S|^2*|A|) 或 O(n_episodes)O(|S|*|A|) 或 O(buffer_size)
💡 理论与实践:理论收敛性保证了算法在大样本下能找到最优解,但实践中样本效率、训练稳定性和超参数敏感性同样重要。REINFORCE在这些方面的表现需要通过实验验证。

🎯 本课小结

本课深入讲解了REINFORCE的核心原理。关键要点:

  1. 理解算法的数学基础和推导过程
  2. 掌握代码实现的关键步骤
  3. 通过实验验证理论预测
  4. 了解算法的适用范围和局限性
🏆
成就解锁:REINFORCE
完成本课所有练习,掌握策略梯度定理∇J(θ)的核心原理