CS224R · HOMEWORK AS FORGE · ONLINE REINFORCEMENT LEARNING

HW2 — Online RL

A 5×4 grid, two goals, and a reward change that reverses their preference. Build from tabular Q-learning to PPO and replay-based actor-critic: shared ideas about future reward, distinct learning objectives, and different tradeoffs in how experience is used.

Prerequisites: basic algebra + comfort with a neural network as a function. No prior RL assumed. NumPy & PyTorch ideas introduced as needed — the code you run here is numpy only.
12
Chapters
12
Live Sims
4
Code Labs
1
Forge Studio

A companion & practice forge for Stanford's CS 224R Homework 2 (Online RL). It credits the public course materials and teaches you to implement the core math yourself — it is not a copy-paste answer bank. The real starter-code contracts you meet here (choose_action, the Q-update, compute_gae, the clipped surrogate, update_critic) are the ones from the homework, shrunk to a scale you can run in your browser with numpy alone — no torch, no gym, no cluster.

Chapter 0: The Flip

You have a tiny robot on a 5×4 grid. It starts bottom-left. There are two goal squares: a far, valuable one up in the top-right corner, and a near, cheaper one straight to the right. The robot knows nothing — no map, no rules, just the ability to try moves and feel a reward.

Train it with a mild cost of −1 per step. It learns a staircase path with four right moves and three up moves to the far, valuable goal. Sensible: the extra reward there is worth the extra walking.

Now change one number. Make each step cost −2 instead of −1. Retrain from scratch. The same algorithm, same grid, same goals — and the robot now walks straight to the near, cheaper goal and ignores the valuable one entirely. A single knob, and the optimal behavior flipped.

One number flips the policy

The 5×4 grid. G1 (far, +10) sits top-right; G2 (near, +5) sits to the right of the start. Drag the step reward. Watch the arrows — the learned policy — and the highlighted path re-solve to a different goal. This is the crux of the whole first problem.

step reward −1
The big reveal. The optimal policy is not set by which goal is bigger. It is set by the whole reward landscape — and the per-step cost reshapes that landscape. At −1 the 3 extra steps to the far goal cost 3, less than its 5-point bonus edge, so the far goal wins. At −2 those 3 extra steps cost 6, wiping the bonus out, so the near goal wins. Reward design is that sharp.

This is reward shaping — and it is one of the hardest problems in real RL. The robot optimizes the reward and termination contract you provide. Get it slightly wrong (a positive step reward under a continuing contract, say) and the robot may prefer loitering to finishing. We will make that loophole visible, on purpose, in Chapter 6.

You double the per-step penalty (from −1 to −2) and the goal rewards stay the same. Before any math — what is the most likely reason the robot switches from the far, valuable goal to the near, cheap one?
Where we are headed. Twelve chapters. We build tabular Q-learning from the Bellman equation, watch the reward flip (Problem 1), then connect that value machinery to two neural methods: PPO — on-policy, advantage + GAE + a clipped surrogate (Problem 2) — and an off-policy actor-critic with an ensemble of critics and a min-of-two-random target (Problem 3). There is a Forge Studio (the ⚒ button) where you build representative HW2 kernels on live instruments: the gridworld with its flipping arrows, a PPO advantage strip, and a critic-ensemble bar chart. Numpy only.

Chapter 1: The Gridworld

Before we touch a robot arm or a neural network, we solve a simpler version of the same problem in a world you can hold entirely in your head. That is the point of Problem 1 — and it is the right warm-up because tabular Q-learning has every property of "real" RL except one: function approximation.

The world

A grid 5 wide, 4 tall. The agent always starts at (0, 0) — bottom-left. There are two goal cells:

GoalPositionDistance from startReward
Goal 1 (far)(4, 3) top-right7 steps (4 right, 3 up)the bigger bonus
Goal 2 (near)(4, 0) right edge4 steps (4 right)the smaller bonus

Four actions: left, right, up, down, integer-coded {0, 1, 2, 3}. Moving off the edge keeps you in place but still costs the per-step reward — walls are not free. Reaching either goal ends the episode.

The reward function — three numbers

The reward is parameterized by three scalars. Every step the agent gets r_step; landing on a goal adds a one-time bonus:

r(s, a, s′) = rstep + R1 · 𝟙[s′ = Goal 1] + R2 · 𝟙[s′ = Goal 2]

where 𝟙[·] is the indicator — 1 if the condition holds, 0 otherwise. Three scenarios use the same algorithm and produce three different optimal policies:

ScenariorstepR1R2Optimal goal
1−1105Goal 1 (far)
2−2105Goal 2 (near)
3+111continuing fixture favors loitering; finite horizon depends on masks
The hidden hard part is already here. The optimal policy is not determined by goal magnitude alone — the per-step reward reshapes the value landscape. Scenario 3 exposes a reward-hacking loophole: under the continuing interpretation, a positive step reward can make loitering more profitable than finishing. The finite-horizon and terminal semantics still matter.

Why tabular first

Because the state is enumerable and actions are discrete, you can store every value in a table — a cube of shape (height, width, 4). No neural network. No PyTorch. Just numpy. That means three superpowers:

The (x, y) vs (y, x) trap. States are stored as (x, y) tuples (column, row), but the Q-table is indexed q[y, x, action] (row, column, action) — because numpy is row-major, so the first axis is the slowest-varying and q[y] gives you a whole grid row. Get the flip wrong and you train the wrong cells with no crash. A silent bug. The code's action_values(q, state) helper keeps the flip in one place.
Why is the Q-table shaped (height, width, 4) and not (width, height, 4)?

Chapter 2: RL Foundations

Before deriving anything, we nail the language. Five words. Each is a precise object; sloppiness here costs you the whole homework.

TermSymbolWhat it is
StatesA complete description of the world right now. Here, just (x, y). It has the Markov property: it contains everything needed to predict the next state and reward, no history required.
ActionaWhat the agent does — one of {left, right, down, up}. The agent's only way to influence the world.
Rewardr(s, a, s′)A scalar: how good was this transition? The agent's only goal is to maximize total reward. We design r to encode what we want.
Policyπ(a | s)A rule for choosing actions. In Q-learning we derive it from the table by argmax: pick the action with the highest Q-value.
ReturnGtThe total discounted reward from time t onward (below).

The return, and why we discount

The return is the sum of all future rewards, each future one shrunk by a factor of the discount γ:

Gt = rt + γ rt+1 + γ2 rt+2 + γ3 rt+3 + …

Here γ = 0.98. Rewards far in the future are worth less than rewards now. Three reasons this matters:

The bounded-sum number, worked. In the continuing interpretation of Scenario 3, the agent can earn +1 forever. The return of "+1 every step, discounted at γ = 0.98" is the geometric series 1 + 0.98 + 0.98² + … = 1 / (1 − 0.98) = 50. A finite 20-step rollout instead sums to (1 − 0.9820) / (1 − 0.98) ≈ 16.62; a trained finite-horizon table need not equal either number exactly. The 50 value is a continuing-task derivation, not a measured convergence claim.

The objective

All of RL boils down to one line: find the policy π that maximizes the expected return.

maximize   𝔼[Gt] = 𝔼[ rt + γ rt+1 + γ2 rt+2 + … ]

The expectation accounts for randomness in the policy and the environment. We want a policy that performs well on average — not one that got lucky once. The Q-function is the tool that lets us find it. That is the next chapter.

The discount, visualized

Each bar is one future reward's present value — a reward of 1 that arrives k steps from now is worth γk today. Drag γ and watch the horizon shrink or stretch. At γ = 1 nothing decays (and infinite sums blow up); at γ = 0.9 the future fades fast.

discount γ 0.980
In Scenario 3 (rstep = +1), what is the exact discounted return of "collect +1 forever" at γ = 0.98, and why does the math need γ < 1?

Chapter 3: The Bellman Equation

All of Q-learning rests on one recursion, and it falls out of the definitions with a single algebraic trick. Watch it happen.

From return to Q-value

Define the Q-value as the expected return if you take action a in state s, then follow π thereafter. The expectation is over the environment and policy randomness:

Qπ(s, a) = 𝔼[ Gt | st = s, at = a ] = 𝔼[ rt + γ rt+1 + γ2 rt+2 + … ]

The trick. Pull the first reward out of the sum, and factor a γ out of everything that remains:

Qπ(s, a) = 𝔼[ rt + γ(rt+1 + γ rt+2 + γ2 rt+3 + …) ] = 𝔼[ rt + γ Gt+1 ]

But the bracketed thing is just the return from time t+1 — which is itself a Q-value, at the next state and next action. So the Q-value refers to itself one step ahead:

Qπ(s, a) = 𝔼[ r(s, a, s′) + γ Qπ(s′, a′) ]

This is the Bellman expectation equation. The value of (s, a) decomposes into the immediate reward plus the discounted value of where you land. That recursion is the entire engine of temporal-difference learning — tabular, neural, on-policy, off-policy, all of them.

The optimality version

If we want the optimal Q-function — the value of always acting greedily from here on — we replace "follow π" with "always take the best next action":

Q*(s, a) = 𝔼s′[ r(s, a, s′) + γ maxa′ Q*(s′, a′) ]

The maxa′ is what makes this an optimality condition: from the next state, take whichever action has the highest Q-value. This is a fixed-point equation — Q* is its unique solution. Solve it, even approximately, and you have solved the RL problem, because the optimal policy is just π*(s) = argmaxa Q*(s, a).

Two ways to solve a fixed point. Value iteration sweeps every state and sets Q to the expected right-hand side — but it needs the environment's transition probabilities, which we usually do not know. Q-learning instead samples transitions by acting, and nudges Q toward the observed right-hand side — no transition model required. We use Q-learning. Next chapter turns this equation into an update rule.
Bellman backup: the value flows one step

A single Bellman backup at a cell. The reward on the edge plus the discounted best value at the next cell becomes the new value here. Press step to watch this controlled fixture ripple one cell outward from the goal per backup — an illustration of how evidence can propagate during training.

What single algebraic step turns the definition of the return into the Bellman recursion?

Chapter 4: Q-Learning, Derived

Q-learning is the Bellman optimality equation, rewritten as an update rule. Here is the whole derivation.

The naive idea, and why it fails

If we knew the right-hand side r + γ maxa′ Q*(s′, a′) exactly, we would just set Q[s, a] equal to it. Done. But we do not know it — we only have one noisy sample of it, computed from a single observed transition. So instead of overwriting, we nudge: move Q a fraction α of the way toward the observed target.

Q(s, a) ← Q(s, a) + α [ r + γ maxa′ Q(s′, a′) − Q(s, a) ]

