Actor-Critic and PPO (Intro)
Policy gradients ended with a working method and an unflattering number: REINFORCE’s baseline cut the gradient norm 35× and moved the final score by 0.27. Variance reduction that changes nothing is a strange result to leave standing.
This page takes the two standard next steps and measures both. Actor-critic replaces the batch-mean baseline with a learned value function. PPO attacks a different axis entirely — not the variance of each update, but how many updates you can extract from each batch of experience.
One of them makes a large difference here and the other does not.
What you’ll learn
Section titled “What you’ll learn”- Why a learned critic is a better baseline than a batch mean, and what it cost here.
- The PPO clipped objective derived, and what the ratio actually measures.
- The measured result: 170 episodes to target against 544, a 3.2× sample-efficiency win.
- When the clip matters — measured by sweeping until it does.
- Why “PPO is more stable” is a claim about the worst seed, not the average.
The four methods
Section titled “The four methods”All on the same CartPole, the same 16-episode batches, the same 120 updates, three seeds
each. The environment is the hand-written vectorised one from _rl.py; a random policy
survives 23.09 steps and the episode cap is 200.
| Method | Weight on each log-probability |
|---|---|
| REINFORCE | the raw discounted return |
| + batch-mean baseline | |
| + learned critic | , whitened |
| PPO | the same advantage, but four clipped gradient steps per batch |
| Method | Final score | Episodes to reach 150 | Seeds solved | Seconds |
|---|---|---|---|---|
| REINFORCE (raw returns) | 193.9 | 544 | 3/3 | 30.2 |
| + batch-mean baseline | 194.2 | 624 | 3/3 | 35.7 |
| + learned critic | 195.0 | 613 | 3/3 | 43.0 |
| PPO (4 steps per batch) | 199.9 | 170 | 3/3 | 52.4 |
That leaves PPO, whose win is not on the score axis at all.
Sample efficiency is the axis that moved
Section titled “Sample efficiency is the axis that moved”Every method here consumed exactly 1,920 episodes over 120 updates, because the batch size is fixed. The difference is where they were on the curve when they had spent 170 of them.
The mechanism is direct: REINFORCE takes one gradient step per batch and then throws the data away. PPO takes four. Four times the learning from the same experience, if the four steps are all valid — and the reason they are not all valid by default is the whole reason PPO exists.
Why you cannot just take four steps
Section titled “Why you cannot just take four steps”The policy-gradient estimator is only correct for the policy that collected the data. After one gradient step the policy has changed, so the second step is computed on experience from a policy that no longer exists. PPO makes that explicit with a probability ratio:
At the first gradient step , so exactly. As the steps accumulate, drifts away from 1 — and an advantage multiplied by a large ratio produces an enormous update justified by data the current policy would never have generated.
The clipped objective bounds that:
The is what makes it a bound rather than a rescaling. When and the ratio has already climbed past , the clipped term is smaller and gets chosen — there is no further gradient for making an already-favoured action more likely. When and the ratio has fallen below , the same logic applies in reverse. Improvements outside the trust region are simply not counted.
ratio = tf.exp(log_probability - old_log)
clipped = tf.clip_by_value(ratio, 1 - clip, 1 + clip)
loss = -tf.reduce_mean(tf.minimum(ratio * advantages, clipped * advantages))flowchart TD C["collect 16 episodes
under policy theta_old"] --> S["store log pi_old(a|s)"] S --> L["step 1: ratio = 1 exactly"] L --> L2["step 2: ratio drifts"] L2 --> L3["step 3, 4, ... further still"] L3 --> Q{"bounded?"} Q -->|"clipped"| G["update capped at
1 +/- epsilon"] Q -->|"unclipped"| B["huge update justified by
data this policy never saw"] G --> C B --> F["worst seed: 9.2
below the random policy"]
When the clip actually matters
Section titled “When the clip actually matters”The obvious experiment is to remove the clip and watch PPO fail. It was run at the page’s default of four gradient steps per batch, and it did not fail: 199.9 with the clip against 199.4 without. That is a null result, and the reason is measurable — after four steps the policy has barely moved, so there is nothing for a trust region to bound.
So the sweep was extended until there was:
| Gradient steps per batch | Clipped | Clipped, worst seed | Unclipped | Unclipped, worst seed |
|---|---|---|---|---|
| 1 | 198.3 | 197.3 | 198.3 | 197.3 |
| 4 | 199.9 | 199.7 | 199.4 | 198.5 |
| 16 | 194.7 | 185.0 | 129.3 | 9.2 |
| 32 | 196.6 | 191.5 | 70.2 | 9.4 |
Three things worth stating precisely:
At one step the two are byte-for-byte equivalent. The ratio is exactly 1 for every sample, the clip never binds, and both columns read 198.3. That is a correctness check on the implementation as much as a result.
The failure is a variance failure. At 16 steps the unclipped mean of 129.3 is not three mediocre runs; it is two runs near 190 and one at 9.2 — worse than acting at random. The clip’s contribution is bounding the worst case, which is exactly what the word “proximal” is about and exactly what a mean-only report would hide.
More steps is not monotonically better even with the clip. 199.9 at four steps, 194.7 at sixteen, 196.6 at thirty-two. Past a point the extra steps are extracting increasingly stale signal, and the clip prevents disaster rather than delivering gains.
What actor-critic is still for
Section titled “What actor-critic is still for”The critic earned nothing on CartPole, so it is worth being explicit about the condition under which it does, rather than leaving the impression that it never does.
A batch-mean baseline subtracts the same number from every state’s return. It therefore cannot express “this state was always going to score badly, so a below-average return here is not the action’s fault”. A learned can. That distinction is worth nothing when all states in a batch have similar value — which on CartPole they do, since every non-terminal state is worth roughly the same — and worth a great deal when they do not, as in any task with distinct good and bad regions.
The measured cost of carrying it: 43.0 s against 35.7 s, a second network, and one more learning rate to get wrong.
Pitfalls
Section titled “Pitfalls”- Reading the final scores. All four methods land between 193.9 and 199.9 on a task capped at 200. The ranking there is noise; the sample-efficiency column is the result.
- Claiming the critic helped. 195.0 against 194.2, for 43.0 s against 35.7 s and a second network. On CartPole it did not.
- Testing the clip at four steps. It measured 199.9 against 199.4 — a null result that would wrongly suggest the clip is decorative. It binds from 16 steps.
- Reporting only the mean when the failure is variance. Unclipped at 16 steps averages 129.3; its worst seed is 9.2, below random. Those are different findings.
- Assuming more reuse is better. 199.9 at 4 steps, 194.7 at 16, 196.6 at 32, even with the clip.
- Forgetting to store the old log-probabilities. needs , captured before the first step. Recomputing it inside the loop makes every ratio exactly 1 and silently turns PPO back into repeated REINFORCE.
- Using
model.predictin the rollout. ~470 ms per call against ~1.2 ms for a tracedtf.function, and this page makes one call per environment frame.
- All four methods solved CartPole — 193.9, 194.2, 195.0 and 199.9 — so the final score separates nothing on this task.
- PPO reached mean length 150 in 170 episodes against REINFORCE’s 544, a 3.2× sample-efficiency win, for 1.7× the wall clock.
- The mechanism is reuse: four gradient steps per batch instead of one, made valid by bounding the probability ratio inside .
- A learned critic bought 0.8 points and 613 episodes against 624 — nothing, on a task where every state has roughly the same value. It is worth carrying where state values genuinely differ.
- The clip is worthless at 4 steps and decisive at 16: 194.7 against 129.3, with the unclipped worst seed at 9.2, below the random policy’s 23.09.
- At one gradient step per batch the clipped and unclipped runs are identical (198.3 both), because exactly — which doubles as a correctness check.
That is the reinforcement-learning phase. Phase 8 — Scaling & Deploying Deep Models turns to getting any of these models out of a notebook.
-
All four methods finished between 193.9 and 199.9 on a task capped at 200. What does that make the final-score column?
pch.quizShowAnswer
B — Nearly uninformative — the task is saturated, so the methods have to be separated on another axis, which here is episodes to reach the target
-
Removing PPO's clip at four gradient steps per batch gave 199.4 against 199.9 — no measurable difference. Why?
pch.quizShowAnswer
B — After four steps the policy has barely moved from the one that collected the data, so the ratio stays near 1 and the trust region never binds
-
Unclipped PPO at 16 steps averaged 129.3, with individual seeds at roughly 190, 190 and 9.2. What does the clip actually provide?
pch.quizShowAnswer
B — A bounded worst case — it stops one run in three from destroying itself, which is what 'proximal' refers to and what a mean-only report hides
-
A learned critic scored 195.0 against a batch-mean baseline's 194.2, for 43.0 s against 35.7 s. Under what condition would the critic be worth it?
pch.quizShowAnswer
B — When states in a batch have genuinely different values, so that a single subtracted number cannot express 'this state was always going to score badly'
-
At one gradient step per batch, the clipped and unclipped runs both scored exactly 198.3. Why is that worth reporting?
pch.quizShowAnswer
B — Because at one step r_t = 1 for every sample by construction, so the clip cannot bind — an identical result is a correctness check on the implementation
-
What happens if you recompute the old log-probabilities inside the inner loop instead of storing them before the first step?
pch.quizShowAnswer
B — Every ratio becomes exactly 1, the clip never binds, and PPO silently degrades into taking several plain policy-gradient steps on stale data
🧪 Try It Yourself
Section titled “🧪 Try It Yourself”pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading