Skip to content
coden

Diffusion models: how pure noise becomes an image

The forward process of adding noise, a network that estimates the noise, DDPM and DDIM written from scratch, eight clusters without mode collapse, how many sampling steps are needed, a comparison with VAE and GAN on digits and the road to images from text.

12 minutes Matěj Žalmánek 2026-10-16

Series AI basics Part 46 of 52
  1. Map of artificial intelligence: what is what and how it all fits together
  2. A brief history of artificial intelligence: from Turing to language models
  3. Data, model, training, inference: the basic terms on a single example
  4. How to read mathematical notation: symbols, indices, sums and quantifiers
  5. Sets and relations: the building blocks of definitions in AI
  6. Functions and mappings: why every AI model is a function
  7. Logic and proofs: the rules AI reasons by
  8. Graphs and trees: the structure that both search and neural networks stand on
  9. Linear algebra for machine learning: vectors, matrices and why AI mostly multiplies
  10. Derivatives, the gradient and the chain rule: how a model finds out which way to learn
  11. Gradient descent: how a model learns
  12. What probability is: from rolling dice to the Kolmogorov axioms
  13. Conditional probability and Bayes’ theorem: how to update your estimate when you learn something new
  14. Random variables and the cumulative distribution function: one function that tells you everything about a distribution
  15. Probability density and probability mass functions: why a continuous variable has zero probability at a point
  16. Expected value, variance and covariance: where a distribution lies, how wide it is and what goes with what
  17. Overview of probability distributions: Bernoulli, binomial, Poisson, uniform, normal and exponential
  18. Law of large numbers and central limit theorem: why averages behave so predictably
  19. Statistics and parameter estimation: maximum likelihood, MAP and confidence intervals
  20. Information theory: entropy, cross-entropy and KL divergence — where loss functions come from
  21. State-space search: BFS, DFS, UCS and A* with animations
  22. Games and adversaries: minimax, alpha-beta pruning and Monte Carlo tree search
  23. Constraint satisfaction: backtracking, constraint propagation and AC-3 on sudoku and a map of Czech regions
  24. Logic and knowledge systems: forward and backward chaining, resolution and expert systems
  25. Planning: STRIPS, the blocks world, heuristics from relaxation and planning in AI agents
  26. Local search and simulated annealing: a travelling salesman through the Czech regional capitals
  27. Genetic algorithms: population, crossover, mutation and selection on a backpack for a hike
  28. Evolution strategies and genetic programming: from the one-fifth rule to Kepler’s law
  29. Ant colony optimization: how a pheromone trail finds a short path
  30. Particle swarm optimisation: inertia, personal experience and the swarm’s experience
  31. Neuroevolution: evolving the weights and shape of neural networks, and the NEAT algorithm
  32. Fuzzy sets and membership functions: how to write down “somewhat warm”
  33. Fuzzy logic and probability: why “somewhat warm” does not mean “probably warm”
  34. Fuzzy logic: t-norms, implications and IF–THEN rules
  35. Fuzzy control: Mamdani and Sugeno from a fan to a pole on a cart
  36. Fuzzy clustering: the fuzzy c-means algorithm and points between clusters
  37. Neuro-fuzzy systems (ANFIS): fuzzy rules that learn from data
  38. Markov chains: the transition matrix, the stationary distribution and PageRank
  39. Hidden Markov models: the Viterbi algorithm and the dishonest casino
  40. Bayesian networks: why the grass is wet and what it means for the sprinkler
  41. The Kalman filter: how to estimate where a car is and how fast it goes from imprecise measurements
  42. Types of machine learning: supervised, unsupervised, semi-supervised and more
  43. Prompts, context and RAG: how to give a model the right information
  44. Variational autoencoders (VAE): new images from a continuous latent space
  45. Generative adversarial networks (GAN): the forger versus the detective
  46. Diffusion models: how pure noise becomes an image
  47. Markov decision processes: states, actions, rewards and the Bellman equations
  48. Q-learning and DQN: learning the value of actions, from a table to a neural network
  49. Policy gradient and PPO: learning the policy directly
  50. How an AI agent works: a language model in a loop with tools
  51. Programming with AI agents: the task, tests and review
  52. Evaluating and securing AI systems: how to measure and how to keep watch

Full series syllabus 52 of 83 topics published

What you will take away
  • How the forward and reverse diffusion processes work.
  • Why diffusion trains more stably than a GAN and what the number of steps costs.
  • How diffusion becomes a text-to-image generator.

Useful to know first: Generative adversarial networks (GAN): the forger versus the detective, Variational autoencoders (VAE): new images from a continuous latent space

