强化学习策略梯度详解:从策略本质到REINFORCE与PPO实战

发布时间:2026/10/19 19:27:28
强化学习策略梯度详解:从策略本质到REINFORCE与PPO实战
1. 策略的本质从“小马过河”说起如果只用一句话点破强化学习的灵魂我会说是“策略”。智能体怎么决策、面对某个状态选哪个动作、走一步看几步、是保守还是激进统统归结为策略。很多教程习惯先讲奖励、价值函数、Q表但回头你会发现那些东西最终都是为了一件事服务找到更好的策略。所以第一篇笔记我决定先把策略拎出来讲透。先抛一个生活化的类比。你让一个新手做菜他面对一筐食材会手足无措先放油还是先放盐火开大还是开小他需要一份“菜谱”来告诉他在“锅热了”这个状态下应该“倒油”而不是“倒水”。菜谱就是策略。强化学习里的智能体本质上就是在一遍遍试错中自己写出一份越来越好的菜谱——不是靠人教的而是靠吃出来的经验。策略的定义其实非常朴素策略是从环境状态到动作选择的映射用数学符号写就是 π(a|s)读作“在状态 s 下选择动作 a 的概率或确定值”。它回答的问题是“我现在该干什么”。这里的“现在”不是某一刻而是每一个时间步。整个过程就是在和环境不断交互观察状态 s_t按策略选动作 a_t环境返回奖励 r_t 和下一个状态 s_{t1}然后周而复始。那策略为什么值得单独拿出来深入研究因为它是整个强化学习问题的求解目标。价值函数也好、Q函数也好它们是辅助工具用来“评估”策略的。真正的“决策者”就是策略本身。业务上真正上线部署的也是策略自动驾驶的车该往左打多少方向盘、推荐系统该推哪件商品、游戏 AI 该释放哪个技能全部是策略在做最终拍板。把策略搞明白了强化学习的一半就算入了门。还有一点新手容易忽略策略形式的选择直接决定了你能用哪类算法。你选了确定性策略网络输出就是一个具体的动作值你选了随机性策略网络输出就是动作的概率分布。前者适合动作空间连续的机器人控制后者在博弈类场景里天然具备探索能力。后面会详细说这里先记住一个结论策略是强化学习的目标也是算法分类的一条主线。2. 策略的两副面孔确定性策略与随机性策略2.1 确定性与随机性的区别策略表面看就是“映射”但映射有两种风格。确定性策略记作 a μ(s)同一个状态永远输出同一个动作没有花招输入相同输出必相同。随机性策略记作 a ~ π(a|s)给定状态后输出的是一个概率分布从这个分布里采样得到动作同样的状态两次采样可能得到不同的动作。这两种风格没有绝对的优劣但适用场景差异很大。拿围棋举例职业棋手面对同一个棋局落子其实是带概率的——他会考虑几种候选着法而不是每次只认死一个点。这就是随机性策略的价值它保留了不确定性给了智能体探索的空间。反过来看机器人关节控制你让机械臂以 0.5 的概率往左、0.5 的概率往右那机械臂会原地抽搐生产线上根本没法用。这种场景就要确定性策略。从数学表达上看随机性策略的概率分布可以是离散的比如分类问题常见的 softmax 输出也可以是连续的比如高斯分布用均值 μ(s) 和方差 σ(s) 刻画一个概率密度。确定性策略大多直接用一个回归网络输出动作向量简单直接。2.2 随机策略中的探索与利用随机性策略还有一个隐藏福利它把“探索”这件事内建在了决策过程里。经典强化学习里有著名的探索-利用困境你是用当前已知最好的动作去“利用”还是尝试一些次优动作去“探索”赌它们未来会带来更高回报如果策略是确定性的你得额外加 ε-greedy 之类的扰动如果策略天生带熵比如输出一个概率分布那么每次采样天然就有可能选到非最优动作探索就自动发生了。但随机策略的探索也不是白来的。它的问题在于方差如果输出分布的熵太大动作太分散训练过程会像喝醉了酒走路——明明知道方向每一步都在踉跄。所以后来的算法普遍要控制熵的权重比如在损失函数里加一个熵正则项让策略既保持一定的随机性又不至于散得太离谱。这个熵正则的系数我建议新手从 0.01 起步调参时观察训练曲线如果策略太早就收敛到确定性分布说明探索不足可以适当调大如果一直随机乱试不收敛就调小。2.3 策略的参数化从表格到神经网络早期的强化学习用表格存策略状态有多少个动作有多少个表格就有多大。比如 10 个状态 5 个动作就是一个 10 行 5 列的矩阵每格存一个概率。这种方法在小规模环境中很直观但一碰到连续状态就崩了——自动驾驶里车速、位置、角度是连续值状态空间接近无穷大表格根本开不出来。所以现代强化学习几乎都走参数化路线用一个参数为 θ 的神经网络来表示策略记作 π_θ(a|s)。网络的输入是状态特征输出是动作或者动作分布。这意味着“策略学习”问题变成了“参数优化”问题——我们真正要操心的是怎么调这组 θ。这也是所有深度强化学习算法的共性问题目标函数怎么设计、梯度怎么算、网络怎么更新。参数化还有一层好处泛化能力。表格策略没见过某个状态就彻底抓瞎神经网络对相近状态能给出相近的输出有一定的外推能力。当然这既是优点也是隐患神经网络的泛化有时候会“自信过头”对没见过的状态输出一个离谱的动作这个问题到后面实际部署时要想办法兜底。3. 策略的标尺用价值函数给策略打分3.1 状态价值与动作价值策略本身没有高下得靠一把尺子量。强化学习里的尺子就是价值函数。两个最核心的概念状态价值 V^π(s)从状态 s 出发按照策略 π 行动未来能拿到的累计折扣奖励的期望。动作价值 Q^π(s, a)在状态 s 先执行动作 a之后按照策略 π 继续行动未来累计奖励的期望。区别就在那个“先”。V 问的是“我现在这个局面值多少钱”Q 问的是“我现在做这个动作值多少钱”。一个类比你站在十字路口V 是“站在这个路口本身的价值”Q 是“选择左转这个具体动作的价值”。显然 Q 比 V 多了一维信息也因此更难算。价值函数和策略的关系像一个嵌套循环。给定策略 π我们能用它去估 V 和 Q反过来有了准确的 V 和 Q我又能改进策略——看到哪个动作的 Q 值高就把它的概率调大。这个循环就是强化学习的核心引擎。3.2 策略评估怎么算出当前策略的价值给定一个策略 π算出它的 V^π 和 Q^π这个操作叫策略评估。原理上就是解贝尔曼方程方程的核心思想是递归当前状态的价值 即时奖励 折扣后的下一状态价值。数学上写成V^π(s) Σ_a π(a|s) Σ_{s′,r} p(s′,r|s,a) [r γV^π(s′)]这个式子看着吓人拆开就是按概率对当前状态下所有可能动作求期望再对环境的转移概率求期望。真实环境里你往往不知道 p(s′, r|s, a)所以没人真的去解这个期望而是靠采样。让智能体按策略 π 跑很多轮把每一轮得到的回报记下来平均一下这就是蒙特卡洛策略评估。它简单、无偏但是方差大需要跑很多轮才稳定。另一种做法是时序差分TD它用单步的奖励加上对下一状态的估计来更新当前状态的价值。一个经典的更新式是V(s) ← V(s) α [r γV(s′) − V(s)]方括号里那项叫 TD 误差。它用 r γV(s′) 作为对真实价值的“有偏估计”替代完整回报。这种做法方差小、也能在线学习所以被绝大多数现代算法采用。代价是引入了偏差尤其是在函数近似比如用神经网络做价值函数的情况下TD 容易出现估计偏高或偏低的问题。3.3 策略改进从“打分”到“换动作”估完价值之后怎么办答案自然是改进策略。当一个动作的价值比当前策略下所有动作的平均价值都高那就应该提高选它的概率。贪心策略改进就是最直接的思路状态 s 下选 Q(s, a) 最大的那个动作。这个逻辑好懂但它直接把随机策略变成确定性策略在探索和博弈场景里不一定好用。更温和的做法是软策略迭代用 softmax 把 Q 值变成动作概率温度参数 T 控制“软”的程度。T 大各动作概率接近探索多T 小策略趋于贪心稳定但探索少。这就是后续很多算法里 softmax 策略的雏形。现在很多强化学习框架里你看到的“行动选择概率”基本就是从这派生的。这个评估-改进循环持续迭代理论上会一步一步逼近最优策略 π*。整个过程就是广义策略迭代几乎所有基于价值的方法Q-learning、DQN 那一脉都默默遵循这个框架。理解了这条主线后面看 DQN、PPO 的论文你会发现它们都只是这个循环的某个环节做了工程化改良。4. 策略的优化器策略梯度方法的原理与实战4.1 为什么直接调策略会有困难价值函数方法走过的路线是先学价值函数再从价值函数里提取策略。策略梯度方法跳过了中间步骤直接对策略参数 θ 求梯度往让期望回报增大的方向更新。直觉上这更直接但是有一个坎期望回报 J(θ) 是轨迹上的积分可轨迹的生成过程里动作是从策略里采样得到的而采样这个操作是不可导的。你不能直接对“随机采样出来的动作”求梯度这是策略梯度方法最初让人困惑的地方。解决办法很漂亮把对数似然这个技巧用起来。对期望回报的梯度可以写成对每一步的“对数策略概率 × 回报”求期望。最终形式就是策略梯度定理∇J(θ) E[∇_θ log π_θ(a_t|s_t) · R(τ)]这个公式的意义是回报高的轨迹我们要增大其中所有动作的概率回报低的轨迹要减小其中的动作概率。这跟人类学习很像——考得好的那次把当时的解题策略记住考砸了就把当时的选择忘掉。4.2 REINFORCE 算法的完整拆解最朴素的策略梯度算法是 REINFORCE也叫蒙特卡洛策略梯度。它的流程我拆成了四个步骤用当前策略 π_θ 与环境交互采集一整条轨迹记录状态、动作、奖励。计算这条轨迹的总回报 R(τ)可以带折扣。对轨迹中的每个时间步计算梯度项 ∇_θ log π_θ(a_t|s_t) × R(τ)。用梯度上升更新参数 θ。写个简化伪代码大家感受一下全貌for episode in range(num_episodes): trajectory [] state env.reset() done False while not done: action policy.sample(state) # 从当前策略采样动作 next_state, reward, done, _ env.step(action) trajectory.append((state, action, reward)) state next_state # 计算折扣回报 returns [] G 0 for _, _, reward in reversed(trajectory): G reward gamma * G returns.append(G) returns list(reversed(returns)) # 更新策略参数 policy.zero_grad() for (state, action), G in zip(trajectory, returns): log_prob policy.log_prob(state, action) loss -log_prob * G # 梯度上升转梯度下降取负号 loss.backward() policy.step()这段代码有一个很值得注意的地方梯度是每个时间步单独算但 PyTorch 默认会把梯度累积所以更新完要 zero_grad同时 loss 取负号是因为优化器默认做梯度下降。REINFORCE 的实现很简单但方差极大经常会出现“训练一整个 epochloss 在跳策略就是不提升”的现象。那个时候我踩坑踩得非常深后来才知道核心问题是高方差。4.3 方差控制的核心手段基线技术与 Actor-CriticREINFORCE 的方差大根因在于 R(τ) 本身波动很大同样一个策略这次拿 100 分下次可能只有 -50 分直接把梯度项推得忽正忽负。解决思路是引入基线 b(s)把梯度改成∇J(θ) E[∇_θ log π_θ(a_t|s_t) · (R(τ) − b(s_t))]这个减法不会改变梯度的期望因为 E[∇log π · b(s)] 0但能大幅降低方差。直观理解如果回报是 100基线是 90那“这步选择比平均好多少”的信息是 10如果回报是 100基线是 0那信息是 100里面很大一部分是环境本身带来的噪声不是策略的功劳。减去基线相当于把“环境的固有难度”剔除出去只留“策略的增量贡献”。最经典的做法是用价值函数 V^π(s) 做基线于是被减项就变成了 Q^π(s, a) − V^π(s)这就是优势函数 A(s, a) 的定义。把基线从“还不错的常数”升级成“动态估计的价值函数”之后REINFORCE 就进化成了 Actor-Critic 架构Actor 是策略网络负责输出动作Critic 是价值网络负责给动作打分。Actor-Critic 是当下几乎所有主流算法的底座。A2C/A3C 是同步/异步版本的 Advantage Actor-CriticPPO 是把更新步长控制住的 Actor-CriticSAC 是加了熵正则和软更新的 Actor-Critic。所以我的建议是如果你想在 2025 年认真学强化学习REINFORCE 可以自己手写一遍加深理解但真正要熟练掌握的是 Actor-Critic 这种双网络架构。4.4 PPO 的截断机制让策略更新别太“飘”PPO 全称是近端策略优化它解决了一个实际工程问题策略梯度更新的步长太难选了。步长太小训练慢到怀疑人生步长太大一次更新把策略推得太远性能直接崩盘。PPO 的做法是用重要性采样把新旧策略的比值算出来然后加一个 clip 操作把比值限制在 [1−ε, 1ε] 的区间里。ratio torch.exp(new_log_prob - old_log_prob) surr1 ratio * advantage surr2 torch.clamp(ratio, 1 - clip_epsilon, 1 clip_epsilon) * advantage loss -torch.min(surr1, surr2).mean()这个 clip 的直觉是新策略比旧策略好太多我只给你这么多信任坏太多我也只扣这么多。允许小幅改进但不允许冒进训练就稳定得多。我在实际项目中用过 PPO 训练机器人运动控制默认参数下很少出现“训练到一半突然变成瘸子”的情况这在深度强化学习里是难能可贵的。5. 实操环节从零搭建一个策略梯度训练流程5.1 环境准备与工具选型工欲善其事必先利其器。我推荐的学习路径是先用一个轻量级环境跑通逻辑再上重型框架。环境选择上OpenAI Gym现在的 Gymnasium依然是最好的入门选择API 稳定、环境丰富。拿 CartPole 这样的经典控制问题来练手动作空间只有 2 个状态是 4 维连续向量非常适合验证策略梯度算法。框架层面PyTorch 是当前做强化学习的主力动态计算图对调试友好。尽量不要直接用 RLlib、Stable-Baselines3 这类封装过高的库来入门——先用原生 PyTorch 手写一遍 REINFORCE 和 Actor-Critic你会对数据流、梯度流有真正的体感。等理解了底层原理再用现成框架提效也不迟。5.2 策略网络的设计细节策略网络的结构可以很简单但输入输出的设计要谨慎。以 CartPole 为例class PolicyNetwork(nn.Module): def __init__(self, state_dim, action_dim, hidden_dim128): super().__init__() self.fc1 nn.Linear(state_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim) self.fc_out nn.Linear(hidden_dim, action_dim) def forward(self, state): x torch.relu(self.fc1(state)) x torch.relu(self.fc2(x)) logits self.fc_out(x) return logits注意最后一层不用激活函数。它输出的是 logits未归一化的分数在采动作时用 softmax 转成概率分布。采样用torch.multinomial或者torch.distributions.Categorical后者更方便直接附带log_prob()方法dist Categorical(logitslogits) action dist.sample() log_prob dist.log_prob(action)写到这里必须提醒一个隐蔽坑梯度更新时必须保证这个 log_prob 与采样的动作保持在同一张计算图里。很多人图省事采样之后把动作和 log_prob 都存成 numpy 数组下一轮再转回 tensor结果反向传播时梯度根本传不到策略网络loss 不降反升你还找不到原因。5.3 训练超参数的选择与调试方法训练一轮强化学习代码你会遇到很多让人抓狂的参数。我给出一个从实际项目里总结的起始配置参数推荐起始值说明学习率1e-3 ~ 3e-4REINFORCE 用偏大的PPO 用偏小的折扣因子 γ0.99任务越长越接近 1CartPole 默认 0.99隐藏层宽度128小任务足够复杂任务建议 256更新回合数1000 ~ 2000CartPole 一般 500 个 episode 能看出收敛趋势熵正则系数0.01探索不足就加大太散就减小我的调试黄金法则是先固定所有参数只改一个变量跑完画图再看。不要一上来就调三个参数否则出了问题你根本不知道是谁惹的祸。画图强烈推荐直接记录每一轮的 episode reward跑完用平滑处理比如滑动平均看趋势。如果曲线像心电图一样剧烈跳动说明方差大接基线、上 Actor-Critic如果曲线整体缓慢上升但到平台期就上不去可能是步长太小或网络容量不够。5.4 完整流程演示REINFORCE 训练 CartPole这里放一段我实际调试通过的 REINFORCE 完整实现可以作为你跑通第一个策略梯度算法的参考模板import torch import torch.nn as nn import gymnasium as gym from torch.distributions import Categorical class PolicyGradientAgent: def __init__(self, state_dim, action_dim, lr1e-2, gamma0.99): self.gamma gamma self.policy PolicyNetwork(state_dim, action_dim) self.optimizer torch.optim.Adam(self.policy.parameters(), lrlr) def select_action(self, state): state torch.FloatTensor(state).unsqueeze(0) logits self.policy(state) dist Categorical(logitslogits) action dist.sample() return action.item(), dist.log_prob(action) def update(self, log_probs, rewards): returns [] G 0 for r in reversed(rewards): G r self.gamma * G returns.insert(0, G) returns torch.FloatTensor(returns) # 用回报均值作为基线降低部分方差 baseline returns.mean() loss - (torch.cat(log_probs) * (returns - baseline)).sum() self.optimizer.zero_grad() loss.backward() self.optimizer.step() env gym.make(CartPole-v1) agent PolicyGradientAgent(state_dim4, action_dim2) reward_history [] for episode in range(500): state, _ env.reset() done False log_probs, rewards [], [] while not done: action, log_prob agent.select_action(state) next_state, reward, terminated, truncated, _ env.step(action) done terminated or truncated log_probs.append(log_prob) rewards.append(reward) state next_state agent.update(log_probs, rewards) total_reward sum(rewards) reward_history.append(total_reward) if episode % 50 0: avg_reward sum(reward_history[-50:]) / 50 print(fEpisode {episode}, avg reward (last 50): {avg_reward:.1f})这个版本的 REINFORCE 我用一个简单的均值基线已经把纯度很高的梯度下降换成了带方差削减的版本。输出里如果看到最近 50 轮平均回报超过 400恭喜CartPole 已经被你驯服了。别急着开心这只是入门。把这份感觉迁移到 Actor-Critic 会很快因为里面唯一的区别就是拿一个神经网络代替均值基线实时地估 V(s)。6. 常见问题排查与避坑指南6.1 损失函数不下降甚至上升这是新手遇到最多的现象。看到 loss 一路飙升第一反应是哪里写错了但其实策略梯度的 loss 和监督学习里的 loss 含义完全不同——它只是一个代数值并不代表“错误程度”。真正要看的指标是每轮的平均回报曲线而不是 loss 曲线。如果你的 loss 在涨但 reward 也在涨那就是正常的因为策略熵变小了、log_prob 更集中了。6.2 回报曲线剧烈震荡震荡的根源几乎都是方差。我排查的顺序是这样的第一检查是不是基线没加第二检查回报归一化advantage normalization有没有做第三看学习率是不是太大。PPO 里还有第四步检查 clip 范围。还有一个常被忽略的原因更新频率太高。每个 episode 都更新模型在早几步的过渡期新旧策略差异太大容易出现震荡。我的建议是收集多个 episode 的样本打包成一个 batch 再做更新batch 大小 64 到 256 之间效果都不错。6.3 策略过早收敛到次优策略训练很快回报曲线也稳定了但最终效果就是不如别人论文里的好。这是典型的“早熟”问题策略太早就失去了探索能力输出的分布几乎变成了确定性分布但那个解不是最优的。对策很简单调大熵正则系数或者降低学习率让更新更慢、更细。实战里我会开一个探针打印每个 batch 的平均熵值如果熵值掉到某个很小阈值以下就自动加一点熵系数这个技巧在 SAC 和 PPO 里非常管用。6.4 环境重置与 done 信号的坑CartPole 这种环境有两个布尔量一个叫terminated真终止比如杆子倒了一个叫truncated截断比如超时。很多代码教程只判断done不区分两者这在简单环境里问题不大但真实任务里区别巨大超时截断不应该算作“策略失败”。如果你在训练一个长期任务建议把truncated当成独立的结束标记不要在奖励累计和回报计算上把它和失败混为一谈。否则策略会错误地学会“在封顶的回合里故意拖延”训练目标都被带偏了。6.5 实战排查速查表现象可能原因排查手段loss 不降reward 也不动梯度未回传 / 网络输出恒为常数检查 log_prob 是否在计算图中打印梯度范数reward 波动巨大方差高、未加基线 / 未做优势归一化用 V(s) 做基线对 advantage 做标准化reward 上限很低探索不足、策略早熟调大熵正则系数或减小学习率训练后期突然崩溃更新步长过大、策略被推过头换 PPO 加 clip或降低学习率与文档环境不一致Gym 版本 API 差异检查 reset 返回值、done 的语义跑 smoke test有一条我做项目时反复验证的心得先在玩具环境里把代码跑通再换到自己的场景。如果你连 CartPole 都稳定不了直接上真实业务环境只会更绝望。别觉得玩具环境简单把玩具环境调教得又快又稳本身就是对算法理解和调参功力的考验。7. 策略学习的进阶方向与我的经验总结现阶段把这些基础吃透之后你的下一步可以往两个方向走。一个是模型无关的策略优化家族PPO、SAC、TD3 这些工程上极其成熟的算法。另一个是离线强化学习比如 IQL、CQL——它们关注的是如何从已有数据里学策略而不需要实时与环境交互这在真实业务里价值极高因为很多场景不允许你在线试错。我个人的学习路径建议是先手写 REINFORCE再手写 A2C然后跳到 PPO。不要一上来就啃 SAC 的论文它的数学推导看着优雅但工程实现里熵系数调节、目标网络软更新的细节能把人折磨到怀疑人生。先把 PPO 调稳整个策略梯度家族的理解就能立起来。最后分享一个自己踩过的大坑强化学习代码里有一类非常隐蔽的逻辑错误——one-step return 的计算。REINFORCE 里如果折扣回报算错了一个符号或者下标训练不会直接报错而是表现为“明明奖励在改善但 loss 数值和直觉对不上”。这让我排查了整整两个晚上后来通过在状态里加了一个已知的探针单元才定位到问题。我的建议是写回报计算函数时先用一个手算的正确小例子做单元测试再往主流程里接。强化学习项目 debug 的难点不在于语法而在于数据流中的逻辑错位这一点一定要有预期。策略说到底是“面对某个局面选择怎么做”的学问。看起来仿佛简单但把这个问题在数学、算法和工程三个层面都解决好需要理解的细节远比你想象的要多。把这篇文章里的每一段代码亲手敲一遍、每一个参数亲手调一遍策略梯度就从“看起来懂了”变成“真的会用了”。