One object unlocks three things at once. The score — the gradient of the log-density — is the arrow pointing toward more data. It links the deterministic ODE to the noisy SDE, gives a second way to train, and — through guidance — is how a model learns to obey your prompt.
Quick recap of where Lectures 1 and 2 left us. A flow model is an ODE: start at a random Gaussian point X0, follow a learned vector field uθt(x) — the arrows — until time t = 1, and the endpoint X1 looks like a data sample. Lecture 2 told us how to learn those arrows: pick a Gaussian conditional probability path that drags a clean data point z toward noise, and regress the network onto the conditional vector field ut(x|z). The Gaussian path is
Read that as: "at time t, given the clean data point z, the noisy sample x is Gaussian, centered at a shrunken copy αtz of the data, with spread βt." The schedules satisfy α0 = 0, α1 = 1 (start with no data, end at full data) and β0 = 1, β1 = 0 (start at full noise, end with none). At t = 1 the sample is exactly z; at t = 0 it is pure N(0, I).
Here is the roadmap, mapped onto the slides:
We will end by building a guided 2D sampler with a guidance-scale slider, watching samples snap onto a target cluster — and over-saturate when you crank the scale too high, exactly the artifact you see in real over-guided images.
Forget dynamics for a moment. Take any probability distribution with density q(x) over points x in space. The score function is defined as the gradient of its log-density:
Let's unpack every piece, because the score is one of those objects that sounds abstract and is actually dead simple once you picture it.
The analogy that makes it stick: imagine the density as a mountain range, with peaks where data clusters and flat plains where there is nothing. Stand anywhere and ask "which way is uphill?" That answer — a direction and a steepness — is the score. On a steep slope near a cluster's edge it is a big arrow pointing inward; on a flat plain or right at a peak it is tiny.
Here is the punchline that motivates the rest of the lecture. If you are a particle and you want to drift toward where data lives, you follow the score uphill. That is exactly the noise-injecting dynamics (Langevin dynamics) we will build in Chapter 4. The score is the compass needle of every diffusion sampler.
The shaded heat is a density q(x) — a mixture of two Gaussian clusters (brighter = more data). The arrows are the score ∇ log q(x): at every point they point toward the nearest cluster center (uphill in density), shrinking to nothing exactly at the peaks. Drag cluster gap to pull the clusters apart and watch the arrows in the valley flip allegiance. This is the compass a sampler follows to find data.
Now connect this to our generative setting. Lecture 2 worked with a time-indexed family of densities: the conditional probability path pt(x|z) (the noised version of one data point z) and the marginal probability path pt(x) (the noised version of the whole dataset). Each is a density, so each has a score:
And just as the marginal vector field is an average of conditional vector fields over the data, the marginal score is a (posterior-weighted) average of conditional scores — the same averaging structure. That parallel is what makes everything in Chapter 3 work: the trick that let us learn the vector field will, line for line, let us learn the score.
Abstract scores are nice, but we can only train what we can write down. For the Gaussian path — the path that powers essentially every diffusion model — the conditional score has a clean closed form. Let's derive it from scratch, because the derivation is short and it makes the formula impossible to forget.
Recall the conditional path is a Gaussian centered at the shrunken data point with spread βt:
A d-dimensional isotropic Gaussian with mean μ and variance βt2 in each coordinate has density proportional to exp of a negative squared distance. Its log is:
where ||x − αtz||2 is the squared distance from x to the mean αtz, and C collects everything that does not depend on x (the normalizing constant and the log of βt). We are about to take a gradient in x, so C is going to vanish — this is exactly the "score ignores normalization" property from Chapter 1, in action.
Differentiate with respect to x. The constant C drops. For the quadratic, recall the one-line rule ∇x ||x − a||2 = 2(x − a) (the multivariable analogue of d⁄dx (x − a)2 = 2(x − a)). With a = αtz:
Sign: if x is to the right of the mean (x − αtz > 0), the score is negative — it pushes you left, back toward the center. Correct: density is higher near the mean. Scale: early in the path βt is large (lots of noise), so the spring is weak — everything is vague. Late in the path βt → 0, so 1⁄βt2 blows up — the score becomes a razor-sharp pull onto the exact data point. (That blow-up is real and will bite us numerically in Chapter 3.)
Build this formula with your hands and verify it numerically against the definition (finite differences of the log-density) in the lab below.
We want a network sθt(x) that outputs the marginal score ∇ log pt(x) at any noisy x and time t. We cannot regress onto the marginal score directly — we never have a formula for it (it averages over the whole unknown data distribution). But Lecture 2 already taught us the escape hatch, and it transfers here unchanged.
Here is the step that gave "denoising diffusion models" their name. We never sample x out of thin air — we build it from a clean data point z and a fresh noise draw ε, following the Gaussian path:
Now substitute this x into the conditional score from Chapter 2. Watch the cancellation:
# Denoising score matching (Algorithm 4) for each minibatch: z = sample_data() # a clean data point z ~ p_data t = uniform(0, 1) # a random time eps = sample_gaussian() # the noise, eps ~ N(0, I) x = alpha_t * z + beta_t * eps # the corrupted sample loss = || eps_theta(x, t) - eps ||^2 # predict the noise you added backprop_and_step(loss)
That is the entire training procedure for a denoising diffusion model. No SDEs, no scores explicitly — just "corrupt a data point with known noise, and train a network to guess that noise." Build it and confirm that a fitted noise-predictor lowers the loss and that its implied score matches the analytic one.
Lecture 1 sampled with a deterministic ODE (a flow). But diffusion models are stochastic — they inject fresh noise at every step. Why would you want noise, and how do you add it without destroying the result? The answer is the score, and it is one of the most elegant facts in the whole subject.
Adding raw Brownian noise to an ODE would smear the distribution — it would diffuse mass outward, making pt fatter than intended. The score correction is a counter-force: it pushes mass back uphill toward where the density should be dense, precisely cancelling the smearing. Noise spreads things out; the score (pointing toward density) pulls them back in. Balance the two and the marginal distribution is preserved exactly — you get extra randomness in which path a sample takes, with no change in where the samples end up.
Take the SDE trick and freeze the target — let the probability path be a single fixed distribution p (so ut = 0, nothing to flow toward in time). What's left is pure score-following plus noise:
You'll watch Langevin dynamics pull a cloud of particles into a two-cluster target in the showcase. The takeaway for now: the score converts a flow into a diffusion. ODE and SDE are two ways to traverse the same probability path; the score is the bridge between them.
Particles (dots) start clumped at the origin and evolve by the Langevin SDE toward a two-cluster target (shaded). Each step drifts uphill along the score and adds a Brownian kick. Drag noise σ: too little and particles get stuck in one cluster; just right and they explore and fill both clusters in the correct proportions. Press Run to re-seed. This is the SDE trick with the vector field switched off — pure score-following.
"ODE and SDE traverse the same path, the score is the bridge" is not a slogan — for a Gaussian path it is an exact algebraic identity. Prove it: rebuild the velocity field from the score alone.
We now have a sampler. Recall the Euler–Maruyama step from Lecture 1: take the deterministic drift step and a scaled Brownian kick. For diffusion sampling the drift is the corrected drift from Chapter 4:
Here uθt is the learned flow field and sθt is the learned score. For a Gaussian path you don't even need two networks: by the conversion formula of Chapter 2 you can build the score from the vector field (or vice versa) and plug it into the same step. The √h on the noise term is the Brownian-motion scaling from Lecture 1 — variance grows like elapsed time, so the per-step standard deviation grows like the square root of the step.
# Sample a diffusion model with Euler-Maruyama # Input: flow net u_theta, score net s_theta, noise schedule sigma_t, steps n t = 0.0 h = 1.0 / n x = sample_gaussian() # X_0 ~ p_init = N(0, I) for i in range(n): drift = u_theta(x, t) + 0.5*sigma_t**2 * s_theta(x, t) x = x + h*drift + sigma_t*sqrt(h)*sample_gaussian() # one EM step t = t + h return x # X_1 ~ p_data (approximately)
Set σt = 0 and this collapses to the Lecture-1 ODE sampler: no score correction, no noise, just the flow field. Crank σt up and you inject more stochasticity per step.
You already built the Euler–Maruyama step in Lecture 1 and the score in Chapter 2 — the diffusion sampler is just those two snapped together with the (σ2/2)·score correction. The showcase in Chapter 8 lets you watch ODE-vs-SDE side by side; for now, lock in the structure.
Build the simplest member of the family: Langevin dynamics — the SDE sampler with the vector field switched off (u = 0), so a known score alone drives the sampling.
Everything so far generates some object — "make an image." But you usually want a specific kind of object — "make an image of a cat baking a cake." That extra information is a prompt y (a class label, a text caption). We want to sample from the guided data distribution pdata(· | y): images that fit the prompt.
The simplest idea works, in principle. Give the network the prompt as an extra input: a guided vector field uθt(x | y) that takes x, t, and y. Train it exactly like before but with paired (image, prompt) data; at sampling time, fix y and run the ODE/SDE. This is vanilla guidance. Clean, and theoretically it samples pdata(· | y).
To see why vanilla guidance underperforms — and how to fix it — rewrite the guided score using Bayes' rule. Bayes says the guided density factors as pt(x | y) ∝ pt(x) · pt(y | x). Take the gradient of the log (the score), and because the gradient is in x, any term that does not depend on x drops:
People noticed the prompt-matching term was too weak — images came out only loosely related to the prompt. The natural hack: multiply that term by a guidance scale w > 1 to amplify it:
This is classifier guidance. The term ∇ log pt(y | x) is just the gradient of a classifier trained on noisy images (it must classify x at every noise level), so you can learn it by ordinary supervised learning — then add w times its gradient to the drift at sampling time.
Classifier guidance has two real costs that doomed it in practice: (1) you must train and store a second network (the noisy-image classifier) alongside your generator, and (2) for high-dimensional prompts like free text, pt(y | x) is brutally hard to model and its gradient hard to obtain. The next chapter removes the classifier entirely — that's why classifier-free guidance won.
Classifier-free guidance (CFG) achieves the same amplification as classifier guidance without ever training a classifier. It is the single most important practical trick in this lecture: virtually every AI image or video you have seen was sampled with CFG, typically at a scale around w ≈ 4.
Start from classifier guidance and substitute the Bayes identity for the classifier gradient, ∇ log pt(y | x) = ∇ log pt(x | y) − ∇ log pt(x). After the dust settles (full derivation in the notes), the scaled guided vector field becomes a simple linear combination of the guided and unguided fields — no classifier in sight:
The formula needs both a conditional field ut(x | y) and an unconditional one ut(x | ∅). Sounds like two networks again. The beautiful trick: train one network to be both. Add a special null token ∅ to your label set meaning "no prompt," and during training randomly drop the real label — replace y with ∅ with some probability η (say 10–35%). The network learns the conditional behavior on the kept labels and the unconditional behavior on the dropped ones.
# CFG training (Algorithm 5) -- one network does both jobs for each minibatch: z, y = sample_paired_data() # image + its prompt t = uniform(0, 1) eps = sample_gaussian() x = alpha_t*z + beta_t*eps # corrupt the image if random() < eta: # drop the label with prob eta y = NULL # the empty token, no prompt loss = || u_theta(x, t, y) - u_target(x | z) ||^2 backprop_and_step(loss)
At sampling time you make two calls to the same network — one with y, one with ∅ — and combine them with the CFG formula above. One model, two passes, a slider w.
Implement the CFG combination yourself and measure how guidance pulls samples toward the prompt's cluster — and how far past it you go.
This is the payoff. Below, a flow model has been (hand-)set up over a 2D toy with several data clusters, and one of them is the "prompt" cluster you want. Drag the guidance scale w and watch the generated samples respond exactly as the theory predicts — and then misbehave.
Faint shaded blobs are the data clusters; the teal ring marks the prompt cluster. Orange dots are generated samples after running the guided field ucfg = (1+w)·ucond − w·uuncond. At w = 0 the field is purely conditional, so samples land right on the prompt. Raise guidance scale w and the prompt's pull is amplified — samples sharpen and then overshoot past the target into a saturated blob (dots pile up at the frame edge). Press Resample for fresh noise. The readout reports how far past the prompt the samples land.
You now hold the second master object of the course. With the vector field (Lectures 1–2) and the score (this lecture), you can train, sample deterministically or stochastically, and obey a prompt. Here is the whole lecture on one page.