Diffusion models
Define the forward Gaussian noising process, derive the reverse-time denoising objective, and train DDPM.
Diffusion models are the dominant generative paradigm of the 2020s. The recipe: add Gaussian noise to data in many small steps (the forward process), then learn to reverse the noising step by step. At inference, start from pure noise and denoise to a clean sample. The math is clean (variational like a VAE, hierarchical with shared parameters), the training is stable, and the samples are state-of-the-art across images, audio, and video.
The forward process
Given a data sample , define a sequence by gradually adding Gaussian noise:
with noise schedule . The marginals have a closed form:
where and . By steps with a reasonable schedule, , and is nearly pure noise.
The single number controls the signal-to-noise mix at step : the clean sample enters with weight and the noise with weight . On the standard linear schedule (, from to ) those weights trace the whole range. At , : signal weight , noise weight , the image barely perturbed. At , : signal , noise , half-melted. At , : signal , noise , indistinguishable from a draw of . The schedule is just this monotone slide from all-signal to all-noise.
A key reparameterization:
So at any timestep, is a known interpolation between and random noise.
The reverse process
The forward process is fixed; the reverse process is learned:
To sample, start at and apply the learned reverse step times: .
For Gaussian with small steps, the reverse process is also nearly Gaussian. The model just needs to predict the mean (and optionally the variance).
The DDPM objective
Ho et al. (2020). After algebra on the ELBO, the training objective reduces to a stunningly simple form[1]:
The model takes a noisy sample and the timestep and predicts the noise that was added. Training:
- Sample a clean data point .
- Sample a timestep .
- Sample noise .
- Compute .
- Loss: .
Why it works: the noise prediction task is well-posed across timesteps (easy when is small, hard when is large), and the model gets a diverse curriculum of noise levels by sampling uniformly.
Sampling (DDPM)
Given the learned :
with . Iterate from down to .
The reverse step takes the current noisy sample, predicts the noise to remove, and applies that estimate plus a smaller amount of fresh noise (except at ).
A typical is 1000 for DDPM, meaning 1000 forward passes per sample. This is slow; the next sections cover faster samplers.
DDIM and other faster samplers
Song et al. (2021). DDIM generalizes the sampling step:
where is the predicted clean sample.
When , the process becomes deterministic, and you can skip many timesteps without losing quality. DDIM can sample in 10-50 steps instead of 1000.
Newer samplers (DPM-Solver, EDM, consistency distillation) push this further to 1-8 steps per sample.
What the model actually learns
The denoiser is implicitly the score function of the noisy data:
The score is the gradient of the log-density of the noisy data. The reverse process is then gradient ascent on the noisy log-density: at each step, move in the direction the data is more likely.
This connection (diffusion = score-based generative model) was made rigorous by Song & Ermon and gives a unified view of DDPM and the continuous-time score-based formulation[2].
Conditional generation
For class-conditional or text-conditional generation, concatenate the condition to the input of the denoiser: .
Classifier-free guidance (CFG) is the dominant technique for strengthening conditioning:
where is "no condition" (trained by randomly dropping the condition during training). Higher (typically 7-15) produces sharper, more on-prompt samples at the cost of diversity.
What diffusion gave the field
- Stable training at scale.
- State-of-the-art sample quality on images, audio, and video.
- Strong conditional generation via cross-attention.
- A clean likelihood-bound objective that scales with compute.
The next two lessons cover the productionized form (latent diffusion) and the modern continuous-time generalization (flow matching).
Check it on arrays
import numpy as np
rng = np.random.default_rng(0)
T = 100 # diffusion timesteps
beta = np.linspace(1e-4, 0.02, T) # linear schedule
alpha = 1.0 - beta
alpha_bar = np.cumprod(alpha)
# Forward process: closed form
def forward(x0, t, rng):
"""Sample x_t given x_0 and timestep t (1-indexed)."""
eps = rng.normal(size=x0.shape)
return np.sqrt(alpha_bar[t - 1]) * x0 + np.sqrt(1 - alpha_bar[t - 1]) * eps, eps
# 1D toy: data is mixture of two Gaussians
x0 = np.concatenate([rng.normal(-2, 0.3, 500), rng.normal(2, 0.3, 500)])
print("forward process: noise grows with t")
for t in [1, 25, 50, 75, 100]:
xt, _ = forward(x0, t, rng)
print(f" t={t:>3}: x_t mean={xt.mean():+.3f}, std={xt.std():.3f}")
# A perfect denoiser would output the true epsilon; here mock with a learned-fit
# Fit a tiny linear "denoiser" that maps x_t to noise: epsilon_pred = a * x_t + b
A = np.zeros(T); B = np.zeros(T)
for t in range(1, T + 1):
xt, eps = forward(x0, t, rng)
# OLS for one-dim eps = a * x + b
a = np.cov(xt, eps)[0, 1] / np.var(xt)
b = eps.mean() - a * xt.mean()
A[t - 1] = a; B[t - 1] = b
# DDPM sampling from this naive denoiser
def sample(n, A, B, rng):
x = rng.normal(size=n) # start from N(0, 1)
for t in range(T, 0, -1):
eps_pred = A[t - 1] * x + B[t - 1]
mu = (x - beta[t - 1] / np.sqrt(1 - alpha_bar[t - 1]) * eps_pred) / np.sqrt(alpha[t - 1])
if t > 1:
x = mu + np.sqrt(beta[t - 1]) * rng.normal(size=n)
else:
x = mu
return x
samples = sample(2000, A, B, rng)
print(f"\nsampled distribution: mean={samples.mean():.3f}, std={samples.std():.3f}")
print(f"true data: mean={x0.mean():.3f}, std={x0.std():.3f}")The forward process visibly noisifies the data; the naive linear denoiser plus DDPM sampling recovers a sample distribution close to the original.
Exercises
Exercise 1. The forward process is with . Show that the marginal given is with , by composing two steps and inducting.
Solution
Step 1. Write each step in reparameterized form: with independent .
Step 2. Compose two steps. Substitute :
Step 3. The last two terms are independent zero-mean Gaussians, so they sum to a single Gaussian whose variance is the sum of variances: . Hence with .
Step 4. Induct: each composition multiplies the signal coefficient by the next and leaves the total variance as one minus that product. After steps, , i.e. . The closed form is why a single draw of gives at any without simulating the chain.
Exercise 2. On the standard linear schedule (, from to ), compute the signal weight and noise weight at and verify they match the lesson's quoted and .
Solution
Step 1. At , . The schedule starts at , so .
Step 2. Signal weight , which rounds to .
Step 3. Noise weight , the quoted . At the first step the image is almost pure signal with a noise admixture, the start of the monotone slide the lesson describes.
Exercise 3 (code). Implement one forward noising step in closed form and confirm it empirically. Draw many samples of for a fixed and check the sample mean and variance match and .
Solution
import numpy as np
rng = np.random.default_rng(0)
T = 1000
beta = np.linspace(1e-4, 0.02, T)
alpha = 1.0 - beta
alpha_bar = np.cumprod(alpha)
t = 500 # 1-indexed timestep
ab = alpha_bar[t - 1]
x0 = 1.3 # fixed clean scalar
N = 2_000_000
eps = rng.normal(size=N)
xt = np.sqrt(ab) * x0 + np.sqrt(1 - ab) * eps # closed-form forward step
mean_emp, var_emp = xt.mean(), xt.var()
mean_true, var_true = np.sqrt(ab) * x0, 1 - ab
print(f"t={t}: alpha_bar={ab:.4f}")
print(f"mean empirical {mean_emp:.4f} true {mean_true:.4f}")
print(f"var empirical {var_emp:.4f} true {var_true:.4f}")
assert np.isclose(mean_emp, mean_true, atol=2e-3)
assert np.isclose(var_emp, var_true, atol=2e-3)The sampled mean is and the sampled variance is , the Gaussian marginal Exercise 1 derived. At the signal has shrunk to about a quarter while the noise dominates.
The next lesson moves to the production form: latent diffusion (Stable Diffusion).
References
- [1]Ho, J., Jain, A., & Abbeel, P. (2020). "Denoising diffusion probabilistic models (DDPM)". NeurIPS. arXiv:2006.11239. The noise-prediction objective and DDPM sampling.↩
- [2]Song, Y., & Ermon, S. (2019). "Generative modeling by estimating gradients of the data distribution". NeurIPS. arXiv:1907.05600. Score matching across noise levels, the score-based view of denoising.↩