← All posts

Generative9 min read

Demystifying diffusion models

How diffusion models like DALL-E and Midjourney work, from the forward noising process to the simple loss that trains them (DDPM).

Parth Shastri

If you haven’t been living under a rock you might have seen the phenomenal capabilities of the recent DALL.E model released by OpenAI. This model can generate new images based on a simple text prompt, What a time to be alive ! DALL.E 2 can make high-quality novel images from just a text input and just look how creative can it get !! Many other similar models have been released in the recent past including Midjourney, Stable Diffusion etc.

A man raises his voice to the clouds in a flood, as generated by Midjourney AI.
"A man raises his voice to the clouds in a flood" as generated by Midjourney AI.

You must be wondering how exactly does DALL.E or Midjourney do it? The answer is “Diffusion Models” !!!. Let’s take a deep-dive into, how exactly do these diffusion models work.

In this blog, we will study the basic principles that drive the diffusion models. There are a lot of new architectures in the diffusion models family, which can be a little overwhelming. We will focus on the most initial and prominent of them, Denoising Diffusion Probabilistic Models i.e. DDPMs by Sohl-Dickstein et al. and then proposed by Ho et al.

The underlying idea: the diffusion process#

The basic idea behind Diffusion models is simple they take an input image x0x_0 from a data distribution and gradually add gaussian noise to it over TT timesteps. This is inspired from the Diffusion Process in thermodynamics, which is defined as the “spreading” of particles from regions of higher concentrations to lower concentration. This process of perturbing the input image with gaussian noise over TT timesteps is called the forward process.

After the forward process a neural network is trained to recover the original image by reversing the noising process. This is called the reverse process or the sampling of the generative model. Using the learned information we can generate novel samples from the trained diffusion model.

Let us look into the underlying maths to make things crystal clear!

Forward process#

Diffusion models are viewed similar to latent variable models similar to Variational Autoencoders (VAEs) which have a continuous hidden latent space.

Diffusion models are modelled using a Markov chain having TT steps. In Markov chains, each step only depends on the previous step. These Markov chain form the underlying assumption in the Diffusion models.

We take a datapoint x0x_0 from a data distribution q(x)q(x), we can define a forward diffusion process by adding noise. At each step t<Tt < T we add gaussian noise with some variance βt\beta_t to the datapoint xt−1x_{t-1}, this produces a new datapoint xtx_t with a distribution q(xt∣xt−1)q(x_t | x_{t-1}). This variance follows some fixed schedule (cosine or linear or any other). In the original DDPM paper they choose βt\beta_t to follow a linear schedule ranging from 0.0001 to 0.02.1 This forward diffusion process can be formulated by:

q(xt∣xt−1):=N(xt;1−βt xt−1,βtI)\boxed{q(x_t|x_{t-1}) := \mathcal{N}(x_t; \sqrt{1-\beta_t}\,x_{t-1}, \beta_t I)}

Note that q(xt∣xt−1)q(x_t | x_{t-1}) is still a normal distribution. Thus we can go from x0x_0 to xTx_T in a tractable manner, this is given by the equation:

q(x1:T∣x0)=∏t=1Tq(xt∣xt−1)\boxed{q(x_{1:T}|x_0) = \prod_{t=1}^T q(x_t|x_{t-1})}

This is also known as the posterior probability. Here q(x1:T)q(x_{1:T}) means we apply the same process from time step 1 to TT. But this means that in order to get xtx_t at a certain time step t=200 we will have to apply the forward diffusion process 200 times to the input x0x_0. Well this can be lets say… computationally taxing! But to overcome this problem we have a cool trick up our sleeve!!

Diagram of the forward diffusion process.
The forward diffusion process (image source: Ho et al.)

The reparameterization trick#

Instead of applying the forward process many times repeatedly to get the desired datapoint xtx_t at t<Tt < T, we can apply this simple reparameterization to get the desired output by simply precomputing the variances and certain parameters.

Let us define αt=1−βt\alpha_t = 1-\beta_t and αˉt=∏i=1tαi\bar{\alpha}_t = \prod_{i=1}^{t} \alpha_i where ϵ∼N(0,I)\epsilon \sim \mathcal{N}(0, I). Using this we can sample xtx_t at any tt, using this trick:

xt=αˉt x0+1−αˉt ϵ\boxed{x_t = \sqrt{\bar{\alpha}_t}\,x_0 + \sqrt{1-\bar{\alpha}_t}\,\epsilon}
Why does this work? (the two-line derivation)

