尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

策略梯度算法详解:从REINFORCE到PPO的原理、推导与实战排查

策略梯度算法详解:从REINFORCE到PPO的原理、推导与实战排查 策略梯度这块内容我其实很早就想写一篇足够系统的梳理了。外面讲策略梯度的文章要么只讲一个PPO要么数学推导一笔带过要么代码和理论完全对不上初学者想靠碎片信息搭起完整认知框架确实很难。这篇我打算换个思路把策略梯度从“为什么要用它”到“它到底在优化什么”再到“REINFORCE、Actor-Critic、PPO这些变体各自在折腾什么”最后到“训练时挂掉的常见原因和排查方法”完整串一遍。内容比较多建议收藏了慢慢看或者直接拉到对应章节。1. 策略梯度的核心思想从“选动作”到“调概率”1.1 为什么强化学习需要策略梯度强化学习解决的核心问题是智能体在未知环境里通过试错学会一套决策方式让长期累积奖励最大化。传统方法里Q-learning这类基于价值的方法先把每个状态-动作对的价值函数估出来然后靠贪心策略“选当前价值最高的动作”——这种做法简单有效但有几个先天问题。第一动作空间一旦连续Q-learning就非常难受。你没办法枚举所有动作来求最大化只能靠优化算法去逼近那个最大值而逼近过程本身又引入误差。第二基于价值的方法学出来的是确定性策略很多场景下最优策略其实是带随机性的。比如扑克里的虚张声势如果每次都做同样的动作对手很快就能摸透你的套路。第三价值方法对策略的改进是“间接”的——价值函数估准了策略自然就好了。但价值函数稍微估偏一点策略就可能跟着跑偏这种级联误差在复杂环境里尤其致命。策略梯度换了一条路我不估价值再转策略我直接把策略本身参数化然后用梯度上升去调参数让累积奖励的期望值变大。这就像“既然是爬山我直接朝山脚坡度最陡的方向迈步而不是先画一张等高线地图再找路”。对于连续控制、高维动作空间、随机策略建模这些场景策略梯度天然更契合。1.2 一行公式看懂策略梯度策略梯度的目标函数是所有轨迹的累积奖励期望通常写作J(θ) E_{τ ~ π_θ}[ R(τ) ]这里的 τ 是一条轨迹状态、动作、奖励的序列R(τ) 是这条轨迹的总回报π_θ 是神经网络参数化的策略。要最大化 J(θ)最直接的办法就是对 θ 求梯度然后做梯度上升。但这个梯度不是简单求导能算出来的因为期望的分布本身就依赖 θ而且环境动态转移概率通常是未知的。这里就需要用到那个经典的技巧——log derivative trick把对期望求梯度转化成对策略对数概率求梯度∇J(θ) E_{τ ~ π_θ}[ ∇log π_θ(τ) * R(τ) ]这个式子推导的核心是想办法把“采样轨迹”和“计算梯度”解耦我们无法对 R(τ) 求导但我们可以对 log π_θ(τ) 求导而 π_θ(τ) 只由策略本身决定不依赖环境动态。所以哪怕环境是完全黑盒只要我们能采样轨迹就能用蒙特卡洛的方式估计出策略梯度然后用梯度上升不断改进策略。后面所有的变体像REINFORCE的减baseline、Actor-Critic的引入价值网络、PPO的clip操作本质上都是在想方设法让这个梯度的估计更稳定、方差更低同时不引入过大的偏差。记住这条主线你再看任何策略梯度算法都不会迷路。2. 数学推导与关键公式梯度为什么长这样2.1 log derivative trick的前世今生刚才给的公式有点“从天而降”我把它拆开推导一遍。先把目标函数按定义展开J(θ) ∫ π_θ(τ) R(τ) dτ对 θ 求梯度因为 R(τ) 不依赖 θ所以梯度只作用在 π_θ(τ) 上∇J(θ) ∫ ∇π_θ(τ) R(τ) dτ现在问题是∇π_θ(τ) 本身不是概率分布没法直接做蒙特卡洛期望。这时候用恒等式 ∇log z ∇z / z得到 ∇z z * ∇log z代入∇J(θ) ∫ π_θ(τ) ∇log π_θ(τ) R(τ) dτ这个形式就是 E_{τ}[ ∇log π_θ(τ) * R(τ) ]齐活。这一串变换只有一个目的把梯度搬到一个我们能在采样中算出来的量上。这个“搬移”的过程一点都没丢失信息所以它是个无偏估计。接下来是马尔可夫链的乘积分解轨迹的概率 π_θ(τ) 等于初始状态概率、每一步的转移概率和策略概率的连乘π_θ(τ) p(s_0) ∏_{t0}^{T} π_θ(a_t|s_t) p(s_{t1}|s_t, a_t)取对数后连乘变成连加而转移概率项 p(s_{t1}|s_t, a_t) 跟 θ 无关求导时就没了。这就是策略梯度的“美丽之处”你不需要知道环境的动力学模型就能算出策略的梯度。2.2 从轨迹公式到单步公式上面那个公式是对整条轨迹求梯度实际应用时通常会把轨迹展开成逐时间步的形式。因为轨迹的回报 R(τ) 可以拆成每一步的折扣回报经过一系列重排和推导可以得到更常用的形式∇J(θ) E_{s_t, a_t ~ π_θ}[ ∇log π_θ(a_t|s_t) * A_t ]这里 A_t 是优势函数代表“当前状态-动作对相对平均水平的超额回报”。这个变换有一个直观解释如果某个动作带来的回报高于平均水平我就提高它的概率如果低于平均水平我就压低它的概率。调整幅度和优势大小成正比调节速度由学习率控制。这跟人类从经验中学习的方式很像——做对了就记下“下次还这么做”做错了就记下“下次换个做法”。策略梯度只是把这个直觉变成了严格的数学操作。2.3 为什么需要baseline和优势函数REINFORCE算法Williams在1992年提出的经典策略梯度算法直接用轨迹回报作为 R(τ) 来估计梯度理论上是无偏的但实际使用中方差大得惊人。方差大的原因是不同轨迹之间回报的绝对数值差异很大而梯度更新方向被这些绝对数值主导导致策略参数剧烈震荡训练极度不稳定。解决方向是找到一个“减法项”——一个只依赖状态、不依赖动作的量从回报中减掉。这个量叫baseline最常用的是状态价值函数 V(s)。减去baseline后梯度期望不变这是可以严格证明的但方差会显著下降。减去baseline在数学上为什么不会改变期望关键在于减掉的项和动作无关。具体来说把梯度拆开看那个减掉的部分可以分解成“每个状态的概率加权和”乘以“该状态下期望的梯度”而每个状态的概率加权和正好是1所以整体加总为0。这就是baseline不损害无偏性的原因。从baseline再往前走一步就是优势函数 A(s,a) Q(s,a) - V(s)。QA告诉你这个动作在期望上有多好V告诉你在当前状态下平均有多好两者相减就是“这个动作比一般动作好多少”。用优势替代回报相当于把回报先按状态标准化了一遍梯度的“信噪比”大幅提升。这一改进是策略梯度从理论走向实践的关键一步。3. 主流变体详解REINFORCE、Actor-Critic与PPO3.1 REINFORCE最朴素的策略梯度REINFORCE是策略梯度家族的老祖宗思路极其直白采样一整条完整的轨迹计算累积回报然后对这条轨迹中的每一步动作都计算梯度用累积回报作为权重去更新策略。算法代码核心就几行# 伪代码REINFORCE for episode in range(n_episode): states, actions, rewards collect_trajectory(env, policy) # 从轨迹末尾往回计算折扣回报 returns compute_discounted_returns(rewards, gamma0.99) # 每一步用一个baseline通常是状态均值减一下降低方差 advantage returns - returns.mean() # 最大化优势期望最小化交叉熵 policy_loss -(log_prob(actions) * advantage).mean() optimizer.zero_grad() policy_loss.backward() optimizer.step()实际代码中log_prob一般通过神经网络输出动作概率分布后取对数得到。在PyTorch里这通常用categorical.log_prob(action)直接算出来。整个过程不需要任何价值网络实现简单逻辑直观。但 REINFORCE 的致命弱点也很明显一条轨迹从头到尾充满随机性某个动作在早期可能不是最优的但后续几步运气好拿了大奖励这个动作也跟着被“奖励”贡献了噪声方差爆炸。另外整条轨迹采样完才能更新学习速度极慢样本效率很低。再一个如果某条轨迹回报特别高梯度更新幅度会异常大直接崩掉训练。实操中我用REINFORCE跑过最简单的CartPole环境轨迹够短20~50步勉强能收敛但学习曲线几乎没有平滑过像心电图一样。稍微复杂一点的环境比如二维机器人导航基本就是白费算力。所以REINFORCE现在更多是教学工具用来帮助理解策略梯度的基础原理真正实用的是下面这些改进版本。3.2 Actor-Critic让策略梯度用上“价值估计”Actor-Critic的核心改进是引入一个参数化的价值网络专门负责预估状态价值或状态-动作价值这个网络就是Critic。策略网络是Actor负责做决策价值网络是Critic负责给Actor的每个动作打分。两者一起训练Critic用TD误差时间差分误差即目标价值和当前预测价值之差来拟合真实回报Actor利用Critic提供的优势信号更新策略。Actor-Critic和REINFORCE最大的区别在于REINFORCE用一条轨迹的真实回报做“事后评价”而Actor-Critic用价值网络的预测做“实时评价”。事后评价虽无偏但方差大实时评价偏差更大但方差小。两者是一种权衡而最终实用的做法是找一个折中方案。# 伪代码Actor-Critic单步更新 for step in range(max_steps): state env.reset() log_prob, value actor_critic(state) action sample(log_prob) next_state, reward, done env.step(action) next_log_prob, next_value actor_critic(next_state) # TD误差 td_target reward 0.99 * next_value * (1 - done) critic_loss mse(value, td_target.detach()) # Actor用优势TD误差做权重 advantage td_target - value.detach() actor_loss -(log_prob(action) * advantage).mean() loss actor_loss critic_loss optimizer.zero_grad() loss.backward()Actor-Critic比REINFORCE幸运的地方在于它可以每步更新而非整条轨迹更新样本效率大幅提升。但它也有自己的麻烦Actor和Critic同时在学是“两个菜鸟带路”的状态价值网络估不准策略网络就会接收到垃圾信号训练不稳定。实际调参时我经常遇到Critic快速收敛、Actor还在原地打转的情况或者反过来策略震荡导致价值网络也跟着震荡。这个问题在后来有了一个系统性解法——GAEGeneralized Advantage Estimation。GAE本质上是对“多步优势”做指数加权平均在偏差和方差之间用 λ 参数进行连续调节。λ0时它退化成一步TD偏差大但方差小λ接近1时它近似于蒙特卡洛无偏但方差大。实际中使用 λ0.95 是常见起点这个值在大量任务上都表现不错。3.3 PPO稳定性和实现复杂度之间的最佳平衡PPOProximal Policy Optimization在2017年由OpenAI提出它解决的核心问题是策略梯度更新步长怎么选。普通策略梯度对更新步长非常敏感——太大了容易把策略一下推出悬崖太小了训练慢得让人怀疑人生。TRPOTrust Region Policy Optimization用二阶信息严格约束新旧策略的KL散度效果好但计算复杂、实现麻烦不适合大规模推广。PPO的clip机制是它的灵魂。它裁掉clip新旧策略概率比将其限制在目标函数的阈值范围内粗暴但有效地防止了策略突变。具体公式展开如下# 伪代码PPO-Clip 核心计算 ratio torch.exp(new_log_prob - old_log_prob) # 新旧策略概率比 unclipped ratio * advantage clipped torch.clamp(ratio, 1 - eps, 1 eps) * advantage actor_loss -torch.min(unclipped, clipped).mean()这里的 eps 通常取0.2含义是“单次更新中任何动作的概率变化幅度不超过20%”。如果新旧策略的概率比超出这个范围梯度就会被裁断从源头阻止更新步长过大。PPO用一阶优化实现了接近TRPO的稳定性实现难度却小得多因此成为现代强化学习的事实标准。PPO还有一个关键的配套点是重要性采样。采样数据是用旧策略跑的更新却要滚到新策略上中间需要一个修正因子——新旧策略概率比。这个比率保证即使偶尔一次超出clip范围更新方向也不会失控。实践中我通常跑多个环境并行采样一次性收集上千步数据然后做多轮小batch更新再把旧策略更新为当前策略重新采样。这个流程在OpenAI的baselines实现里已经写得很通用稍微改改就能迁移到自己的任务上。3.4 变体对比与选型建议写到这里把三类算法放到一起对比算法核心思路更新方式方差/偏差适用场景REINFORCE轨迹回报加权整条轨迹结束后方差极高教学、极简单环境Actor-Critic价值网络实时评分每步或n步方差中引入偏差能够稳定训出价值网络的任务PPO新旧策略比clip多步批量更新方差低且受约束复杂环境、连续控制、大规模并行训练选型建议如果是做课程作业或理解概念老老实实从REINFORCE开始如果是真实的科研或工程项目直接上PPO不要浪费时间在裸Actor-Critic上打磨。裸Actor-Critic更像教科书里的过渡产物实际工程中很少单独用。4. 实践中的关键技巧让策略梯度真正work4.1 奖励归一化容易被忽视的一步很多人把策略梯度代码写完一跑发现损失曲线嗡嗡乱跳然后开始怀疑算法写错了。我踩过的坑是忘了对奖励或优势做归一化。策略梯度的更新幅度直接受优势值大小影响如果不同轨迹的奖励尺度差异巨大比如有的环境单步0.1有的环境单步100梯度方向就会被大奖励轨迹霸占小奖励但高效的动作被无视。通用的做法是计算一个batch里的advantage均值和方法然后标准化# 归一化 advantage advantage (advantage - advantage.mean()) / (advantage.std() 1e-8)加小常数1e-8是防除零。这个trick在PPO和A2CAdvantage Actor-Critic里几乎是标配。需要注意的是这个操作只改梯度尺度不改变相对方向因此不会破坏最优性。4.2 熵正则防止策略过早“锁死”训练后期常见的一个问题是策略退化——网络输出某个动作的概率接近1其他动作概率全部趋近于0。这听起来像是“学得很好了”但通常其实是策略提前收敛到局部最优尤其当环境探索不足时策略过早失去了探索能力。解决办法是在loss中加一个策略熵的负项让策略保持一定随机性# 熵正则项PyTorch probs torch.softmax(logits, dim-1) entropy -(probs * torch.log(probs 1e-8)).sum(dim-1).mean() actor_loss actor_loss - entropy_coef * entropy熵正则系数entropy_coef一般取0.01到0.1之间。系数太小防不住策略坍缩系数太大策略倾向“摆烂”保持高随机性学不到有效动作。我习惯在前20%的训练轮次里用较大的系数0.1之后线性衰减到0.01这样既保护了早期的探索又不影响后期的精调。4.3 折扣因子与GAE的选择折扣因子 γ 决定了模型看多远。γ接近1表示关注长期回报但方差会变大γ太小则短视容易陷入局部最优。一个常见的经验组合是 γ0.99GAE的 λ0.95。GAE在策略梯度的框架下起着“平滑优势”的作用让每一时间步的优势都被前后几步的回报信息“抹匀”。如果训练不稳定优先把 λ 调到0.9试试如果感觉学习太慢可以升到0.98。这个参数非常值得多调几次。4.4 学习率策略网络和价值网络最好分开设这算是一个进阶技巧。Actor网络策略和Critic网络价值在同一个loss里相加但收敛速度往往不一致。Critic学得通常比Actor快因为价值拟合是一个回归任务信号密度高Actor要靠Critic提供的信号更新存在延迟。如果共用一个学习率经常是Critic趋稳而Actor还在波动或者反过来。更精细的做法是分设学习率比如Actor用3e-4Critic用1e-3两者相差几倍给Critic更大步长让它更快跟上真实价值。许多高效实现还会用独立的优化器分别维护两个网络避免梯度互相拉扯。如果你发现训练时价值损失明显较大而策略损失很小可以试试提高Critic的学习率。5. 常见问题与排查技巧实录5.1 训练时loss一直没有下降正常吗先说结论策略梯度的loss不下降并不代表没在学。传统监督的loss下降是因为模型在不断逼近目标而策略梯度的actor loss是“期望回报的负值”如果策略已经在一个不错的状态loss曲线可能就不会再大幅下降甚至会上涨。真正该看的是回报均值曲线而不是loss曲线。如果回报均值曲线长时间平平无奇排查点按优先级排序检查奖励信号是否正确有没有符号搞反、检查价值网络是否收敛看TD误差曲线、检查优势归一化是否生效、检查熵值是否过早归零。5.2 reward曲线上下剧烈震荡怎么排查reward剧烈震荡是最常见的问题。可能原因有三类学习率太大导致单一batch的梯度步覆盖了太多参数空间batch_size太小导致回报信号噪声过大clip阈值设置不当导致更新幅度失控。第一优先把学习率降到原来的1/5甚至1/10经验上这个调整就能解决七成以上的震荡问题。如果降学习率没有用检查GAE的λ是不是太小λ小会让优势曲线“锯齿化”噪声信号直接传导到策略更新里。另外多环境并行采样能显著平滑训练曲线建议一次至少跑8~16个环境实例收集数据。5.3 训练后期策略完全退化动作概率全集中到一个动作上这是熵坍缩的典型症状。可以先看策略熵曲线如果熵在训练中期就掉到接近0说明策略过早放弃了探索。处理方式是加大熵正则系数甚至临时把熵正则作为主要优化目标跑几百步“解冻”策略。极端情况下可以给动作概率分布加一个小的均匀噪声比如ε-greedy强制维持探索。另外一个容易被忽视的原因是reward scale太小比如单步奖励在0.01量级网络输出差异被淹没在数值精度里学习不到区分度。这种情况把奖励乘以一个常数放大或者对advantage做强归一化效果立竿见影。5.4 一个重要心得监控熵和advantage分布训练策略梯度类算法我强烈建议在TensorBoard或wandb里至少监控四条曲线回报均值、策略熵、advantage的均值和标准差、新旧策略的KL散度。前两条判断训练健康度后两条判断更新是否合理。KL散度如果一直飙升说明每步更新都离旧策略太远PPO的clip机制可能没生效检查一下是不是忘了把old_log_prob包在no_grad里。6. 我的实操经验与建议策略梯度这个家族从REINFORCE到PPO表面上公式越来越复杂但本质思路一直没有变采样轨迹、评价好坏、调整动作概率。所有后来的改进都围绕一个目标——让“评价”的信号更稳定、更高效。我给想入门的人建议是不要直接跳到PPO就完事。先手写REINFORCE跑通CartPole感受一下什么叫“方差爆炸”再实现一个最简Actor-Critic看看价值网络的引入如何缓解这个问题最后再上手PPO你才能真正理解为什么clip操作对训练稳定性这么重要。跳过这些中间步骤你会面对一个黑盒调参器很难积累出真正的经验。最后分享一个我常用来调试的最小环境组合CartPole验证算法能不能学LunarLander连续版验证算法的稳定性MuJoCo的Hopper验证算法在连续控制上的表现——跑通这三个大部分策略梯度问题你已经有足够手感了。
返回列表