12.3.1 DDPM Basics
Train a Denoising Diffusion Probabilistic Model on African fabric patterns by learning to reverse a 1,000-step noise corruption process; understand the forward schedule, the U-Net's noise-prediction objective, and the iterative reverse-process sampler.
Overview
By 2020, GANs had dominated generative image modelling for six years. Then Ho, Jain and Abbeel published “Denoising Diffusion Probabilistic Models” [1], achieved competitive image quality without adversarial training, and within two years diffusion-based architectures had displaced GANs as the dominant approach. Stable Diffusion [2], DALL-E 2 [3], Imagen, and Midjourney all use variants of the same core mechanism.
The trick is structurally elegant. Start with a clean image, add Gaussian noise in 1,000 small steps until the image is pure noise, and you have a forward process whose math is closed-form. Now train a neural network to reverse one step of that process: given a noisy image at timestep t, predict the noise component. Once trained, sample new images by running the reverse process from t = 999 down to t = 0, starting from pure noise and denoising step by step. No adversarial training, no mode-collapse risk, no carefully-balanced discriminator — just MSE on noise prediction.
This lesson trains a DDPM on the same African fabric dataset used in Lesson 12.1.2 (DCGAN) and Lesson 12.1.3 (StyleGAN), enabling a direct three-way comparison of the three dominant generative paradigms on identical data. Training takes 15–20 hours on a modern GPU; the pre-trained checkpoint is on a GitHub Release for everyone who’d rather skip to generation.
Learning objectives
- Explain the forward diffusion process: what
β_tcontrols, whyᾱ_tmatters, and why the closed-form jump fromx_0tox_tis what makes training tractable. - Describe the reverse-process training objective in one sentence (predict the noise) and the architecture (U-Net with timestep conditioning, self-attention, residuals) that makes it work.
- Sample new images by running the trained reverse process; tune sampling-step count via DDIM and predict the quality trade-off.
- Compare DDPM’s training stability and mode coverage against DCGAN and StyleGAN on the same dataset.
Quick start — load checkpoint, sample 4 fabrics
Download the pre-trained checkpoint from the GitHub Release. The denoising-diffusion-pytorch library handles the U-Net plumbing; we just point it at the weights.
ddpm_african_fabrics.pt — pre-trained 545 MB checkpoint (GitHub Release)from denoising_diffusion_pytorch import Unet, GaussianDiffusion
from torchvision.utils import save_image
import torch
model = Unet(dim=64, dim_mults=(1, 2, 4, 8), channels=3)
diffusion = GaussianDiffusion(
model, image_size=64, timesteps=1000, sampling_timesteps=250,
)
ckpt = torch.load('models/ddpm_african_fabrics.pt', map_location='cpu')
ema = {k.replace('ema_model.', ''): v
for k, v in ckpt['ema'].items() if k.startswith('ema_model.')}
diffusion.load_state_dict(ema)
diffusion.eval()
with torch.no_grad():
samples = diffusion.sample(batch_size=4) # [4, 3, 64, 64]
samples_up = torch.nn.functional.interpolate(samples, scale_factor=4, mode='nearest')
save_image(samples_up, 'quick_start.png', nrow=2, padding=4, pad_value=1) That’s four fabric patterns generated from pure noise via 250 DDIM steps. Each step is one U-Net forward pass; the whole sample takes ~10 seconds on CPU, sub-second on GPU.
Core concepts
Concept 1 — Why a reversed corruption process beats adversarial training
GANs train a generator against a discriminator and hope the equilibrium is favourable. In practice, that equilibrium is fragile: mode collapse, vanishing gradients, oscillation between the two networks (see Lesson 12.1.1). The trade-off you accept is fundamental — adversarial training is what gives GANs their sharpness, and it’s also what makes them temperamental.
Diffusion takes the opposite design stance. There’s no adversary. The training objective is mean squared error on a noise-prediction task, the same flavour of loss used in any regression problem. The training dynamics are correspondingly boring: loss decreases monotonically, no instabilities, no balance to maintain. The trade-off you accept is slow sampling — 250–1,000 forward passes through the network per generated image, versus a GAN’s one.
| Aspect | DDPM | GAN (DCGAN / StyleGAN) |
|---|---|---|
| Training stability | Stable, monotonic loss | Adversarial; mode collapse, oscillation |
| Loss function | MSE on noise prediction | Adversarial; min-max objective |
| Sampling cost | 250-1,000 forward passes per image | 1 forward pass |
| Diversity | Covers full data distribution | Can drop modes |
| Quality | State-of-the-art (FID) | Excellent but artefact-prone |
| Controllability | Native via guidance | Requires conditioning architecture |
Sohl-Dickstein et al.’s 2015 paper [4] introduced the theoretical framework borrowing from non-equilibrium thermodynamics; Ho, Jain and Abbeel’s 2020 paper [1] made it work on natural images. The five-year gap is a useful reminder that “elegant theory” and “competitive system” are not the same thing.
Concept 2 — Forward diffusion: noise schedule and the closed-form shortcut
The forward process gradually adds Gaussian noise to a clean image x_0 over T = 1,000 timesteps. At each step:
q(x_t | x_{t-1}) = N(x_t; √(1 - β_t) · x_{t-1}, β_t · I)β_t is the noise schedule — small per-step values (typically 0.0001 to 0.02) that control how much fresh noise gets mixed in. The √(1 - β_t) factor lightly shrinks the previous image to keep variance bounded.
The closed-form shortcut is what makes training efficient. Because the noise additions are independent Gaussians, you can sample x_t directly from x_0 without iterating through t - 1 intermediate steps:
q(x_t | x_0) = N(x_t; √(ᾱ_t) · x_0, (1 - ᾱ_t) · I)where ᾱ_t = ∏ᵗ_{s=1}(1 - β_s) is the cumulative product. This is the reason training is fast: each training step samples a random t, jumps directly to x_t in one closed-form operation, asks the network to predict the noise, and computes one gradient. No sequential rollout.
The choice of schedule matters. The linear schedule in the original DDPM paper aggressively destroys signal by t ≈ 250; the cosine schedule introduced by Nichol and Dhariwal [5] preserves signal longer at middle timesteps, which means the model gets more informative gradients during training.
Concept 3 — Reverse diffusion: U-Net predicts the noise
Generation reverses the process. Start at x_T ~ N(0, I) (pure noise); at each timestep, ask the network to predict the noise component, subtract a scaled version, and arrive at x_{t-1}. Repeat 1,000 times — or, with DDIM acceleration, 250 times — and land at a clean image x_0.
The training objective is one of the cleanest in modern deep learning:
L = E_{t, x_0, ε} [ ‖ε - ε_θ(√(ᾱ_t)·x_0 + √(1-ᾱ_t)·ε, t)‖² ]Read inside-out: take a clean x_0, corrupt it to timestep t using the closed-form shortcut, ask the network ε_θ to predict the noise ε that was added, and minimise the squared error. The trained network learns a noise-prediction function conditioned on the timestep.
The architecture is a U-Net [6] — encoder-decoder with skip connections at every resolution level. The skips matter because noise prediction requires pixel-level output detail; without them, the bottleneck would discard the high-frequency information the decoder needs to reconstruct.
Four architectural details matter for diffusion specifically:
- Timestep conditioning — sinusoidal embeddings encode the integer timestep as a vector; every block receives this signal so the network can specialise behaviour by noise level.
- Self-attention [7] — at middle and bottom resolutions, attention layers let each spatial position see every other; essential for long-range coherence (a fabric pattern’s colour palette must agree across the whole image).
- Residual connections [8] — every conv block computes
output = block(x) + x; gradients flow through the shortcut, training a deep network is tractable. - Group normalisation [9] — diffusion uses small batches due to memory; BatchNorm statistics become noisy below ~16 samples per batch, so GroupNorm (which normalises across channel groups within a single image) is the safer default.
Concept 4 — DDIM: turning 1000 steps into 250 (or 50)
Sampling 1,000 sequential U-Net forward passes per image is slow. Song, Meng and Ermon’s DDIM [10] (Denoising Diffusion Implicit Models) showed that a deterministic version of the reverse process can skip many of those steps. The same trained DDPM model works — only the sampling procedure changes.
The intuition: in the original DDPM sampler, each step has stochasticity that compounds over the trajectory. DDIM removes that stochasticity and chooses a sparser timestep schedule (e.g., every 4th step) to integrate the deterministic reverse process. With 250 DDIM steps you get quality comparable to 1,000 DDPM steps; with 50 steps you get usable but visibly softer output.
diffusion = GaussianDiffusion(
model, image_size=64,
timesteps=1000, # training schedule (unchanged)
sampling_timesteps=250, # DDIM accelerated sampling
)DDIM is what makes diffusion practical for interactive use. The newer Flow Matching family (Lesson 12.7.1) takes the trajectory-straightening idea further — straight paths from noise to data, integrable in 20–50 Euler steps without retraining the model differently.
Exercises
Generate 16 fabric patterns from the pre-trained checkpoint
Place the .pt in models/, then:
pip install denoising-diffusion-pytorch torch torchvision
python exercise1_generate.py Output: exercise1_output.png — a 4×4 grid of 16 generated patterns sampled with 250 DDIM steps. Generation takes ~3 minutes on CPU, ~15 seconds on GPU.
Reflection
- The trained model was never shown an image with the exact pattern in any of the 16 outputs. How can it generate them?
- The same noise vector decoded by DDPM, DCGAN, and StyleGAN would produce three different fabric patterns. Why — they were all trained on the same data?
- Why does generation take so much longer than a DCGAN forward pass that takes milliseconds?
- The patterns look slightly less sharp than StyleGAN’s at the same resolution. What’s the structural reason?
Discussion
-
The model learned the distribution of African fabric patterns, not any specific image. The 1,059 training images are samples from a much larger space; the trained network can produce any plausible member of that space by walking the reverse process from any starting noise.
-
They have different architectures, different latent spaces, different training objectives, and different inductive biases. The same noise tensor is interpreted differently by each. None of them is “the right” generator — they’re three different approximations of the data distribution.
-
Each generated sample requires 250 forward passes through the U-Net (one per denoising step). A DCGAN sample is one forward pass through the generator. The trade-off is fundamental: diffusion buys stability and diversity at the cost of inference speed.
-
The MSE noise-prediction objective optimises for the mean of plausible reconstructions at each step, similar to how VAEs (Lesson 12.2.1) produce softer outputs than GANs. GANs’ adversarial objective explicitly rewards sharpness because the discriminator can detect softness; MSE has no such pressure.
Sampling steps and denoising visualisation
python exercise2_explore.py Produces exercise2_sampling_comparison.png (quality at different step counts) and exercise2_denoising.gif (a single sample’s reverse process visualised step by step).
Goal 1 — Step count sweep. Try sampling_timesteps ∈ {50, 100, 250, 1000}. The relationship is sub-linear: doubling steps from 250 → 500 produces almost no visible improvement.
Goal 2 — Different seeds. Change RANDOM_SEED to see how the starting noise affects output. Two close seeds give similar outputs; very different seeds give unrelated ones.
Goal 3 — Custom DDIM schedules. Read the source of denoising_diffusion_pytorch.GaussianDiffusion.ddim_sample to see how the timestep selection works. Try a logarithmic instead of uniform spacing to see if it changes anything.
What to expect — step count
50 steps: usable, slightly soft. 100 steps: comfortable default for fast iteration. 250 steps: the production default. 1,000 steps: marginally sharper, 4× the compute. The diminishing returns curve flattens hard around 250.
What to expect — different seeds
Two seeds that differ by 1 produce visually similar but distinguishable patterns. Two distant seeds produce unrelated patterns. The noise space is continuous in the sense that nearby points decode to nearby images, which is what makes the morphing animation in Exercise 3 possible.
Morph between two patterns + (optional) train from scratch
Two paths. Most learners should do Option A unless they have a GPU and a free weekend for Option B.
exercise3_morph.py — morph two patterns in noise space exercise3_train.py — full DDPM training (15-20h on GPU)Option A — Morphing animation in noise space
The “noise space is continuous” property from Concept 3’s discussion enables smooth morphing animations. Interpolate between two noise tensors, run the reverse process from each interpolated tensor, and you get a sequence of related patterns.
python exercise3_morph.pyProduces my_morph.gif — a smooth morph between two patterns by walking through noise space.
Option B — Train from scratch (15-20 hours GPU)
Reproduces the GitHub Release checkpoint. Same dataset as 12.1.2 and 12.1.3, same hyperparameters.
| Hyperparameter | Value |
|---|---|
| Image size | 64×64 RGB |
| Diffusion timesteps | 1,000 |
| Batch size | 32 |
| Learning rate | 8e-5 (AdamW) |
| Training steps | 500,000 |
| EMA decay | 0.995 |
| Backbone | U-Net, ~36M params |
| Dataset | 1,059 African fabric images |
python exercise3_train.pySaves checkpoints every 5,000 steps. Loss decreases monotonically; expect ~15-20 hours on an RTX 5070Ti. The final checkpoint should be functionally equivalent to the one on the GitHub Release.
What to watch for during training
- Loss decreases from ~0.1 to ~0.02 over the first 50k steps, then slowly drifts down to ~0.015 by 500k.
- Sample images saved every 5k steps; structure visible by 50k, clear patterns by 200k, final quality by ~400k.
- No oscillation, no mode collapse — diffusion training is boring in the best sense.
- If loss plateaus above 0.05 by 50k, learning rate may be too high; try 4e-5.
Make it your own
After getting either option working, try sampling at different truncation-style settings. DDPM doesn’t have a literal truncation parameter but you can achieve similar effect by sampling noise from N(0, σ²) with σ < 1 — pulls samples toward the mean of the noise distribution, trading diversity for consistency similar to StyleGAN’s ψ.
Summary
Common pitfalls
- Confusing training timesteps with sampling timesteps.
timesteps=1000is fixed by training;sampling_timesteps=250is your inference-time choice. - Running with the wrong checkpoint key. The downloadable
.ptcontains both the trained weights and the EMA-tracked copy; you usually want the EMA version (ema_model.*), which is what the quick-start code extracts. - Skipping the
eval()mode call before sampling — BatchNorm/Dropout in training mode produces noisier outputs. - Trying to interpret the trained model’s outputs as “average of training images.” It’s not — it’s samples from the learned distribution, which is structurally different from the empirical training distribution.
- Comparing FID scores between DDPM, DCGAN, and StyleGAN runs at different image resolutions or with different image-preprocessing pipelines. The numbers are only comparable under matched conditions.
References
- [1] Ho, J., Jain, A. & Abbeel, P. (2020). Denoising Diffusion Probabilistic Models. Advances in Neural Information Processing Systems, 33, 6840–6851. arxiv:2006.11239
- [2] Rombach, R., Blattmann, A., Lorenz, D., Esser, P. & Ommer, B. (2022). High-Resolution Image Synthesis with Latent Diffusion Models. CVPR. arxiv:2112.10752
- [3] Ramesh, A., Dhariwal, P., Nichol, A., Chu, C. & Chen, M. (2022). Hierarchical Text-Conditional Image Generation with CLIP Latents (DALL-E 2). arXiv preprint. arxiv:2204.06125
- [4] Sohl-Dickstein, J., Weiss, E. A., Maheswaranathan, N. & Ganguli, S. (2015). Deep Unsupervised Learning using Nonequilibrium Thermodynamics. ICML. arxiv:1503.03585
- [5] Nichol, A. & Dhariwal, P. (2021). Improved Denoising Diffusion Probabilistic Models. ICML. arxiv:2102.09672
- [6] Ronneberger, O., Fischer, P. & Brox, T. (2015). U-Net: Convolutional Networks for Biomedical Image Segmentation. MICCAI. arxiv:1505.04597
- [7] Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, Ł. & Polosukhin, I. (2017). Attention Is All You Need. NeurIPS. arxiv:1706.03762
- [8] He, K., Zhang, X., Ren, S. & Sun, J. (2016). Deep Residual Learning for Image Recognition. CVPR. arxiv:1512.03385
- [9] Wu, Y. & He, K. (2018). Group Normalization. ECCV. arxiv:1803.08494
- [10] Song, J., Meng, C. & Ermon, S. (2021). Denoising Diffusion Implicit Models (DDIM). ICLR. arxiv:2010.02502