Stable Diffusion, Midjourney, DALL·E 3 and the models that generate video all rest on the same idea, and it is surprisingly simple. Destroying an image is easy — just keep adding noise to it until only noise is left. If we learned to undo each small step, we could start from pure noise and peel an image out of it step by step. In this part we program it from scratch and compare it with VAE and GAN on the same data.

It builds on the parts Generative adversarial networks and Variational autoencoders.

1. Forward process: an image dissolves in noise

Definition — forward (diffusion) process

Let d ≥ 1 be the dimension of the data, x0 ∈ ℝd an image from the data, T ≥ 1 the number of steps and β1, …, βT ∈ (0, 1) the variance schedule. Let ε1, …, εT ~ N(0, I) be mutually independent random vectors, independent of x0 (I is the d × d identity matrix). The forward (diffusion) process is the sequence of random vectors x1, …, xT ∈ ℝd given by the recurrence

xt = √1 − βt · xt−1 + √βt · εt,   t = 1, …, T.

Denote

ᾱt = ∏s=1t (1 − βs)   for t = 1, …, T,   ᾱ0 = 1.

Then for every t the closed form holds (theorem below)

xt = √ᾱt · x0 + √1 − ᾱt · ε,   ε ~ N(0, I) independent of x0,

i.e. given x0, xt has the distribution N(√ᾱt x0, (1 − ᾱt) I). The sequence ᾱt is decreasing, and when it is almost zero for large enough t, xt has almost the distribution N(0, I) — pure noise in which almost no trace of x0 is left.

In plain words

A drop of ink in a glass of water: for a moment you can see the shape of the drop, then it spreads out, and in the end the water is evenly coloured and the original shape cannot be recognised. The closed formula says you can jump straight to any moment — there is no need to simulate all the steps.

xt = √ᾱt · x0 + √1 − ᾱt · ε

xt
the noisy image at step t — exactly what the network gets as input during training
√ᾱt
how much of the original image is left; ᾱt is the product of all (1 − βs) up to step t, so it falls with every step
x0
the original image from the data
√1 − ᾱt
how much noise there is; the square roots are there because it is the variances that add up: (√ᾱ)² + (√1 − ᾱ)² = 1, so data with variance 1 have variance 1 at every step and at the end pass smoothly into N(0, I)
ε
one sample of noise from N(0, I) — exactly what the network will guess; knowing it turns training into ordinary regression
Theorem — why one jump is enough

Let x0, x1, …, xT be the forward process with schedule β1, …, βT ∈ (0, 1) and ᾱt = ∏s=1t (1 − βs). Then for every t = 1, …, T there exists a random vector ε ~ N(0, I) independent of x0 such that

xt = √ᾱt x0 + √1 − ᾱt ε.

Proof. By induction on t. For t = 1 it is directly the definition of the step, since ᾱ1 = 1 − β1. Suppose the claim holds for t − 1, i.e. xt−1 = √ᾱt−1 x0 + √1 − ᾱt−1 ε′ with ε′ ~ N(0, I) independent of x0. Substitute into step t:

xt = √1 − βt √ᾱt−1 x0 + √1 − βt √1 − ᾱt−1 ε′ + √βt εt.

The first term is √ᾱt x0, because ᾱt = (1 − βt) ᾱt−1. The other two are independent normal vectors with mean 0 (ε′ depends only on x0 and ε1, …, εt−1); their sum is again normal with mean 0 and the variances add up:

(1 − βt)(1 − ᾱt−1) + βt = 1 − (1 − βt) ᾱt−1 = 1 − ᾱt.

The sum therefore equals √1 − ᾱt ε for some ε ~ N(0, I) independent of x0. ∎

Example — our noise schedule in numbers

β grows linearly: βt = β1 + (βT − β1) · t − 1/T − 1, that is by βT − β1/T − 1 = 0.00101 per step.

tβtᾱt√ᾱt (signal)√1 − ᾱt (noise)
10.000100.99991.0000.010
20.001110.99880.9990.035
250.024320.73510.8570.515
370.036430.50450.7100.704
380.037440.48560.6970.717
500.049550.28300.5320.847
1000.100000.00560.0750.997

Let us check the second row by hand: ᾱ2 = (1 − β1)(1 − β2) = 0.9999 · 0.99889 = 0.99879. Signal and noise are equal (ᾱ = 0.5) between steps 37 and 38; at step 100 the original image has a weight of only 0.075 against 0.997 for the noise (colours as in the formula above).

In our case T = 100 and β grows linearly from 0.0001 to 0.1. The share of the original signal √ᾱt is 0.86 at step 25, 0.53 at step 50 and only 0.07 at step 100.

A three in the forward process (from the left t = 0, 10, 25, 50, 75, 100)

2. Reverse process: a network that guesses the noise

Definition — DDPM (denoising diffusion probabilistic model)

Let T, β1, …, βT and ᾱt be as in the forward process, let pdata be the data distribution on ℝd, and let εθ : ℝd × {1, …, T} → ℝd be a neural network with parameters θ that estimates the added noise from a noisy image and the step number. DDPM (denoising diffusion probabilistic model; Jonathan Ho, Ajay Jain and Pieter Abbeel, 2020; the idea of Jascha Sohl-Dickstein et al., 2015) is a pair of procedures:

  1. Training: the parameters θ are chosen by minimizing the mean squared error L(θ) = Ex0, t, ε ‖ε − εθ(√ᾱt x0 + √1 − ᾱt ε, t)‖², where x0 ~ pdata, t is uniformly distributed on {1, …, T}, ε ~ N(0, I), and all three are independent.
  2. Sampling (input: the trained network εθ; output: a sample x0 ∈ ℝd):
    1. draw xT ~ N(0, I);
    2. for t = T, T − 1, …, 1 draw z ~ N(0, I) (for t = 1 set z = 0) and compute xt−1 = 1/√1 − βt (xt − βt/√1 − ᾱt · εθ(xt, t)) + √βt · z;
    3. after step t = 1 return x0.

The network thus learns by ordinary regression to estimate the noise, and during sampling it subtracts it step by step.

In plain words

The network gets a blurry, noisy photo and the question “how much noise is in it and what does it look like?”. If it can estimate the noise, it can also subtract a little of it. We repeat this a hundred times, each time with a bit of fresh randomness — and an image emerges from the noise. Training is ordinary regression: no opponent as in a GAN, no compromises as in a VAE.

L(θ) = Ex0, t, ε ‖ε − εθ(√ᾱt x0 + √1 − ᾱt ε, t)‖²

ε
the noise we added to the image ourselves — the correct answer, which is why we know it without labelling any data
εθ(…, t)
the network’s estimate of the noise; it also gets the step number t, because at t = 2 it has little noise to find and at t = 100 xt is almost pure noise
√ᾱt x0 + √1 − ᾱt ε
the noisy image xt produced in one jump — thanks to the closed formula it is enough to draw t and ε for each example; nothing is simulated step by step
The expectation is over images from the data, a uniformly drawn step t ∈ {1, …, T} and noise ε ~ N(0, I); in practice an average over the batch. Ho et al. obtained this simple loss by simplifying the variational lower bound (the one a VAE also optimises) — they dropped the weights of the individual steps.

xt−1 = 1/√1 − βt · (xt − βt/√1 − ᾱt · εθ(xt, t)) + √βt · z

xt
the current noisy state — we start from pure noise xT ~ N(0, I)
εθ(xt, t)
the estimate of the noise that is in xt
βt/√1 − ᾱt
how big a piece of the estimated noise to subtract — only the share belonging to one step (βt), rescaled to the total noise level √1 − ᾱt; in our case 0.01 at step 1 and 0.10 at step 100
1/√1 − βt
undoes the shrinking from the forward step (there it was multiplied by √1 − βt); at step 100 it is 1.054
√βt · z
fresh noise z ~ N(0, I) (in the last step z = 0) — the reverse process is random like the forward one; without it the samples would be pulled towards average images. The size √βt is one of the two choices Ho et al. tried
The bracket multiplied by the first factor is an estimate of the mean of an image one step less noisy; then as much randomness is added to it as belongs to the step.
# training (DDPM)
repeat 16,000×:
    for each of the 64 examples in the batch:
        x₀ = random example from the data
        t  = random from 1 … 100
        ε  = vector of random numbers from N(0, 1)
        x_t = √ᾱ_t · x₀ + √(1 − ᾱ_t) · ε
        error = ‖ε − ε_θ(x_t, code(t))‖² / dimension
    Adam step on the mean error

# sampling (DDPM, 100 steps)
x = vector from N(0, 1)
for t = 100, 99, …, 1:
    ε̂ = ε_θ(x, code(t))
    z = vector from N(0, 1), zero at step t = 1
    x = (x − β_t / √(1 − ᾱ_t) · ε̂) / √(1 − β_t) + √β_t · z
return x
Definition — sinusoidal time encoding

Let T ≥ 1 be the number of steps and t ∈ {1, …, T}. The sinusoidal time encoding is the vector code(t) ∈ [−1, 1]8,

code(t) = (sin πt/T, cos πt/T, sin 2πt/T, cos 2πt/T, sin 4πt/T, cos 4πt/T, sin 8πt/T, cos 8πt/T),

