Skip to content

PPO

DQN解决了离散问题,于是人们开始着手于解决更加现实的连续控制问题。时至今日,连续控制领域基本被PPO(on-policy)和SAC(off-policy)这两个算法统治,但在早期发展中依然经历了诸多变革。本章以最终形态PPO作为核心,简要整理on-policy路线中最核心的概念。

PPO(Proximal Policy Optimization)由OpenAI在2017年提出,是目前工业界和学术界使用最广泛的强化学习算法之一。从ChatGPT的RLHF训练,到机器人控制,到游戏AI(OpenAI Five),PPO几乎无处不在。它的核心卖点是:在策略梯度算法的基础上,用极其简洁的代码实现了接近TRPO的理论性能保证,同时训练稳定、调参简单。

发展历程

在理解PPO之前,我们需要先梳理从最基础的策略梯度到PPO的整条进化链路:

\[ \text{REINFORCE} \to \text{Actor-Critic} \to \text{A3C/A2C} \to \text{TRPO} \to \text{PPO} \]

A3C - Asynchronous Advantage Actor-Critic

A3C由DeepMind在2016年提出(Mnih et al., "Asynchronous Methods for Deep Reinforcement Learning"),是第一个真正在连续控制任务上大规模成功的深度强化学习算法。

核心思想: 用多个并行的worker(线程/进程)同时和各自独立的环境副本交互,异步地更新一个共享的全局网络。

A3C的关键创新有两点:

  1. 异步并行训练: 每个worker独立地收集经验并计算梯度,然后异步地把梯度推送(push)到全局网络。全局网络一收到梯度就立即更新参数,然后worker再从全局网络拉取(pull)最新参数继续训练。这种方式极大地提高了数据收集效率,并且由于不同worker看到的状态分布不同,天然地起到了"打破数据相关性"的作用——这和DQN用经验回放解决的是同一个问题,但A3C用了完全不同的思路。

  2. Advantage Actor-Critic架构: A3C同时训练两个网络(通常共享底层参数):

    • Actor(策略网络): 输出动作的概率分布 \(\pi_\theta(a|s)\)
    • Critic(价值网络): 输出状态价值 \(V_\phi(s)\)

    Actor根据Advantage函数 \(A(s,a) = Q(s,a) - V(s)\) 来更新策略。用Advantage代替原始的回报 \(G_t\) 可以大幅降低方差,同时保持无偏性。

A3C的问题: 异步更新会导致"梯度过期"问题。当worker A计算完梯度准备上传时,全局网络可能已经被worker B更新过了,worker A的梯度是基于旧参数算的,可能会把全局网络往错误的方向拉。

A2C - Synchronous Advantage Actor-Critic

A2C是A3C的同步版本。它的改进很简单:所有worker同步地收集一轮数据,一起等齐了,再统一更新全局网络。

虽然这牺牲了一些时间效率(快的worker要等慢的),但消除了异步带来的梯度噪声。实验表明,A2C在相同的计算预算下往往能达到和A3C相同甚至更好的性能,同时实现更简单、更容易调试。

A2C是理解PPO的重要铺垫,因为PPO本质上就是在A2C的基础上加了一个"信赖域"约束来稳定训练。

Trust Region - 信赖域的概念

在正式讲TRPO之前,我们需要理解一个优化领域的核心概念:信赖域(Trust Region)

在传统的梯度下降中,我们计算出梯度方向,然后沿着这个方向走一步。但走多远?这由学习率(Learning Rate)决定。问题是:梯度只在当前点的极小邻域内是准确的。 如果学习率太大,你走出了这个邻域,梯度方向就不再可靠,可能导致性能暴跌。

信赖域方法的思路完全不同:

  1. 在当前参数 \(\theta\) 的附近,构造一个目标函数的局部近似模型(比如二次近似)。
  2. 定义一个信赖域(Trust Region),即我们"信任"这个近似模型准确的范围。
  3. 在这个信赖域内,找到近似模型的最优解,作为下一步的参数。
  4. 如果新参数确实让真实目标函数变好了,就扩大信赖域;如果变差了,就缩小信赖域。

在强化学习中,信赖域的概念尤其重要。因为策略梯度算法有一个致命问题:策略更新一步太大,可能导致策略直接崩溃,而且很难恢复。 这不像监督学习中,即使某一步loss增大了,下一步还可以修正回来。在RL中,一旦策略变差,它收集到的数据质量也会变差,进而导致下一轮更新更差——形成恶性循环。