Write one step as xt=αt xt−1+1−αt ϵt−1x_t = \sqrt{\alpha_t}\,x_{t-1} + \sqrt{1-\alpha_t}\,\epsilon_{t-1} and plug in the same expression for xt−1x_{t-1}:

xt=αtαt−1 xt−2+αt(1−αt−1) ϵt−2+1−αt ϵt−1x_t = \sqrt{\alpha_t \alpha_{t-1}}\,x_{t-2} + \sqrt{\alpha_t(1-\alpha_{t-1})}\,\epsilon_{t-2} + \sqrt{1-\alpha_t}\,\epsilon_{t-1}

The two noise terms are independent Gaussians, so their sum is one Gaussian with variance αt(1−αt−1)+(1−αt)=1−αtαt−1\alpha_t(1-\alpha_{t-1}) + (1-\alpha_t) = 1 - \alpha_t\alpha_{t-1}. Keep unrolling down to x0x_0 and the product of all the α\alphas is αˉt\bar{\alpha}_t.

Thus to put it more mathematically we can produce a sample xtx_t using this distribution:

q(xt∣x0)=N(xt;αˉt x0,(1−αˉt)I)\boxed{q(x_t|x_0) = \mathcal{N}(x_t; \sqrt{\bar{\alpha}_t}\,x_0, (1-\bar{\alpha}_t)I)}

As discussed earlier we can precompute βt\beta_t, αt\alpha_t, and αˉt\bar{\alpha}_t at all timesteps. Thus we can sample xtx_t arbitrarily at any timestep, we will use this “nice” property later in defining the training objective for our model. Try it below. Every frame is one evaluation of the equation above, not 200 steps of noise.

Interactive
xt
1.5005001000√ᾱt signal√(1−ᾱt) noise
βt0
ᾱt1
signal √ᾱt1
noise √(1−ᾱt)0
The forward process with the linear DDPM schedule. Drag t to add noise, step by step. The plot shows how much of the image (signal) and how much noise is left.

Reverse diffusion#

When T→∞T \to \infty, we approach an isotropic gaussian distribution, i.e with a diagonal variance matrix. Now if we learn the reverse distribution q(xt−1∣xt)q(x_{t-1} | x_t) we can iteratively sample a novel datapoint with a starting prior distribution xTx_T from N(0,I)\mathcal{N}(0, I).

Let us see how can we model the reverse distribution.

Diagram of the reverse diffusion process learnt by a neural network.
The reverse diffusion process, learnt by a neural network (image source: Ho et al.)

Approximating reverse diffusion using a neural network#

The reverse distribution q(xt−1∣xt)q(x_{t-1} | x_t) is practically intractable as calculating it involves the whole data distribution. Instead we can approximate this by a parameterized model pθp_\theta, this can be a neural network. For a small enough βt\beta_t we can choose pθp_\theta to be a gaussian and parameterize its mean and variance.

pθ(xt−1∣xt):=N(xt−1;μθ(xt,t),Σθ(xt,t))\boxed{p_\theta(x_{t-1}|x_t) := \mathcal{N}(x_{t-1}; \mu_\theta(x_t, t), \Sigma_\theta(x_t, t))}

If we apply the reverse formula and additionally condition on timestep t we can make the model to predict the mean μθ\mu_\theta and the variance Σθ\Sigma_\theta at each timestep.

Training the diffusion model#

As mentioned before diffusion models are similar to variational autoencoders (VAEs), now looking at the formulation of qq and pp we can clearly see the similarities. So similar to VAEs we can train it by maximizing the ELBO (Evidence Lower Bound) or the negative log-likelihood of the training data. We can write the ELBO as follows:

LVLB=Eq[LT⏟constant+∑t>1DKL(q(xt−1∣xt,x0) ∥ pθ(xt−1∣xt))⏟Lt−1−log⁡pθ(x0∣x1)⏟L0]\boxed{L_{VLB} = \mathbb{E}_q \Big[ \underbrace{L_T}_{\text{constant}} + \sum_{t>1} \underbrace{D_{KL}(q(x_{t-1}|x_t, x_0) \,\|\, p_\theta(x_{t-1}|x_t))}_{L_{t-1}} - \underbrace{\log p_\theta(x_0|x_1)}_{L_0} \Big]}

Breaking down and analysing the objective:

  • The LTL_T term can be ignored as it is constant, q has no learnable parameters and xTx_T is gaussian noise.
  • The L0L_0 term can be modelled using a different decoder as a reconstruction term similar to the term in the ELBO of the VAE. Ho et al. modelled this term using a separate decoder.
  • The LtL_t term formulates the desired difference between the noising steps qq and denoising steps pp.

