মূল লেখায় যান

রোডম্যাপ / ফেজ ৯ · Reinforcement Learning

Proximal Policy Optimization (PPO)

A2C throws away each rollout after one update. PPO wraps the policy gradient in a clipped importance ratio so you can do 10+ epochs on the same data without the policy exploding. Schulman et al. (2017). Still the default policy-gradient algorithm in 2026.

Type: Build Languages: Python Prerequisites: Phase 9 · 06 (REINFORCE), Phase 9 · 07 (Actor-Critic) Time: ~75 minutes

The Problem

A2C (Lesson 07) is on-policy: the gradient Eπθ[A⋅∇log⁡πθ]\mathbb E_{\pi_\theta}[A \cdot \nabla \log \pi_\theta] requires data sampled from the current πθ\pi_\theta. Take one update, and πθ\pi_\theta changes; the data you used is now off-policy. Re-use it and your gradient is biased.

Rollouts are expensive. On Atari, one rollout across 8 envs × 128 steps = 1024 transitions and a dozen seconds of environment time. Throwing that away after one gradient step is wasteful.

Trust Region Policy Optimization (TRPO, Schulman 2015) was the first fix: constrain each update so the KL divergence between old and new policy stays below δ\delta. Theoretically clean, but requires a conjugate-gradient solve per update. Nobody runs TRPO in 2026.

PPO (Schulman et al. 2017) replaces the hard trust-region constraint with a simple clipped objective. One extra line of code. Ten epochs per rollout. No conjugate gradients. Good-enough theoretical guarantees. Nine years later it is still the default policy-gradient algorithm for everything from MuJoCo to RLHF.

The Concept

PPO clipped surrogate objective: ratio clipping at 1 ± ε

The importance ratio.

rt(θ)=πθ(at∣st)πθold(at∣st)r_t(\theta) = \frac{\pi_\theta(a_t \mid s_t)}{\pi_{\theta_{\text{old}}}(a_t \mid s_t)}

This is the likelihood ratio of the new policy vs the policy that collected the data. rt=1r_t = 1 means no change. rt=2r_t = 2 means the new policy is twice as likely to take ata_t as the old.

The clipped surrogate.

LCLIP(θ)=Et[min⁡(rt(θ)At,clip(rt(θ),1−ϵ,1+ϵ)At)]L^{\text{CLIP}}(\theta) = \mathbb{E}_t \left[ \min\left( r_t(\theta) A_t, \mathop{\mathrm{clip}}(r_t(\theta), 1-\epsilon, 1+\epsilon) A_t \right) \right]

Two terms:

  • If the advantage At>0A_t > 0 and the ratio tries to grow past 1+ϵ1 + \epsilon, the clip flattens the gradient — don't push a good action further than +ϵ+\epsilon above old probability.
  • If the advantage At<0A_t < 0 and the ratio tries to fall below 1−ϵ1 - \epsilon (meaning we would make a bad action much less likely than the old policy did), the clip flattens the gradient: don't push a bad action further than −ϵ-\epsilon below old probability.

The min handles the other direction: if the ratio has moved in the beneficial direction, you still get the gradient (no clipping on the side that would hurt you).

Typical ϵ=0.2\epsilon = 0.2. Plot the objective as a function of rtr_t: a piecewise-linear function with a flat roof on the "good side" and a flat floor on the "bad side."

The full PPO loss.

L(θ,ϕ)=LCLIP(θ)−cv⋅(Vϕ(st)−Vttarget)2+ce⋅H(πθ(⋅∣st))L(\theta, \phi) = L^{\text{CLIP}}(\theta) - c_v \cdot (V_\phi(s_t) - V_t^{\text{target}})^2 + c_e \cdot H(\pi_\theta(\cdot \mid s_t))

Same actor-critic structure as A2C. Three coefficients, usually cv=0.5c_v = 0.5, ce=0.01c_e = 0.01, ϵ=0.2\epsilon = 0.2.