TRPO - Trust Region Policy Optimization

TRPO由Schulman等人在2015年提出,是第一个将信赖域方法正式引入策略梯度的算法。它在理论上保证了每一步更新都能单调提升策略性能(或至少不会变差)。

理论基础: TRPO的出发点是Kakade和Langford在2002年证明的一个重要不等式。对于任何两个策略 \(\pi\)\(\pi'\),新策略的期望回报可以表示为:

\[ \eta(\pi') = \eta(\pi) + \mathbb{E}_{s \sim d^{\pi'}, a \sim \pi'} [A^\pi(s, a)] \]

其中 \(\eta(\pi)\) 是策略 \(\pi\) 的期望回报,\(A^\pi(s,a)\) 是旧策略下的优势函数,\(d^{\pi'}\) 是新策略下的状态访问分布。

问题在于,右边的期望是在新策略 \(\pi'\) 的状态分布下计算的,而我们还没有执行新策略,无法获得这个分布。TRPO的关键洞察是:如果新旧策略足够接近,我们可以用旧策略的状态分布来近似,得到一个代理目标函数(Surrogate Objective)

\[ L_{\pi_{\theta_{\text{old}}}}(\pi_\theta) = \mathbb{E}_{s \sim d^{\pi_{\theta_{\text{old}}}}, a \sim \pi_{\theta_{\text{old}}}} \left[ \frac{\pi_\theta(a|s)}{\pi_{\theta_{\text{old}}}(a|s)} A^{\pi_{\theta_{\text{old}}}}(s, a) \right] \]

为了保证这个近似是合理的(新旧策略确实足够接近),TRPO对策略更新施加了一个KL散度约束

\[ \max_\theta \quad L_{\pi_{\theta_{\text{old}}}}(\pi_\theta) \quad \text{s.t.} \quad \mathbb{E}_s \left[ D_{\text{KL}}(\pi_{\theta_{\text{old}}}(\cdot|s) \| \pi_\theta(\cdot|s)) \right] \leq \delta \]

其中 \(D_{\text{KL}}\) 是KL散度,衡量两个概率分布之间的差异;\(\delta\) 是一个很小的阈值,限制新旧策略的差异不能太大。

求解方法: 这是一个带约束的优化问题,不能直接用SGD求解。TRPO使用了以下技巧:

  1. 对目标函数做一阶Taylor展开(线性近似):
\[ L(\theta) \approx L(\theta_{\text{old}}) + g^T (\theta - \theta_{\text{old}}) \]

其中 \(g = \nabla_\theta L |_{\theta_{\text{old}}}\) 是策略梯度。

  1. 对KL约束做二阶Taylor展开(二次近似):
\[ D_{\text{KL}}(\theta_{\text{old}} \| \theta) \approx \frac{1}{2} (\theta - \theta_{\text{old}})^T F (\theta - \theta_{\text{old}}) \]

其中 \(F\)Fisher信息矩阵(Fisher Information Matrix),描述了参数空间中策略变化的曲率。

  1. 用共轭梯度法(Conjugate Gradient)求解: 直接计算 \(F^{-1}g\) 需要对Fisher矩阵求逆,对于大规模神经网络是不可行的。TRPO巧妙地使用共轭梯度法,只需要计算Fisher矩阵与向量的乘积 \(Fv\)(这可以通过自动微分高效完成),就能近似地求解出更新方向。

  2. 线搜索(Line Search): 在共轭梯度给出更新方向后,还需要沿着这个方向做线搜索,确保KL约束确实被满足,并且目标函数确实提高了。

TRPO的致命缺点:

  • 实现极其复杂(需要共轭梯度、线搜索、Fisher向量积等)
  • 不兼容参数共享的网络架构(Actor和Critic共享层时很难处理)
  • 不兼容Dropout、Batch Normalization等常见的正则化技术
  • 计算开销大

正是因为TRPO这些工程上的问题,PPO才应运而生。

PPO核心算法

PPO的设计哲学是:保留TRPO"限制策略更新步长"的核心思想,但用一种极其简单的方式来实现。 不需要共轭梯度,不需要Fisher矩阵,不需要线搜索。只需要一阶优化器(如Adam)就能搞定。

策略梯度回顾

在讲PPO的具体公式之前,我们先回顾策略梯度(Policy Gradient)的基本定理。

策略梯度的目标是最大化期望回报:

\[ J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta} \left[ \sum_{t=0}^{T} \gamma^t r_t \right] \]

根据策略梯度定理(Policy Gradient Theorem),\(J(\theta)\) 对参数 \(\theta\) 的梯度为:

\[ \nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta} \left[ \sum_{t=0}^{T} \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot A^{\pi_\theta}(s_t, a_t) \right] \]

其中:

  • \(\pi_\theta(a_t|s_t)\) 是策略在状态 \(s_t\) 下选择动作 \(a_t\) 的概率
  • \(A^{\pi_\theta}(s_t, a_t)\) 是优势函数,衡量动作 \(a_t\) 相对于平均水平的好坏
  • \(\nabla_\theta \log \pi_\theta(a_t|s_t)\) 是对数概率的梯度,指明了增大该动作概率的参数更新方向

直觉理解: 如果动作 \(a_t\) 的优势 \(A > 0\)(比平均好),就增大这个动作的概率;如果 \(A < 0\)(比平均差),就减小这个动作的概率。增大/减小的幅度与 \(|A|\) 成正比。

原始策略梯度的问题: 它是一个on-policy算法,每收集一轮数据只能更新一次参数,然后数据就必须丢掉(因为数据是旧策略生成的,不能用于新策略的梯度估计)。这导致样本效率极低。

Importance Sampling - 重要性采样

为了提高样本效率,我们希望能用旧策略 \(\pi_{\theta_{\text{old}}}\) 收集的数据来更新新策略 \(\pi_\theta\)。这就需要用到重要性采样(Importance Sampling)

重要性采样的基本原理是:如果我们想计算函数 \(f(x)\) 在分布 \(p(x)\) 下的期望,但我们只有来自另一个分布 \(q(x)\) 的样本,可以通过加权来修正:

\[ \mathbb{E}_{x \sim p}[f(x)] = \mathbb{E}_{x \sim q}\left[\frac{p(x)}{q(x)} f(x)\right] \]

其中 \(\frac{p(x)}{q(x)}\) 就是重要性权重(Importance Weight)

应用到策略梯度中,我们用旧策略的数据来估计新策略的目标函数:

\[ L^{IS}(\theta) = \mathbb{E}_{(s,a) \sim \pi_{\theta_{\text{old}}}} \left[ \frac{\pi_\theta(a|s)}{\pi_{\theta_{\text{old}}}(a|s)} A^{\pi_{\theta_{\text{old}}}}(s, a) \right] \]

我们定义概率比(Probability Ratio)

\[ r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)} \]

那么代理目标函数可以简写为:

\[ L^{IS}(\theta) = \mathbb{E}_t \left[ r_t(\theta) \cdot A_t \right] \]

\(\theta = \theta_{\text{old}}\) 时,\(r_t = 1\),代理目标函数就退化为普通的策略梯度目标。

重要性采样的问题: 当新旧策略差异太大时,重要性权重 \(r_t(\theta)\) 可能非常大或非常小,导致梯度估计的方差极大,训练不稳定。TRPO通过KL散度约束来限制这个差异,而PPO选择了一种更直接的方式——Clipping。

Clipped Surrogate Objective - 截断代理目标

这是PPO最核心的创新,也是整个算法最精妙的地方。

PPO-Clip直接在目标函数里对概率比 \(r_t(\theta)\) 进行截断:

\[ L^{\text{CLIP}}(\theta) = \mathbb{E}_t \left[ \min \left( r_t(\theta) A_t, \; \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) A_t \right) \right] \]

其中:

  • \(r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)}\) 是概率比
  • \(\epsilon\) 是一个超参数,通常取 \(0.2\),控制截断范围
  • \(\text{clip}(r_t, 1-\epsilon, 1+\epsilon)\) 将概率比限制在 \([0.8, 1.2]\) 之间
  • \(\min\) 操作取两项中的较小值

为什么Clipping能起作用?我们分四种情况来分析:

情况1:\(A_t > 0\)(动作好于平均),\(r_t > 1\)(新策略更倾向于选这个动作)

  • 不截断项:\(r_t \cdot A_t\),随着 \(r_t\) 增大而增大
  • 截断项:\(\text{clip}(r_t, 0.8, 1.2) \cdot A_t = 1.2 \cdot A_t\),被封顶
  • \(\min\) 取较小值 \(= 1.2 \cdot A_t\)(被截断的那个)

效果: 新策略已经比旧策略更倾向于选这个好动作了,但目标函数不再给予额外奖励。防止"过度优化"——不要因为某个动作碰巧好了一次,就疯狂地把这个动作的概率拉到极高。

情况2:\(A_t > 0\)(动作好于平均),\(r_t < 1\)(新策略不太倾向于选这个动作)

  • 不截断项:\(r_t \cdot A_t\),小于 \(A_t\)
  • 截断项:因为 \(r_t < 1 < 1+\epsilon\),截断通常不生效,\(\text{clip}(r_t, 0.8, 1.2) = \max(r_t, 0.8)\)
  • \(\min\) 取较小值 \(= r_t \cdot A_t\)(不截断项更小)

