PPO

Proximal Policy Optimization Algorithms

两阶段循环

为什么可以“多轮”

通常情况下,如果对同一批数据进行多轮优化,策略会因为更新过头而崩溃。但 PPO 引入了 Clipped Objective(裁剪目标函数)

  • 安全护栏:在每一轮优化中,PPO 会计算新策略和采样时的旧策略的概率比。如果这个比值超出了设定的范围(比如 0.81.20.8 \sim 1.2),梯度就会被“截断”。
  • 效果:这确保了即使在这一批数据上反复“薅羊毛”优化,新策略也不会跑得离旧策略太远,从而保证了训练的稳定性。

1. 采样阶段 (Sampling Phase)

  • 动作:让当前的策略 πθold\pi_{\theta_{old}} 在环境中运行一段时间。
  • 产出:收集一批轨迹数据(包括状态 ss、动作 aa、奖励 rr 等)。
  • 性质:这些数据是“新鲜”的,反映了当前策略的行为模式。

在这个阶段,神经网络的参数是固定不动的(即 θold\theta_{old})。Actor (策略网络):在环境中根据概率分布选择动作。数据收集:把 (st,at,rt,st+1)(s_t, a_t, r_t, s_{t+1}) 存入一个临时的 Buffer。目标:收集足够数量的轨迹(比如 2048 个时间步)。

1.1. 计算“标签” (Preprocessing)

在开始训练前,利用收集到的数据计算两个关键值:

  • A^t\hat{A}_t (Advantage):优势函数,用来衡量这个动作比平均水平好多少。
  • RtR_t (Returns):这一步动作带来的累积奖励。

注意到

  • rt(θ)r_t(\theta):新旧策略概率比(用于 Actor)。
  • A^t\hat{A}_t:优势估计(用于 Actor,决定更新方向)。
  • RtR_t:回报目标值(用于 Critic,提升估值精度)。

如果只用即时奖励 rtr_t 作为目标,Critic 就会变得非常“短视”。即时奖励 rtr_t:只代表当前这一秒好不好。回报目标 RtR_t:代表了“做出这个动作后,直到最后我一共拿了多少分”。目的:我们希望 Critic 能够预判未来。所以我们要让 V(st)V(s_t) 去拟合这个 RtR_t

Rt=A^t+V(st)R_t = \hat{A}_t + V(s_t)

RtR_t (Returns):作为 Critic 网络的监督信号(标签)。

  • 计算逻辑:通过 A^t\hat{A}_t(优势)与采样时旧的 V(st)V(s_t) 相加得到:Rt=A^t+V(st)R_t = \hat{A}_t + V(s_t)
  • 物理意义:它代表了在当前策略下,从状态 sts_t 开始预期能获得的折现总奖励。Critic 的优化目标就是让预测值 Vθ(st)V_\theta(s_t) 尽可能接近这个 RtR_t

这意味着:

  1. 先用 GAE 算出了优势估计 A^t\hat{A}_t
  2. 通过 A^t+V(st)\hat{A}_t + V(s_t),你就反向推导出了这一步动作对应的“目标回报” RtR_t
  3. 价值损失 (Value Loss) 就变成了:MSE(Vnew(st),Rt)MSE(V_{new}(s_t), R_t)
总结
  • Actor:利用 A^t\hat{A}_t(相对好坏)来决定 θ\theta 的更新方向。
  • Critic:利用 RtR_t(绝对得分)来修正自己对世界的认知。

2. 优化阶段 (Optimization Phase)

多轮优化 (Several Epochs)

  • 动作:将刚才采样的这一批数据反复输入神经网络进行多次梯度更新。
  • 关键点:在传统的 On-policy 算法(如普通的策略梯度)中,这批数据更新一次就必须扔掉。但 PPO 允许你在同一批数据上跑 3 轮、5 轮甚至 10 轮(Epochs)。

要把 Buffer 里的数据,分成更小的 Mini-batches,重复训练 KK 个 Epochs(比如 K=10K=10)。在每一个 Epoch 里的微观操作:计算概率比 rt(θ)r_t(\theta):用当前正在更新的 θ\theta 计算动作概率,除以采样时的 θold\theta_{old} 计算的概率。应用裁剪 CLIPCLIP:如果 rt(θ)r_t(\theta) 偏离 1 太远(比如超过 20%),就强行截断。梯度更新:通过反向传播更新参数 θ\theta

为什么 rt(θ)r_t(\theta) 允许“多轮更新”。PPO 能够从 On-policy 转向近乎 Off-policy 的理论支柱,PPO 本质上是利用了重要性采样技术。

  • 理论背景:我们想优化新策略 πθ\pi_\theta,但手里只有旧策略 πθold\pi_{\theta_{old}} 采到的数据。
  • 补偿机制:通过概率比率 rt(θ)r_t(\theta),我们修正了数据分布的偏差。
  • 约束:重要性采样要求两个分布不能差太远,否则方差会爆炸。这正是 LCLIPL^{CLIP} 存在的根本原因——它在数学上维护了重要性采样的有效区间。

2.1. 优势估计

通常采用 GAE (Generalized Advantage Estimation)。

简单来说,优势函数 A^t\hat{A}_t 的目标是回答:“在状态 sts_t 下采取动作 ata_t,比平均情况(即 Baseline)好多少?”

2.1.1. 计算时序差分残差(Temporal Difference Error)

首先计算每一个时间步的即时偏差 δt\delta_t。它衡量了“实际观测到的奖励 + 下一步的估值”与“当前估值”之间的差距:

δt=rt+γV(st+1)V(st)\delta_t = r_t + \gamma V(s_{t+1}) - V(s_t)

  • rtr_t:当前步获得的奖励。
  • V(st+1)V(s_{t+1}):神经网络(Critic)对下一步状态的估值。
  • V(st)V(s_t):神经网络(Critic)对当前状态的估值。
2.1.2. 累加衰减

[0, T)

优势估计 A^t\hat{A}_t 不是只看当前这一步,而是要把未来的 δ\delta 都考虑进来,但要进行指数衰减。公式如下:

A^t=δt+(γλ)δt+1+(γλ)2δt+2++(γλ)T1tδT1\hat{A}_t = \delta_t + (\gamma\lambda)\delta_{t+1} + (\gamma\lambda)^2\delta_{t+2} + \cdots + (\gamma\lambda)^{T-1-t}\delta_{T-1}

这里有两个关键的超参数:

  • γ\gamma (Gamma):折扣因子(通常 0.99),决定了对远期奖励的重视程度。
  • λ\lambda (Lambda):GAE 因子(通常 0.95),用于在偏差(Bias)和方差(Variance)之间做权衡。

实现时,逆序(t)计算

  • 如果 λ=0\lambda = 0A^t=δt\hat{A}_t = \delta_t。这叫 1-step TD。它很稳定(方差小),但如果你的 VV 函数估值不准,它就会错得离谱(偏差大)。

  • 如果 λ=1\lambda = 1A^t\hat{A}_t 变成了从当前步到截断点 TT 的所有奖励累加。这很真实(无偏差),但环境随机性太强,导致数值跳变剧烈(方差大)。

这就是 λ\lambda 用于在偏差(Bias)和方差(Variance)之间做权衡的物理意义。PPO 选取 λ=0.95\lambda = 0.95 它在“相信神经网络的估值”和“相信实际观测到的奖励”之间取了一个折中。

2.1.3. 标准化 (Advantage Normalization)

在算出 TT 个时间步的所有 A^t\hat{A}_t 后,工程上通常会进行一次标准化处理:

A^t=A^tmean(A^)std(A^)+108\hat{A}_t = \frac{\hat{A}_t - \text{mean}(\hat{A})}{\text{std}(\hat{A}) + 10^{-8}}

  • 稳定梯度:在一个 Batch 中,优势值的数值跨度可能很大。标准化后,它们的均值为 0,标准差为 1。
  • 逻辑闭环:这确保了在一个 Batch 里,大约有一半的动作会被认为是“好于平均”(正值,增加概率),另一半是“差于平均”(负值,减小概率)。这对于 Adam 优化器的稳定收敛极其重要。
总结计算流程

