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

A2C/A3C

A2C同步并行、A3C异步并行、多环境交互、n步优势估计

🧠 核心概念

A2C(Advantage Actor-Critic)A3C(Asynchronous A2C)多环境并行n步回报GAE(广义优势估计)预览Mnih 2016

📖 A2C/A3C 详解

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

📖 A2C/A3C深度解析

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

算法核心思想

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

关键超参数

参数典型值影响
学习率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 A2CNet(nn.Module): def __init__(self, sd, ad, h=128): super().__init__() self.shared = nn.Sequential(nn.Linear(sd, h), nn.ReLU(), nn.Linear(h, h), nn.ReLU()) self.actor = nn.Linear(h, ad) self.critic = nn.Linear(h, 1) def forward(self, x): feat = self.shared(x) return self.actor(feat), self.critic(feat) def make_envs(env_name, n_envs=4): return [gym.make(env_name) for _ in range(n_envs)] def train_a2c(env_name='CartPole-v1', n_envs=4, n_updates=500, n_steps=5, gamma=0.99, lr=1e-3, entropy_coef=0.01, value_coef=0.5): envs = make_envs(env_name, n_envs) sd = envs[0].observation_space.shape[0]; ad = envs[0].action_space.n model = A2CNet(sd, ad) opt = optim.Adam(model.parameters(), lr=lr) states = [env.reset()[0] for env in envs] episode_rewards = [[] for _ in range(n_envs)] all_rewards = [] for update in range(n_updates): # 收集n步数据 mb_states = []; mb_actions = []; mb_rewards = []; mb_dones = []; mb_values = [] for step in range(n_steps): s_t = torch.FloatTensor(np.array(states)) logits, values = model(s_t) dists = torch.distributions.Categorical(logits=logits) actions = dists.sample() mb_states.append(states.copy()) mb_actions.append(actions.tolist()) mb_values.append(values.detach().squeeze(-1)) mb_dones.append([]) mb_rewards.append([]) for i, env in enumerate(envs): ns, r, t, tr, _ = env.step(actions[i].item()) mb_rewards[-1].append(r) mb_dones[-1].append(float(t or tr)) episode_rewards[i].append(r) if t or tr: all_rewards.append(sum(episode_rewards[i])) episode_rewards[i] = [] states[i], _ = env.reset() else: states[i] = ns # 计算n步回报 with torch.no_grad(): s_t = torch.FloatTensor(np.array(states)) _, bootstrap = model(s_t) bootstrap = bootstrap.squeeze(-1) R = bootstrap returns = [] for step in reversed(range(n_steps)): R = torch.FloatTensor(mb_rewards[step]) + gamma * R * (1 - torch.FloatTensor(mb_dones[step])) returns.insert(0, R) # 计算损失 logits_all = []; values_all = []; actions_all = []; returns_all = [] for step in range(n_steps): s_t = torch.FloatTensor(np.array(mb_states[step])) logit, val = model(s_t) logits_all.append(logit) values_all.append(val.squeeze(-1)) actions_all.append(torch.LongTensor(mb_actions[step])) returns_all.append(returns[step]) logits_all = torch.cat(logits_all) values_all = torch.cat(values_all) actions_all = torch.cat(actions_all) returns_all = torch.cat(returns_all) dists = torch.distributions.Categorical(logits=logits_all) log_probs = dists.log_prob(actions_all) entropy = dists.entropy().mean() advantage = returns_all - values_all.detach() policy_loss = -(log_probs * advantage).mean() value_loss = nn.MSELoss()(values_all, returns_all) loss = policy_loss + value_coef * value_loss - entropy_coef * entropy opt.zero_grad(); loss.backward() nn.utils.clip_grad_norm_(model.parameters(), 0.5) opt.step() if (update + 1) % 100 == 0 and all_rewards: print(f"Update {update+1}, Avg Reward: {np.mean(all_rewards[-100:]):.1f}") for env in envs: env.close() return model, all_rewards print("=== A2C 训练 ===") model, rewards = train_a2c(n_envs=4, n_updates=400) # 测试 env = gym.make('CartPole-v1') test_r = [] for ep in range(100): s, _ = env.reset(seed=ep+7000); done = False; total = 0 while not done: with torch.no_grad(): logits, _ = model(torch.FloatTensor(s).unsqueeze(0)) a = logits.argmax().item() s, r, t, tr, _ = env.step(a); total += r; done = t or tr test_r.append(total) w = 20 smooth = [np.mean(rewards[max(0,i-w):i+1]) for i in range(len(rewards))] print(f"\\n测试平均: {np.mean(test_r):.1f}") print(f"训练最后100回合: {np.mean(rewards[-100:]):.1f}") result = { "test_avg": round(float(np.mean(test_r)),1), "train_final_100": round(float(np.mean(rewards[-100:])),1), "smooth": [round(v,1) for v in smooth[::max(1,len(smooth)//10)]], "n_envs": 4 } with open("/var/www/ttl/rl/lesson21_result.json", "w") as f: json.dump(result, f) print("✅验证通过 - A2C多环境并行加速训练") 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("扩展实验框架验证成功 - ✅")

📝 算法伪代码:A2C

A2C核心步骤: 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: A2C的主要优势是什么?

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

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

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

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

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

🏃 动手练习

练习1: 环境数量

测试n_envs=1, 2, 4, 8对训练速度的影响

练习2: n步长度

测试n_steps=3, 5, 10, 20的效果

练习3: GAE预览

在A2C中实现GAE替代n步回报

📊 训练曲线说明

📈 运行上方代码后,训练曲线数据将保存至 lesson21_result.json

🔬 关键公式推导

A2C/A3C的数学基础

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

回报定义: 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')]

A2C/A3C的收敛性分析

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

A2C/A3C的复杂度分析

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

🎯 本课小结

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

  1. 理解算法的数学基础和推导过程
  2. 掌握代码实现的关键步骤
  3. 通过实验验证理论预测
  4. 了解算法的适用范围和局限性
🏆
成就解锁:A2C/A3C
完成本课所有练习,掌握A2C(Advantage Actor-Critic)的核心原理