效果: 好动作的概率在下降,目标函数给出正常的梯度信号,鼓励恢复这个动作的概率。

情况3:\(A_t < 0\)(动作差于平均),\(r_t < 1\)(新策略减少了这个动作的概率)

  • 不截断项:\(r_t \cdot A_t\),注意这是两个数相乘,\(r_t\) 越小结果越大(越接近0)
  • 截断项:\(0.8 \cdot A_t\),被封底
  • \(\min\) 取较小值 \(= 0.8 \cdot A_t\)(被截断的那个)

效果: 新策略已经在减少这个坏动作的概率了,但目标函数不再给予额外惩罚。防止"过度回避"。

情况4:\(A_t < 0\)(动作差于平均),\(r_t > 1\)(新策略反而增加了这个动作的概率)

  • 不截断项:\(r_t \cdot A_t\)\(r_t\) 越大结果越小(越负)
  • 截断项:通常不生效
  • \(\min\) 取较小值 \(= r_t \cdot A_t\)

效果: 坏动作的概率在上升,目标函数给出正常的梯度信号,惩罚这种行为。

总结: Clipping的本质逻辑是——当策略变化方向正确时(好动作概率增大或坏动作概率减小),限制变化幅度;当策略变化方向错误时,不限制,让梯度自由修正。 这就实现了"悲观的下界"估计:我们总是在优化目标函数的一个下界,确保真实性能不会比我们估计的更差。

值函数损失

PPO中的Critic网络负责估计状态价值函数 \(V_\phi(s)\),用于计算优势函数。Critic的损失函数通常是简单的MSE:

\[ L^{VF}(\phi) = \mathbb{E}_t \left[ (V_\phi(s_t) - V_t^{\text{target}})^2 \right] \]

其中 \(V_t^{\text{target}}\) 是价值函数的目标值,通常取GAE计算出的回报估计 \(\hat{R}_t\)(后面会详细介绍):

\[ V_t^{\text{target}} = \hat{R}_t = A_t + V_{\phi_{\text{old}}}(s_t) \]

在PPO原论文中,作者还提出了对值函数也做Clipping的变体:

\[ L^{VF,\text{clip}}(\phi) = \mathbb{E}_t \left[ \max \left( (V_\phi(s_t) - V_t^{\text{target}})^2, \; (V_{\phi}^{\text{clip}}(s_t) - V_t^{\text{target}})^2 \right) \right] \]

其中 \(V_\phi^{\text{clip}} = \text{clip}(V_\phi(s_t), V_{\phi_{\text{old}}}(s_t) - \epsilon, V_{\phi_{\text{old}}}(s_t) + \epsilon)\)。但实践中发现,值函数Clipping的效果不太一致,很多高性能实现中直接使用简单的MSE。

熵奖励

为了防止策略过早收敛到某个确定性的动作(即"早熟"),PPO在目标函数中加入了一个熵奖励(Entropy Bonus)

\[ H(\pi_\theta(\cdot|s_t)) = -\sum_a \pi_\theta(a|s_t) \log \pi_\theta(a|s_t) \]

熵衡量的是策略输出的"随机程度"。熵越大,策略越随机(各动作概率越均匀);熵越小,策略越确定(集中于某一个动作)。