rt(θ)=πθ(atst)πθold(atst)r_t(\theta) = \frac{\pi_\theta(a_t | s_t)}{\pi_{\theta_{old}}(a_t | s_t)}

  1. 运行 TT 步采样,收集所有的 rr 概率比例和 VV 状态价值。
  2. 从后往前计算(这样可以用 At+1A_{t+1} 算出 AtA_t):
  • At=δt+(γλ)At+1A_t = \delta_t + (\gamma\lambda) A_{t+1}
  1. 对整个 Batch 进行标准化。
  2. 将算好的 A^\hat{A} 输入 LCLIPL^{CLIP} 进行优化。

2.2. 损失函数

优势估计 A^t\hat{A}_t 和概率比率 rt(θ)r_t(\theta) 都准备好了,进入 PPO 执行阶段构建 Loss 函数并进行参数更新

Adam 优化器并不是只优化策略,它其实是在同时优化三个目标。

总损失函数 LtCLIP+VF+SL^{CLIP+VF+S}_t 通常长这样:

Lttotal(θ)=LtCLIP(θ)c1LtVF(θ)+c2S[πθ](st)L_t^{total}(\theta) = L_t^{CLIP}(\theta) - c_1 L_t^{VF}(\theta) + c_2 S[\pi_\theta](s_t)

LCLIP(θ)=E^t[min(rt(θ)A^t,clip(rt(θ),1ϵ,1+ϵ)A^t)]L^{CLIP}(\theta) = \hat{\mathbb{E}}_t \left[ \min \left( r_t(\theta) \hat{A}_t, \text{clip}(r_t(\theta), 1 - \epsilon, 1 + \epsilon) \hat{A}_t \right) \right]

这三个部分分工明确:

  • LtCLIP(θ)L_t^{CLIP}(\theta) (策略损失):利用 A^t\hat{A}_trt(θ)r_t(\theta) 进行裁剪优化。它负责告诉 Actor:“哪些动作该多做,但别改得太猛。”
  • LtVF(θ)L_t^{VF}(\theta) (价值损失):通常是均方误差 MSE(Vθ(st),Vtarget)MSE(V_\theta(s_t), V_{target})。它负责告诉 Critic:“你的预言(估值)要更准一点。”
  • S[πθ](st)S[\pi_\theta](s_t) (熵奖励):鼓励策略保持一定的随机性。它负责告诉模型:“别太早固定死某一个动作,多去探索其他可能性。”

MSE 均方误差

2.2.1. 执行 Adam 更新

Adam 优化器, 梯度下降 (Gradient Descent)

有了总损失后,流程如下:

  1. 计算梯度:对总损失关于参数 θ\theta 求导(即之前提到的 L wrt θL \text{ wrt } \theta)。
  2. 反向传播:将梯度传回神经网络。
  3. 参数更新:Adam 优化器根据动量和自适应学习率微调 θ\theta

进入 KK 个 Epoch 的循环

针对同一批采样数据(那 NTNT 个样本),反复进行 KK 次上述的“计算 Loss -> 更新参数”过程。

  • 在第 1 遍时:rt(θold)=1r_t(\theta_{old}) = 1,大家都在正常学习。
  • 在第 KK 遍时:由于参数已经改了好几次,新旧策略的偏差 rt(θ)r_t(\theta) 可能会很大。这时候 Clipping(裁剪) 就会大显身手,强行把那些偏移过大的梯度归零,防止模型跑飞。

注:虽然 PPO 的理论目标是最大化奖励,但在代码实现中,我们通过对总目标函数取负值,将其转化为一个最小化损失的问题,从而利用 Adam 优化器进行参数更新。

2.2.2. 更新旧策略 (θoldθ\theta_{old} \leftarrow \theta)

KK 次迭代结束,这一批数据的价值就被“榨干”了。
此时,我们将当前的最新参数 θ\theta 赋值给 θold\theta_{old}。然后清空缓存的数据,回到环境里,开启下一轮 N×TN \times T 的数据采集。

3. Hyperparameters 参考

参数 常用值 作用
ϵ\epsilon 0.10.20.1 \sim 0.2 裁剪阈值,限制单次更新步长
γ\gamma 0.990.99 长期奖励折扣因子
λ\lambda 0.950.95 GAE 平衡因子
c1c_1 0.50.5 价值损失权重(MSE 权重)
c2c_2 0.010.01 熵系数(鼓励探索,防止过早收敛)
KK 3103 \sim 10 每个 Batch 的重复训练次数(Epochs)