This is the most important equation in the homework. Read it as three pieces:

PieceNameMeaning
r + γ maxa′ Q(s′, a′)TD targetWhat we now think Q(s, a) should be, after one more step of evidence.
Q(s, a)current estimateWhat we thought before.
target − Q(s, a)TD errorHow wrong we were. Positive → push Q up; negative → push Q down.

So the update reads: "new Q = old Q + α · (how wrong we were)." Equivalently, it is a smoothed running average:

Q(s, a) ← (1 − α) Q(s, a) + α · target

With α = 0.2 (this homework's default), each update moves us 20% of the way toward the latest evidence, keeping 80% of the old belief. A gentle running mean over one-sample targets.

The terminal edge case — a real bug, not a nicety

If s′ is terminal (the episode just ended), there is no future. The target is just the reward:

target = r   if done,    else    target = r + γ maxa′ Q(s′, a′)
Named failure mode: the missing if done. If you always use the bootstrap formula even on terminal steps, you add γ max Q(goal, ·) to the target instead of using the terminal reward alone. That is a semantic bug: depending on which terminal states are stored and updated, it can bias values near the goal upward and contaminate later backups. In this local corridor fixture, the terminal row is not updated, so the plotted starter and corrected values can look similar; the check below isolates the wrong terminal target directly. The fix is the if done branch, with a separate reset/truncation mask when a timeout is not true termination.

Why it converges

Convergence contract. With a finite Markov decision process, sufficient visits to every relevant (s, a) pair, bounded rewards, and an appropriate decaying step size, the standard tabular Q-learning theorem gives convergence to Q*. The update is a semi-gradient stochastic approximation to a moving Bellman target; it is useful to understand the target as a noisy sample of the recursion, rather than as ordinary gradient descent on a fixed Bellman-residual objective. The contraction and step-size assumptions are doing real work.

The intuition that survives all the math. You are never told the right answer. You only ever see one-step samples of reality. Each sample lets you nudge one table entry a little, toward less error. With the theorem's visits and step sizes, repeated updates average the noise and approach the Bellman fixed point; a fixed episode count is not a universal guarantee.

A worked propagation, by hand

Positive goal bonuses arrive only at goals, while each move still carries the step reward — yet after enough episodes every cell on the path can "know" it leads to reward. Here is the mechanism, step by step, with α = 0.2, γ = 0.98, rstep = −1, R1 = 10.

Controlled backup 1. Start with a zero table and isolate a transition from (4, 2) going UP into Goal 1. Transition: reward = −1 + 10 = 9, done = True. Terminal target = 9:

Q[(4,2), UP] ← 0 + 0.2 · (9 − 0) = 1.8

In this isolated fixture, only that entry has changed. During an actual episode, earlier negative step rewards would also update visited entries.

Controlled backup 2. Next isolate a transition from (3, 2) going RIGHT to (4, 2): reward = −1, not done. Bootstrap target uses the 1.8 we just learned:

target = −1 + 0.98 · 1.8 = 0.764  →  Q[(3,2), RIGHT] ← 0 + 0.2 · 0.764 = 0.1528

Now (3, 2) has evidence that going right is on a path to reward. Episode after episode, updates can spread that evidence toward the start — a bright wave spreading like dye through water. In this controlled corridor fixture, repeated backups approach the discounted path values; stochastic visits, step costs, terminal handling, and finite episodes determine the actual table.

What does the update Q ← Q + α(target − Q) mean intuitively, and what is target − Q called?

Chapter 5: Explore vs Exploit

The Q-update is one half of the algorithm. The other half is: which (s, a) do we update? If we always take the action our current Q says is best, we will never try alternatives — and we might miss a better path forever.

The dilemma

You need both. Pure greedy gets stuck in bad habits; pure random never acts on what it learned.

ε-greedy: the simplest balance

π(a | s) = uniform over the 4 actions  with prob ε  (explore)    else   argmaxa′ Q(s, a′)  with prob 1 − ε  (exploit)

Set ε high early (explore a lot) and decay it low later (mostly exploit). This homework decays linearly from 0.4 to 0.02 across 5000 episodes:

python
def epsilon_for_episode(scenario, episode_idx):
    fraction = episode_idx / max(1, scenario.episodes - 1)
    return scenario.epsilon_start + fraction * (scenario.epsilon_end - scenario.epsilon_start)
Named failure mode: no exploration at all. At episode 0 the Q-table is all zeros. argmax([0,0,0,0]) returns 0 — LEFT under this action ordering. With zero step reward and no exploration, the deterministic tie can make the agent walk into the left wall, never see a goal, and leave the table unchanged. A negative step cost is different: the first non-terminal update can make the attempted action negative, changing later ties, so the frozen-zero story is not universal. The cure is a non-zero ε early: random moves give the table a chance to see goals, after which rewards can propagate backward.

Two subtle implementation bugs

The homework's choose_action is four lines, and two mistakes lurk:

The ε schedule and its effect

Top: ε decaying linearly 0.4 → 0.02 across training. Drag the episode marker. The dots below show a sample of the agent's move choices at that point — mostly random early (blue = explore), mostly greedy late (teal = exploit). Learning needs the early scatter.

episode 200
The connection forward. Neural RL has the same problem on continuous actions. PPO (Chapter 7–8) explores via an entropy bonus on a stochastic policy; the off-policy actor-critic (Chapters 9–10) explores via fixed-std Gaussian noise + replay-buffer diversity. All three are the same idea: keep the policy random enough to keep discovering reward.
Why is ε set high at the start of training and decayed low by the end?

Chapter 6: The Reward Flip

Now the payoff of Problem 1. Same algorithm, same grid, same two goals. We change only rstep and watch three completely different optimal policies emerge — including one that refuses to do the task at all. This is the heart of the homework: the optimal policy is not determined by goal magnitude — the per-step reward reshapes the value landscape.

The arithmetic, done by hand

Both goals are reachable by an optimal-length path. Compute the total return of each path directly:

Scenario 1 — rstep = −1, R1 = 10, R2 = 5:

Goal 1 (far, 7 steps): 7 × (−1) + 10 = 3     Goal 2 (near, 4 steps): 4 × (−1) + 5 = 1

Goal 1 wins by 2. The far, valuable goal is worth the walk.

Scenario 2 — rstep = −2, R1 = 10, R2 = 5: goals unchanged, penalty doubled:

Goal 1 (far): 7 × (−2) + 10 = −4     Goal 2 (near): 4 × (−2) + 5 = −3

The flip. Goal 2 now wins by 1. The 3 extra steps to Goal 1 cost 6 with the doubled penalty — more than the 5-point reward gap. A single number moved, and the optimal goal switched from far to near.

Scenario 3 — rstep = +1, R1 = R2 = 1: the step reward is now positive:

Reach the near goal (4 steps): 4 × (+1) + 1 = 5     Stall against a wall for the whole horizon: 20 × (+1) + 0 = 20

Stalling can win in the continuing version of the task: the agent can collect +1 per step without ending the stream. A finite 20-step rollout collects only the discounted 20-step sum, while the continuing derivation is 50. Reaching a goal may still be optimal if the environment terminates or assigns a larger terminal return; the result depends on the full reward and termination contract.

Reward hacking, named. Scenario 3 is the canonical example: the agent maximizes the reward you specified, which is not necessarily the behavior you intended. In real RL, reward design is one of the hardest engineering problems. Completion-only rewards can remove this particular loitering incentive when the completion detector is correct, but they make exploration and credit assignment harder and do not prevent every possible loophole.

Reading illustrative Q-values

For a representative illustrative snapshot, the numbers tell the story. In Scenario 1 the start-state Q-values are shown as (LEFT, RIGHT, DOWN, UP) = (1.219, 2.265, 1.219, 2.265). LEFT and DOWN tie low (both walk into a wall, wasting the step); RIGHT and UP head toward the far goal by different staircase paths. In Scenario 2 an illustrative snapshot is (−4.996, −3.057, −4.996, −4.329) — now RIGHT is distinctly best and UP is worse, because the optimal path is straight right to Goal 2. These displayed values explain the policy flip; they are fixtures for intuition, not a benchmark claim.

The reward flip — the whole grid re-solves

The trained 5×4 gridworld. Each cell's shade = its state value (warm = high). Each arrow = the greedy action there. The highlighted path runs from the start to whichever goal the current policy prefers. Flip rstep and watch the arrows and the path re-solve — far goal, near goal, or stall in place.

step reward rstep −1
Carry this. Three small changes to one number produced three optimal policies — far goal, near goal, and "never finish." That sensitivity is reward shaping. It is the same lever that, scaled up, makes real robot-reward design so hard — and it is why the neural problems ahead use sparse, completion-only rewards you cannot game.
Doubling the step penalty flips the optimal goal from far to near. What is the precise reason, in return arithmetic?

Chapter 7: PPO — Advantage & GAE

Leave the grid behind. Now the state is 39 numbers describing a robot arm, the action is a 4-D continuous vector, and the reward is sparse — 0 every step, +1 the instant a nail is driven, then done. You cannot enumerate a Q-table over a continuous action. So we switch to a policy-gradient method: PPO. It is on-policy: collect a rollout under a frozen old-policy snapshot, reuse that batch for the configured few epochs (three in the starter), then collect a fresh rollout after the policy update.

The objective and the policy gradient

We want to maximize expected discounted return, J(θ) = 𝔼τ~πθ[ Σt γt rt ]. The policy gradient theorem gives its gradient in a form we can estimate from samples:

∇θ J(θ) = 𝔼τ[ Σt γt ∇θ log πθ(at | st) · Gt ]

In English: take each action you sampled and increase its log-probability in proportion to how good the future turned out after it. Good actions — push up. Bad actions — push down. The miracle of the log-derivative trick (∇ log p = (1/p) ∇ p) is that the unknown environment dynamics cancel out of the gradient. Implement just this and you have REINFORCE — correct, but painfully slow. Two problems make it slow, and PPO fixes both.

Problem 1: variance → use a baseline (advantage)

The return Gt swings wildly across rollouts. We can subtract any state-dependent baseline b(st) from Gt without changing the gradient's expectation (the cross-term provably has zero mean) and a well-chosen baseline can reduce variance; an arbitrary baseline need not. The natural baseline is the state-value V(s) = 𝔼[Gt | st = s] — how good is this state on average? Subtracting it gives the advantage:

A(s, a) = Q(s, a) − V(s)

"How much better was action a than the policy's average action in this state?" Positive → above average, push toward it; negative → below average, push away. The clean policy gradient becomes:

∇θ J = 𝔼[ Σt γt ∇θ log πθ(at | st) · A(st, at) ]

Problem 2: how do we estimate A? → GAE

We do not know V, so we train a critic Vφ(s) alongside the policy. With a critic, the two extreme advantage estimators are:

EstimatorFormulaBiasVariance
1-step TDrt + γ V(st+1) − V(st)depends on critic error; zero with exact Vlow
Monte CarloGt − V(st)zero under exact returnshigh (sums many random rewards)

Generalized Advantage Estimation (Schulman et al., 2016) blends the two with a knob λ ∈ [0, 1]. This is the recurrence implemented by your compute_gae, together with its rollout masks:

δt = rt + γ(1 − dt) V(st+1) − V(st)   (the 1-step TD error)
At = δt + γ λ (1 − dt) At+1   (the backward recursion)

Computed backwards through the trajectory. The (1 − dt) done-mask zeros the bootstrap at true episode ends; a timeout or environment reset needs the separate mask required by the rollout contract. Unroll the recursion and it is a geometrically-weighted sum of future TD errors:

At = δt + (γλ) δt+1 + (γλ)2 δt+2 + (γλ)3 δt+3 + …

Each future TD error contributes less by a factor of γλ. The backward pass is just the O(T) way to compute this sum. At λ = 0 you get pure 1-step TD; at λ = 1, Monte Carlo minus the baseline on a complete terminated trajectory; truncated rollouts can retain a final-value bootstrap. This homework uses λ = 0.99 — "trust the real rewards mostly, blend in the critic to dampen variance."

The returns output, and why grads are off. compute_gae returns both advantages and returns, where returnst = At + V(st). Form returns = A + V_old before normalizing advantages. This bootstrapped return estimate is the fixed target for critic regression. Critically, the whole GAE computation runs inside torch.no_grad(): these are targets. If gradients flowed through them, the critic would be trained against its own moving prediction and chase its own tail; an unstable or drifting critic loss is a possible symptom. Preserve both the true-terminal mask and the reset/truncation mask required by the rollout contract.
GAE: the bias–variance dial

A toy 8-step trajectory with a sparse reward at the end. Each bar is the advantage At at that step. Drag λ from 0 (pure 1-step TD — blocky, typically biased, low variance) toward 1 (Monte Carlo-like — smooth credit flowing all the way back, higher variance). Watch the credit-assignment reach further back as λ grows.

GAE λ 0.95
Why does GAE iterate backwards through time, and why is the whole computation wrapped in torch.no_grad()?

Chapter 8: PPO — The Clip

Vanilla policy gradient can be unstable when a single big update moves the policy far from the rollout distribution. PPO uses an objective that removes incentive for some large ratio changes per batch; it is a trust-region-like heuristic, not a hard bound on every policy update or ratio.

The importance ratio

Define the ratio between the new and the rollout-time (old) policy for the same action:

ρt = πθ(at | st) / πθold(at | st) = exp( log πθ(at | st) − log πθold(at | st) )

If the policy has not changed, ρ = 1. Doubled the action's probability → ρ = 2; halved → ρ = 0.5. We compute it as exp(new_log_prob − old_log_prob), not a direct division, because densities for a 4-D continuous action can be tiny (1e-30); subtracting log-probs and exponentiating is numerically safe.

The clipped surrogate

LCLIP = − meant [ min( ρt · At, clip(ρt, 1−ε, 1+ε) · At ) ]

Two terms inside the min. ρ · A is the unclipped surrogate (what vanilla PG uses). clip(ρ, 1−ε, 1+ε) · A caps ρ to [0.9, 1.1] for this homework's ε = 0.1. Then: take the elementwise min, average, negate (PyTorch minimizes; the objective is a max).

Why min — the pessimistic cap

Walk both signs of the advantage:

The min always picks the worse outcome from the optimizer's view — a pessimistic batch surrogate. It can reduce the incentive to lunge past the clip, while diagnostics and the KL term still matter because the objective does not guarantee every update stays inside the interval.

Why min and not average? Because averaging the two surrogates can leave incentive to move past the clip wherever the unclipped term is favorable. The min is a pessimistic batch surrogate: the optimizer sees the worse of the two for each sample, reducing one way to exploit the clip while leaving update and KL diagnostics necessary.

The three extra loss terms

The full PPO loss adds three terms to the clipped surrogate:

L = LCLIP + cv · Lvalue − cH · H[π] + cKL · DKL(πθ ‖ πref)
TermWhat it does
Value loss — MSE(Vφ(s), returns)Trains the critic toward the GAE returns. Coefficient ~0.5.
Entropy bonus −cH H[π]Rewards a spread-out action distribution — keeps exploration alive so the policy doesn't collapse to nearly deterministic. Subtracted from the loss (added to the objective). Coefficient ~0.001–0.01.
Reverse KL to a frozen referencePenalizes drift from the BC-pretrained policy (below).

Distribution caveat. The starter clamps Gaussian action samples but inherits the ordinary Normal log-density. That density is not the exact distribution of the clamped output, which has boundary mass. Its PPO ratio and KL calculation therefore follow the implementation convention; they are not exact importance ratios or KL values for the bounded executed-action distribution.

Reverse KL to a frozen BC reference

Before RL, the policy is warm-started by behavior cloning on 20 expert demos. A frozen snapshot of that BC policy becomes the reference_actor. The reverse-KL term keeps the RL policy anchored near it:

LKL = 𝔼old[ ρ · (log πθ − log πref) ]   (an estimate of DKL(πθ ‖ πref))

Why: with sparse rewards, BC is a useful starting policy that pure RL can drift away from. It is called "reverse" because it is the KL from the new policy to the reference; that direction has different support and mode effects from the forward KL, so describe the direction rather than promising a universal mode-seeking behavior. The off-policy problem addresses demo retention with alternating BC updates, which we meet in Chapter 9.

Named failure mode: entropy collapse. If entropy_coef is too small or other losses dominate, the actor's action std can shrink early — the policy becomes nearly deterministic, stops exploring, and may freeze at an accidental behavior. Symptom: actor_std trends toward 0 while success plateaus below the task's achievable rate. The useful response is to inspect the entropy coefficient, KL, and rollout diagnostics rather than rely on a fixed step threshold.
The clip: the pessimistic cap in action

The PPO objective per transition as a function of the ratio ρ. Toggle the advantage sign. For a good action (A > 0) the objective climbs then plateaus at ρ = 1+ε — the flat region removes this sample’s incentive to increase the ratio further. For a bad action (A < 0), the upper plateau is reached as the ratio falls below 1−ε. Drag ε to inspect the surrogate region; it is not a hard safe region for every update.

clip ε 0.10
For a good action (A > 0), what happens to the gradient once the ratio ρ exceeds 1 + ε?

Chapter 9: Off-Policy & the Deadly Triad

Same robot, same hammer, same nail, same sparse reward. Completely different algorithm. The assignment uses on-policy and off-policy setups to study a sample-reuse tradeoff; any step counts or success rates depend on seeds, implementation, and evaluation. The curves later in this page are illustrative schematics, not measured results.

Why off-policy can reuse old data

PPO is on-policy: its gradient is an expectation over (s, a) drawn from the rollout-time policy, so stale data eventually gives the wrong gradient even though one batch can be reused for a few PPO epochs. Off-policy learns a Q-function whose Bellman target is defined by the transition dynamics plus the policy used for the next action:

Qπ(s, a) = r(s, a) + γ 𝔼s′,a′~P,π[ Qπ(s′, a′) ]

A transition can remain useful under a different behavior policy when the critic and actor target are defined consistently, subject to coverage and distribution-shift limits. The finite replay buffer stores recent transitions and re-samples them several times; it does not keep every transition forever. That reuse can improve sample efficiency, while stale or narrow data can bias learning.

Why Q, not V, here. PPO used V(s) as a baseline. Off-policy uses Q(s, a) because actions matter directly: to improve the actor we push it toward actions the critic scores high, which needs an action-level signal. With V(s) we would only know whether a state is good on average and would have to recover action-level signal through the noisy policy gradient. Q-learning skips that middleman.

The deadly triad

The clean story above hides a landmine. Three properties together create a well-known risk of instability in neural Q-learning:

IngredientWhy it's dangerous
Function approximationA neural Q leaks: updating one (s, a) changes many others.
BootstrappingThe target r + γ Q(s′, a′) uses the same network we're optimizing — we regress toward a moving label.
Off-policy dataThe sampled (s, a) distribution doesn't match the policy being evaluated, so errors can amplify.

The combination can amplify errors; it does not guarantee divergence on every task or seed. Two concrete failure modes and their fixes:

Failure 1: the moving target. If the same critic computes both the prediction and the target, one gradient step moves both. You reach for the flag and it runs away at your own speed. Fix: a target network — a slow copy of the critic used only for targets, updated by a soft Polyak average φ̄ ← (1−τ)φ̄ + τφ with τ = 0.005. On the timescale of one gradient step, the target looks frozen. Forgetting it can produce stale or rapidly moving targets; inspect target and online predictions together rather than assuming one fixed symptom.

Failure 2: maximization bias. Vanilla Q-learning's target uses maxa′ Q(s′, a′). If Q is noisy — some actions overestimated, some under, on average correct — the max tends to pick an overestimated one. Watch:

true Q = [1.0, 1.0, 1.0];   noisy estimate = [1.1, 0.9, 1.0];   max(estimate) = 1.1  (biased UP by 0.1);   true max = 1.0

You train Q toward an upward-biased target, so overestimation can compound across backups. Fix: clipped double-Q — take the min over two critics, which reduces upward bias on average at the cost of possible underestimation. That is Chapter 10.

The actor-critic fix for continuous actions

You cannot enumerate argmaxa Q(s, a) over a continuous 4-D action. So we train an actor πθ(a | s) whose job is to output the maximizer. Its loss is simply:

Lactor(θ) = − 𝔼s~buffer[ Qφ(s, πθ(s)) ]

"Plug the policy's chosen action into the critic; that scalar is what the actor wants to maximize" (minus to minimize). Gradient flows from Q back into θ via a reparameterized differentiable sample: the starter uses a clamped/TruncatedNormal-style action distribution with fixed Gaussian noise for the sample path. The executed action is therefore noisy during training, while the actor remains differentiable. The critic tells you which actions are good; the actor learns to produce them; they bootstrap each other up. That ping-pong is actor-critic.

Behavior cloning during RL. With sparse reward, random exploration almost never sees a +1. So we warm-start the actor by BC on 20 demos (Lbc = −mean[log πθ(a|s)]), then keep mixing in BC steps during RL. Why during? Early on the critic is random junk; a pure actor step would walk the policy toward the critic's random preferences and forget the demos. The BC anchor holds it near the expert distribution until the critic becomes trustworthy. Same disease as PPO's reverse-KL, different antibiotic.
On-policy discards; off-policy replays

Both learn from transitions (dots). PPO (top) collects a rollout, reuses it for a few epochs, then refreshes the batch. Off-policy (bottom) places recent transitions in a finite replay buffer and re-samples them many times. Press play; watch the reuse. With sparse rewards, a rare +1 transition (warm) can be sampled repeatedly while it remains in the buffer.

How can off-policy critic learning reuse older transitions, and how does this differ from PPO batch reuse?

Chapter 10: Ensemble Critics & the Min-of-Two Target

The fix for maximization bias is the technical heart of Problem 3's update_critic. Three stabilization tricks stack into four lines.

Trick 1 — target network (Polyak/EMA)

φ̄ ← (1 − τ) φ̄ + τ φ,  τ = 0.005

Each step the target critic moves 0.5% toward the online critic. After 200 updates about 36.7% of a fixed initial gap remains; the response half-life is about 138 updates. It deliberately lags the online critic. Called once per critic update via utils.soft_update_params(net, target_net, tau).

Trick 2 — an ensemble of N critics

Maintain N independently initialized critic networks. In the starter, the ensemble evaluates the same sampled batch and its members can disagree on out-of-distribution inputs; that disagreement is a diagnostic. Default N = 2; the ablation uses N = 10. With N = 10 plus a high update-to-data ratio you get a REDQ-like configuration; its stability and sample efficiency are empirical properties to measure, not guarantees.

Trick 3 — random pair + min for the target

When computing the TD target, do not use all N critics. Pick 2 at random, take the elementwise min:

i, j ~ sample(1..N, 2 distinct);   y = r + γ(1 − done) · min( Q̄i(s′, a′), Q̄j(s′, a′) )

The min often counters maximization bias when critics are noisy, though it can under-estimate. The randomization — vs always using critics 1 and 2 — is a REDQ-style ingredient: the loss still trains every critic, while random pairs vary the target signal and make ensemble coverage measurable.

Named failure mode from the Common Bugs list — the shape-mismatch silent killer. Inspect the actual batch contract: the starter's replay samples and critic outputs are documented as column-shaped [B, 1]. If a loader gives [B], normalize each reward and discount to [B, 1] before arithmetic; blindly unsqueezing an existing column can create [B, 1, 1]. A raw [B] + [B,1] can broadcast to [B, B] — the loss is a number, training runs, and it is gibberish. Other entries on the list: (1) forgetting torch.no_grad() on the target can destabilize the critic; (2) using random.choices (with replacement) instead of random.sample can pick the same critic twice; (3) only updating the 2 sampled critics leaves the other N−2 drifting, so the loss must be summed over all N; (4) using self.critic instead of self.critic_target for the target.

The target and replay shape contract

The equation above is the one-step case. The starter uses three-step replay: reward already contains the discounted reward sum and discount already includes the accumulated gamma factors and terminal mask. Use them directly; multiplying by gamma again is a bug.

python
def batch_col(x):
    x = torch.as_tensor(x)
    if x.ndim == 1: x = x[:, None]
    assert x.ndim == 2 and x.shape[1] == 1
    return x
with torch.no_grad():
    a_next = actor(s_next).sample(clip=stddev_clip)     # next action from current policy
    target_q_list = critic_target(s_next, a_next)        # list of N tensors
    Qi, Qj = random.sample(target_q_list, 2)              # 2 DISTINCT critics
    y = batch_col(reward) + batch_col(discount) * torch.min(Qi, Qj)
    assert y.shape == Qi.shape
# loss over ALL N online critics:
critic_loss = sum(F.mse_loss(q, y) for q in critic(s, a))
Why the ensemble can support aggressive replay (UTD). The update-to-data ratio (UTD) is how many critic updates you do per environment step. UTD > 1 exploits each transition more, but can overfit a recent buffer and let actor errors compound. A larger ensemble and random-pair target may improve robustness in the tested setup; how much UTD it tolerates is empirical. That pairing (UTD=5 + N=10) is a REDQ-like ablation, not a universal recipe.
Maximization bias vs the min-of-two

N critic estimates of Q(s′, a′) as bars (true value = the dashed line). Two are highlighted — the randomly-sampled pair — and their min is the teal marker used as the target. The red ghost bar is what max over the ensemble would give in this sample. Reshuffle to resample the pair and the noise: min-of-two is often conservative and max is often optimistic, but either statistic can land above or below the truth on a finite draw.

When computing the TD target we sample only 2 critics and take their min — but the critic loss is computed over all N. Why train all N if only 2 are used in the target?

Chapter 11: Showcase & Field Guide

One homework, three algorithm families, and related value and policy ideas underneath them. The Bellman equation you derived in Chapter 3 is central to the tabular and off-policy critics; PPO adds a policy-gradient objective with GAE and a value baseline. Here is the arc in one picture, then the cheat sheet.

Showcase: three learners, one task

Illustrative schematic curves for the sparse-reward hammer task. PPO (on-policy) is drawn climbing slowly, while the off-policy, N=2 UTD=1 and off-policy, N=10 UTD=5 (REDQ-like) curves show how replay and update-to-data choices might change a learning trace. The shapes and step counts are fixtures for comparison, not measured benchmark results. Press play to inspect the schematic at a given step budget.

env steps 1000k

The three-algorithm comparison

Tabular Q (P1)PPO (P2)Off-policy AC (P3)
Classvalue, tabularpolicy gradientQ-learning + actor
Dataonline samplesrollout batch, reused for a few epochsfinite off-policy replay buffer
Core updateQ += α(target − Q)clipped surrogate on advantageTD regression to min-of-2 target
Stability trickε-greedy + γclip + reverse-KL + entropytarget net + ensemble + min-of-2
Assignment budget5000 training episodesat least 25% success by 1M stepsat least 90% before 100k; N=10, UTD=5 before 40k
Don't-forget-demos—reverse-KL to frozen BC refBC steps mixed in during RL
The one idea underneath all three. Bellman-style value backups appear in the tabular and off-policy critics, while PPO uses a policy gradient with GAE and a separate value baseline. Tabular writes its update directly into a cell. PPO estimates advantages from returns and takes a clipped surrogate step. Off-policy regresses a neural Q toward a stabilized bootstrap target. These are related engineering realizations, chosen by your constraint: enumerable states → table; fresh-rollout policy optimization → PPO; replay-based action-value learning → off-policy.

Cheat sheet — the equations to carry

NameEquation
Bellman optimalityQ*(s, a) = 𝔼[ r + γ maxa′ Q*(s′, a′) ]
Q-learning updateQ ← Q + α[ r + γ maxa′ Q(s′, a′) − Q ]
ε-greedyuniform w.p. ε, else argmaxa Q(s, a)
GAEδt = r + γ(1−d)V(s′) − V(s);   At = δt + γλ(1−d)At+1
PPO clip−mean[ min( ρA, clip(ρ, 1±ε)A ) ],   ρ = exp(logπnew − logπold)
Off-policy targety = r + γ(1−done) min( Q̄i(s′,a′), Q̄j(s′,a′) )
Critic lossΣk=1..N ( Qk(s,a) − y )2  (all N)
Actor loss−(1/N) Σk Qk(s, πθ(s))
Polyak targetφ̄ ← (1−τ)φ̄ + τφ

Named failure modes — symptom → cause

SymptomCause & cure
Agent can walk into the wall, Q stays unchangedA deterministic all-zero tie with no exploration, especially when the step reward is zero. Negative step costs can break this tie, so inspect the actual reward contract; use ε-greedy with a high start.
Q-values near the goal are biased upwardMissing if done in the target — bootstrapping off a terminal cell. The size and visibility of the symptom depend on terminal-state storage and updates.
Robot never finishes, loiters insteadReward hacking — a positive step reward pays more than the goal. Check the success detector and reward contract; completion-only reward removes this particular loitering incentive.
actor_std crashes to 0, success plateaus lowEntropy collapse in PPO. Raise entropy_coef.
critic_loss becomes unstable or driftsPossible causes include gradients through the target, shape-mismatch broadcasting, an overly fast target, or an unsuitable learning rate; inspect tensor shapes and target/online traces.
critic_loss stuck at exactly 0.0Gradients not flowing — wrong optimizer, missing backward, or loss disconnected.
Q-values become unstable, actor exploits nonsensePossible maximization bias or deadly-triad interaction — inspect target construction, critic diversity, replay coverage, and UTD; min-of-2 can reduce upward bias but is not a guarantee.

Self-quiz — answer these without re-reading and you're ready

  1. Why does Q-learning track actions in the table, not just states?
  2. What would happen with γ = 1 in Scenario 3, and why?
  3. Where does the if done: break go in the inner loop, and what breaks if it's misplaced?
  4. What does λ control in GAE, and what are λ = 0 and λ = 1 called?
  5. Why is the PPO ratio computed as exp of a difference, not a direct division?
  6. Why does the min in the PPO surrogate stabilize training?
  7. How does off-policy replay differ from the limited reuse of a fresh PPO rollout batch?
  8. Why sample 2 critics for the target but train all N?
  9. What's the difference between critic and critic_target, and when does each update?
  10. What tradeoffs could make a replay-based method use fewer environment samples than a fresh-rollout method? Treat any exact step counts as experiment-specific.
  11. What does the BC step during RL accomplish that pretraining alone can't?

Take it back to class. If a friend asks "How does Q-learning work?" — you say:

"It's a semi-gradient stochastic approximation to a Bellman target. You never know the right Q-values, but every transition gives a noisy sample of the right-hand side — immediate reward plus your best guess of the discounted future. Move toward that sample by α each time. Under the tabular assumptions, repeated visits and suitable step sizes recover the optimal fixed point. Two tricks: discount by γ to keep sums finite, and ε-greedy so every state-action gets sampled. Neural methods add different policy-gradient, replay, target, and critic-stabilization machinery rather than becoming one identical algorithm."

Where this connects

This forge is the second stop in the CS224R arc (HW1 was imitation learning). Related on the site:

"What I cannot create, I do not understand." — Feynman. You have now created all three: the table that nudges toward the Bellman target, the clipped policy gradient that dares not move too far, and the ensemble critic that refuses to fool itself. Build them in the Studio and the understanding is yours.

Companion & practice forge for Stanford CS 224R Homework 2 (Online RL). Credits the public course materials. Implement the core math yourself; this is not a solution answer bank.