加入熵奖励的目的:

  1. 鼓励探索: 防止策略在训练初期就坍缩到某个局部最优解。
  2. 避免动作概率趋零: 如果某个动作的概率趋近于0,\(\log \pi\) 会趋近负无穷,数值上会出问题。熵奖励会阻止这种情况发生。
  3. 提高鲁棒性: 稍微随机的策略在面对环境扰动时更加鲁棒。

PPO的完整目标函数

将上述三个部分合在一起,PPO的完整目标函数为:

\[ L^{\text{PPO}}(\theta, \phi) = \mathbb{E}_t \left[ L_t^{\text{CLIP}}(\theta) - c_1 \cdot L_t^{VF}(\phi) + c_2 \cdot H(\pi_\theta(\cdot|s_t)) \right] \]

其中:

  • \(L_t^{\text{CLIP}}(\theta)\):截断代理目标(最大化)
  • \(L_t^{VF}(\phi)\):值函数损失(最小化,所以前面是负号)
  • \(H(\pi_\theta)\):策略熵(最大化,所以前面是正号)
  • \(c_1\):值函数损失的系数,通常取 \(0.5\)\(1.0\)
  • \(c_2\):熵奖励的系数,通常取 \(0.01\)

注意,这里PPO的常见实现有两种做法:

  1. 共享网络: Actor和Critic共享底层特征提取层(如CNN的卷积层),最后分出两个头。此时三项损失放在一起优化,\(c_1\)\(c_2\) 用于平衡三者的重要性。
  2. 分离网络: Actor和Critic使用完全独立的网络。此时策略损失和值函数损失可以分开优化。

实现细节

PPO的论文本身非常简洁,但真正让PPO表现优秀的,是大量的实现细节。这些细节在原论文中要么一笔带过,要么完全没提,但它们对性能影响巨大。

GAE - Generalized Advantage Estimation

GAE(广义优势估计)是PPO中计算优势函数 \(A_t\) 的标准方法,由Schulman在2016年单独发表的论文中提出。

为什么需要GAE? 计算优势函数面临一个经典的bias-variance trade-off:

方法1:蒙特卡洛回报(高方差,零偏差)

\[ A_t^{MC} = \left( \sum_{l=0}^{T-t} \gamma^l r_{t+l} \right) - V(s_t) \]

用完整的回合回报来估计优势。优点是无偏,缺点是方差很大(因为回报受后续所有随机性的影响)。

方法2:单步TD误差(低方差,有偏差)

\[ A_t^{TD} = r_t + \gamma V(s_{t+1}) - V(s_t) = \delta_t \]

只看一步的奖励加上下一步的价值估计。方差小(只依赖一步的随机性),但有偏差(因为 \(V(s_{t+1})\) 本身就是一个不完美的估计)。

GAE的解法:用一个参数 \(\lambda\) 来平滑地在两者之间做插值。

首先定义单步TD误差:

\[ \delta_t = r_t + \gamma V(s_{t+1}) - V(s_t) \]

然后,GAE被定义为TD误差的指数加权平均:

\[ \hat{A}_t^{\text{GAE}(\gamma, \lambda)} = \sum_{l=0}^{T-t} (\gamma \lambda)^l \delta_{t+l} \]

展开来看:

\[ \hat{A}_t = \delta_t + (\gamma\lambda)\delta_{t+1} + (\gamma\lambda)^2\delta_{t+2} + \cdots \]
  • \(\lambda = 0\) 时:\(\hat{A}_t = \delta_t\),退化为单步TD误差(低方差,高偏差)
  • \(\lambda = 1\) 时:\(\hat{A}_t = \sum_{l=0}^{T-t} \gamma^l \delta_{t+l}\),等价于蒙特卡洛回报减去基线(高方差,零偏差)

实践中,\(\lambda\) 通常取 \(0.95\),在方差和偏差之间取得良好的平衡。

GAE的递推计算: 在实现中,GAE可以从轨迹末端反向递推计算,效率很高:

\[ \hat{A}_T = \delta_T \]
\[ \hat{A}_t = \delta_t + \gamma \lambda \hat{A}_{t+1} \]

这和计算折扣回报的递推公式形式完全一致,代码实现非常简洁:

# GAE计算(从后往前递推)
advantages = torch.zeros_like(rewards)
last_gae = 0
for t in reversed(range(T)):
    delta = rewards[t] + gamma * values[t+1] * (1 - dones[t]) - values[t]
    advantages[t] = last_gae = delta + gamma * lam * (1 - dones[t]) * last_gae