The training loop.

  1. Collect N×TN \times T transitions across NN parallel envs for TT steps each.
  2. Compute advantages (GAE), freeze them as constants.
  3. Freeze πθold\pi_{\theta_{\text{old}}} as a snapshot of current πθ\pi_\theta.
  4. For KK epochs, for each minibatch of (s, a, A, V_target, log π_old(a|s)):
    • Compute rt(θ)=exp⁡(log⁡πθ(a∣s)−log⁡πold(a∣s))r_t(\theta) = \exp(\log \pi_\theta(a \mid s) - \log \pi_{\text{old}}(a \mid s)).
    • Apply LCLIPL^{\text{CLIP}} + value loss + entropy.
    • Gradient step.
  5. Discard the rollout. Return to step 1.

K=10K = 10 and minibatches of 64 is a standard hyperparameter set. PPO is robust: the exact numbers rarely matter within ±50%.

KL-penalty variant. The original paper proposed an alternative using an adaptive KL penalty: L=LPG−β⋅KL(πθ∥πold)L = L^{\text{PG}} - \beta \cdot \mathrm{KL}(\pi_\theta \Vert \pi_{\text{old}}) with β\beta adjusted based on observed KL. The clipping version became dominant; the KL variant survives in RLHF (where KL to the reference policy is a separate constraint you always want anyway).

Interactive figure “ppo-clip”: open it on the curriculum site ↗

Build It

Step 1: capture log π_old(a | s) at rollout time

python
for step in range(T):
    probs = softmax(logits(theta, state_features(s)))
    a = sample(probs, rng)
    s_next, r, done = env.step(s, a)
    buffer.append({
        "s": s, "a": a, "r": r, "done": done,
        "v_old": value(w, state_features(s)),
        "log_pi_old": log(probs[a] + 1e-12),
    })
    s = s_next

The snapshot is taken once, at rollout time. It does not change during the update epochs.

Step 2: compute GAE advantages (Lesson 07)

Same as A2C. Normalize across the batch.

Step 3: clipped surrogate update

python
for _ in range(K_EPOCHS):
    for mb in minibatches(buffer, size=64):
        for rec in mb:
            x = state_features(rec["s"])
            probs = softmax(logits(theta, x))
            logp = log(probs[rec["a"]] + 1e-12)
            ratio = exp(logp - rec["log_pi_old"])
            adv = rec["advantage"]
            surrogate = min(
                ratio * adv,
                clamp(ratio, 1 - EPS, 1 + EPS) * adv,
            )
            # backprop -surrogate, add value loss, subtract entropy
            grad_logpi = onehot(rec["a"]) - probs
            if (adv > 0 and ratio >= 1 + EPS) or (adv < 0 and ratio <= 1 - EPS):
                pg_grad = 0.0  # clipped
            else:
                pg_grad = ratio * adv
            for i in range(N_ACTIONS):
                for j in range(N_FEAT):
                    theta[i][j] += LR * pg_grad * grad_logpi[i] * x[j]

The "clipped → zero gradient" pattern is the heart of PPO. If the new policy has already drifted too far in the beneficial direction, the update stops.

Step 4: value and entropy

Add standard MSE to the critic target and an entropy bonus on the actor, same as A2C.

Step 5: diagnostics

Three things to watch every update:

  • Mean KL E[log⁡πold−log⁡πθ]\mathbb{E}[\log \pi_{\text{old}} - \log \pi_\theta]. Should stay in [0, 0.02]. If it blows past 0.1, reduce K_EPOCHS or LR.
  • Clip fraction — the fraction of samples whose ratio lies outside [1−ϵ,1+ϵ][1-\epsilon, 1+\epsilon]. Should be ~0.1-0.3. If ~0, the clip never triggers → raise LR or K_EPOCHS. If ~0.5+, you are over-fitting the rollout → lower them.
  • Explained variance 1−Var(Vtarget−Vpred)/Var(Vtarget)1 - \text{Var}(V_{\text{target}} - V_{\text{pred}}) / \text{Var}(V_{\text{target}}). Critic quality metric. Should climb toward 1 as the critic learns.

Pitfalls

  • Clip coefficient mistuned. ϵ=0.2\epsilon = 0.2 is the de-facto standard. Going to 0.1 makes updates too timid; 0.3+ invites instability.
  • Too many epochs. K>20K > 20 routinely destabilizes because the policy drifts far from πold\pi_{\text{old}}. Cap epochs, especially for large networks.
  • No reward normalization. Large reward scales eat into the clip range. Normalize rewards (running std) before computing advantages.
  • Forgetting advantage normalization. Per-batch zero-mean/unit-std normalization is standard. Skipping it wrecks PPO on most benchmarks.
  • Learning rate not decayed. PPO benefits from linear LR decay to zero. Constant LR is often worse.
  • Importance ratio math errors. Always exp(log_new - log_old) for numerical stability, not new / old.
  • Wrong gradient sign. Maximize the surrogate = minimize −LCLIP-L^{\text{CLIP}}. A flipped sign is the most common PPO bug.

