Chapter 15 · Section 15.6 · 70 min

Trust regions and PPO

Every policy gradient method so far uses each batch of experience for exactly one gradient step and then throws it away. For a robot simulator that is wasteful. For a language model it is painful, because the experience is generated text, and generating a batch of long responses from a large model can take longer than the gradient step itself. The obvious fix is to take many gradient steps on each batch. The obvious fix fails, and understanding why it fails, and what to do instead, leads to the most widely used RL algorithm of the last decade, proximal policy optimization (PPO), the algorithm behind InstructGPT and the RLHF stage of §16.4.

This section is the payoff for the chapter. We will see why large policy steps break things in a way they never do in supervised learning, reuse old data correctly with importance sampling, derive the surrogate objective and the trust-region idea behind TRPO, and then derive PPO's clipped objective case by case. Then we write a complete PPO in NumPy, with GAE, a critic, an entropy bonus, minibatch epochs, and the diagnostics practitioners watch, and run the ablation that shows what the clip is for.

Why big policy steps break

In supervised learning, a bad gradient step is cheap. The next batch comes from the same fixed dataset, its gradient points back toward good parameters, and the damage is undone. In RL the next batch comes from the new policy. If a step makes the policy bad, the data it collects is bad (a robot that falls over immediately learns only about falling over), and the policy may never see the states it would need to recover. Errors compound through the data distribution.

Three facts make large steps dangerous.

  • The gradient is local. The policy gradient tells you the slope of J(θ)J(\theta) at the current parameters and nothing about what happens a long way off.
  • Distance in parameters is not distance in behavior. A change of 1 in the logits of a softmax that sits at (0,0)(0, 0) moves the probabilities from (0.5,0.5)(0.5, 0.5) to (0.73,0.27)(0.73, 0.27). The same change at logits (6,0)(6, 0) moves the probability of the second action from 0.00250.0025 to 0.00090.0009, which barely matters in absolute terms. Measured by the KL divergence from the old distribution to the new one, the first step moves the policy by 0.12 nats and the second by 0.0009, more than a hundred times less, for the same step in parameters. The geometry that matters is the distance between the action distributions, which a fixed learning rate in parameter space ignores.
  • Reusing a batch overfits to it. Take many steps on one batch's advantage estimates and the policy chases their noise. Actions that happened to look good once get their probability driven toward 1, the policy becomes nearly deterministic, its softmax gradient vanishes, and the exploration needed to find out it was wrong is gone.

We will watch the third failure happen in the gridworld at the end of the section. First, the tools for doing better.

Reusing data with importance sampling

Suppose the data was collected by an old policy πθold\pi_{\theta_{\text{old}}} and we want to evaluate, or differentiate, an expectation under a new one. Importance sampling corrects for the mismatch. For any distributions pp and qq with q(x)>0q(x) > 0 wherever p(x)>0p(x) > 0,

Ex∼p[f(x)]=∑xp(x)f(x)=∑xq(x)p(x)q(x)f(x)=Ex∼q[p(x)q(x)f(x)].\mathbb{E}_{x\sim p}[f(x)] = \sum_x p(x)f(x) = \sum_x q(x)\frac{p(x)}{q(x)}f(x) = \mathbb{E}_{x\sim q}\Big[\frac{p(x)}{q(x)}f(x)\Big].

In plain words, to average under pp using samples from qq, reweight each sample by how much more (or less) likely pp makes it than qq did. The weight p(x)/q(x)p(x)/q(x) is the likelihood ratio of §0.5.

The estimator is unbiased, but its variance can be enormous. Its second moment is Eq[(p/q)2f2]=Ep[(p/q)f2]\mathbb{E}_q[(p/q)^2f^2] = \mathbb{E}_p[(p/q)f^2], which blows up wherever pp puts mass that qq rarely visits. For two unit-variance Gaussians whose means differ by Δ\Delta, the weights have second moment Eq[(p/q)2]=eΔ2\mathbb{E}_q[(p/q)^2] = e^{\Delta^2}, so a shift of 3 standard deviations makes the weights' variance about e9−1≈8,100e^{9} - 1 \approx 8{,}100. A common diagnostic is the effective sample size, (∑iwi)2/∑iwi2(\sum_iw_i)^2/\sum_iw_i^2 for weights wiw_i, which says how many samples from pp the reweighted batch is worth.

Python · cell 1

At a shift of 3 the hundred thousand samples are worth about forty, and the estimate is 20% off. Importance sampling works only when the two distributions are close. For policies, this tells us what to reweight. A whole trajectory's probability ratio is a product of TT per-step ratios πθ(at∣st)/πθold(at∣st)\pi_\theta(a_t\mid s_t)/\pi_{\theta_{\text{old}}}(a_t\mid s_t) (the dynamics cancel, as in §15.3), and a product of many ratios that are each near 1 can still be wildly far from 1, so trajectory-level weights have variance that grows exponentially with the horizon. Policy optimization methods therefore reweight one action at a time and handle the change in the state distribution differently, by staying close.

