强化学习Actor-Critic全面解析:从策略梯度到PPO的实现与避坑指南

发布时间:2026/10/13 4:29:39
强化学习Actor-Critic全面解析:从策略梯度到PPO的实现与避坑指南 最近整理强化学习入门系列的时候有个同学跑来问我他照着教程实现了 REINFORCE最基础的策略梯度算法在 CartPole 上跑了整整一个下午平均回报忽高忽低明明动作空间就两个为什么收敛这么慢我一看代码就明白了——这个算法靠蒙特卡洛采样整条轨迹来估计累积回报方差大到离谱单条轨迹的回报会被环境里各种随机因素污染得很厉害。这个痛点正是 Actor-Critic 系列算法被发明出来的根本原因。这篇文章就聚焦Actor-Critic演员-评论家这个大家族。我会先解释为什么单纯的策略梯度法和单纯的价值函数法都存在死角然后拆解 Actor 和 Critic 两个网络的分工协作机制再从数学层面把策略梯度定理、优势函数、TD 误差讲透接着顺着 A2C、DDPG、TRPO、PPO 的演进脉络串一遍家族历史最后给出一份我自己实测用的迷你 AC 实现要点和避坑清单。适合已经了解 MDP、DQN、策略梯度基础的读者把这套阅读完你对整个强化学习的主流算法版图会有一个通通透透的认识。1. 为什么需要 Actor-Critic两种主流路线的死角1.1 策略梯度的高方差问题在讲 Actor-Critic 之前得先明白它到底在解决什么难题。策略梯度方法的核心思想是直接对策略 πθ(a|s) 的参数 θ 求梯度用环境反馈的累积回报来告诉参数哪个方向是好方向。REINFORCE 的梯度写成这样∇θ J(θ) E[ ∇θ log πθ(a|s) · G ]其中 G 是一条完整轨迹的累积回报。问题就出在这个 G 上。它是一次蒙特卡洛采样的结果从当前状态到回合结束之间的所有奖励累加中间每一步都受环境随机性影响。换句话说这相当于让一个射箭运动员只射一箭用这一箭的落点去判断他的瞄准姿势哪里有问题——单次结果里运气成分太大了。打个更生活化的比方你想评估一家餐厅的菜品质量策略梯度法不是去问一百个顾客再取平均而是只随机挑一个顾客他说好就涨权重说差就降权重。这个顾客今天心情好不好前一顿吃了什么都会严重干扰结果。实际训练中你看到的表现就是回报曲线疯狂震荡、明明离最优解很近了又会突然掉下去。1.2 价值函数自举的偏差问题另一条路线是用价值函数来指导决策代表作就是 DQN。它学习一个 Q(s,a)用贝尔曼方程不断自举Q(s,a) ≈ r γ · max Q(s,a)这个方法方差很小因为每一步只用一步实际奖励加估计值不需要等整条轨迹跑完。但它有另一个麻烦估计值在迭代中不断拿自己预测自己一旦初值偏离真实值误差会沿着自举路径不断累积。这就是所谓的偏差。就像一个人凭记忆给别人指路别人再凭记忆给下一个人指路传到后面路线一定走样。更关键的是价值函数方法天然不擅长处理连续动作空间。动作一旦连续max Q(s, a) 这个操作根本没法暴力枚举只能近似搜索而优化策略时又要把每个动作代进去比较计算成本极大。1.3 组合逻辑baseline 思想让两者互补策略梯度方差大但方向是无偏的价值函数偏差积累但方差小、能为每个状态提供稳定的基准。那能不能让两者合作答案在策略梯度的方差公式里藏着。为策略梯度加一个只依赖状态、与具体动作无关的基线 b(s)期望不会改变却能大幅降低方差。数学上说因为 ∑a π(a|s) · b(s) · ∇logπ(a|s) b(s) · ∇∑a π(a|s) 0所以减去基线不会让梯度偏掉。直觉上讲基线帮你判断这个动作到底是本身好还是只是因为当前状态普遍容易拿高分。用同样的射箭类比如果所有选手顺风都能射到 9 环你要评估的是弓弦拉法带来的增量而不是那个百分比的绝对成绩。价值函数正是天然适合当基线的东西。状态价值 V(s) 代表在这个状态下按当前策略平均能拿多少你实际拿到的回报或者 Q 值减去 V(s)得到的就是这个动作相对平均水平的优势。把这一层逻辑做进去就诞生了 Actor-Critic 的基本雏形。所以现在可以给出一个高度概括的定义AC 就是用策略网络Actor负责怎么做用价值网络Critic负责做得怎么样Critic 的价值判断还兼任了策略梯度的基线一举两得。2. Actor 与 Critic 的分工机制两个网络如何协同更新2.1 Actor 网络输出什么Actor 就是策略网络输入是状态 s输出是策略分布。这里要分动作空间来设计离散动作空间输出层用 softmax输出每个动作的概率。比如 CartPole 两个动作输出就是一个维度为 2 的向量。连续动作空间通常输出一个高斯分布的均值和方差训练时从 N(μ, σ²) 中采样动作推理时直接取均值。要记住一个关键点Actor 更新靠的是对数概率 log πθ(a|s) 的梯度。在实现阶段很多初学者会直接把当前动作对应的概率 p存下来然后算 loss -log(p) · advantage。这本质上就是交叉熵损失的特殊形态。也会有人困惑为什么 loss 是负号这是因为我们希望增大高优势动作的概率而梯度上升等价于对负的交叉熵做梯度下降写成 loss 自然是负数。2.2 Critic 网络怎样评价动作Critic 是价值网络输入也是状态 s输出是状态价值 V(s)。它的训练目标很直接让 V(s) 逼近真实的期望回报。最常用的做法是用 TD 误差作为学习信号δt rt γV(s) − V(s)对于 Critic 来说rt γV(s) 是它要逼近的目标值V(s) 是当前预测两者的差就是一个回归 Loss通常是 MSE 或者 smooth-L1。看到这个公式你会发现Critic 其实相当于一个在线的价值评估器它没有去存一张表而是通过神经网络泛化到未见过的状态。Critic 的另一层身份是给 Actor 的梯度当裁判。Actor 采样了动作 a 之后Critic 算出 δt这个 δt 可以近似看作这个动作比当前状态平均水平好多少。δt 为正说明动作值得鼓励梯度往增大该动作概率的方向走δt 为负说明动作低于平均水平梯度往减小概率的方向走。2.3 单步更新流程的拆解下面我把一个完整单步更新的流程列出来这是整个 AC 系列所有算法的骨架从环境中拿到当前状态 sActor 根据策略 πθ(·|s) 采样一个动作 a环境执行动作返回奖励 r、下一状态 s′ 和回合结束标志 doneCritic 预测 V(s) 和 V(s′)如果本回合结束V(s′) 退化为 0否则按公式计算 TD 误差 δt用 δt 作为 Critic 的回归目标更新 Critic 参数用 δt 作为优势估计计算 Actor 的 loss −log πθ(a|s) · δt乘以一个合适系数更新 Actor 参数。这里有个非常容易踩的细节Actor 更新时δt 要当作常量来处理也就是要 detach 掉梯度。你对 δt 本身求梯度是完全没意义的它的意义是指导 Actor 往哪个方向调而不是让 Actor 去指挥 Critic 的输出变得更大。实操中如果你的梯度回传关系写错了哪怕直觉上看起来 loss 表达式一样训练也会非常不稳定。还可以用一张表记住两个网络的职责差异维度ActorCritic做什么输出策略决定动作评估状态价值预测期望回报更新依据对数概率 × 优势回归到 TD 目标梯度方向增大/减小动作概率降低价值预测误差常见问题过早确定化、探索不足过拟合、偏差累积3. 数学视角优势函数、TD 误差与策略梯度定理3.1 策略梯度定理的推导脉络要理解 AC 为什么把对数概率 × 优势作为标准梯度形式最好看一眼策略梯度定理的本质。策略目标函数是期望回报 J(θ) Eτ[∑γᵗrₜ]对它求梯度关键在于把回报 Gt 和策略参数 θ 分离。借助对数导数技巧 log 的导数是倒数乘导数期望内部的乘积可以改写成∇θ E[Gt] E[Gt · ∇θ log πθ(a|s)]这是整个策略梯度家族的地基。推导中有一个微妙但重要的点回报 Gt 本身也是由策略采样出来的理论上它对 θ 也有依赖但策略梯度定理证明当我们把 ∇θ log πθ 放进期望里再用采样轨迹来估计时可以忽略回报对 θ 的直接依赖。这也是为什么我们可以放心地拿一条轨迹估计梯度而不是像某些朴素直觉担心的那样梯度对 Gt 也要展开。3.2 TD 误差与优势函数的关系优势函数 A(s,a) 的定义是 Q(s,a) − V(s)代表这个动作相对平均水平的超额收益。理论上用它来做策略梯度最好因为方差比直接用 Gt 低很多但 Q 也要学一个网络任务繁重。实际 AC 中常用 TD 误差 δt 来近似优势为什么可以我们来验证一下。对 TD 误差在给定状态和动作下求期望E[δt | s,a] E[rt γV(s′) | s,a] − V(s)E[rt γV(s′) | s,a] 就是动作价值 Q(s,a) 的定义前提是 V 是准确的价值函数。因此当 Critic 足够准的时候TD 误差的期望恰好等于优势函数E[δt | s,a] ≈ Q(s,a) − V(s) A(s,a)但注意这个结论依赖 Critic 的准确度。Critic 不准确时δt 是有偏的。这就是 AC 算法架构里偏差的主要来源。不过在大多数工程场景里一点偏差换大幅降低方差整体是划算的这也是 AC 能成为主流工作范式的原因。3.3 GAE 与偏差-方差权衡既然知道了偏差和方差这对矛盾一个自然的想法是能不能在 TD低方差高偏差和蒙特卡洛低偏差高方差之间插值通用优势估计器 GAE 就是干这事的。它把多步 TD 误差按衰减系数 λ 加权求和Â̂t^GAE δt γλδt1 (γλ)²δt2 ...λ 0 时退化成一步 TDλ 1 时等价于蒙特卡洛回报减去基线。你可以把 λ 理解成信任实际情况的程度越大越依赖真实采样累计回报越小越依赖 Critic 的自我预测。实际调参时 λ 取 0.95 是相当常见的默认值因为它在多数环境里兼顾了稳定性和收敛速度。明白这一点你就懂了 GAE 为什么是 AC 系列里最常用的优势估计器——它把好坏两个世界用一根旋钮接了起来。4. AC 家族演进A2C、DDPG、TRPO 与 PPO 的逻辑线4.1 A2C/A3C多环境并行解决样本不足最原始的 AC 是用一条环境交互流一路跑到底样本相关性强一个回合的数据用完就丢效率很低。A3C 的思路是用多个并行的环境各自采样把梯度汇总起来更新共享的 Actor 和 Critic。后来大家发现 A3C 里异步更新带来的复杂度并不全是好处索性改成同步的 A2C——等所有并行环境都算完梯度统一更新一次参数。这个细节值得展开A2C 和 A3C 在理论上性能接近但 A2C 因为同步机制更容易复现和调试。我在实践里观察到并行环境数量从 4 加到 8训练速度提升明显再多收益就开始递减了因为梯度平均后单步更新带来的信息增量变小而且每个环境需要同样的计算资源。并行 AC 的价值在于打散样本相关性让梯度方向更像一个平均意见而不是某一个极端样本的偏激判断。4.2 DDPG连续动作空间的经验回放改造前面说过价值函数方法处理连续动作空间很难纯策略梯度方法样本效率低。DDPG 把 AC 框架用在了连续控制任务上关键改造有三点。第一Actor 输出确定性动作而不是随机策略也就是 a μθ(s)训练时为了探索再给动作加上噪声。第二引入经验回放池把交互样本存起来随机抽取打破相关性这是从 DQN 借来的招。第三Critic 学习 Q(s,a)不再只学 V(s)Actor 的梯度通过 Q 对动作的偏导传导∇θ J ≈ E[ ∇a Q(s,a)|aμθ(s) · ∇θ μθ(s) ]用大白话说决定动作往哪个方向调要看 Critic 对动作的敏感方向。这套机制在机器人控制这类环境里效果惊艳但 DDPG 的稳定性一直被人诟病超参数稍微不对就容易发散后面 TD3 算法又针对性地做了双 Critic 和延迟更新等改进主线依然是 AC 框架。4.3 TRPO 到 PPO信任域计算成本与裁剪捷径AC 家族另一个重要分支是围绕更新幅度别太大来设计的。TRPO 用 KL 散度约束新策略和旧策略之间的距离保证每次更新都在一个信任域内听起来很稳健但算 KL 约束需要解一个带约束的优化问题工程上又复杂又慢。PPO 的做法聪明得多直接在目标函数里把策略更新比率限制住。策略比率 ρt πθ(a|s) / π_old(a|s)PPO 的剪辑目标是L E[ min( ρt · Ât, clip(ρt, 1−ε, 1ε) · Ât ) ]这个公式的意思是如果这个动作的新概率和旧概率差太多就把贡献的梯度硬剪到一个范围里。如果 Ât 是正的但新概率涨得太猛梯度就被 1ε 的上限拦住如果 Ât 是负的但新概率跌得太猛也被 1−ε 的下限兜住。这一下就把别更新太贪的诉求用最朴素的元素级操作实现了不需要算任何复杂的二阶信息GPU 上跑得飞快。从 TRPO 到 PPO 的演进给所有做工程的人一个启示理论再漂亮落地时要优先考虑计算成本可控的近似方案。5. 迷你 AC 的实现要点与踩坑记录5.1 网络结构设计和超参数经验值不管用哪个 AC 变体你动手实现时肯定会遇到设计细节。我的经验是先从一个最小的共享网络开始别一上来就叠大模型。推荐的结构是两层 64 或 128 的全连接层作为共享特征提取器然后分叉出 Actor head 和 Critic head。这样两个网络共享底层特征表示训练效率高参数总量也小。如果你发现两个任务在特征层面需求差别大再把共享层拆开也来得及。超参数这块我实测相对稳妥的起点组合是Actor 和 Critic 学习率都取 3e-4 左右RMSProp 或 Adam折扣因子 γ 取 0.99GAE 的 λ 取 0.95PPO 的话 clip 范围 ε 取 0.2entropy 系数取 0.01。如果你是刚跑通第一个环境先从这些默认值出发比照着网上各种神秘的黑科技参数去调要靠谱得多。5.2 训练崩溃信号与排查方法AC 系列到 PPO 已经相当稳了但新手还是会遇到训练崩坏。我把自己踩过的坑总结成一张排查表崩溃信号最可能原因检查点Loss 爆炸成 NaN学习率太高 / 回报未归一化把回报除以标准差设置梯度裁剪行为越来越同质化entropy 系数太小探索消失检查策略熵适当调大 entropy coefficientCritic loss 一直不降目标值本身在漂移网络震荡用 GAE 前先确认 V 的初始化合理平均回报上不去也没下降优势估计完全失效信号太弱可视化 TD 误差/优势数值是否长期接近 0回报归一化这个点特别想强调。强化学习的回报尺度在不同环境下差异巨大有的环境奖励在十位数有的在 0.01 级别。如果不做归一化同一个学习率在一套环境里合适、在另一套环境里就会爆炸。我现在的习惯是计算 GAE 优势后对优势做一次标准化再进 Actor 的 loss。这个操作成本极低效果却立竿见影。5.3 收敛检查清单与伪代码参考训练到后期怎么判断模型收敛了不要只看一张曲线就下结论。我一般同时盯四个信号滑动平均回报持续保持在某个区间不再上升Critic 的预测价值和实际回报曲线越来越贴近策略输出分布的熵维持在一个合理范围没有降到 0 附近万能模板里那个回合数在几百上千之间了后面的提升显著放缓。下面给一份我能跑通迷你 CartPole 的简化 AC 更新代码核心教学版去掉了 GAE 和并行环境这些工程细节方便你把主干逻辑看懂import torch class ActorCritic(torch.nn.Module): def __init__(self, state_dim, action_dim): super().__init__() self.actor torch.nn.Linear(state_dim, 128) self.actor_head torch.nn.Linear(128, action_dim) self.critic torch.nn.Linear(state_dim, 128) self.critic_head torch.nn.Linear(128, 1) def forward(self, x): a torch.relu(self.actor(x)) v torch.relu(self.critic(x)) logits self.actor_head(a) value self.critic_head(v) return logits, value # 伪代码循环 for _ in range(total_steps): logits, value model(state) dist torch.distributions.Categorical(logitslogits) action dist.sample() next_state, reward, done, _ env.step(action.item()) _, next_value model(next_state) # 用 TD 误差当作优势估计 td reward 0.99 * next_value * (1 - int(done)) - value # 更新 Critic critic_loss td.detach() ** 2 # 更新 Actor log_prob dist.log_prob(action) actor_loss -(log_prob * td.detach()).mean() # 合并反向传播注意 td 要 detach total_loss critic_loss actor_loss optimizer.zero_grad() total_loss.backward() optimizer.step()这段代码是标准的单步 AC真实项目会用 GAE 把多步的优势都累计进来用优势标准化甚至加熵正则。但如果你能把这段代码跑起来并且看懂每一步的数值流向你就具备了给任何 AC 变体加功能的基础。最后再分享一个小技巧把 TD 误差的数值打印出来看。正常训练下它应该围绕 0 附近小幅波动如果某个阶段突然出现大量绝对值很大的值说明 Critic 和环境的吻合度罢工了这时候别急着加网络层数先回头查一下回报值域、折扣因子和更新频率。把这个习惯保持住整个 AC 家族你都会玩得很顺。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询