从QAC到A2C:Actor-Critic强化学习方法与离轨修正实战解析 首先得承认Actor-Critic这个名字很容易劝退刚接触强化学习的人。我当年学这里的时候也绕了不少弯REINFORCE都知道Q-learning也知道但为什么要把策略网络和价值网络放在一起训QAC、A2C、Off-policy AC这些变体之间到底差在哪直到自己动手在几个模拟环境里跑了一圈才把它们的脉络理顺。这篇东西就是按我自己的理解讲清楚这条线从最朴素的QAC聊到A2C再讲到离轨(Off-policy)方法里的关键修正最后是一份能直接照着调参的踩坑记录。适合已经把策略梯度、时序差分这些基础概念过了一遍、准备自己写代码的读者。任何刚接触深度强化学习的人看到A2C实现代码的时候都会有一种感觉明明算法公式写得很优雅怎么落到代码上全是细节这也是我写这篇文章的出发点。QAC作为最原始的Actor-Critic框架理论简单但实践价值有限A2C是真正能用起来的入门框架离轨AC则是通往更高样本效率的必经之路。三个方法串起来正好是理解策略梯度扩展的一条主线。1. 为什么要有Actor-Critic策略梯度与值函数的一场联姻先从策略梯度方法说起。REINFORCE的做法是让策略网络沿着“累积回报”的方向更新梯度也就是∇θ J(θ) E[ Gt * ∇θ log π(a|s) ]这里Gt是从t时刻到回合结束的折扣回报。这个梯度估计在数学上是无偏的方差却大得惊人。原因很简单Gt是一个从随机环境中采样得到的随机变量包含了很多与当前动作无关的噪声比如环境本身的随机性、未来所有随机决策的影响。同样的状态动作对玩十次十次回报差别巨大直接用这个梯度更新策略参数就会像喝醉了一样乱晃。我在一个连续控制任务里跑过上千次REINFORCE回合奖励曲线不是在上升而是在振荡中缓慢爬行。值函数方法比如Q-learning正好相反。它用自举方式估计Q值单步更新方差小但有偏差——Q网络的估计误差会随着环境的动态逐步传播。而且原始的DQN处理离散动作对于连续动作要么离散化要么就用更复杂的策略表示。值函数法本身不输出策略只能在训练中根据Q值贪心选择动作。Actor-Critic的思路就是让这两个方法互相补短板。Actor负责输出策略π(a|s)Critic负责估计一个“评价”信号——可以是Q值、状态值或者优势值——来替代原始的累积回报Gt。这样Actor不再依靠“蒙特卡洛采样的噪声回报”而是依靠Critic给出的低方差评价。这个评价不可能完美有偏差但偏差相对平稳通常比原始回报更容易学习。这里的关键认知是Actor更新信号不需要绝对准确只需要相对一致。打个比方REINFORCE是在用一个高考阅卷老师的随机打分来调整答题策略分数波动极大Actor-Critic则是用一个常驻导师给每个动作打分虽然导师打分可能有点偏但比随机波动稳定得多。所以“策略网络 价值网络”不是简单的拼凑而是用一种结构性的偏差换取可接受的方差让策略更新真正顺畅起来。2. 第一站QAC用Q值当老师的最朴素Actor-Critic2.1 QAC的核心直觉与算法流程QACQ-learning Actor-Critic是History里第一个自然出现的Actor-Critic结构。它的想法非常直接REINFORCE的更新信号从Gt换成Q(s,a)。策略梯度积分可以改写成∇θ J(θ) E[ Qπ(s,a) ∇θ log π(a|s) ]QAC的做法是Actor策略网络π(a|s)参数θ用上面的梯度更新。CriticQ网络Q(s,a)参数w用SARSA/Q-learning方式更新比如用时序差分目标。算法跑起来大致是这样用当前策略π初始化行为策略与环境交互得到(s, a, r, s)。更新Critic误差 r γ Q(s,a) - Q(s,a)其中a由目标策略给出如果走SARSA或max_a Q(s,a)如果走Q-learning。用Critic的Q(s,a)作为Actor的梯度加权值更新策略参数。重复。这个框架逻辑很顺。AC结构里的“批评家”用更平滑的Q值给学生打分学生就照着分数调整自己的动作概率。Q值高的动作出现概率就上调Q值低的动作概率就下调。2.2 QAC为什么不好用复合偏差与双网络收敛问题理论上没什么问题实践里QAC几乎不会给你一个漂亮的训练曲线。问题出在两个地方。第一Critic的Q值本身还在学习过程中误差很大。Q网络初始参数全是随机估计出来的Q值可能是噪音。直接拿这种未收敛的Q值到Actor的更新里相当于导师自己还没备课就开始打分学生被带偏。更麻烦的是Actor更新后策略变了接下来采集到的新数据分布也变了Critic又跟着适应新分布然后再次给出新的打分。两个网络互相追赶整个系统像一个互相喂错答案的闭环。第二QAC最病态的问题在于更新信号方差还是不小。Q(s,a)虽然比Gt平滑一些但依然包含了“这个状态整体好不好”的偏移。比如某个状态本身就很差所有动作的Q值都低Actor在这个状态无论如何都要降低所有动作的概率这是不合理的——有时候调整幅度过大直接让策略崩塌。我自己曾经在某个迷宫导航任务里试过QAC勉强让Critic收敛后Actor依然在原地晃荡。原因就是Q函数作为更新信号缺少一个“基准量”。这就是为什么出现了更聪明的A2C。3. 第二个版本A2C优势函数成为主角3.1 从Q值到A值为什么减去V能降方差A2CAdvantage Actor-Critic做的事情就是把QAC里的Q(s,a)替换成优势函数A(s,a) Q(s,a) - V(s)这里V(s)是状态值函数。为什么要减掉V(s)核心原因是减掉了一个只依赖于状态s、与动作a无关的基准线。期望上这个减法不会改变策略梯度的期望值因为E_{a~π}[ V(s) ∇θ log π(a|s) ] V(s) * E[∇θ log π(a|s)] 0但在方差上A(s,a)远比Q(s,a)小。直观理解一个状态的平均情况已经被V(s)“扣除”了剩下的优势告诉你的只是“这个动作比平均水平好多少”。等同于老师打分时去掉基准分只看你为总分贡献的增量部分。这样每个状态下的动作调整幅度就更集中方向更清晰。理论推导中优势函数的估计不需要额外再搭一套Q网络。常见做法是用TD误差近似优势A(s,a) ≈ δ r γ V(s) - V(s)这是单步近似。用多步或者GAE可以进一步平衡偏差和方差。A2C在实践中基本都用GAE(λ)我建议第一次实现先用单步TD误差把基线搞清楚再上GAE。3.2 A2C完整算法与Pytorch代码骨架A2C的训练流程是典型的“收集一段经验然后更新”。核心特点是多环境并行采样同步更新参数。具体步骤创建N个并行的环境实例每个环境都独立跑自己的轨迹。在每一步用当前的策略网络对所有N个环境同时采样动作得到(s, a, r, s)。累积到一定步数比如n-step或T步用Critic网络计算每个状态的价值估计算出优势。计算Actor的损失负值优势 * log π(a|s)加上熵正则项。计算Critic的损失MSE(V(s), return)。反向传播更新两个网络清空临时存储继续循环。下面是我用PyTorch写的一个核心更新片段算是A2C最精简的骨架q 0 returns [] for t in reversed(range(len(rewards))): q rewards[t] gamma * q returns.insert(0, q) values critic(states) advantages torch.tensor(returns) - values.squeeze(1) log_probs actor.get_log_prob(states, actions) policy_loss -(log_probs * advantages.detach()).mean() entropy actor.get_entropy(states) policy_loss policy_loss - entropy_coef * entropy.mean() value_loss 0.5 * (torch.tensor(returns) - values.squeeze(1)).pow(2).mean() optimizer_p.zero_grad() policy_loss.backward() optimizer_p.step() optimizer_v.zero_grad() value_loss.backward() optimizer_v.step()注意几个细节优势要.detach()不能让策略梯度回传到价值网络。这里的returns是蒙特卡洛回报用TD目标的话要改写成r γ * next_value。熵正则项的作用是防止策略提前收敛到确定性动作保持探索能力。系数一般0.01~0.1调太大会导致策略一直乱逛。A2C的更新比较频繁但每次用的数据量相对有限T步 × N环境。所以并行环境数量N很关键一般取8~16比较稳。N太小数据不相关方差大N太大虽然稳定但每个环境都要独立跑计算成本高。3.3 A2C调参的坑学习率、n-step、并行数量A2C看起来就那几个参数真正跑起来全是坑。先说学习率。A2C用的是两个网络共享更新步调如果策略网络学习率太大策略会迅速偏向某几个动作导致熵迅速塌缩然后Critic还在后面追整个系统突然崩塌。我见过最典型的表现是前两万步奖励稳步上升两万五千步突然掉到地板再也回不来。这时候通常是策略崩了不是环境出了问题。n-step或GAE中的λ也很有讲究。单步TD的偏差低但方差高因为需要精确的价值估计多步回报偏差高但方差低——但前提是Critic估得还行。我自己的经验是在类似CartPole这种小任务上用5步回报比单步效果好很多收敛速度快了将近一半。在更复杂的任务中GAE的λ取0.95左右是常用值。并行环境数量直接影响训练稳定性。只开4个环境的话每次更新的数据很稀疏策略梯度噪声大开32个环境则占用资源多且每个环境如果随机性太大可能稀释了单步学习的信号。我常用16个并行环境作为折中跑各种环境基本都稳定。4. 离轨AC方法Off-policy下的修正与重塑4.1 什么是离轨重要性采样与比率剪裁讨论到现在A2C和QAC都是“在轨”方法on-policy意思是更新用的数据必须来自当前策略。每次策略一变之前收集的样本就“过期”了。这导致样本效率低因为每轮只能收集少量数据然后用一次就扔。离轨off-policy方法允许你通过一个行为策略behavior policyβ收集数据然后用来更新另一个目标策略target policyπ。这样历史数据可以存进回放缓冲区反复使用样本效率大幅提升。但直接用β采集的数据去估计π的期望需要乘上重要性采样比率ρ_t π(a|s) / β(a|s)在AC框架里基于重要性采样的离轨策略梯度为∇θ J(θ) ≈ E_{β}[ ρ * Qπ(s,a) ∇θ log π(a|s) ]注意这里的Qπ是目标策略的Q值不是行为策略的。如果你只能拿到Qβ严格来说这个等式不成立所以在实际中要么用四重相关方法近似要么利用一个重要的理论结果——当Critic使用Qπ且可以离线评估时梯度可写成∇θ J(θ) ≈ E_{β}[ Qπ(s,a) ∇θ log π(a|s) ]也就是说只要Critic估计的是目标策略的动作值重要性采样比率就可以省掉。这个结论出自Degris等于2012年发表的分析很多人在这一点上栽过跟头——他们以为离轨AC必须乘重要性权重结果权重一乘方差爆炸得更快。我建议先记住结论**离轨AC中真正的关键是让Critic逼近目标策略的Q函数而不是机械地套重要性比率。**至于乘不乘权重取决于你采样时的策略。4.2 经典离轨ACOff-PAC的训练流程与DDPG的关系经典离轨ACOff-Policy Actor-Critic我简单梳理它的流程维护行为策略β通常带噪声的探索策略和目标策略π待学习的策略。用β在环境中收集样本存入经验回放池。Critic通过Q-learning离轨自举更新目标是 r γ * V(s)其中V由目标策略的状态值给出。Actor利用Q函数提供的梯度更新θ α * ∇θ π(a|s) * Q(s,a)。实际工程里更常用的是带经验回放的DDPG——它是确定性策略的离轨ACActor输出确定性动作而不是分布Critic直接用Q值传播梯度到Actor。DDPG和Off-PAC在思想上一脉相承只是动作表示换了形式。因此做离轨AC实验时我建议先用DDPG作为参照物。它在连续控制上足够稳定代码也简单跑通之后把它换成随机策略的Off-PAC就能对比出重要性采样对梯度的真实影响。4.3 离轨AC实操中的重要性采样风险如果你确实需要乘重要性采样的场景例如用旧策略数据训练新策略最大的风险是比率乘积爆炸。比如行为策略β对某个动作输出概率0.01目标策略π输出0.99那ρ99梯度加权非常大。一次异常样本就能把参数推飞。处理办法给ρ设置上限比如ρ_max10或者用截断的比率类似PPO的clip操作。对重要性比率做白化归一化除以当前批次中ρ的均值。对Critic和Actor分别独立参与的重要性约束减小误差传递。我之前处理过一个离轨AC任务行为策略是用ε-greedy从旧策略生成的ε0.1。听起来差距不大但实际运行时ρ的最小值可以是0最大值能到20多。不裁剪平均奖励曲线全是毛刺一裁剪曲线才开始平滑上升。别迷信理论推导里的无偏性工程上一定是先求稳。5. 三兄弟对比什么时候选谁三种方法适合的场景完全不一样。我做了一个简单但直观的对比表方法更新信号数据用途样本效率稳定性典型场景QACQ(s,a)在线按轨低差双网络互相干扰理论演示几乎不用A2C优势A(s,a)在线按轨中等较好并行与熵正则加持离散/小规模连续控制入门前选离轨ACQ(s,a)或A(s,a)可带ρ修正经验回放离轨高复杂依赖修正策略样本昂贵、需要复用历史数据的大规模问题QAC只适合你理解概念时用。A2C是我推荐给新人的第一个完整AC框架因为代码简洁训练稳定调参试错空间大。离轨AC则更贴近生产环境的需求你很难在一个真实物理系统上跑几十万步在线采样这时必须用历史数据反复回放。但离轨AC的工程实现细节远比A2C复杂需要处理回放、目标网络、重要性采样、策略软更新等一系列机制。选型时我也考虑计算资源的约束。A2C需要同时开多个环境并行CPU占用率高离轨AC配合经验回放单环境也能跑适合显卡训练为主、环境交互较慢的情形。6. 实战踩坑与调试技巧6.1 常见问题速查表我把自己在三种方法上踩过的坑整理成了一张速查表写代码遇到问题时先对着查一遍症状可能原因建议处理A2C前几轮奖励上升然后突然崩溃策略熵塌缩或学习率过大降低学习率提高熵系数加梯度裁剪QAC不收敛且Critic损失震荡Q网络更新过快Actor被噪声Q误导降低Critic学习率用优势替代Q或直接换A2C离轨AC训练初期待方差爆炸重要性采样比率过大加比率裁剪上限设10~20归一化权重Critic很快收敛但Actor没有提升优势信号太小或被V完全吸收调大优势权重A2C中可乘一个常数用GAE λ并行多个环境时结果差异巨大环境随机种子或初始化导致的数据分布不均固定种子或者增加环境数量取平均连续动作任务中A2C动作方差塌缩熵系数过低调大熵系数或对log_std加约束6.2 一次调参实录A2C在CartPole上的曲线变化举一个具体例子。我在某个简单的平衡杆环境里跑A2C初始设定学习率2e-3熵系数0.0116个并行环境5步回报。一开始跑了三千步奖励均值在200附近震荡上不去。我把学习率降到1e-3熵系数提到0.05跑一万步后奖励均值到了450。随后我把n-step从5改成20结果曲线反而变差。原因是在这个环境里20步的回报估计已经太靠蒙特卡洛方差重新变大。最终我回归到5步回报加了一个优势归一化——把优势除以它的标准差——训练曲线立刻变平滑稳态奖励稳定在490。这次调参给我的体会是A2C的好不在于找到一个万能参数而在于它能通过几个旋钮控制策略更新的激进程度。优势归一化这个技巧在几乎所有AC方法里都很管用因为不同状态的优势尺度差别很大归一化之后梯度更新方向更一致。再说一个离轨AC的失败案例。我尝试从历史回放数据中更新策略没做重要性裁减。训练到第三万步时策略突然输出NAN检查发现是因为ρ的值出现了极大值让梯度爆炸把Actor网络权重全部冲散。加上ρ裁剪之后NAN问题彻底消失。这再次验证了离轨AC里的一个铁律稳定优先宁可有偏不要爆炸。这跟传统的无偏性理论有冲突但你在工程里必须妥协。最后分享一个小技巧AC方法调试时别只盯着奖励曲线。要把策略熵、价值估计值、TD误差、优势均值这些辅助曲线一起打印出来。有一次A2C奖励曲线看起来很差但熵和TD误差都很平稳最后发现是环境本身的奖励尺度太小导致Actor的梯度被Critic的梯度淹没。把优势乘一个权重系数后立刻正常。这种问题不看辅助曲线根本定位不到。QAC、A2C、离轨AC这三个方法本质上都是在回答同一个问题网络该拿什么当老师的评价。QAC用一个有偏且波动大的Q值A2C用优势把Q减去状态均值让评价更集中离轨AC则引入了历史数据和修正机制让评价可以反复使用。理解了这条线再去看PPO、DDPG、SAC这些高级算法时你会发现它们都只是这条主线上的变体。建议你动手把A2C先完整实现一遍然后在此基础上加经验回放和重要性修正体会“在轨”到“离轨”的关键跃迁。只有亲手把策略梯度从理论公式变成能收敛的代码才算真正入了门。