Warm-up

Importance-weighted estimates from log-probabilities

Given values f of shape (N,) at samples drawn from a behavior distribution qq, and the log-probabilities of those samples under the target pp and the behavior qq (logp_target, logp_behavior, each (N,)), implement importance_estimate(f, logp_target, logp_behavior) returning a dict with "ordinary" (the unbiased estimate 1N∑iwifi\frac1N\sum_iw_if_i), "self_normalized" (∑iwifi/∑iwi\sum_iw_if_i/\sum_iw_i), and "ess", the effective sample size (∑w)2/∑w2(\sum w)^2/\sum w^2. Compute the weights from log-probabilities, and make the self-normalized estimate and the ESS robust to log-ratios of several hundred.

Hidden tests, hints, and AI help, from $1.99 a month.

The surrogate objective

How much better is a new policy π′\pi' than the old one π\pi? There is an exact answer, the performance difference lemma of Kakade and Langford (2002),

J(π′)−J(π)=Eτ∼π′[∑t=0∞γtAπ(st,at)],J(\pi') - J(\pi) = \mathbb{E}_{\tau\sim\pi'}\Big[\sum_{t=0}^{\infty}\gamma^t A^{\pi}(s_t, a_t)\Big],

where AπA^\pi is the advantage function of the old policy and the trajectory is drawn from the new one. In plain words, the improvement is the total advantage, as judged by the old policy, of the actions the new policy takes in the states it visits.

∂ Proof of the performance difference lemma

Recall that Aπ(s,a)=Qπ(s,a)−Vπ(s)=E[rt+γVπ(st+1)−Vπ(st)∣st=s,at=a]A^\pi(s,a) = Q^\pi(s,a) - V^\pi(s) = \mathbb{E}[r_t + \gamma V^\pi(s_{t+1}) - V^\pi(s_t)\mid s_t = s, a_t = a], an expectation over the reward and the next state only, which follow the environment whichever policy chose aa. So along a trajectory of π′\pi', by the tower rule,

Eτ∼π′[∑tγtAπ(st,at)]=Eτ∼π′[∑tγt(rt+γVπ(st+1)−Vπ(st))].\mathbb{E}_{\tau\sim\pi'}\Big[\sum_t\gamma^tA^\pi(s_t,a_t)\Big] = \mathbb{E}_{\tau\sim\pi'}\Big[\sum_t\gamma^t\big(r_t + \gamma V^\pi(s_{t+1}) - V^\pi(s_t)\big)\Big].

The value terms telescope, ∑tγt(γVπ(st+1)−Vπ(st))=−Vπ(s0)\sum_t\gamma^t(\gamma V^\pi(s_{t+1}) - V^\pi(s_t)) = -V^\pi(s_0) (the tail γTVπ(sT)\gamma^TV^\pi(s_T) vanishes as T→∞T\to\infty with γ<1\gamma < 1, or is zero at a terminal state). What is left is Eτ∼π′[∑tγtrt]−Es0∼p0[Vπ(s0)]=J(π′)−J(π)\mathbb{E}_{\tau\sim\pi'}[\sum_t\gamma^tr_t] - \mathbb{E}_{s_0\sim p_0}[V^\pi(s_0)] = J(\pi') - J(\pi), because both policies start from the same p0p_0. □\square

