A Flappy Bird, a 4-number observation, twenty future moves to predict. The simplest imitation-learning algorithm there is — why it flies clean on easy mode, why it drives straight into the wall on hard mode, and the two orthogonal fixes that rescue it.
A companion & practice forge for Stanford's CS 224R Homework 1. 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 (BCPolicy, mse_loss, FlowMatchingSchedule, DeterministicExpert) are the ones you meet here, shrunk to a scale you can run in your browser.
Imagine training a bird from demonstrations of target heights. A single opening gives the expert one route to follow. This illustration isolates what changes when the demonstrations contain two incompatible routes.
On hard mode, pipes alternate between one and two openings. Train the same architecture and squared-error objective on hard-mode demonstrations. When equally likely expert routes share an observation, their mean target can point straight into the solid wall between the openings. This is an idealized mechanism, not a measured claim that every trained policy crashes.
Even a correct implementation can produce this result: minimizing prediction error is not the same objective as avoiding collisions.
Two valid demonstrations from the same state: one expert aims at the upper gap (y = 0.30), another aims at the lower gap (y = 0.70). Press play. Watch where a mean-seeking policy — one trained to minimize squared error — decides to fly.
This is not a bug in your code. It is a mathematical property of the loss function you chose. Fixing it is what launches the rest of imitation learning — and this lesson walks the whole road: how behavior cloning works, why the wall appears, and the two very different escapes (a richer model called flow matching, and a data trick called DAgger).
Before we can fix anything we need to know the machine. The environment is a physics-based Flappy Bird. A bird falls under gravity. Pipes scroll left. Each pipe has one gap (easy) or two (hard). The bird must thread the gaps without touching a pipe, the ceiling, or the floor.
The agent does not press "flap." At every timestep it outputs a single number: a target y-position, normalized to [0, 1], where 0 is the top of the screen and 1 is the bottom. A built-in PD controller — a proportional-derivative feedback loop — converts that target into thrust, so the bird has momentum and cannot teleport. Aim for a height; the controller flies you there with realistic lag.
The policy sees a 4-dimensional vector, every entry normalized to roughly [0, 1]:
| Index | Meaning | Note |
|---|---|---|
obs[0] | distance to the next pipe | counts down as the pipe approaches |
obs[1] | y-position of gap 1 (upper) | the two gaps are ~0.40 normalized (about 179 px) apart |
obs[2] | y-position of gap 2 (lower) | on easy mode, equal to gap 1 |
obs[3] | the bird's current y-position | the bird's velocity is not observed |
obs[1] ≠ obs[2] — there are two gaps — but nothing in the observation tells the policy which gap the expert will pick. Same four numbers, two valid answers. That is the seed of the whole multimodality problem.| Mode | Pipes | Expert | Plain BC |
|---|---|---|---|
| Easy | one gap (gap1 == gap2) | raw target is the one gap; returned command is smoothed | evaluate performance |
| Hard | alternating single- and double-gap pipes | randomly picks a gap → multimodal | can average incompatible routes |
Behavior cloning learns from expert action labels. Reinforcement learning optimizes a reward signal, often using environment interaction. These signals can be combined. Imitation learning includes interactive methods such as DAgger.
| Reinforcement Learning | Imitation Learning | |
|---|---|---|
| Data source | trial and error in the environment | expert demonstrations; interactive methods also query experts on learner rollouts |
| Signal | a reward function | the expert's actions, treated as labels |
| Algorithm family | Q-learning, policy gradients, … | BC uses supervised learning; other IL methods differ |
| Hard part | exploration, sparse reward, credit assignment | distribution shift, multimodal experts |
| Analogy | learn chess by self-play | learn chess by watching humans |
Strip behavior cloning (BC) down to its essence and it is literally just supervised learning. If you have ever trained an image classifier, you have already done every mechanical step of BC. The only thing that changes is the data: states in, expert actions out.
Run the expert in the environment N times. Record every (state, action) pair. You get
where each ai* is the height the expert aimed for at state si. In the real homework the default is roughly 500 demonstration episodes, with an episode of length L yielding max(0, L−19) overlapping 20-step training windows.
Find the network parameters θ that make the policy's action match the expert's action, averaged over the whole dataset:
Here L is a loss that measures how far off the prediction is. For Problem 1 of the homework, L is mean squared error. Problem 2 swaps in a generative loss (flow matching). Same overall structure — different L.
s of shape [B, 4]. Output tensor πθ(s) of shape [B, 20] (twenty future target heights — Chapter 3 explains the 20). Target tensor a* of shape [B, 20]. The loss reduces the two to a single scalar. Backprop, Adam step, repeat. Nothing exotic.The optimization is easy. The hardness lives in three failure modes — and each of the follow-up problems attacks one:
| Failure mode | What goes wrong | Fixed by |
|---|---|---|
| Multimodal experts | two valid actions per state get averaged into an invalid one (the wall) | Problem 2 · flow matching or Problem 3 · deterministic expert |
| Distribution shift | small errors compound until the bird is in states the expert never visited | Problem 3 · DAgger |
| Causal confusion | the network learns a shortcut that works on training data but not in deployment | better data / representations (beyond HW1) |
A single state, several expert demonstrations (dots), and the line a mean-squared-error fit draws through them. Drag the slider to add spread. On unimodal data the fit sits right on the cluster. Keep this picture — Chapter 5 shows what happens when the dots split into two clusters.
Most textbook BC predicts one action at a time: see a state, output an action, repeat. Modern robot-learning policies — Diffusion Policy, ACT/ALOHA, π0 — do something that looks strange at first: they predict a whole chunk of future actions per policy query. Generative policies may use several network evaluations to produce that chunk.
In this homework ACTION_CHUNK = 20: the policy predicts twenty future target heights at once. But it does not execute all twenty. At rollout time it executes only the first EXECUTE_STEPS = 10 before firing again. That "predict 20, execute 10, re-plan" pattern is called receding-horizon control.
The blue bar is the 20-step plan the policy just predicted. The solid segment is the 10 steps actually executed; the faded segment is discarded. Press play: watch the plan slide forward, always re-planning before the executed part runs out.
Three concrete reasons, each a real engineering decision:
| Chunk vs execute | Effect |
|---|---|
| fixed prediction horizon, execute fewer | more frequent policy feedback, with more inference calls |
| fixed prediction horizon, execute more | longer open-loop commitment; fewer calls but slower reaction to disturbances |
| predict 20, execute 10 (this HW) | the standard compromise — predict more than you execute, re-query before the buffer runs dry |
This is the whole reason the policy's output has 20 dimensions. Input: a state of shape [B, 4]. Output: a chunk of shape [B, 20] — each output number is one future target height. The action_dim argument of BCPolicy defaults to 20 because that is the chunk size. Keep that number in mind; it is the shape everything downstream must match.
Now the two design decisions that are Problem 1: the network architecture and the loss. Both are textbook — the interesting part (Chapter 5) is what they do together on bimodal data.
The policy, BCPolicy, is a plain multilayer perceptron — the simplest neural network, a stack of matrix multiplies with nonlinearities between them:
Why an MLP and nothing fancier? The problem is small — a 4-D input, a 20-D output, simple physics. A convolutional net would be overkill (no spatial structure). A transformer would be overkill (no sequential input structure). Two hidden layers of 256 units is the supplied baseline choice. (Compare: Problem 2's flow-matching policy is a 1D U-Net, which models structure across the action chunk while conditioning velocity predictions on the observation and generation time.)
Why ReLU? It keeps positive inputs and zeros negative ones, adding a nonlinearity between affine layers. It does not saturate on the positive side, but inactive units have zero derivative; healthy gradients are not guaranteed.
Why Sigmoid? It maps real outputs into (0,1), matching the normalized height range. This constraint does not ensure good control or strong gradients: sigmoid saturates near the ends of its range.
nn.Sequential(nn.Linear(4,256), nn.ReLU(), nn.Linear(256,256), nn.ReLU(), nn.Linear(256,20), nn.Sigmoid()). Call super().__init__() before assigning submodules. Otherwise normal module assignment raises an initialization error; it does not silently train with unregistered layers.Mean squared error is the average, over the dataset and over all 20 chunk dimensions, of the squared gap between prediction and expert action:
What is the unrestricted population optimum of MSE regression? A finite network may only approximate it. Take the expected squared error at a state and set the derivative with respect to the prediction p to zero:
The MSE-minimizing prediction is the conditional mean of the expert's actions at that state:
| Loss | Minimizer | Tradeoff |
|---|---|---|
| L2 · MSE · (p−a)2 | conditional mean | smooth gradient; but averages multimodal targets |
| L1 · MAE · |p−a| | conditional median | robust to outliers; non-smooth at 0; a median need not be unique or safe |
MSE is a common regression objective for continuous commands. Its smoothness is a gift for optimization — and its mean-seeking is a curse for multimodal data.
This is the conceptual climax of Problem 1. Everything before was setup; everything after is a fix. Spend time here.
Hard mode alternates single-gap and double-gap pipes. When the expert sees a double-gap pipe it hovers at the midpoint while far away, then — when it gets within a commit distance of the pipe — randomly picks one of the two gaps and aims there. Same observation, two different choices, decided by a coin flip inside the expert. To isolate the averaging mechanism, consider two equally likely raw targets at one observation:
s (a double-gap configuration), action = aim at the upper gap, y = 0.30.s (the same state), action = aim at the lower gap, y = 0.70.The random gap choice is internal to the expert. The actual returned command is smoothed using the expert’s history, so these two raw target values are an idealized example, not a claim that every recorded label is exactly a gap center.
From Chapter 4: the MSE minimizer is the conditional mean. For equally probable targets, the conditional mean of 0.30 and 0.70 is
So the ideal squared-error predictor outputs 0.50 at this state — which, because the two gaps are about 0.40 apart (~179 px) and each opening is only about 0.167 tall (75 px, i.e. half-opening ±0.084), is exactly the solid wall between them. The policy is not doing anything wrong. It is faithfully reproducing the conditional mean of the expert distribution. The conditional mean is a valid normalized command, but its target lies in the wall.
Start with all demos aiming at one gap (unimodal). Drag the slider to send more and more of them to the other gap. The dashed line is the MSE optimum — the mean of the demos. Watch it leave the safe gap and drift into the wall as the data becomes bimodal.
Problem 1 exposes a possible multimodal-regression failure on hard mode. Evaluate mean episode length against the 1000-step cap, and the homework asks you to explain why in a couple of sentences. The answer is exactly: multimodality of the expert + mean-seeking of MSE.
And there are two clean escapes, which take orthogonal routes:
| Fix | Idea | Changes |
|---|---|---|
| Problem 2 · Flow matching | make the model richer — learn the full distribution p(a | s), then sample one mode | the model (data untouched) |
| Problem 3 · DAgger | add consistent new labels on learner-visited states — using a deterministic gap choice | the data (model untouched) |
These address different failure mechanisms and can be combined. Measure performance rather than assuming either change guarantees success.
The first escape keeps the data exactly as-is and makes the model richer. Instead of a regressor that outputs one number per state, we train a generative model that learns the whole distribution of expert actions at each state — and then samples from it. Same state, different samples can represent either route; an imperfect model can still produce unsafe commands. This is flow matching, the tool of Problem 2. (For a gentler standalone tour see the site's Flow Matching Gleam.)
| Regressor (Problem 1) | Generative model (Problem 2) | |
|---|---|---|
| Given a state, returns | one deterministic action | a sample from p(a | s) |
| Same state twice | same action | possibly different actions |
| On bimodal data | the mean (the wall) | can represent either mode; no safety guarantee |
Flow matching generates a sample from a complex distribution by continuously carrying a sample from a simple one — Gaussian noise — along a learned vector field. Picture the 20-D action space (think of it as a plane). At every point and time, an arrow says "flow this way." Follow the arrows from time τ=0 to τ=1 and a noise sample is carried to a data sample.
Different noise points can produce different modes under the learned field. Noise lives in 20-dimensional chunk space; its position is not the physical altitude of the bird. The projected illustration is not a guarantee about every learned trajectory.
The training objective is startlingly simple. Take a clean expert action a1. Draw independent noise a0 ~ N(0, I). Pick a random time τ ~ U(0,1) and interpolate along the straight line between them:
Differentiate that straight line and the velocity is constant — it does not even depend on τ:
So the loss is: regress the network's output toward that constant difference.
To generate an action for state s, start from noise and take small steps in the direction of the learned velocity. With num_steps = 20, the step size is h = 1/20 = 0.05:
Repeat 20 times from τ=0 to τ=1, then clamp to [0,1]. Straight conditional training paths do not ensure straight marginal sampling trajectories. Step count trades numerical error against computation; flow matching is not universally faster than every diffusion sampler.
Schematic transport, not a trained-vector-field measurement: twenty noise samples on the left (τ=0) move rightward in a schematic field toward a bimodal target — upper gap (teal) and lower gap (blue). Press play. Notice: the illustrated samples reach different modes; real learned samples are not guaranteed safe.
The second escape keeps the model and the loss exactly as Problem 1 — plain MLP, plain MSE — and changes the data. It is the tool of Problem 3, and the homework combines interventions for: the multimodality you just met, and a second, more fundamental failure of BC called distribution shift.
BC learns from expert-visited observations, but deployment observations depend on the learner's actions. An error can move it into poorly covered regions, where later errors can compound. This is a possible failure mechanism, not a fixed fifty-step deadline.
The green band is the states the expert visited (the only states BC was trained on). Press play: the agent starts inside it, but each small error nudges it out, and the further out it goes the worse it acts — a runaway. Raise the per-step error to see the drift accelerate.
DAgger (Dataset Aggregation; Ross, Gordon, Bagnell 2011) closes the gap directly. Each round: roll out the current policy to collect the states it actually visits, ask the expert what to do at those states, add those (state, expert-action) pairs to the dataset, and retrain. Aggregation adds supervision on learner-induced states; finite rounds do not guarantee matching distributions or zero error.
Here is the cleverest part of HW1. The original expert is multimodal — it randomly picks a gap. If we relabeled with it, we would keep adding multimodal labels, and MSE would keep averaging into the wall. DAgger would fix distribution shift but not multimodality.
So for relabeling we swap in a deterministic expert that always picks the same gap (gap 1, the upper one) when it commits:
Original expert (multimodal):
if dist < commit_dist: target_gap = np.random.choice([0, 1]) # coin flip! committed = True
Deterministic expert (unimodal):
if dist < commit_dist: committed = True raw_target = float(gap1_y) # ALWAYS gap 1
One wrinkle: the policy predicts 20-step chunks but executes 10 (receding horizon). During a rollout you query the expert at every step, storing a per-step list of (state, expert-action). Afterward you window that list into chunks: state st gets paired with the next 20 expert actions [a*t, …, a*t+19]. Window only within one episode and reset the expert between episodes. These labels are corrections along the learner trajectory, not necessarily the actions on an expert-controlled rollout from the first state.
This is a scripted illustration, not an evaluation of trained policies. Three methods — plain BC regression, flow matching, DAgger — attacking the same problem: a multimodal expert on a long-horizon control task. Pick a method, press play, and watch the bird fly the hard course. In this simplified animation, methods select scripted aims; real policies also differ in training and computation. The displayed difference is where the bird aims when it sees a double-gap pipe.
Buttons choose the policy. BC-MSE aims for the mean of the two gaps → the wall → crash. Flow samples one gap and commits. DAgger was retrained on deterministic-expert labels and always takes gap 1. Watch the "aiming at" readout and the crash counter.
| Method | Hard-mode result | How it solves the problem |
|---|---|---|
| BC regression (P1) | measure mean ± std | estimates the conditional mean, which can lie between safe routes. |
| Flow matching (P2) | measure mean ± std | keep the data, enrich the model — a generative policy samples a chunk per query. |
| DAgger (P3, final round) | measure mean ± std | keep the model, fix the data — consistent new labels reduce one ambiguity while aggregation improves state coverage. |
BCPolicy.forward, mse_loss, flow_matching_loss, the schedule’s interpolate + sample, DeterministicExpert.act, and rollout_and_relabel — on a live Flappy Bird stage. The bird crashes until your code goes green, then clears the gap. The browser kernels practice these concepts; completing the full starter requires the actual framework and rollout contracts.Everything worth carrying out of HW1, on one page. If you can reconstruct this from memory, you can teach it.
| P1 · BC-MSE | P2 · Flow Matching | P3 · DAgger | |
|---|---|---|---|
| What you change | — (baseline) | the model | the data |
| Model | 3-layer MLP | 1-D U-Net (given) | 3-layer MLP (same as P1) |
| Loss | MSE on actions | MSE on velocities | MSE on actions (same as P1) |
| Fixes multimodality? | no | can represent multiple modes | reduces new-label ambiguity (consistent strategy) |
| Fixes distribution shift? | no | no | can improve coverage (expert labels on learner states) |
| Inference cost | 1 forward pass | 20 (Euler) | 1 forward pass |
| Needs interactive expert? | no | no | yes |
| You implement | File | In one line |
|---|---|---|
BCPolicy.__init__ / forward | networks.py | the nn.Sequential MLP; return self.net(state) |
mse_loss | losses.py | F.mse_loss(policy(s), a) |
FlowMatchingSchedule.interpolate | networks.py | x_t = t*x1+(1-t)*eps; v = x1-eps |
FlowMatchingSchedule.sample | networks.py | Euler loop over num_steps, then clamp(0,1) |
flow_matching_loss | losses.py | interpolate → model → mse_loss(v_pred, v_target) |
DeterministicExpert.act | dagger.py | raw_target = float(gap1_y) when committed |
rollout_episode / rollout_and_relabel | dagger.py | roll out policy, relabel with expert, window into chunks |
| Failure mode | Symptom you would actually see |
|---|---|
| Multimodal collapse | low hard-mode episode length despite better easy-mode results; the bird hits the wall between two open gaps |
Forgot super().__init__() | assigning submodules before base initialization normally raises an error |
| Forgot the output Sigmoid | predictions leave [0,1]; unstable, random-looking performance even on easy mode |
Wrong action_dim | outputs one number instead of a 20-chunk; the shape mismatch or unintended broadcasting can invalidate the loss |
| Flow: querying model at τ+h | subtly off-distribution samples; performance drops with no crash |
Flow: num_steps = 1 | ideal independent-noise endpoint calculation gives the mean; finite models may differ |
| DAgger: labels from policy not expert | no improvement over rounds; the policy just reinforces its own mistakes |
DAgger: forgot obs.copy() | if the environment reuses mutable arrays, stored states can alias the latest value |
If a friend asks "Why does behavior cloning fail on hard mode?" — you say:
An MSE predictor estimates the conditional mean, which can lie between valid routes. A generative policy can represent multiple choices. DAgger adds expert labels on learner-visited states; this homework also makes new gap choices consistent while retaining old demonstrations. Evaluate the resulting closed-loop behavior.
This forge is the entry point to the CS224R arc. Related on the site:
"What I cannot create, I do not understand." — Feynman. You have now created all three: the mean-seeking regressor, the noise-transporting flow, and the self-correcting relabeler. Build them in the Studio and the understanding is yours.
Companion & practice forge for Stanford CS 224R Homework 1 (Imitation Learning). Credits the public course materials. Implement the core math yourself; this is not a solution answer bank.