It is evident that the objective function boils down to maximizing the LtL_t term in the ELBO.

Using Bayes rule we can derive:

q(xt−1∣xt,x0)=q(xt∣xt−1,x0)q(xt−1∣x0)q(xt∣x0)\boxed{q(x_{t-1}|x_t, x_0) = q(x_t|x_{t-1}, x_0) \frac{q(x_{t-1}|x_0)}{q(x_t|x_0)}}

The mean μ~t(xt,x0)\tilde{\mu}_t(x_t, x_0) can be written as:

μ~t(xt,x0)=αˉt−1βt1−αˉtx0+αt(1−αˉt−1)1−αˉtxt\boxed{\tilde{\mu}_t(x_t, x_0) = \frac{\sqrt{\bar{\alpha}_{t-1}}\beta_t}{1-\bar{\alpha}_t}x_0 + \frac{\sqrt{\alpha_t}(1-\bar{\alpha}_{t-1})}{1-\bar{\alpha}_t}x_t}

Further we can make the μ\mu in the above equation dependent on only xtx_t by using the “nice” property we defined above:

μ~t=1αt(xt−βt1−αˉtϵt)\boxed{\tilde{\mu}_t = \frac{1}{\sqrt{\alpha_t}} \left( x_t - \frac{\beta_t}{\sqrt{1-\bar{\alpha}_t}}\epsilon_t \right)}

We have to make our network predict this μt\mu_t via a parameterization of μ\mu on θ\theta. Now the objective function in ELBO, LtL_t can be further simplified as:

Lt=Ex0,ϵ[βt22σt2αt(1−αˉt)∥ϵ−ϵθ(xt,t)∥2]\boxed{L_t = \mathbb{E}_{x_0, \epsilon} \left[ \frac{\beta_t^2}{2\sigma_t^2 \alpha_t(1-\bar{\alpha}_t)} \|\epsilon - \epsilon_\theta(x_t, t)\|^2 \right]}

This loss function shows us that the model will learn to predict the noise ϵ\epsilon at each timestep tt, instead of predicting the mean at each timestep.

Ho et al. further simplified this objective by ignoring the weighing term in the above equation. They suggested this loss was enough to make the model learn its generative properties.

Ltsimple=Et,x0,ϵ[∥ϵ−ϵθ(αˉtx0+1−αˉtϵ,t)∥2]\boxed{L_t^{\text{simple}} = \mathbb{E}_{t, x_0, \epsilon} \left[ \| \epsilon - \epsilon_\theta(\sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon, t) \|^2 \right]}

In code, one training step is just a few lines:

t = torch.randint(0, T, (batch_size,))  # a timestep per image
eps = torch.randn_like(x0)               # the noise we add
xt = a_bar[t].sqrt() * x0 + (1 - a_bar[t]).sqrt() * eps
loss = F.mse_loss(model(xt, t), eps)     # predict the noise

The detailed proofs to all the above equations are clearly explained in this great, must read article from Lilian Weng. Ho et al. also proposed to keep the posterior variance fixed instead of making the network predict that as well. This was further improved in Nichol et al.

The algorithms to train and sample the model as illustrated in Ho et al.:

The training and sampling algorithms from Ho et al.
The algorithms to train and sample the model, as in Ho et al.

Architecture#

The neural network used to predict the noise and essentially model this diffusion process is an architecture very similar almost same to UNet, proposed by Ronneberger et al. in 2015 primarily for medical image segmentation. It is a symmetrical architecture with the inputs and outputs having the same dimensions. The networks also employ skip connections at each level. In the original implementation of DDPMs, UNet architecture with wide ResNet blocks, group normalization and self attention blocks is used.

The original UNet architecture diagram.
The original UNet architecture (image source: Ronneberger et al.)

Conclusion#

We successfully studied the working of the famous diffusion models in its original form, further more this architecture was iteratively improved conditioning it on additional information or using this architecture along with classifier-guidance, textual information. But the core principle of this method of modelling remains surprisingly same. These models have proven to generate great quality images with just a text prompt in any style, environment that we provide. Further improvements will definitely be seen in this promising method in different fields apart from just image generation!!

A grid of images generated by DDPM.
Images generated by DDPM (image source: Ho et al.)

Further reading#

You can check out these excellent blogposts and papers to learn about diffusion and related research.

Footnotes#

  1. Nichol & Dhariwal later showed that a cosine schedule works better. With the linear one, the last steps are already close to pure noise, so they teach the model very little. ↩