Writing the expectation with the discounted visitation ρπ′\rho^{\pi'} of §15.3 gives J(π′)−J(π)=∑sρπ′(s)∑aπ′(a∣s)Aπ(s,a)J(\pi') - J(\pi) = \sum_s\rho^{\pi'}(s)\sum_a\pi'(a\mid s)A^\pi(s,a). Everything here is computable from old data except ρπ′\rho^{\pi'}, the states the new policy would visit. The surrogate pretends the state distribution has not changed, and uses importance sampling for the actions,

Lπ(π′)=J(π)+∑sρπ(s)∑aπ′(a∣s)Aπ(s,a)=J(π)+11−γ Es∼dπ, a∼π[π′(a∣s)π(a∣s)Aπ(s,a)],L_{\pi}(\pi') = J(\pi) + \sum_s\rho^{\pi}(s)\sum_a\pi'(a\mid s)A^{\pi}(s,a) = J(\pi) + \frac{1}{1-\gamma}\,\mathbb{E}_{s\sim d^\pi,\, a\sim\pi}\Big[\frac{\pi'(a\mid s)}{\pi(a\mid s)}A^\pi(s,a)\Big],

where dπ=(1−γ)ρπd^\pi = (1-\gamma)\rho^\pi is the visitation normalized into a probability distribution, which is what sampling states from the old policy's rollouts approximates. Two facts make it the right object. At π′=π\pi' = \pi the ratio is 1 and ∑aπ(a∣s)Aπ(s,a)=0\sum_a\pi(a\mid s)A^\pi(s,a) = 0, so Lπ(π)=J(π)L_\pi(\pi) = J(\pi). And its gradient at θ=θold\theta = \theta_{\text{old}} is the policy gradient, since ∇θπθ(a∣s)πold(a∣s)∣θold=∇θlog⁡πθ(a∣s)∣θold\nabla_\theta\frac{\pi_\theta(a\mid s)}{\pi_{\text{old}}(a\mid s)}\big|_{\theta_{\text{old}}} = \nabla_\theta\log\pi_\theta(a\mid s)\big|_{\theta_{\text{old}}}, which turns ∇L\nabla L into the policy gradient theorem with advantages. So the surrogate matches JJ to first order. Far from π\pi, the ignored change in ρ\rho makes it wrong, by an amount that grows with how different the policies are.

Schulman et al. (2015) bounded that error. This is a theorem (TRPO, Theorem 1 and its corollary). For any two policies,

J(π′)≥Lπ(π′)−4ϵAγ(1−γ)2 max⁡sDKL(π(⋅∣s) ∥ π′(⋅∣s)),ϵA=max⁡s,a∣Aπ(s,a)∣.J(\pi') \ge L_\pi(\pi') - \frac{4\epsilon_A\gamma}{(1-\gamma)^2}\,\max_s D_{\mathrm{KL}}\big(\pi(\cdot\mid s)\,\Vert\,\pi'(\cdot\mid s)\big), \qquad \epsilon_A = \max_{s,a}|A^\pi(s,a)| .

The right side equals J(π)J(\pi) at π′=π\pi' = \pi and lower-bounds JJ everywhere, so maximizing it can never decrease JJ. That is a minorize-maximize scheme with a monotone improvement guarantee. Its practical lesson is the shape, not the constant. The surrogate can be trusted only within a region where the policy has not changed much, measured by KL divergence.

Here is the surrogate against the true objective in the gridworld, stepping from the uniform policy along the exact policy gradient by increasing amounts.

Python · cell 2

Near the old policy, at a maximum KL of a few hundredths, the surrogate and the true objective agree closely. As the step grows they drift apart, and the surrogate stops being a reliable guide (in this problem it happens to be pessimistic, in others it is optimistic, and with estimated advantages it is noisy as well). The TRPO bound is valid at every step and useless as a step-size rule. Its constant, over 200 here, makes the penalty swamp any improvement after the tiniest step, which is why TRPO itself replaces the penalty with a constraint whose size is chosen by hand.

TRPO, a trust region measured in KL

Trust region policy optimization (Schulman et al. 2015) maximizes the surrogate subject to a limit on how far the policy moves, measured by the average KL divergence over the states the old policy visits,

max⁡θ  Es,a∼πθold[πθ(a∣s)πθold(a∣s)A^(s,a)]subject toEs∼πθold[DKL(πθold(⋅∣s) ∥ πθ(⋅∣s))]≤δKL,\max_\theta\; \mathbb{E}_{s,a\sim\pi_{\theta_{\text{old}}}}\Big[\frac{\pi_\theta(a\mid s)}{\pi_{\theta_{\text{old}}}(a\mid s)}\hat A(s,a)\Big] \quad\text{subject to}\quad \mathbb{E}_{s\sim\pi_{\theta_{\text{old}}}}\Big[D_{\mathrm{KL}}\big(\pi_{\theta_{\text{old}}}(\cdot\mid s)\,\Vert\,\pi_\theta(\cdot\mid s)\big)\Big] \le \delta_{\text{KL}},

with δKL=0.01\delta_{\text{KL}} = 0.01 in the paper's experiments. To solve it, approximate both sides around θold\theta_{\text{old}}. The objective is linear to first order, with slope g\mathbf{g}, the policy gradient. The KL is zero with zero gradient at θold\theta_{\text{old}} (it is minimized there), so to second order it is a quadratic form,

Es[DKL(πθold∥πθold+Δ)]≈12Δ⊤FΔ,F=Es,a∼πθold[∇log⁡πθ(a∣s) ∇log⁡πθ(a∣s)⊤],\mathbb{E}_s\big[D_{\mathrm{KL}}(\pi_{\theta_{\text{old}}}\Vert\pi_{\theta_{\text{old}}+\Delta})\big] \approx \tfrac12\Delta^\top\mathbf{F}\Delta, \qquad \mathbf{F} = \mathbb{E}_{s,a\sim\pi_{\theta_{\text{old}}}}\big[\nabla\log\pi_\theta(a\mid s)\,\nabla\log\pi_\theta(a\mid s)^\top\big],

where F\mathbf{F} is the Fisher information matrix of the policy, the Hessian of the KL at its minimum. The approximate problem is to maximize g⊤Δ\mathbf{g}^\top\Delta subject to 12Δ⊤FΔ≤δKL\frac12\Delta^\top\mathbf{F}\Delta \le \delta_{\text{KL}}. Setting the gradient of the Lagrangian g⊤Δ−μ(12Δ⊤FΔ−δKL)\mathbf{g}^\top\Delta - \mu(\frac12\Delta^\top\mathbf{F}\Delta - \delta_{\text{KL}}) to zero gives Δ=μ−1F−1g\Delta = \mu^{-1}\mathbf{F}^{-1}\mathbf{g}, and making the constraint tight fixes the scale,

Δ=2δKLg⊤F−1g  F−1g.\Delta = \sqrt{\frac{2\delta_{\text{KL}}}{\mathbf{g}^\top\mathbf{F}^{-1}\mathbf{g}}}\;\mathbf{F}^{-1}\mathbf{g}.

The direction F−1g\mathbf{F}^{-1}\mathbf{g} is the natural policy gradient (Kakade 2001), the steepest ascent direction when distance is measured by KL between the policies rather than Euclidean distance between the parameters, so, for small steps, it does not depend on how the policy happens to be parameterized. TRPO computes it without ever forming F\mathbf{F}, by running conjugate gradient with Fisher-vector products (each one a double backward pass), and then does a backtracking line search along Δ\Delta until the true sampled KL is within δKL\delta_{\text{KL}} and the surrogate actually improves.

TRPO works, and its trust region is enforced almost exactly, but the machinery is heavy. It is second-order, it needs a line search, and it does not combine easily with architectures that share parameters between the policy and the value function, or with dropout. PPO was designed to get most of the benefit with first-order optimization alone.

Research

The natural gradient step of a softmax policy

For one state with a softmax policy over KK actions (logits z, shape (K,)) and advantages adv (shape (K,), the advantage of each action), the surrogate's gradient with respect to the logits is g=π⊙(A−Aˉ)\mathbf{g} = \boldsymbol\pi\odot(\mathbf{A} - \bar A) with Aˉ=π⋅A\bar A = \boldsymbol\pi\cdot\mathbf{A}, and the Fisher matrix is F=diag⁡(π)−ππ⊤\mathbf{F} = \operatorname{diag}(\boldsymbol\pi) - \boldsymbol\pi\boldsymbol\pi^\top, which is singular (adding a constant to every logit changes nothing). Implement natural_step(z, adv, delta) returning the new logits z+2δ/(g⊤F+g) F+g\mathbf{z} + \sqrt{2\delta/(\mathbf{g}^\top\mathbf{F}^+\mathbf{g})}\,\mathbf{F}^+\mathbf{g}, where F+\mathbf{F}^+ is the pseudo-inverse. Then show in a comment that F+g\mathbf{F}^+\mathbf{g} is the centered advantage A−Aˉ1\mathbf{A} - \bar A\mathbf{1}, up to a multiple of 1\mathbf{1}, and that g⊤F+g=Var⁡π(A)\mathbf{g}^\top\mathbf{F}^+\mathbf{g} = \operatorname{Var}_{\boldsymbol\pi}(A).

Hidden tests, hints, and AI help, from $1.99 a month.

The exercise has a striking conclusion. For a softmax policy, the natural gradient step adds a multiple of the advantage itself to the logits, za←za+ηA(s,a)z_a \leftarrow z_a + \eta A(s, a), which is the policy πnew(a∣s)∝πold(a∣s) eηA(s,a)\pi_{\text{new}}(a\mid s)\propto\pi_{\text{old}}(a\mid s)\,e^{\eta A(s,a)}. Natural policy gradient on tabular softmax policies is a soft version of policy iteration. We will meet that exponential tilt again as the closed-form solution of the KL-regularized objective in §15.7 and §17.1.

PPO, clipping the incentive to move

Schulman, Wolski, Dhariwal, Radford, and Klimov (2017) asked whether a first-order method could keep the policy inside a trust region without computing one. Write the per-sample probability ratio as

ρt(θ)=πθ(at∣st)πθold(at∣st),\rho_t(\theta) = \frac{\pi_\theta(a_t\mid s_t)}{\pi_{\theta_{\text{old}}}(a_t\mid s_t)},

so that the surrogate (without the constant J(πold)J(\pi_{\text{old}})) is Et[ρt(θ)A^t]\mathbb{E}_t[\rho_t(\theta)\hat A_t], an average over the timesteps of the batch, with A^t\hat A_t from GAE (§15.5). (The PPO paper calls this ratio rt(θ)r_t(\theta) and the objective LL. We use ρt\rho_t to keep rtr_t for rewards and JJ for objectives we maximize.) Maximizing the plain surrogate over many epochs drives each ρt\rho_t as far as it will go in the direction of its advantage's sign, which is exactly the overfitting described above. PPO's clipped objective removes the incentive to do that,

JCLIP(θ)=Et[min⁡(ρt(θ)A^t,  clip⁡(ρt(θ), 1−ϵ, 1+ϵ)A^t)],J^{\text{CLIP}}(\theta) = \mathbb{E}_t\Big[\min\Big(\rho_t(\theta)\hat A_t,\;\operatorname{clip}\big(\rho_t(\theta),\,1-\epsilon,\,1+\epsilon\big)\hat A_t\Big)\Big],

with ϵ=0.2\epsilon = 0.2 as the paper's default, where clip⁡(x,l,u)=max⁡(l,min⁡(x,u))\operatorname{clip}(x, l, u) = \max(l, \min(x, u)).

The formula is compact enough to be confusing, so take it one case at a time. For a single sample, what matters is the sign of the advantage and where the ratio is.

Positive advantage, A^t>0\hat A_t > 0 (the action was better than expected, and we want to make it more likely). The clipped term is min⁡(ρt,1+ϵ)A^t\min(\rho_t, 1+\epsilon)\hat A_t for ρt≥1−ϵ\rho_t \ge 1 - \epsilon, and since both terms are increasing in ρt\rho_t, the minimum is min⁡(ρt,1+ϵ)A^t\min(\rho_t, 1+\epsilon)\hat A_t. The objective rises with ρt\rho_t until ρt=1+ϵ\rho_t = 1 + \epsilon and is flat beyond it. Once the action's probability has grown by 20%, this sample stops pushing.

Negative advantage, A^t<0\hat A_t < 0 (the action was worse than expected, and we want to make it less likely). Multiplying by a negative number flips the order, and the objective is max⁡(ρt,1−ϵ)A^t\max(\rho_t, 1-\epsilon)\hat A_t. It rises as ρt\rho_t falls, until ρt=1−ϵ\rho_t = 1 - \epsilon, and is flat below. Once the action's probability has shrunk by 20%, this sample stops pushing.

The ratio moved the wrong way. If A^t>0\hat A_t > 0 but ρt<1−ϵ\rho_t < 1 - \epsilon (other updates made a good action much less likely), the unclipped term ρtA^t\rho_t\hat A_t is the smaller one, so it is selected, and its gradient pulls the ratio back up. Likewise for A^t<0\hat A_t < 0 and ρt>1+ϵ\rho_t > 1+\epsilon. Clipping only ever removes the incentive to move further in the helpful direction. It never stops the objective from correcting a step that went the harmful way.

advantageratioterm selected by the mingradient with respect to ρt\rho_t
A^>0\hat A > 0ρ<1−ϵ\rho < 1-\epsilonρA^\rho\hat A (unclipped)A^\hat A, push up
A^>0\hat A > 01−ϵ≤ρ≤1+ϵ1-\epsilon \le \rho \le 1+\epsilonρA^\rho\hat A (both equal)A^\hat A, push up
A^>0\hat A > 0ρ>1+ϵ\rho > 1+\epsilon(1+ϵ)A^(1+\epsilon)\hat A (clipped)0, stop
A^<0\hat A < 0ρ<1−ϵ\rho < 1-\epsilon(1−ϵ)A^(1-\epsilon)\hat A (clipped)0, stop
A^<0\hat A < 01−ϵ≤ρ≤1+ϵ1-\epsilon \le \rho \le 1+\epsilonρA^\rho\hat A (both equal)A^\hat A, push down
A^<0\hat A < 0ρ>1+ϵ\rho > 1+\epsilonρA^\rho\hat A (unclipped)A^\hat A, push down

Since ∇θρt=ρt∇θlog⁡πθ(at∣st)\nabla_\theta\rho_t = \rho_t\nabla_\theta\log\pi_\theta(a_t\mid s_t), the full gradient of one sample is ρtA^t∇θlog⁡πθ(at∣st)\rho_t\hat A_t\nabla_\theta\log\pi_\theta(a_t\mid s_t) when the unclipped term is selected and zero otherwise. At θ=θold\theta = \theta_{\text{old}} every ratio is 1, so the first epoch's first step is exactly the vanilla policy gradient with advantages. The clip only matters on later steps over the same data.

Two more properties are worth stating precisely. The clipped objective is a pessimistic bound on the unclipped one. Each sample's term is the minimum of the unclipped term and something else, so JCLIP≤Et[ρtA^t]J^{\text{CLIP}} \le \mathbb{E}_t[\rho_t\hat A_t], with equality whenever every ratio is inside [1−ϵ,1+ϵ][1-\epsilon, 1+\epsilon]. And clipping is not a constraint. Nothing stops a ratio from leaving the interval, because other samples' gradients move shared parameters, and a sample in the flat region just stops resisting. Engstrom et al. (2020) measured that ratios in real PPO runs do routinely leave the clip range, and that the trust region PPO actually maintains depends heavily on the rest of the recipe.

What does PPO's clipped objective look like as a function of the probability ratio?
One sample's term, min(ρÂ, clip(ρ, 1−ε, 1+ε)Â), for a positive and a negative advantage. The dashed line is the unclipped surrogate ρÂ. Drag the ratio to see the value and the slope the optimizer feels.
Loading interactive figure…
Interview

The PPO clipped surrogate and its gradient

Implement ppo_clip_loss(logp_new, logp_old, adv, eps) for a batch of NN samples. Return (loss, dloss_dlogp) where loss is the negative clipped objective averaged over the batch,

loss=−1N∑imin⁡(ρiA^i, clip⁡(ρi,1−ϵ,1+ϵ)A^i),ρi=elog⁡πnew−log⁡πold,\text{loss} = -\frac1N\sum_i\min\big(\rho_i\hat A_i,\ \operatorname{clip}(\rho_i, 1-\epsilon, 1+\epsilon)\hat A_i\big),\qquad \rho_i = e^{\log\pi_{\text{new}} - \log\pi_{\text{old}}},

and dloss_dlogp is its gradient with respect to logp_new, shape (N,). Treat logp_old and adv as constants. At a ratio exactly on a clip boundary, use the unclipped gradient.

Hidden tests, hints, and AI help, from $1.99 a month.

The KL-penalty variant

The PPO paper also tried a softer alternative to the clip, a penalty on the sampled KL divergence, Et[ρtA^t]−β Et[DKL(πθold(⋅∣st)∥πθ(⋅∣st))]\mathbb{E}_t[\rho_t\hat A_t] - \beta\,\mathbb{E}_t[D_{\mathrm{KL}}(\pi_{\theta_{\text{old}}}(\cdot\mid s_t)\Vert\pi_\theta(\cdot\mid s_t))], with β\beta adapted after each update toward a target KL dtargd_{\text{targ}}. If the measured KL dd is below dtarg/1.5d_{\text{targ}}/1.5, halve β\beta. If it is above 1.5 dtarg1.5\,d_{\text{targ}}, double it. On their continuous-control benchmark the clipped objective with ϵ=0.2\epsilon = 0.2 scored best (an average normalized score of 0.82, against 0.74 for the best adaptive-KL setting and −0.39-0.39 for the unclipped surrogate with no penalty, which on one task did worse than the random initial policy). The KL penalty survives in a different role in RLHF, as a penalty toward a fixed reference model rather than toward the previous policy (§15.7), and adaptive controllers in the same spirit keep that KL near a target (Ziegler et al. 2019 use a proportional controller on β\beta).

The rest of the objective, value loss and entropy

PPO trains a critic VψV_\psi (a value network with parameters ψ\psi) alongside the policy, on the value targets R^t=A^t+Vold(st)\hat R_t = \hat A_t + V_{\text{old}}(s_t) from §15.5, with a squared error LV=Et[(Vψ(st)−R^t)2]\mathcal{L}^V = \mathbb{E}_t[(V_\psi(s_t) - \hat R_t)^2], and adds an entropy bonus H[πθ(⋅∣st)]=−∑aπθ(a∣st)log⁡πθ(a∣st)H[\pi_\theta(\cdot\mid s_t)] = -\sum_a\pi_\theta(a\mid s_t)\log\pi_\theta(a\mid s_t) that discourages the policy from becoming deterministic too early. The combined objective, maximized each iteration, is

JPPO(θ,ψ)=Et[ JtCLIP(θ)−cv(Vψ(st)−R^t)2+cH H[πθ(⋅∣st)]],J^{\text{PPO}}(\theta, \psi) = \mathbb{E}_t\Big[\,J^{\text{CLIP}}_t(\theta) - c_v\big(V_\psi(s_t) - \hat R_t\big)^2 + c_H\,H\big[\pi_\theta(\cdot\mid s_t)\big]\Big],

with coefficients cvc_v (the paper's c1c_1, often 0.5 or 1) and cHc_H (the paper's c2c_2, 0.01 in its Atari experiments and 0 in MuJoCo). When the policy and the critic are separate networks the value term does not affect θ\theta and the two can be trained independently. When they share a body, cvc_v balances the two gradients.

The entropy's gradient with respect to softmax logits is worth deriving once, since you will implement it. From H=−∑iπilog⁡πiH = -\sum_i\pi_i\log\pi_i and ∂πi/∂zj=πi(1[i=j]−πj)\partial\pi_i/\partial z_j = \pi_i(\mathbb{1}[i=j] - \pi_j),

∂H∂zj=−∑iπi(1[i=j]−πj)(log⁡πi+1)=−πj(log⁡πj+1)+πj∑iπi(log⁡πi+1)=−πj(log⁡πj+H).\frac{\partial H}{\partial z_j} = -\sum_i\pi_i(\mathbb{1}[i=j]-\pi_j)(\log\pi_i + 1) = -\pi_j(\log\pi_j + 1) + \pi_j\sum_i\pi_i(\log\pi_i + 1) = -\pi_j\big(\log\pi_j + H\big).

In plain words, the entropy bonus lowers the logits of actions that are more likely than the "typical" action (log⁡πj>−H\log\pi_j > -H) and raises the others, pulling the policy toward uniform.

Many implementations also clip the value function, mirroring the policy clip, and use the larger of the clipped and unclipped squared errors. Huang et al. (2022) document it as one of PPO's standard details. Andrychowicz et al. (2021), in a study of on-policy RL with over 250,000 agents trained, found that value clipping did not help and recommended against it. It is a detail you should be able to implement and to question.

Core

The value loss with optional clipping

Implement ppo_value_loss(v_new, v_old, returns, clip=None) for arrays of shape (N,). Without clipping return 12⋅1N∑i(vinew−R^i)2\frac12\cdot\frac1N\sum_i(v^{\text{new}}_i - \hat R_i)^2. With a clip ϵ\epsilon, let viclip=viold+clip⁡(vinew−viold,−ϵ,ϵ)v^{\text{clip}}_i = v^{\text{old}}_i + \operatorname{clip}(v^{\text{new}}_i - v^{\text{old}}_i, -\epsilon, \epsilon) and return 12⋅1N∑imax⁡((vinew−R^i)2,(viclip−R^i)2)\frac12\cdot\frac1N\sum_i\max\big((v^{\text{new}}_i - \hat R_i)^2, (v^{\text{clip}}_i - \hat R_i)^2\big), the CleanRL convention.

Hidden tests, hints, and AI help, from $1.99 a month.

The algorithm

Here is PPO in the actor-critic form of the paper's Algorithm 1, with the details almost every implementation adds.

  1. Run the current policy in NN parallel environments for TT steps each. Store states, actions, the log-probabilities log⁡πθold(at∣st)\log\pi_{\theta_{\text{old}}}(a_t\mid s_t), rewards, values, next-state values, and termination and truncation flags.
  2. Compute GAE advantages and value targets once, with the critic as it was during collection (§15.5).
  3. For KK epochs, shuffle the NTNT samples into minibatches. For each minibatch, normalize its advantages, compute the ratios against the stored old log-probabilities, and take a gradient step on the combined objective (in practice with Adam and a global gradient-norm clip, typically at 0.5).
  4. Monitor the approximate KL between the old and new policy on the batch (the k3k_3 estimator (ρ−1)−log⁡ρ(\rho - 1) - \log\rho of §0.5, averaged) and the clip fraction, the share of samples with ∣ρ−1∣>ϵ|\rho - 1| > \epsilon. Many implementations stop the epochs early if the KL exceeds a target.
  5. Set θold←θ\theta_{\text{old}}\leftarrow\theta and repeat.

The paper's MuJoCo settings were T=2048T = 2048, 10 epochs, minibatches of 64, Adam with step size 3×10−43\times10^{-4}, γ=0.99\gamma = 0.99, and λ=0.95\lambda = 0.95. Its Atari settings used 8 actors, T=128T = 128, 3 epochs, ϵ=0.1\epsilon = 0.1 annealed to zero, cv=1c_v = 1, and cH=0.01c_H = 0.01.

A complete PPO in NumPy

Now we build it, for the gridworld, with a tabular softmax policy (one logit per state and action) and a tabular critic, so that every gradient can be written by hand and we can compare the result with V⋆V^\star from §15.2. The structure is exactly that of a deep RL implementation. Only the networks are missing. The gridworld is the one defined above. First the reference answer, from value iteration, which we use only to grade the result (PPO itself never sees PP).

Python · cell 3

Next, collection. Sixteen copies of the gridworld run in lockstep, episodes are cut off after 50 steps (a truncation, not a termination), and the environments persist from one iteration to the next, so a rollout of 32 steps usually ends in the middle of episodes, which the last-step truncation flag handles. GAE is the function from Exercise 15.5b.

Python · cell 4

The update is the heart of it. Advantages and targets are computed once. Then, for each epoch and minibatch, the advantages are normalized, the ratios are computed against the stored log-probabilities, the clipped surrogate's gradient flows into the logits through the score ea−π\mathbf{e}_a - \boldsymbol\pi, the entropy bonus adds −π⊙(log⁡π+H)-\boldsymbol\pi\odot(\log\boldsymbol\pi + H), and the critic takes a regression step toward its targets. Each line maps to an equation above.

Python · cell 5

And the outer loop, which alternates collection and update and prints the diagnostics a practitioner would watch.

Python · cell 6

In 40 iterations, about 20,000 transitions, PPO climbs from −0.41-0.41 (the random walker) to about 0.34, within 0.01 of the optimal 0.351. Its greedy action is optimal in every cell, and its critic has learned the value of its own policy. The policy is still slightly random, because the entropy bonus keeps it from collapsing completely, which costs a little return. Watch the diagnostics. Early on the approximate KL per update is about 0.01 and over 20% of the samples end up clipped, because the advantages are large and the policy wants to move a lot. As the policy converges, both fall toward zero. A KL that grows instead, or a clip fraction near 1, is the usual sign of a learning rate that is too high.

What the clip is for

Now the ablation. Make the updates aggressive, with a smaller batch (8 environments, 16 steps), 30 epochs over each batch in one big minibatch, and a large learning rate, and train with and without the clip.

Python · cell 7

Without the clip, three of the five runs end far below the optimum, at −0.23-0.23, −0.01-0.01, and −0.32-0.32, two of them closer to the random walker's −0.41-0.41 than to V⋆V^\star. Their policies are nearly deterministic, with entropy close to zero, and they have locked onto wrong actions, for instance walking into the edge of the grid forever from the top-left cell. Thirty epochs on a small batch let a few lucky advantage estimates drive some ratios to enormous values, the softmax saturated, and once the policy stopped exploring, no new data could correct it. With the clip, each sample stops pushing once its ratio leaves [0.8,1.2][0.8, 1.2], the same aggressive settings are safe, and every run ends between 0.31 and 0.34. This is the whole argument for PPO, in one table. It lets you reuse a batch for many epochs, which is what makes it sample-efficient, without letting the reuse destroy the policy.

The implementation details that matter

PPO's paper is short, and its reference implementation contains many choices the paper barely mentions. Engstrom et al. (2020) showed that these "code-level optimizations" account for most of PPO's advantage over TRPO in their experiments, and Huang et al. (2022) catalogued 37 of them, 13 of which they call core. The ones you should know, and why each exists, are these.

detailtypical settingwhy
vectorized environmentsNN envs stepped togethermore diverse data per update, and fast
orthogonal initialization, small policy headgain 2\sqrt2 in hidden layers, 0.01 on the policy output, 1 on the value outputthe initial policy is near uniform, so early exploration is broad
Adam epsilon10−510^{-5} instead of 10−810^{-8}stabilizes steps for parameters with tiny second moments
learning-rate annealinglinear to 0, from 3×10−43\times10^{-4} (MuJoCo) or 2.5×10−42.5\times10^{-4} (Atari)smaller steps as the policy settles
GAEγ=0.99\gamma = 0.99, λ=0.95\lambda = 0.95, with value bootstrapping at truncationslow-variance advantages (§15.5)
minibatches and epochs4 to 32 shuffled minibatches, 4 to 10 epochssample reuse with stochastic steps
advantage normalizationper minibatchstep size insensitive to reward scale (§15.4)
clipped surrogateϵ=0.2\epsilon = 0.2 (MuJoCo), 0.1 (Atari)removes the incentive to move ratios far
value clippingsame ϵ\epsilonstandard but questionable (Andrychowicz et al. 2021)
entropy bonuscH=0.01c_H = 0.01 (Atari), 0 (MuJoCo)delays collapse to a deterministic policy
global gradient clippingnorm 0.5guards against rare huge gradients
debug variablesapproximate KL, clip fraction, value loss, entropythe dashboard that tells you a run is healthy
shared or separate networksseparate is the safer defaultkeeps the value gradient from disrupting the policy

The large study of Andrychowicz et al. (2021) gives complementary advice from their experiments, among it to start with a clipping threshold of 0.25, to initialize the last policy layer with 100 times smaller weights, to always normalize observations, to use GAE with λ=0.9\lambda = 0.9, and to treat the discount factor as one of the most important hyperparameters. RLHF adds its own layer of details on top of these (reward whitening, KL control, separate value models, and more), which §16.6 covers.

In PyTorch

In PyTorch the PPO loss is a few lines, and autograd handles the clip's piecewise gradient automatically. Here we compute it for a batch from a small policy network, check its gradient with respect to the log-probabilities against the analytic one, and take one optimizer step with the usual gradient clipping.

🔥 In PyTorchfor a machine with PyTorch

Read more

Watch next

🎬 L4 TRPO and PPO (Foundations of Deep RL Series) · Pieter AbbeelSurrogate objectives, trust regions, and the step from TRPO to PPO, from a co-author of TRPO and GAE.
🎬 Deep RL Bootcamp Lecture 5: Natural Policy Gradients, TRPO, PPO · AI PrismJohn Schulman's own lecture on natural gradients, TRPO, and PPO.
🎬 Part 1 of 3 — Proximal Policy Optimization Implementation: 11 Core Implementation Details · CoreWeaveA code walkthrough of the core PPO implementation details, one by one.