i.e. pairs (sin fπt/T, cos fπt/T) for the frequencies f = 1, 2, 4, 8. Instead of the number t the network gets these eight numbers. The slow frequencies say “roughly where in the process we are”, the fast ones tell neighbouring steps apart; all the numbers lie between −1 and 1, so no input outweighs the others.

For t = 25 (a quarter of the process, angles π/4, π/2, π and 2π): code(25) = (0.707, 0.707, 1.000, 0.000, 0.000, −1.000, 0.000, 1.000).

Our network is the ordinary multilayer network from the part on GANs: the input is xt plus eight numbers of the sinusoidal encoding of time t (like the positional encoding in a transformer), the output is the estimate of the noise. On the circle it has two hidden layers of 64 neurons, on the digits of 128. It is trained with Adam for 16,000 steps.

3. Eight clusters without collapse

The same data as in the previous part: eight clusters on a circle. This is what 400 samples look like during the reverse process:

t = 100 t = 50 t = 25 t = 10 t = 0 coordinate x₁ (no unit) x₂ (no unit)
Reverse process: from noise (t = 100) to data (t = 0)

Even at step 25 the points are scattered almost uniformly (quality 3%), at step 10 the circle starts to show, and in the last ten steps the points gather into clusters. At the end all 8 clusters are covered and each got 28 to 50 of the 500 samples. The quality (share within 3σ of a centre) is 63%.

The difference from a GAN is not in quality but in reliability. The GAN covered all eight clusters in only one of four runs; diffusion did it in the single run we tried (one run is no proof, but it agrees with what is known about diffusion). The diffusion loss is not a game but a regression, and the network is penalised for every data sample — it cannot “give up” on any cluster.

4. How many steps are needed

Definition — DDIM

Let εθ be the trained DDPM network, ᾱt as in the forward process and ᾱ0 = 1. For times 0 ≤ s < t ≤ T and a state xt ∈ ℝd we define the estimate of the clean image and the DDIM step from time t to time s:

x̂0 = xt − √1 − ᾱt εθ(xt, t)/√ᾱt,   xs = √ᾱs x̂0 + √1 − ᾱs εθ(xt, t).

DDIM sampling with n steps (Jiaming Song, Chenlin Meng and Stefano Ermon, 2020):

  1. choose times T = tn > tn−1 > … > t0 = 0 (in our case evenly spaced);
  2. draw xT ~ N(0, I);
  3. for i = n, n − 1, …, 1 perform the DDIM step from time ti to time ti−1;
  4. return x0 (in the last step ᾱ0 = 1, so x0 = x̂0).

DDIM thus uses the same trained network as DDPM, but after choosing xT it draws nothing more (it is deterministic) and it can jump in arbitrarily large steps.

In plain words

Instead of a hundred tiny steps we take twenty bigger ones. In each, the network asks “what would the finished image probably look like?” and jumps part of the way accordingly. When the jumps are too big, the network misses.

xs = √ᾱs · x̂0 + √1 − ᾱs · εθ(xt, t),   x̂0 = xt − √1 − ᾱt εθ/√ᾱt

xt
the state we are in right now
εθ
the noise estimate — the same network as for DDPM, nothing is retrained
x̂0
the estimate of the finished image: the closed formula of the forward process solved for x0; it divides by √ᾱt, which is small at the start, so this is where the estimate is least accurate
√ᾱs
how much of the image there should be at the target time s — the same factor as in the forward process, just for a different time
√1 − ᾱs
how much noise should remain; DDIM uses the same estimated noise and draws no new one — that is why it is deterministic
A step is “denoise completely, then add noise again up to the level of time s”. That is why DDIM can jump arbitrarily far.
Example — why two steps are not enough (simplified)

DDIM with n steps goes through evenly spaced times: with 5 steps 100 → 80 → 60 → 40 → 20 → 0, with 2 steps 100 → 50 → 0. The first jump is always from t = 100, where √ᾱ100 = 0.075.

If the network’s noise estimate is off by δ, the estimate x̂0 is off by δ · √1 − ᾱ100/√ᾱ100 = 13.3 δ — the error is amplified more than tenfold. After substituting into the jump to time s, the new state differs from the correct one by δ · (√1 − ᾱs − √ᾱs · 13.3) (colours as in the DDIM formula above):

  • 5 steps, jump 100 → 80: factor −1.59,
  • 2 steps, jump 100 → 50: factor −6.23,

