I recently spent some time reading about the algorithms behind Stable Diffusion and similar image generation models. They have been linked with an interesting 40-years-old result on diffusion processes1. In short, this result states that there exists an explicit path from an initial probability distribution to a random noise (a normal distribution), and that this path can be reversed.
One application of this concept is sampling : we can draw a sample from a random noise and use the backward diffusion to obtain a sample from . In the context of computer vision, this distribution would be multivariate with dimension , where and are respectively the width and height of a RGB image in pixels. The initial release of Stable Diffusion was using .
In this document, I'll delve into the mechanics of reverse-time diffusions in dimension 1 (one pixel, one color channel) and derive equations to build a better understanding and intuition.
Forward diffusion#
The transition from an image to noise is accomplished by repetitively introducing random perturbations to our initial distribution. This can be done in a continuous time setting using an Ornstein-Uhlenbeck stochastic process. The Ornstein-Uhlenbeck process is defined by the following stochastic differential equation (SDE) :
where is a Brownian motion defined on some probability space . The first term is forcing the mean to converge to , while the second term is adding noise.
We can show using Itô's formula with that
This means that the marginals of conditional to follow a normal distribution
We note that the mean tends to , and the variance to . We can then reparameterize the SDE such that the distribution of converges to a standard normal distribution (with unit variance) :
and :
At this point we can notice a few things :
- Impact of the noise level : the noise (or volatility) parameter has a similar impact on the diffusion as the time . Increasing will lead to noise being added faster
- Distribution of : the distribution of given is normal, but the unconditional distribution is not in general. In the case where is itself normal, each step of the diffusion will generate a new normal distribution as we sum independant Gaussian variables together
Backward diffusion#
We follow the notations of Haussmann and Pardoux2 and define for . It can be shown12 that the reverse process follows the SDE
where
is the score function associated with distribution (derivative of its logarithm) and another Brownian motion. In practice, the score is intractable and needs to be approximated.
Score matching#
One method is score matching34. The score function is approximated using a parametrical model :
The constant is only a convention. We can simplify this expression by using an integration by part trick5. First we develop :
Since the last term does not depend on , minimizing the rest will suffice. Thus our new cost function reads, after integration by part :
This removes the dependency to an explicit formulation of the distribution . Instead we can rely on Monte-Carlo estimation for instance to compute the cost function.
Note 1 : is the best constant model, but is beaten by linear models with negative slope. Indeed, the second term pushes the derivative of the score to be negative. This is consistent with the Gaussian case.
Note 2 : In dimension , the formulation becomes5 :
Neural networks are used in practice, where represents the weights of the network. The U-Net6 architecture for instance is used to estimate the score function in computer vision7.
Gaussian mixtures#
Another method would be to actually approximate the distribution with a Gaussian mixture. In this case, the score is explicit, which is a nice property. The drawback of this method is probably that the space of Gaussian mixtures is restrictive in some sense.
Centered Gaussian case#
When the initial distribution is Gaussian, the process stays Gaussian, and we get a closed form for the distribution . We can then derive explicitely the reverse process.
Note : going from a normal distribution to a standard normal distribution can be done in one step : . The following derivation is for learning purpose only!
When we get
In this case the score function is
and the reverse SDE reads
where
Some interesting cases :
- yields the forward SDE, which is expected since the initial and target distributions are the same in this case
- means the mean reversion speed is always positive, while when , it can change sign during diffusion
- leads to a SDE that starts as the forward SDE and tends to a singularity as approaches 1. Indeed, the SDE cannot converge to a constant, as an independent noise is added at each step
The backward diffusion is an Ornstein-Uhlenbeck process with time dependent parameters. Using Itô's formula again with , we get :
By rewriting as
one can recognize the derivative of a logarithm and find that
which tends to as . This means that the variance contribution of the firm term is zero. The variance of the second term is more involved but leads to :
where , which tends to
as . Finally, we notice that this expression tends to if is sufficiently high. Similarly, we could have used for a fixed , and let tend to infinity. As previously mentioned, noise level and time play similar roles.
Something interesting to note is that for a given level of convergence of the variance in the forward SDE, that is , the variance of the reverse process will either converge faster or slower relative to depending on the sign of . When the initial distribution has more variance than the random noise, the backward SDE will converge slower and vice versa.
PyTorch implementation#
We consider a 1-dimensional distribution , and use the previous diffusion to generate a random noise. In order to target an end variance of , we let and such that . We use the Euler scheme to discretize the diffusion.
import math
import torch
NB_PATHS = 50
NB_TIMESTEPS = 100
T = 1.0
EPS = 0.001
def forward_diffusion_step(
X: torch.Tensor,
Z: torch.Tensor,
sigma: float,
dt: float,
) -> torch.Tensor:
return X - 0.5 * sigma * sigma * X * dt + sigma * Z * math.sqrt(dt)
dt = T / NB_TIMESTEPS
sigma = math.sqrt(-math.log(EPS) / T) # alpha_T = eps
X0 = torch.normal(0, 1, size=(NB_PATHS,))
X = torch.zeros((NB_TIMESTEPS, NB_PATHS))
Z = torch.normal(0, 1, size=(NB_TIMESTEPS - 1, NB_PATHS))
X[0] = X0
for i in range(NB_TIMESTEPS - 1):
X[i+1] = forward_diffusion_step(X[i], Z[i], sigma, dt)Here is a forward diffusion from to :