Use It

PPO is 2026's default RL algorithm across a surprising number of domains:

Use case PPO variant
MuJoCo / robotics control PPO with Gaussian policy, GAE(0.95)
Atari / discrete games PPO with categorical policy, rolling 128-step rollouts
RLHF for LLMs PPO with KL penalty to reference model, reward from RM at end of response
Large-scale game agents IMPALA + PPO (AlphaStar, OpenAI Five)
Reasoning LLMs GRPO (Lesson 12) — PPO variant without critic
Preference-only data DPO — closed-form collapsing of PPO+KL, no online sampling

The PPO loss shape — clipped surrogate + value + entropy — is the scaffolding for DPO, GRPO, and nearly every RLHF pipeline.

Ship It

Save as outputs/skill-ppo-trainer.md:

markdown
---
name: ppo-trainer
description: Produce a PPO training config and a diagnostic plan for a given environment.
version: 1.0.0
phase: 9
lesson: 8
tags: [rl, ppo, policy-gradient]
---

Given an environment and training budget, output:

1. Rollout size. `N` envs × `T` steps.
2. Update schedule. `K` epochs, minibatch size, LR schedule.
3. Surrogate params. `ε` (clip), `c_v`, `c_e`, advantage normalization on.
4. Advantage. GAE(`λ`) with explicit `γ` and `λ`.
5. Diagnostics plan. KL, clip fraction, explained variance thresholds with alerts.

Refuse `K > 30` or `ε > 0.3` (unsafe trust region). Refuse any PPO run without advantage normalization or KL/clip monitoring. Flag clip fraction sustained above 0.4 as drift.

Exercises

  1. Easy. Run PPO on 4×4 GridWorld with ϵ=0.2,K=4\epsilon=0.2, K=4. Compare sample efficiency to A2C (one epoch per rollout) at matched env steps.
  2. Medium. Sweep K∈{1,4,10,30}K \in \lbrace 1, 4, 10, 30 \rbrace. Plot return vs env steps and track mean KL per update. At what KK does KL explode on this task?
  3. Hard. Replace the clipped surrogate with an adaptive KL penalty (β\beta doubled if KL>2⋅target\mathrm{KL} > 2 \cdot \text{target}, halved if KL<target/2\mathrm{KL} < \text{target}/2). Compare final return, stability, and clip-free-ness.

Key Terms

Term What people say What it actually means
Importance ratio "r_t(θ)" πθ(a∣s)/πold(a∣s)\pi_\theta(a \mid s) / \pi_{\text{old}}(a \mid s); deviation from the policy that collected the data.
Clipped surrogate "PPO's main trick" min⁡(r⋅A,clip(r,1−ϵ,1+ϵ)⋅A)\min(r \cdot A, \mathop{\mathrm{clip}}(r, 1-\epsilon, 1+\epsilon) \cdot A); flat gradient past the clip on beneficial side.
Trust region "TRPO / PPO intent" Limit each update's KL to guarantee monotone improvement.
KL penalty "Soft trust region" Alternative PPO: L−β⋅KL(πθ∥πold)L - \beta \cdot \mathrm{KL}(\pi_\theta \Vert \pi_{\text{old}}). Adaptive β\beta.
Clip fraction "How often clipping triggers" Diagnostic — should be 0.1-0.3; outside means mistuned.
Multi-epoch training "Data reuse" K epochs on each rollout; variance cost traded for sample efficiency.
On-policy-ish "Mostly on-policy" PPO is nominally on-policy but K>1 epochs uses slightly-off-policy data safely.
PPO-KL "The other PPO" KL-penalty variant; used in RLHF where KL-to-reference is already a constraint.

Further Reading

লেসনের লেখা English-এ, AI Engineering from Scratch থেকে নেওয়া। লেখক Rohit Ghumare ও সহযোগীরা। লাইসেন্স: MIT ↗