With a small jump the bad estimate x̂0 is mixed in with a small weight √ᾱ80 = 0.19 and the following steps correct it. With a jump to the middle of the process it enters with weight √ᾱ50 = 0.53 and only a single step remains. The argument follows only one error in one step and assumes the network is otherwise accurate; but it shows why the first, noisiest steps are the most sensitive.

0%25%50%75%100% 63 · 8/8 100 79 · 8/8 50 77 · 8/8 20 70 · 8/8 10 60 · 8/8 5 0 · 0/8 2 sampling steps (count) quality (% of samples within 3σ)
Quality and coverage on the circle by number of sampling steps DDPM (100 steps) DDIM best GAN run

DDIM with 50 steps reaches a quality of 79% — more than DDPM with 100 steps (63%), most likely because it adds no random noise that our imprecise network fails to remove in the last steps. With 10 steps it is still 70%, with 5 steps 60%. With 2 steps sampling falls apart (0%). The number of steps is the main cost of diffusion: each step is one pass through the network, whereas a GAN generates an image in a single pass.

5. Digits: comparing three generators

On 8 × 8 digits (pixels rescaled to the range −1 to 1) the network is trained for 16,000 steps.

diffusion GAN, seed 2 VAE
The first ten samples
modeldistance to the nearest digitleast represented digit (of 200)digits with fewer than 10 samples
VAE (k = 8)0.054100
GAN, seed 10.04933
GAN, seed 20.04206
diffusion (100 steps)0.063120

Here we have to be honest: our diffusion has a distance of 0.063, worse than the VAE (0.054) and the GAN (0.042). Residual noise is visible in the images. But the digits are evenly represented — the least frequent digit has 12 samples, while the GAN with seed 2 misses two digits entirely.

Why is the sharpness worse? A small multilayer network has to denoise images at all hundred noise levels at once, and that is a harder task than the single mapping noise → image of a GAN. In return it improves steadily with compute: the distance of the samples fell during training 0.096 (step 4,000), 0.082 (step 8,000), 0.068 (step 12,000), 0.063 (step 16,000) (a quick estimate from 100 DDIM samples with 20 steps). No collapse and no swings. Real diffusion models use convolutional U-Net networks or transformers with hundreds of millions of parameters and train for weeks — and then they win on sharpness too.

6. From digits to images from text

  • Conditioning. The network εθ(xt, t, c) additionally gets a description c — a text embedding from a language model, attached through attention (the part on attention). It is trained on billions of image–caption pairs.
  • Classifier-free guidance (Jonathan Ho and Tim Salimans, 2021). The network is trained both with and without the description, and during sampling the estimate is shifted in the direction “with description minus without description”, amplified by a coefficient. The image then matches the prompt better at the cost of less variety.
  • Latent diffusion. Diffusion runs in the small latent space of an autoencoder, not in pixels — see the part on VAEs. Thanks to that, Stable Diffusion (2022) runs even on a gaming graphics card.
  • Fewer steps. Better samplers and distillation (a network learns to imitate many steps in one or a few) have cut generation from hundreds of steps to a handful.

ε̃ = εθ(xt, t, ∅) + w · (εθ(xt, t, c) − εθ(xt, t, ∅))

εθ(…, ∅)
the noise estimate without a description (empty condition ∅) — “what any image looks like”; the network learns it because during training the description is occasionally dropped at random
εθ(…, c) − εθ(…, ∅)
how much the estimate changes when the network knows the description — the direction in which the description pulls the image
w
guidance strength: w = 0 ignores the description, w = 1 is an ordinary conditional model, w > 1 exaggerates the influence of the description — the image matches the prompt better, but the samples are more alike
During sampling ε̃ is used in place of εθ; the cost is two passes through the network per step. Ho and Salimans write the same formula as (1 + w′) εθ(…, c) − w′ εθ(…, ∅), that is w = 1 + w′. The diffusers library uses a default of w = 7.5 for Stable Diffusion.

7. Summary

  • The forward process gradually adds noise; you can jump to any step with the formula √ᾱ x₀ + √1 − ᾱ ε.
  • The network learns to estimate the added noise — ordinary regression. Generation runs backwards from pure noise.
  • On eight clusters diffusion covered all of them in a single run (DDIM with 50 steps 79%); the GAN only in one of four runs.
  • Fewer steps (DDIM) speed up sampling; with 10 steps the quality is 70%, with 2 steps it falls apart.
  • On digits diffusion covers all of them, but with our small network it lags behind in sharpness; it improves steadily with compute, though.

Want to get hands-on? Have an AI agent train a small DDPM on two-dimensional data (say, points in the shape of your initials) and plot how the points assemble into letters during the reverse process.

Do you see it differently? Write to support@coden-app.com.

An Agent on your computer, driven from your phone