We write backward diffusion with score function in the normal case
def backward_diffusion_step(
X: torch.Tensor,
Z: torch.Tensor,
sigma: float,
mu0: float,
sigma0: float,
dt: float,
t: float,
) -> torch.Tensor:
sigma2 = sigma * sigma
alpha = math.exp(-sigma2 * (1 - t))
score = -(X - mu0 * math.sqrt(alpha)) / (sigma0 * sigma0 * alpha + 1 - alpha)
return (
X
+ 0.5 * sigma2 * X * dt
+ score * sigma2 * dt
+ sigma * Z * math.sqrt(dt)
)
Finally, we can re-write the score used in the backward SDE when the initial distribution is a Gaussian mixture. Indeed, Gaussian mixtures are stable by addition with a normal variable, and we can proceed similarly to the previous case.
def backward_diffusion_step(
X: torch.Tensor,
Z: torch.Tensor,
sigma: float,
params0: List[Tuple[float, float]],
t: float,
dt: float,
) -> torch.Tensor:
sigma2 = sigma * sigma
alpha = math.exp(-sigma2 * (1 - t))
n = len(params0)
score, divisor = 0, 0
for (mu0, sigma0) in params0:
mut = mu0 * math.sqrt(alpha)
sigmat2 = sigma0 * sigma0 * alpha + 1 - alpha
denominator = math.sqrt(2 * math.pi * sigmat2)
pit = np.exp(-(X - mut) * (X - mut) / (2 * sigmat2)) / denominator
divisor += 1 / n * pit
score -= 1 / n * pit * (X - mut) / sigmat2
score /= divisor
return (
X
+ 0.5 * sigma2 * X * dt
+ score * sigma2 * dt
+ sigma * Z * math.sqrt(dt)
)
That's all for today! ✨
Footnotes#
-
ANDERSON, Brian DO. Reverse-time diffusion equation models. Stochastic Processes and their Applications, 1982, vol. 12, no 3, p. 313-326. ↩ ↩2
-
HAUSSMANN, Ulrich G. et PARDOUX, Etienne. Time reversal of diffusions. The Annals of Probability, 1986, p. 1188-1205. ↩ ↩2
-
SONG, Yang, SOHL-DICKSTEIN, Jascha, KINGMA, Diederik P., et al. Score-based generative modeling through stochastic differential equations. arXiv preprint arXiv:2011.13456, 2020. ↩
-
WEBER, Romann M. The Score-Difference Flow for Implicit Generative Modeling. arXiv preprint arXiv:2304.12906, 2023. ↩
-
HYVÄRINEN, Aapo et DAYAN, Peter. Estimation of non-normalized statistical models by score matching. Journal of Machine Learning Research, 2005, vol. 6, no 4. ↩ ↩2
-
RONNEBERGER, Olaf, FISCHER, Philipp, et BROX, Thomas. U-net: Convolutional networks for biomedical image segmentation. In : Medical Image Computing and Computer-Assisted Intervention–MICCAI 2015: 18th International Conference, Munich, Germany, October 5-9, 2015, Proceedings, Part III 18. Springer International Publishing, 2015. p. 234-241. ↩
-
KARRAS, Tero, AITTALA, Miika, AILA, Timo, et al. Elucidating the design space of diffusion-based generative models. Advances in Neural Information Processing Systems, 2022, vol. 35, p. 26565-26577. ↩