returns = advantages + values[:-1]  # V_target = A + V

Mini-batch Updates

PPO的一个关键实现细节是:收集一大批数据后,将其打散成多个mini-batch,多次遍历更新。

流程如下:

  1. 用当前策略 \(\pi_{\theta_{\text{old}}}\) 与环境交互,收集 \(N\) 个并行环境各 \(T\) 步的数据,共 \(N \times T\) 个transition。
  2. 用Critic网络计算所有状态的价值 \(V(s_t)\),然后用GAE计算所有的优势 \(\hat{A}_t\)
  3. 将这 \(N \times T\) 个数据点随机打散(shuffle)。
  4. 将打散后的数据划分为若干mini-batch(每个大小通常为64或128)。
  5. 对每个mini-batch,计算PPO损失并用Adam更新网络参数。
  6. 重复步骤3-5共 \(K\) 个epoch。
  7. 丢弃所有数据,回到步骤1。

Multiple Epochs per Rollout

PPO允许对同一批数据进行多个epoch的更新(通常 \(K = 3 \sim 10\)),这是相对于原始策略梯度(只能更新一次)的巨大效率提升。

这之所以可行,是因为Clipping机制天然地限制了策略变化的幅度。即使我们在同一批数据上多次更新,策略也不会偏离旧策略太远。当概率比 \(r_t\) 超出 \([1-\epsilon, 1+\epsilon]\) 范围时,梯度会被截断为零,相当于自动停止更新。

但也不能设置太多epoch。经验上,\(K\) 太大会导致:

  • 策略过拟合于当前这一批数据
  • 概率比大面积被截断,有效梯度趋近于零,更新停滞
  • KL散度过大,策略偏离旧策略太远

Advantage Normalization

在每个mini-batch中,通常会对优势函数进行标准化(Normalization):

\[ \hat{A}_t \leftarrow \frac{\hat{A}_t - \mu(\hat{A})}{\sigma(\hat{A}) + \epsilon} \]

其中 \(\mu\)\(\sigma\) 分别是当前mini-batch中优势值的均值和标准差,\(\epsilon\) 是一个很小的数(如 \(10^{-8}\))防止除零。

标准化的好处是:

  1. 使得优势值的尺度不依赖于具体任务的奖励大小
  2. 大约一半的优势值为正(增大概率),一半为负(减小概率),梯度方向更均衡
  3. 显著提高训练稳定性

超参数

以下是PPO最重要的超参数及其常见取值:

超参数 符号 常见取值 说明
学习率 \(\alpha\) \(3 \times 10^{-4}\) Actor和Critic通常使用相同学习率
截断系数 \(\epsilon\) \(0.2\) 控制策略更新幅度,核心超参数
折扣因子 \(\gamma\) \(0.99\) 未来奖励的折扣
GAE参数 \(\lambda\) \(0.95\) 控制偏差-方差权衡
更新轮数 \(K\) \(3 \sim 10\) 每批数据的训练epoch数
Mini-batch大小 \(M\) \(64\) 每次梯度更新使用的样本数
值函数系数 \(c_1\) \(0.5\) 值函数损失的权重
熵系数 \(c_2\) \(0.01\) 熵奖励的权重
并行环境数 \(N\) \(8 \sim 128\) 同时交互的环境副本数
Rollout步数 \(T\) \(128 \sim 2048\) 每个环境收集的步数
最大梯度范数 - \(0.5\) 梯度裁剪,防止梯度爆炸

注意:这些值只是经验值,不同任务可能需要调整。其中 \(\epsilon\) 和学习率对性能影响最大。

PPO的训练流程

将上述所有内容整合,PPO的完整训练流程如下:

PPO算法伪代码:
1. 初始化策略网络 π_θ 和价值网络 V_ϕ(可共享底层参数)
2. for iteration = 1, 2, ... do
3.     用当前策略 π_θ_old 在 N 个并行环境中各运行 T 步
4.     收集轨迹数据:{(s_t, a_t, r_t, s_{t+1}, done_t, log π_θ_old(a_t|s_t))}
5.     用 V_ϕ 计算所有状态的价值估计
6.     用 GAE 计算所有时间步的优势估计 Â_t 和回报目标 R̂_t
7.     for epoch = 1, ..., K do
8.         随机打散所有 N×T 个数据点
9.         将数据划分为大小为 M 的 mini-batch
10.        for each mini-batch do
11.            计算当前策略下的 log π_θ(a_t|s_t)
12.            计算概率比 r_t = exp(log π_θ - log π_θ_old)
13.            计算截断代理目标 L^CLIP
14.            计算值函数损失 L^VF
15.            计算熵奖励 H
16.            总损失 L = -L^CLIP + c1 * L^VF - c2 * H
17.            反向传播,梯度裁剪,Adam更新
18.        end for
19.    end for
20.    θ_old ← θ(更新旧策略参数)
21. end for

PPO vs TRPO

对比维度 TRPO PPO
年份 2015 2017
约束方式 硬约束(KL散度 \(\leq \delta\) 软约束(Clipping截断)
优化方法 二阶优化(共轭梯度 + 线搜索) 一阶优化(Adam SGD)
实现复杂度 极高(需要Fisher矩阵、共轭梯度) 极低(核心代码不到50行)
计算开销 大(每步需要多次共轭梯度迭代) 小(标准的mini-batch SGD)
理论保证 有严格的单调改进保证 没有严格保证,但实践中表现良好
网络架构兼容性 不兼容参数共享、Dropout等 完全兼容所有标准深度学习技术
性能 在大多数任务上两者接近 在大多数任务上两者接近
调参难度 较难(\(\delta\) 的选取敏感) 简单(\(\epsilon = 0.2\) 几乎普适)
工业界使用 很少 极其广泛

PPO的成功告诉我们一个深刻的道理:在工程实践中,简单而鲁棒的方法往往比理论上更优但实现复杂的方法更有价值。 TRPO提供了重要的理论洞察,而PPO将这些洞察转化为了人人都能使用的实用工具。

PPO的变体与补充

PPO-Penalty

除了PPO-Clip,原论文还提出了另一个变体PPO-Penalty,它不使用Clipping,而是直接将KL散度作为惩罚项加入目标函数:

\[ L^{\text{KPPEN}}(\theta) = \mathbb{E}_t \left[ r_t(\theta) A_t - \beta \cdot D_{\text{KL}}(\pi_{\theta_{\text{old}}} \| \pi_\theta) \right] \]

其中 \(\beta\) 是一个自适应系数:

  • 如果实际KL散度 \(> 1.5 \cdot d_{\text{targ}}\)(超过目标值太多),则 \(\beta \leftarrow 2\beta\)
  • 如果实际KL散度 \(< d_{\text{targ}} / 1.5\)(远低于目标值),则 \(\beta \leftarrow \beta / 2\)

实践中,PPO-Clip的表现通常优于PPO-Penalty,因此PPO-Clip是默认选择。

PPO在RLHF中的应用

PPO在大语言模型的RLHF(Reinforcement Learning from Human Feedback)训练中扮演了核心角色。在这个场景中:

  • 环境: 语言模型根据prompt生成回复
  • 动作: 每个token的生成
  • 奖励: 由人类偏好训练的Reward Model给出
  • 策略: 语言模型本身就是策略网络

RLHF中的PPO还加入了一个额外的KL惩罚项,防止模型偏离预训练模型太远:

\[ r_t = r_{\text{RM}}(x, y) - \beta \cdot D_{\text{KL}}(\pi_\theta \| \pi_{\text{ref}}) \]

其中 \(\pi_{\text{ref}}\) 是SFT(Supervised Fine-Tuning)后的参考模型。

关于On-Policy的讨论

PPO是一个on-policy算法,这意味着每次更新后都必须重新收集数据。虽然通过Clipping和多epoch更新提高了样本利用率,但和SAC等off-policy算法相比,PPO的样本效率仍然较低。

然而,PPO有一个off-policy算法难以匹敌的优势:训练稳定性。 由于PPO不存在replay buffer中数据过期的问题(死亡三角),也不需要维护target network,其训练过程通常非常平稳,不容易出现DQN中常见的策略崩塌现象。

在实际选择中:

  • 如果样本获取成本低(如仿真环境): PPO是首选,因为稳定可靠
  • 如果样本获取成本高(如真实机器人): SAC是首选,因为样本效率高
  • 如果动作空间是离散的: PPO天然支持,SAC需要额外改造

评论 #