logo Mr.Robota 具身智能学习手册 中文
Chapter 17: BWM: Tongji Boundless Team's Action Condition World Model

Detailed Explanation and Code Implementation of DDPM

This tutorial starts from the limitations of VAE, explains why DDPM changes “single-step generation” to “multi-step denoising”, and then derives from forward diffusion, reverse generation, ELBO, true posterior, and noise prediction objectives to the final training and sampling formulas.


0. Learning Path

The main process of DDPM can be compressed into five steps:

  1. VAE generates images in one step using latent variables zz, but the assumptions of compression and reconstruction often lead to detail loss.
  2. DDPM does not immediately generate a complete image; instead, it defines a fixed noise addition process to gradually transform the real image into Gaussian noise.
  3. During generation, it starts from Gaussian noise, and the neural network is trained to remove a little bit of noise at each step.
  4. The derivation strictly comes from maximum likelihood and ELBO, but it can ultimately be simplified into a straightforward supervision task: predicting the noise added to the image.
  5. The key to implementation is not the complex loss function, but properly handling the noise schedule, time steps, U-Net input/output, sampling formulas, and numerical ranges.

DDPM first gradually adds noise to the image until it is close to pure noise, and then trains the model to gradually remove the noise in reverse. It breaks the difficult “one-step image generation” process into many simple “predict and remove noise” tasks.

The most common training objective for DDPM is:

Lsimple(θ)=Et,x0,ϵ[∥ϵ−ϵθ(xt,t)∥2] \mathcal{L}_{\mathrm{simple}}(\theta) {=} \mathbb{E}_{t,x_0,\epsilon} \left[ \left\| \epsilon-\epsilon_\theta(x_t,t) \right\|^2 \right]

Among them:

xt=αˉtx0+1−αˉtϵ,ϵ∼N(0,I) x_t {=} \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon, \quad \epsilon\sim\mathcal{N}(0,I)

In other words, during training, we know the true noise ϵ\epsilon, and the network only needs to predict it based on the noisy image xtx_t and the time step tt.


1. Why DDPM is needed

1.1 Several Issues with VAE

VAE is important, but it also has several common issues.

First, VAE typically compresses images into a low-dimensional latent variable:

x→z→x^ x\rightarrow z\rightarrow \hat{x}

If the capacity of zz is insufficient, details will be lost, and the generated image will be blurry.

Second, the reconstruction terms of VAE often correspond to pixel-level likelihoods. Taking Gaussian likelihood as an example, maximizing log⁡pθ(x∣z)\log p_\theta(x|z) is often equivalent to minimizing mean squared error. Mean squared error encourages the model to output a “average answer,” and an average image usually looks dull.

Third, VAE generates xx directly from zz in one step:

z→x z\rightarrow x

This requires the Decoder to generate from abstract semantics to all pixel details during a single forward propagation. For complex images, this mapping burden is quite high.

Therefore, the core contradiction of VAE can be understood as:

It wants to generate images using a structured, sampleable latent space, but this compression and one-step decoding process may lose high-frequency details.

1.2 How DDPM Changes Its Approach

DDPM no longer views generation as:

z→x z\rightarrow x

Instead, consider it as:

xT→xT−1→⋯→x1→x0 x_T\rightarrow x_{T-1}\rightarrow\cdots\rightarrow x_1\rightarrow x_0

Among them:

  • x0x_0 is a real and clean image;
  • x1,…,xTx_1,\dots,x_T is an image with noise added gradually;
  • xTx_T is close to standard Gaussian noise.

Sample first during generation:

xT∼N(0,I) x_T\sim\mathcal{N}(0,I)

Then, for each step, only a small task is done:

Predict the noise based on the current noisy image, and remove a little bit of it.

The benefits of DDPM are:

  1. The training objective is stable, eliminating the need for adversarial training like that of GANs.
  2. Each task step is simple, requiring only local denoising learning.
  3. Generation occurs gradually in the image space, without forcing all details into a very small latent variable.
  4. U-Net can be used to preserve multi-scale spatial details.
  5. Mathematically, it is possible to derive a simple noise prediction MSE from ELBO.

2. Basic Settings of DDPM

DDPM has two processes:

  1. Forward diffusion process: Fixed rules, no learning required; gradually add noise to the real image.
  2. Reverse generation process: Requires learning; gradually remove noise to generate the image.

2.1 Forward Diffusion Process

The forward process starts from the real image x0x_0:

x0→x1→x2→⋯→xT x_0\rightarrow x_1\rightarrow x_2\rightarrow\cdots\rightarrow x_T

DDPM defines it as a Markov chain:

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

A Markov chain means: if xt−1x_{t-1} is already known, then when generating xtx_t, there is no need to directly refer to earlier states.

Each step of noise addition is defined as:

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

Here:

  • βt\beta_t is the noise intensity at step tt;
  • βtI\beta_t I indicates that the variance of independent Gaussian noise of βt\beta_t is added to each dimension;
  • 1−βtxt−1\sqrt{1-\beta_t}x_{t-1} means retaining a portion of the image signal from the previous moment.

General definition:

αt=1−βt \alpha_t=1-\beta_t

So:

q(xt∣xt−1)=N(xt;αtxt−1,(1−αt)I) \boxed{ q(x_t|x_{t-1}) {=} \mathcal{N} \left( x_t; \sqrt{\alpha_t}x_{t-1}, (1-\alpha_t)I \right) }

2.2 Writing the sampling formula from distribution

If:

x∼N(μ,σ2I) x\sim\mathcal{N}(\mu,\sigma^2I)

It can be written as:

x=μ+σϵ,ϵ∼N(0,I) x=\mu+\sigma\epsilon, \quad \epsilon\sim\mathcal{N}(0,I)

Apply this rule to the one-step forward distribution of DDPM:

xt∣xt−1∼N(αtxt−1,(1−αt)I) x_t|x_{t-1} \sim \mathcal{N} \left( \sqrt{\alpha_t}x_{t-1}, (1-\alpha_t)I \right)

Obtained:

xt=αtxt−1+1−αtϵt \boxed{ x_t {=} \sqrt{\alpha_t}x_{t-1} + \sqrt{1-\alpha_t}\epsilon_t }

Among them:

ϵt∼N(0,I) \epsilon_t\sim\mathcal{N}(0,I)

This is the step-plus-noise formula.

2.3 Why the coefficient uses a square root

Many beginners ask: Why is it 1−βt\sqrt{1-\beta_t}, rather than 1−βt1-\beta_t?

Assume that the variance of xt−1x_{t-1} is approximately II, and the variance of noise ϵt\epsilon_t is also II. If:

xt=1−βtxt−1+βtϵt x_t {=} \sqrt{1-\beta_t}x_{t-1} + \sqrt{\beta_t}\epsilon_t

Then the variance is approximately:

Var(xt)=(1−βt)I+βtI=I \mathrm{Var}(x_t) {=} (1-\beta_t)I+\beta_t I {=} I

In other words, after adding noise to each step, the overall scale does not suddenly increase or decrease.

If changed to:

xt=(1−βt)xt−1+βtϵt x_t=(1-\beta_t)x_{t-1}+\sqrt{\beta_t}\epsilon_t

Then the variance will become:

(1−βt)2I+βtI (1-\beta_t)^2I+\beta_t I

It is no longer exactly equal to II. Therefore, DDPM uses a square root coefficient to maintain a stable ratio between the variance of the signal and the noise.


3. From one-step noise addition to noise addition at any time

When training DDPM, it is very slow to add noise step by step from x0x_0 to xtx_t every time. Fortunately, the forward process has a closed-form formula, allowing us to directly obtain xtx_t at any time step from x0x_0.

The final formula is:

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

Among them:

αˉt=∏s=1tαs \bar{\alpha}_t {=} \prod_{s=1}^{t}\alpha_s

The corresponding sampling formula is:

xt=αˉtx0+1−αˉtϵ \boxed{ x_t {=} \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon }

Among them:

ϵ∼N(0,I) \epsilon\sim\mathcal{N}(0,I)

This formula is very important. It indicates that during training, a random time step tt can be selected, and then the corresponding noisy image xtx_t can be constructed in one go.

3.1 Two-step expansion to understand closed-form formulas

One-step noise addition is:

xt=αtxt−1+1−αtϵt x_t {=} \sqrt{\alpha_t}x_{t-1} + \sqrt{1-\alpha_t}\epsilon_t

Write down the previous moment again:

xt−1=αt−1xt−2+1−αt−1ϵt−1 x_{t-1} {=} \sqrt{\alpha_{t-1}}x_{t-2} + \sqrt{1-\alpha_{t-1}}\epsilon_{t-1}

Substitute the first equation:

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

Expand:

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

The last two items are linear combinations of independent Gaussian noise, so they are still Gaussian noise. Its variance is:

α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}

Therefore, it can be merged into a new standard Gaussian noise ϵ\epsilon:

xt=αtαt−1xt−2+1−αtαt−1ϵ x_t {=} \sqrt{\alpha_t\alpha_{t-1}}x_{t-2} + \sqrt{1-\alpha_t\alpha_{t-1}}\epsilon

Continuing the recursion to x0x_0, we get:

xt=αtαt−1⋯α1x0+1−αtαt−1⋯α1ϵ x_t {=} \sqrt{\alpha_t\alpha_{t-1}\cdots\alpha_1}x_0 + \sqrt{1-\alpha_t\alpha_{t-1}\cdots\alpha_1}\epsilon

Definition:

αˉt=αtαt−1⋯α1 \bar{\alpha}_t {=} \alpha_t\alpha_{t-1}\cdots\alpha_1

We get the formula at any given moment:

xt=αˉtx0+1−αˉtϵ x_t {=} \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon

3.2 Why xTx_T approaches standard Gaussian noise

Because:

αˉt=∏s=1tαs \bar{\alpha}_t {=} \prod_{s=1}^{t}\alpha_s

And:

αs=1−βs<1 \alpha_s=1-\beta_s<1

When tt is large enough:

αˉt→0 \bar{\alpha}_t\rightarrow 0

Insert:

xt=αˉtx0+1−αˉtϵ x_t {=} \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon

The first item will disappear:

αˉtx0→0 \sqrt{\bar{\alpha}_t}x_0\rightarrow 0

The second item will become:

1−αˉtϵ→ϵ \sqrt{1-\bar{\alpha}_t}\epsilon\rightarrow\epsilon

So:

xT≈ϵ∼N(0,I) x_T\approx\epsilon\sim\mathcal{N}(0,I)

This explains why generation can start from pure Gaussian noise.


4. Diffusion Process in Reverse

The forward process is artificially fixed and does not require learning. What actually needs to be learned is the reverse process:

xT→xT−1→⋯→x0 x_T\rightarrow x_{T-1}\rightarrow\cdots\rightarrow x_0

The model joint distribution is written as:

pθ(x0:T)=p(xT)∏t=1Tpθ(xt−1∣xt) p_\theta(x_{0:T}) {=} p(x_T) \prod_{t=1}^{T} p_\theta(x_{t-1}|x_t)

Among them:

p(xT)=N(0,I) p(x_T)=\mathcal{N}(0,I)

The reverse step is usually also set as a Gaussian distribution:

pθ(xt−1∣xt)=N(xt−1;μθ(xt,t),σt2I) \boxed{ p_\theta(x_{t-1}|x_t) {=} \mathcal{N} \left( x_{t-1}; \mu_\theta(x_t,t), \sigma_t^2I \right) }

Here:

  • μθ(xt,t)\mu_\theta(x_t,t) is the reverse mean predicted by the neural network;
  • σt2I\sigma_t^2I is the reverse variance, which is not learned in many basic implementations;
  • The network input is the noisy image xtx_t and the time step tt;
  • The network output can be designed to predict the mean, predict the original image x0x_0, or predict the noise ϵ\epsilon.

Intuitively, the reverse step is done as follows:

Given the current noise map xtx_t, guess where the slightly cleaner image xt−1x_{t-1} from the previous step should be located.


5. Training objective: From maximum likelihood to ELBO

As a generative model, DDPM aims to maximize the likelihood of real images:

log⁡pθ(x0) \log p_\theta(x_0)

But:

pθ(x0)=∫pθ(x0:T)dx1:T p_\theta(x_0) {=} \int p_\theta(x_{0:T})dx_{1:T}

This integration requires integrating all intermediate variables x1,…,xTx_1,\dots,x_T, and direct calculation is difficult.

Therefore, DDPM uses the variational lower bound. Minimize the negative ELBO during training:

L=Eq(x1:T∣x0)[−log⁡pθ(x0:T)q(x1:T∣x0)] \mathcal{L} {=} \mathbb{E}_{q(x_{1:T}|x_0)} \left[ {-} \log \frac{ p_\theta(x_{0:T}) }{ q(x_{1:T}|x_0) } \right]

Among them:

pθ(x0:T)=p(xT)∏t=1Tpθ(xt−1∣xt) p_\theta(x_{0:T}) {=} p(x_T) \prod_{t=1}^{T} p_\theta(x_{t-1}|x_t)
q(x1:T∣x0)=∏t=1Tq(xt∣xt−1) q(x_{1:T}|x_0) {=} \prod_{t=1}^{T} q(x_t|x_{t-1})

Detailed algebraic derivations are provided in Appendix A. The main text only retains the conclusions:

L=Eq[DKL(q(xT∣x0)∥p(xT))+∑t>1DKL(q(xt−1∣xt,x0)∥pθ(xt−1∣xt))−log⁡pθ(x0∣x1)] \boxed{ \mathcal{L} {=} \mathbb{E}_q \left[ D_{\mathrm{KL}} \left( q(x_T|x_0)\|p(x_T) \right) + \sum_{t>1} D_{\mathrm{KL}} \left( q(x_{t-1}|x_t,x_0) \| p_\theta(x_{t-1}|x_t) \right) {-} \log p_\theta(x_0|x_1) \right] }

It has three types of items.

Item 1:

DKL(q(xT∣x0)∥p(xT)) D_{\mathrm{KL}} \left( q(x_T|x_0)\|p(x_T) \right)

It indicates that the final noise distribution should be close to standard Gaussian. When TT is large enough, q(xT∣x0)≈N(0,I)q(x_T|x_0)\approx\mathcal{N}(0,I), this term is usually close to a constant.

Intermediate item:

DKL(q(xt−1∣xt,x0)∥pθ(xt−1∣xt)) D_{\mathrm{KL}} \left( q(x_{t-1}|x_t,x_0) \| p_\theta(x_{t-1}|x_t) \right)

It is the core of training. It requires the model to learn a reverse step distribution pθ(xt−1∣xt)p_\theta(x_{t-1}|x_t) that is close to the true posterior q(xt−1∣xt,x0)q(x_{t-1}|x_t,x_0).

Last item:

−log⁡pθ(x0∣x1) -\log p_\theta(x_0|x_1)

Indicates the reconstruction error of restoring x0x_0 from x1x_1.

So the key issue of DDPM becomes:

How to make a neural network approximate the true posterior q(xt−1∣xt,x0)q(x_{t-1}|x_t,x_0)?


6. Real posterior and mean matching

True a posteriori is:

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

It indicates: If the original image x0x_0 and the noisy current image xtx_t are known, what distribution should the state at the previous moment xt−1x_{t-1} have been?

According to the Bayes’ formula:

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

Since the forward process is a Markov chain:

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

So:

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

The three distributions on the right are all Gaussian, so the result is still Gaussian:

q(xt−1∣xt,x0)=N(xt−1;μ~t(xt,x0),β~tI) \boxed{ q(x_{t-1}|x_t,x_0) {=} \mathcal{N} \left( x_{t-1}; \tilde{\mu}_t(x_t,x_0), \tilde{\beta}_tI \right) }

Among them:

μ~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 }

And:

β~t=1−αˉt−11−αˉtβt \boxed{ \tilde{\beta}_t {=} \frac{ 1-\bar{\alpha}_{t-1} }{ 1-\bar{\alpha}_t } \beta_t }

The detailed formula derivation is provided in Appendix B. First, understand its meaning in the main text:

  • The true posterior mean μ~t(xt,x0)\tilde{\mu}_t(x_t,x_0) is a linear combination of x0x_0 and xtx_t;
  • It tells us the most reasonable center position when taking a step back from xtx_t;
  • During model training, the mean predicted by the model μθ(xt,t)\mu_\theta(x_t,t) can be adjusted to approach this true mean.

Model reverse distribution is:

pθ(xt−1∣xt)=N(xt−1;μθ(xt,t),σt2I) p_\theta(x_{t-1}|x_t) {=} \mathcal{N} \left( x_{t-1}; \mu_\theta(x_t,t), \sigma_t^2I \right)

If the fixed variance is maintained, then the part of the KL between the two Gaussian distributions that is related to the model is the mean MSE:

Lt−1=Eq[12σt2∥μ~t(xt,x0)−μθ(xt,t)∥2]+C L_{t-1} {=} \mathbb{E}_q \left[ \frac{1}{2\sigma_t^2} \left\| \tilde{\mu}_t(x_t,x_0) {-} \mu_\theta(x_t,t) \right\|^2 \right] + C

Here, CC is a constant independent of the model parameter θ\theta.

This step transforms the complex probability matching problem into:

Make the reverse mean predicted by the model close to the true posterior mean.


7. From Prediction Mean to Prediction Noise

Theoretically, the network can directly predict:

μθ(xt,t) \mu_\theta(x_t,t)

However, in DDPM practice, prediction noise is more common:

ϵθ(xt,t) \epsilon_\theta(x_t,t)

This step seems sudden, but it actually comes from rewriting the formula.

7.1 Start with predicting x0x_0

The true posterior mean can be written as:

μ~t(xt,x0)=Atx0+Btxt \tilde{\mu}_t(x_t,x_0) {=} A_tx_0+B_tx_t

Among them:

At=αˉt−1βt1−αˉt A_t {=} \frac{ \sqrt{\bar{\alpha}_{t-1}}\beta_t }{ 1-\bar{\alpha}_t }
Bt=αt(1−αˉt−1)1−αˉt B_t {=} \frac{ \sqrt{\alpha_t}(1-\bar{\alpha}_{t-1}) }{ 1-\bar{\alpha}_t }

When generating, the real x0x_0 is unknown, so the original image can be predicted by the network:

x0,θ(xt,t) x_{0,\theta}(x_t,t)

Reconstruction:

μθ(xt,t)=μ~t(xt,x0,θ(xt,t)) \mu_\theta(x_t,t) {=} \tilde{\mu}_t \left( x_t, x_{0,\theta}(x_t,t) \right)

This indicates that the predicted original image x0x_0 and the predicted mean μθ\mu_\theta can be converted into each other.

7.2 Change the prediction from x0x_0 to prediction noise

The forward noise addition formula is:

xt=αˉtx0+1−αˉtϵ x_t {=} \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon

Solve it against x0x_0:

x0=xt−1−αˉtϵαˉt x_0 {=} \frac{ x_t-\sqrt{1-\bar{\alpha}_t}\epsilon }{ \sqrt{\bar{\alpha}_t} }

If the network prediction noise:

ϵθ(xt,t) \epsilon_\theta(x_t,t)

The original image can be reconstructed for prediction:

x0,θ(xt,t)=xt−1−αˉtϵθ(xt,t)αˉt \boxed{ x_{0,\theta}(x_t,t) {=} \frac{ x_t-\sqrt{1-\bar{\alpha}_t}\epsilon_\theta(x_t,t) }{ \sqrt{\bar{\alpha}_t} } }

Substitute x0,θx_{0,\theta} into μθ\mu_\theta from the previous section, to obtain the most common reverse mean parameterization for DDPM:

μθ(xt,t)=1αt(xt−βt1−αˉtϵθ(xt,t)) \boxed{ \mu_\theta(x_t,t) {=} \frac{1}{\sqrt{\alpha_t}} \left( x_t {-} \frac{ \beta_t }{ \sqrt{1-\bar{\alpha}_t} } \epsilon_\theta(x_t,t) \right) }

The complete algebraic simplification of this formula is provided in Appendix C.

7.3 Why Predictive Noise Is Preferred

There are three intuitive advantages of prediction noise.

First, the supervision signals are naturally available. We sample them during training:

ϵ∼N(0,I) \epsilon\sim\mathcal{N}(0,I)

Then construct:

xt=αˉtx0+1−αˉtϵ x_t {=} \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon

So the real noise ϵ\epsilon is a known label.

Second, the target format is consistent. No matter how complex the image content is, the noise always comes from standard Gaussian:

ϵ∼N(0,I) \epsilon\sim\mathcal{N}(0,I)

Third, the sampling formula is intuitive. The model predicts the noise in the current image, and then subtracts it from xtx_t.


8. Final Training Objectives and Sampling Formulas

8.1 From Mean Matching to Noise Prediction MSE

The true posterior mean can be written in a noisy form:

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

The model mean is written as:

μθ(xt,t)=1αt(xt−βt1−αˉtϵθ(xt,t)) \mu_\theta(x_t,t) {=} \frac{1}{\sqrt{\alpha_t}} \left( x_t {-} \frac{ \beta_t }{ \sqrt{1-\bar{\alpha}_t} } \epsilon_\theta(x_t,t) \right)

Subtract the two:

μ~t(xt,x0)−μθ(xt,t)=βtαt1−αˉt(ϵθ(xt,t)−ϵ) \tilde{\mu}_t(x_t,x_0) {-} \mu_\theta(x_t,t) {=} \frac{ \beta_t }{ \sqrt{\alpha_t}\sqrt{1-\bar{\alpha}_t} } \left( \epsilon_\theta(x_t,t)-\epsilon \right)

Therefore:

∥μ~t(xt,x0)−μθ(xt,t)∥2=βt2αt(1−αˉt)∥ϵ−ϵθ(xt,t)∥2 \left\| \tilde{\mu}_t(x_t,x_0) {-} \mu_\theta(x_t,t) \right\|^2 {=} \frac{ \beta_t^2 }{ \alpha_t(1-\bar{\alpha}_t) } \left\| \epsilon-\epsilon_\theta(x_t,t) \right\|^2

By substituting the mean-matching term corresponding to KL, a weighted noise prediction MSE is obtained:

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

In DDPM practice, simplified objectives are often used, and complex weights are removed:

Lsimple(θ)=Et,x0,ϵ[∥ϵ−ϵθ(xt,t)∥2] \boxed{ \mathcal{L}_{\mathrm{simple}}(\theta) {=} \mathbb{E}_{t,x_0,\epsilon} \left[ \left\| \epsilon-\epsilon_\theta(x_t,t) \right\|^2 \right] }

Among them:

xt=αˉtx0+1−αˉtϵ \boxed{ x_t {=} \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon }

This is the most common training objective for DDPM.

8.2 Training Process

One training iteration can be understood as:

  1. Sample real images from the dataset:
x0∼qdata(x0) x_0\sim q_{\mathrm{data}}(x_0)
  1. Random sampling time steps:
t∼Uniform({1,…,T}) t\sim\mathrm{Uniform}(\{1,\dots,T\})
  1. Sampling Gaussian noise:
ϵ∼N(0,I) \epsilon\sim\mathcal{N}(0,I)
  1. Construct noisy images:
xt=αˉtx0+1−αˉtϵ x_t {=} \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon
  1. Introduce network prediction noise:
ϵθ(xt,t) \epsilon_\theta(x_t,t)
  1. Minimize noise prediction error:
∥ϵ−ϵθ(xt,t)∥2 \left\| \epsilon-\epsilon_\theta(x_t,t) \right\|^2

8.3 Sampling Process

After training is completed, start from pure noise:

xT∼N(0,I) x_T\sim\mathcal{N}(0,I)

Then perform it step by step from t=Tt=T to t=1t=1:

xt−1=μθ(xt,t)+σtz x_{t-1} {=} \mu_\theta(x_t,t) + \sigma_tz

Among them:

z∼N(0,I) z\sim\mathcal{N}(0,I)

Insert the mean value in the noise prediction form:

xt−1=1αt(xt−βt1−αˉtϵθ(xt,t))+σtz \boxed{ x_{t-1} {=} \frac{1}{\sqrt{\alpha_t}} \left( x_t {-} \frac{ \beta_t }{ \sqrt{1-\bar{\alpha}_t} } \epsilon_\theta(x_t,t) \right) + \sigma_tz }

Usually, no random noise is added in the last step t=1t=1, that is:

z=0 z=0

This can prevent the final output from being further disturbed.


9. DDPM Code Implementation

This section explains how DDPM is implemented in PyTorch from the STL10 code. The complete code is available at:

  • code/train_stl10_ddpm.py
  • code/generate_stl10_ddpm.py

The dataset uses STL10. split="train" is used in the code, and the image is adjusted to 96×9696\times96 by default. The dataset will be automatically downloaded during the first run. torch and torchvision must be installed in advance.

This implementation uses the most basic DDPM configuration: a linear beta schedule, noise-prediction objective, MSE loss, and a small U-Net noise predictor. Its goal is not to generate the strongest diffusion model in one go, but to first achieve a closed loop where noise is added during training, the model predicts noise, and noise is gradually removed during sampling.

9.1 Dataset and Image Scope

STL10 is a RGB image, so the model input shape is:

x0∈R3×96×96 x_0\in\mathbb{R}^{3\times96\times96}

In the training script, first resize the images to the specified size, and then normalize the pixels to [−1,1][-1,1]:

pythontransform = transforms.Compose(
    [
        transforms.Resize((args.image_size, args.image_size)),
        transforms.ToTensor(),
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
    ]
)
train_set = datasets.STL10(
    root=args.data_dir,
    split="train",
    download=True,
    transform=transform,
)

Here, the Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)) will convert the original [0,1][0,1] pixels into [−1,1][-1,1]:

xnorm=2x−1 x_{\text{norm}}=2x-1

The reason for this is that DDPM gradually brings the image closer to standard Gaussian noise. When the image range approaches [−1,1][-1,1], the data scale and noise scale become more consistent. When saving the generated image, the result is mapped back to [0,1][0,1]:

pythonutils.save_image((images + 1) / 2, out_path, nrow=int(num_samples**0.5))

9.2 Time Embedding

The DDPM network cannot only consider the noisy image xtx_t, but also must know the current time step tt. The same image has a completely different noise level in t=50t=50 and t=900t=900. Without time information, the network doesn’t know how much noise to remove.

In the code, sine time embedding is used to convert integer time steps into continuous vectors:

pythonclass SinusoidalTimeEmbedding(nn.Module):
    def __init__(self, dim: int) -> None:
        super().__init__()
        self.dim = dim

    def forward(self, t: torch.Tensor) -> torch.Tensor:
        half = self.dim // 2
        freq = torch.exp(
            -math.log(10000)
            * torch.arange(half, device=t.device, dtype=torch.float32)
            / max(half - 1, 1)
        )
        angles = t.float()[:, None] * freq[None, :]
        emb = torch.cat([angles.sin(), angles.cos()], dim=-1)
        return F.pad(emb, (0, self.dim % 2))

The obtained time vector is further processed through MLP:

pythonself.time_mlp = nn.Sequential(
    SinusoidalTimeEmbedding(time_dim),
    nn.Linear(time_dim, time_dim),
    nn.SiLU(),
    nn.Linear(time_dim, time_dim),
)

In this way, each time step has a condition vector available for the network to use.

9.3 How the residual block injects time information

Each residual block in U-Net receives two inputs: image features x and temporal features time_emb. The temporal features are first projected through a linear layer to the current number of channels, and then added to the convolutional feature map:

pythonclass ResBlock(nn.Module):
    def __init__(self, in_ch: int, out_ch: int, time_dim: int) -> None:
        super().__init__()
        self.conv1 = nn.Conv2d(in_ch, out_ch, 3, padding=1)
        self.norm1 = nn.GroupNorm(norm_groups(out_ch), out_ch)
        self.conv2 = nn.Conv2d(out_ch, out_ch, 3, padding=1)
        self.norm2 = nn.GroupNorm(norm_groups(out_ch), out_ch)
        self.time_proj = nn.Sequential(nn.SiLU(), nn.Linear(time_dim, out_ch))
        self.skip = nn.Conv2d(in_ch, out_ch, 1) if in_ch != out_ch else nn.Identity()

    def forward(self, x: torch.Tensor, time_emb: torch.Tensor) -> torch.Tensor:
        h = F.silu(self.norm1(self.conv1(x)))
        h = h + self.time_proj(time_emb)[:, :, None, None]
        h = F.silu(self.norm2(self.conv2(h)))
        return h + self.skip(x)

Among them:

  • The shape of time_proj(time_emb) is [B, C];
  • [:, :, None, None] turns it into [B, C, 1, 1];
  • When added to h, it will automatically be broadcast to the entire feature map.

This step corresponds to the conditional network in the formula:

ϵθ(xt,t) \epsilon_\theta(x_t,t)

In other words, the model relies on both the image state xtx_t and the time step tt when predicting noise.

9.4 U-Net Noise Predictor

The core model in the training script is UNet96. It takes a noisy RGB image of 96×9696\times96 as input, and outputs a noise prediction of the same size:

xt∈R3×96×96 x_t\in\mathbb{R}^{3\times96\times96}
ϵθ(xt,t)∈R3×96×96 \epsilon_\theta(x_t,t)\in\mathbb{R}^{3\times96\times96}

The model first downsamples to extract semantic features, and then upsamples to restore spatial resolution:

pythonself.in_conv = nn.Conv2d(in_ch, base, 3, padding=1)
self.down1 = ResBlock(base, base, time_dim)
self.down2 = ResBlock(base, base * 2, time_dim)
self.down3 = ResBlock(base * 2, base * 4, time_dim)
self.down4 = ResBlock(base * 4, base * 8, time_dim)
self.mid = ResBlock(base * 8, base * 8, time_dim)

self.up3 = ResBlock(base * 8 + base * 4, base * 4, time_dim)
self.up2 = ResBlock(base * 4 + base * 2, base * 2, time_dim)
self.up1 = ResBlock(base * 2 + base, base, time_dim)
self.out = nn.Sequential(
    nn.GroupNorm(norm_groups(base), base),
    nn.SiLU(),
    nn.Conv2d(base, in_ch, 3, padding=1),
)

During forward propagation, the up-sampling stage combines the deep features with the features saved during the down-sampling stage:

pythonx1 = self.down1(self.in_conv(x), time_emb)
x2 = self.down2(self.pool(x1), time_emb)
x3 = self.down3(self.pool(x2), time_emb)
x4 = self.down4(self.pool(x3), time_emb)
h = self.mid(x4, time_emb)

h = F.interpolate(h, scale_factor=2, mode="nearest")
h = self.up3(torch.cat([h, x3], dim=1), time_emb)
h = F.interpolate(h, scale_factor=2, mode="nearest")
h = self.up2(torch.cat([h, x2], dim=1), time_emb)
h = F.interpolate(h, scale_factor=2, mode="nearest")
h = self.up1(torch.cat([h, x1], dim=1), time_emb)
return self.out(h)

The “skip connection” here is very important. What DDPM needs to do is predict pixel-by-pixel noise, which requires both global semantics and local texture positions. The downsampling path expands the field of view, while the upsampling path restores resolution; the skip connection sends back the shallow details.

9.5 Diffusion Tool Classes

The Diffusion class is responsible for two things:

  1. Precompute the coefficients required for forward diffusion and reverse sampling;
  2. Provide two core functions: q_sample and sample.

Define the linear beta schedule during initialization:

pythonself.betas = torch.linspace(beta_start, beta_end, timesteps, device=self.device)
self.alphas = 1.0 - self.betas
self.alpha_bars = torch.cumprod(self.alphas, dim=0)
self.sqrt_alpha_bars = torch.sqrt(self.alpha_bars)
self.sqrt_one_minus_alpha_bars = torch.sqrt(1.0 - self.alpha_bars)

This corresponds to what was derived earlier:

αt=1−βt \alpha_t=1-\beta_t
αˉt=∏s=1tαs \bar{\alpha}_t=\prod_{s=1}^{t}\alpha_s

Since these quantities are only related to time steps, they should be calculated once during initialization, rather than being recalculated for each batch.

_extract is used to extract the time-step coefficient for the current batch from the coefficient table with a length of timesteps:

pythondef _extract(self, values: torch.Tensor, t: torch.Tensor, x: torch.Tensor) -> torch.Tensor:
    return values.gather(0, t).view(t.size(0), *((1,) * (x.ndim - 1)))

For example, if a batch contains 32 images, and each image is randomly assigned to a different time step, _extract can retrieve 32 corresponding αˉt\sqrt{\bar{\alpha}_t}, which are then reshaped into [B,1,1,1] to facilitate multiplication with the image tensor.

9.6 How to Construct xtx_t During Training

Training DDPM does not require adding noise step by step from x0x_0 to xtx_t, but instead uses a closed-form formula directly:

xt=αˉtx0+1−αˉtϵ x_t {=} \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon

The q_sample in the code is exactly implementing this formula:

pythondef q_sample(
    self,
    x0: torch.Tensor,
    t: torch.Tensor,
    noise: torch.Tensor | None = None,
) -> torch.Tensor:
    if noise is None:
        noise = torch.randn_like(x0)
    return (
        self._extract(self.sqrt_alpha_bars, t, x0) * x0
        + self._extract(self.sqrt_one_minus_alpha_bars, t, x0) * noise
    )

Here, the noise is the training supervision signal ϵ\epsilon. Since the noise is sampled by us, the training labels are naturally known and do not require manual annotation.

9.7 Training Objectives

One training iteration can be written in five steps:

  1. Extract the real image from the dataset x0x_0;
  2. Randomly sample time steps tt;
  3. Randomly add noise ϵ\epsilon;
  4. Obtain the result using a closed-form formula xtx_t;
  5. Let the model predict ϵ\epsilon based on (xt,t)(x_t,t).

The core code in the training script is:

pythonfor x0, _ in train_loader:
    x0 = x0.to(device)
    t = torch.randint(0, args.timesteps, (x0.size(0),), device=device)
    noise = torch.randn_like(x0)
    xt = diffusion.q_sample(x0, t, noise)
    loss = F.mse_loss(model(xt, t), noise)

    optimizer.zero_grad(set_to_none=True)
    loss.backward()
    optimizer.step()

This corresponds to the final simplification goal:

Lsimple(θ)=Et,x0,ϵ[∥ϵ−ϵθ(xt,t)∥2] \mathcal{L}_{\mathrm{simple}}(\theta) {=} \mathbb{E}_{t,x_0,\epsilon} \left[ \left\| \epsilon-\epsilon_\theta(x_t,t) \right\|^2 \right]

Note that the model does not directly predict clean images x0x_0, but predicts the added noise ϵ\epsilon. After predicting the noise, the sampling phase can construct the reverse mean step by step using the DDPM formula.

9.8 Output of the training script

train_stl10_ddpm.py will be saved:

  • checkpoints/stl10_ddpm.pt: Model weights and necessary configurations;
  • outputs/stl10_sample_epoch_XXX.png: STL10-style images sampled from noise during training.

The following are saved in the checkpoint:

python{
    "model_state": model.state_dict(),
    "base": args.base,
    "timesteps": args.timesteps,
    "image_size": args.image_size,
}

These configurations will be reused in the generated script. Especially for image_size, since STL10 defaults to using 96×9696\times96, the generation phase must be consistent with the training phase.

Example of running mode:

bashpython train_stl10_ddpm.py --epochs 200 --batch-size 32 --image-size 96

The running results are as follows. It can be seen that there was a lot of noise during the initial training. By the 200th iteration, some outlines could be reconstructed, but the performance was still average. To help everyone understand better, the tutorial did not include many improvement techniques, such as attention mechanisms or class conditional generation. After completing this course, you can continue to learn more~

9.9 Sampling Generation Process

After training ends, the generation phase starts with pure Gaussian noise:

xT∼N(0,I) x_T\sim\mathcal{N}(0,I)

Then, perform a reverse loop according to the time step:

xT→xT−1→⋯→x0 x_T\rightarrow x_{T-1}\rightarrow\cdots\rightarrow x_0

The sample function in the code implements this process:

python@torch.no_grad()
def sample(self, model: nn.Module, shape: tuple[int, int, int, int]) -> torch.Tensor:
    model.eval()
    x = torch.randn(shape, device=self.device)

    for step in reversed(range(self.timesteps)):
        t = torch.full((shape[0],), step, device=self.device, dtype=torch.long)
        pred_noise = model(x, t)
        beta_t = self._extract(self.betas, t, x)
        alpha_t = self._extract(self.alphas, t, x)
        alpha_bar_t = self._extract(self.alpha_bars, t, x)
        mean = (1.0 / torch.sqrt(alpha_t)) * (
            x - beta_t / torch.sqrt(1.0 - alpha_bar_t) * pred_noise
        )
        x = mean if step == 0 else mean + torch.sqrt(beta_t) * torch.randn_like(x)

    return x.clamp(-1, 1)

The reverse mean is:

μθ(xt,t)=1αt(xt−βt1−αˉtϵθ(xt,t)) \mu_\theta(x_t,t) {=} \frac{1}{\sqrt{\alpha_t}} \left( x_t {-} \frac{\beta_t}{\sqrt{1-\bar{\alpha}_t}} \epsilon_\theta(x_t,t) \right)

This line in the code:

pythonx = mean if step == 0 else mean + torch.sqrt(beta_t) * torch.randn_like(x)

Indicates that no additional random noise is added in the last step. Otherwise, the generated x0x_0 will be affected by the last noise perturbation.

9.10 Test Generation Script

generate_stl10_ddpm.py only generates content and no longer reads training data. Its process is as follows:

  1. Load checkpoints/stl10_ddpm.pt;
  2. Rebuild UNet96 based on base and image_size in the checkpoint;
  3. Load model weights;
  4. Reverse sample from standard Gaussian noise;
  5. Save the generated image.

Core code is:

pythoncheckpoint = torch.load(args.checkpoint, map_location=device)
image_size = int(checkpoint.get("image_size", 96))
model = UNet96(base=int(checkpoint["base"])).to(device)
model.load_state_dict(checkpoint["model_state"])

diffusion = Diffusion(timesteps=int(checkpoint["timesteps"]), device=device)
with torch.no_grad():
    images = diffusion.sample(model, (args.num_samples, 3, image_size, image_size))
    utils.save_image((images + 1) / 2, args.out, nrow=max(1, int(args.num_samples**0.5)))

Example of running mode:

bashpython generate_stl10_ddpm.py --checkpoint checkpoints/stl10_ddpm.pt --out outputs/generated_stl10.png --num-samples 1

Only one sheet is generated by default. 96×9696\times96 Images are helpful to first check whether the model can actually generate visible structures from noise. If you want to see multiple images, you can increase the scale. --num-samples。

The execution result is as follows:


10. Common Questions

10.1 Is the forward process of DDPM learned?

No. Forward process:

q(xt∣xt−1)=N(xt;αtxt−1,βtI) q(x_t|x_{t-1}) {=} \mathcal{N} \left( x_t; \sqrt{\alpha_t}x_{t-1}, \beta_tI \right)

It is a manually defined noise addition rule. What needs to be learned is the reverse process:

pθ(xt−1∣xt) p_\theta(x_{t-1}|x_t)

10.2 Why No Step-by-Step Noise Addition During Training

Because there is a closed formula:

xt=αˉtx0+1−αˉtϵ x_t {=} \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon

It can directly obtain x0x_0 to get xtx_t at any time step.

10.3 Why the goal is to predict noise, not the image

Since the noise during training ϵ\epsilon is sampled by us, it naturally serves as a supervised label. After predicting the noise, x0x_0 can be restored using a formula or the reverse mean can be constructed:

μθ(xt,t)=1αt(xt−βt1−αˉtϵθ(xt,t)) \mu_\theta(x_t,t) {=} \frac{1}{\sqrt{\alpha_t}} \left( x_t {-} \frac{ \beta_t }{ \sqrt{1-\bar{\alpha}_t} } \epsilon_\theta(x_t,t) \right)

Therefore, the prediction noise is not weak; it is equivalent to learning the reverse denoising step.

10.4 What is the biggest difference between DDPM and VAE?

VAE is:

z∼N(0,I),x∼pθ(x∣z) z\sim\mathcal{N}(0,I), \quad x\sim p_\theta(x|z)

That is, generating an image in one step from latent variables.

DDPM is:

xT∼N(0,I) x_T\sim\mathcal{N}(0,I)
xT→xT−1→⋯→x0 x_T\rightarrow x_{T-1}\rightarrow\cdots\rightarrow x_0

That is, starting from noise, multi-step denoising is performed to generate an image.

10.5 What are the disadvantages of DDPM?

The main drawback is the slow sampling process. It usually requires a step-by-step reverse calculation from TT to 11, and the basic version may require hundreds to thousands of steps. Later, DDIM, an acceleration sampler, or distillation methods can be used to reduce the number of sampling steps.


11. Summary

The core logic of DDPM is:

  1. Fixed forward noise addition:
q(xt∣xt−1)=N(xt;αtxt−1,βtI) q(x_t|x_{t-1}) {=} \mathcal{N} \left( x_t; \sqrt{\alpha_t}x_{t-1}, \beta_tI \right)
  1. Introduce noise at any time:
xt=αˉtx0+1−αˉtϵ x_t {=} \sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon
  1. Learn reverse denoising:
pθ(xt−1∣xt)=N(xt−1;μθ(xt,t),σt2I) p_\theta(x_{t-1}|x_t) {=} \mathcal{N} \left( x_{t-1}; \mu_\theta(x_t,t), \sigma_t^2I \right)
  1. Obtain the true posterior match using ELBO:
DKL(q(xt−1∣xt,x0)∥pθ(xt−1∣xt)) D_{\mathrm{KL}} \left( q(x_{t-1}|x_t,x_0) \| p_\theta(x_{t-1}|x_t) \right)
  1. Convert the Gaussian KL to mean MSE, and then to noise prediction MSE:
Lsimple(θ)=Et,x0,ϵ[∥ϵ−ϵθ(xt,t)∥2] \boxed{ \mathcal{L}_{\mathrm{simple}}(\theta) {=} \mathbb{E}_{t,x_0,\epsilon} \left[ \left\| \epsilon-\epsilon_\theta(x_t,t) \right\|^2 \right] }

Compared to VAE, DDPM does not compress all image information into a low-dimensional latent variable, but instead removes noise gradually in the image space. Its training objective comes from probability derivation, but it ultimately reduces to a very simple supervised learning problem.


12. After-class Exercises

  1. Why is VAE prone to generating blurred images?
  2. Why doesn’t DDPM directly generate images from latent variables in one step?
  3. What does βt\beta_t control in the forward process?
  4. Why is a square root coefficient used in the one-step noise addition formula?
  5. How can xtx_t at any time step be sampled directly from x0x_0?
  6. Why does xTx_T approach standard Gaussian noise?
  7. Why is the reverse process of DDPM modeled as a Gaussian distribution?
  8. What constraint does the intermediate KL term in ELBO decomposition impose?
  9. Why does the true posterior q(xt−1∣xt,x0)q(x_{t-1}|x_t,x_0) have an analytical solution?
  10. Why is the predicted noise ϵ\epsilon equivalent to learning reverse denoising?
  11. Why is no noise added in the last step during sampling?
  12. What problems will occur for DDPM networks without time embedding?

Appendix A: Detailed Derivation of DDPM ELBO Decomposition

DDPM minimizes negative ELBO:

L=Eq(x1:T∣x0)[−log⁡pθ(x0:T)q(x1:T∣x0)] \mathcal{L} {=} \mathbb{E}_{q(x_{1:T}|x_0)} \left[ {-} \log \frac{ p_\theta(x_{0:T}) }{ q(x_{1:T}|x_0) } \right]

Expand the model joint distribution:

pθ(x0:T)=p(xT)∏t=1Tpθ(xt−1∣xt) p_\theta(x_{0:T}) {=} p(x_T) \prod_{t=1}^{T} p_\theta(x_{t-1}|x_t)

Expand the forward distribution:

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

Substitute:

L=Eq[−log⁡p(xT)∏t=1Tpθ(xt−1∣xt)∏t=1Tq(xt∣xt−1)] \mathcal{L} {=} \mathbb{E}_q \left[ {-} \log \frac{ p(x_T) \prod_{t=1}^{T} p_\theta(x_{t-1}|x_t) }{ \prod_{t=1}^{T} q(x_t|x_{t-1}) } \right]

Unpack the log:

L=Eq[−log⁡p(xT)−∑t=1Tlog⁡pθ(xt−1∣xt)+∑t=1Tlog⁡q(xt∣xt−1)] \mathcal{L} {=} \mathbb{E}_q \left[ {-} \log p(x_T) {-} \sum_{t=1}^{T} \log p_\theta(x_{t-1}|x_t) + \sum_{t=1}^{T} \log q(x_t|x_{t-1}) \right]

Usage for t>1t>1:

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

So:

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

By substituting these items and organizing the telescoping items, we can obtain:

L=Eq[−log⁡p(xT)q(xT∣x0)−∑t>1log⁡pθ(xt−1∣xt)q(xt−1∣xt,x0)−log⁡pθ(x0∣x1)] \mathcal{L} {=} \mathbb{E}_q \left[ {-} \log \frac{ p(x_T) }{ q(x_T|x_0) } {-} \sum_{t>1} \log \frac{ p_\theta(x_{t-1}|x_t) }{ q(x_{t-1}|x_t,x_0) } {-} \log p_\theta(x_0|x_1) \right]

Written in KL format:

L=Eq[DKL(q(xT∣x0)∥p(xT))+∑t>1DKL(q(xt−1∣xt,x0)∥pθ(xt−1∣xt))−log⁡pθ(x0∣x1)] \boxed{ \mathcal{L} {=} \mathbb{E}_q \left[ D_{\mathrm{KL}} \left( q(x_T|x_0)\|p(x_T) \right) + \sum_{t>1} D_{\mathrm{KL}} \left( q(x_{t-1}|x_t,x_0) \| p_\theta(x_{t-1}|x_t) \right) {-} \log p_\theta(x_0|x_1) \right] }

This expression indicates that the core of training DDPM is to make the model’s reverse step distribution match the true posterior derived from the forward process.


Appendix B: Detailed Derivation of the Real Posterior q(xt−1∣xt,x0)q(x_{t-1}|x_t,x_0)

Let:

z=xt−1 z=x_{t-1}

We require:

q(z∣xt,x0) q(z|x_t,x_0)

According to the Bayes’ formula:

q(z∣xt,x0)∝q(xt∣z)q(z∣x0) q(z|x_t,x_0) \propto q(x_t|z)q(z|x_0)

Among them:

q(xt∣z)=N(xt;αtz,βtI) q(x_t|z) {=} \mathcal{N} \left( x_t; \sqrt{\alpha_t}z, \beta_tI \right)
q(z∣x0)=N(z;αˉt−1x0,(1−αˉt−1)I) q(z|x_0) {=} \mathcal{N} \left( z; \sqrt{\bar{\alpha}_{t-1}}x_0, (1-\bar{\alpha}_{t-1})I \right)

Only look at the index part related to zz:

q(z∣xt,x0)∝exp⁡(−12βt∥xt−αtz∥2−12(1−αˉt−1)∥z−αˉt−1x0∥2) q(z|x_t,x_0) \propto \exp \left( {-} \frac{1}{2\beta_t} \left\| x_t-\sqrt{\alpha_t}z \right\|^2 {-} \frac{1}{2(1-\bar{\alpha}_{t-1})} \left\| z-\sqrt{\bar{\alpha}_{t-1}}x_0 \right\|^2 \right)

Expand the first item:

∥xt−αtz∥2=∥xt∥2−2αtxt⊤z+αt∥z∥2 \left\| x_t-\sqrt{\alpha_t}z \right\|^2 {=} \left\|x_t\right\|^2 {-} 2\sqrt{\alpha_t}x_t^\top z + \alpha_t\left\|z\right\|^2

The parts related to zz are:

−αt2βt∥z∥2+αtβtxt⊤z {-} \frac{\alpha_t}{2\beta_t} \left\|z\right\|^2 + \frac{\sqrt{\alpha_t}}{\beta_t} x_t^\top z

Expand item two:

∥z−αˉt−1x0∥2=∥z∥2−2αˉt−1x0⊤z+αˉt−1∥x0∥2 \left\| z-\sqrt{\bar{\alpha}_{t-1}}x_0 \right\|^2 {=} \left\|z\right\|^2 {-} 2\sqrt{\bar{\alpha}_{t-1}}x_0^\top z + \bar{\alpha}_{t-1} \left\|x_0\right\|^2

The parts related to zz are:

−12(1−αˉt−1)∥z∥2+αˉt−11−αˉt−1x0⊤z {-} \frac{1}{2(1-\bar{\alpha}_{t-1})} \left\|z\right\|^2 + \frac{\sqrt{\bar{\alpha}_{t-1}}}{1-\bar{\alpha}_{t-1}} x_0^\top z

Merge:

log⁡q(z∣xt,x0)=−12(αtβt+11−αˉt−1)∥z∥2+(αtβtxt+αˉt−11−αˉt−1x0)⊤z+C \log q(z|x_t,x_0) {=} {-} \frac{1}{2} \left( \frac{\alpha_t}{\beta_t} + \frac{1}{1-\bar{\alpha}_{t-1}} \right) \left\|z\right\|^2 + \left( \frac{\sqrt{\alpha_t}}{\beta_t}x_t + \frac{\sqrt{\bar{\alpha}_{t-1}}}{1-\bar{\alpha}_{t-1}}x_0 \right)^\top z + C

The standard exponential form of the Gaussian distribution is:

log⁡N(z;μ,Σ)=−12z⊤Σ−1z+z⊤Σ−1μ+C \log\mathcal{N}(z;\mu,\Sigma) {=} {-} \frac{1}{2} z^\top\Sigma^{-1}z + z^\top\Sigma^{-1}\mu + C

Compare the quadratic coefficient:

Σ−1=(αtβt+11−αˉt−1)I \Sigma^{-1} {=} \left( \frac{\alpha_t}{\beta_t} + \frac{1}{1-\bar{\alpha}_{t-1}} \right)I

So:

Σ=(αtβt+11−αˉt−1)−1I \Sigma {=} \left( \frac{\alpha_t}{\beta_t} + \frac{1}{1-\bar{\alpha}_{t-1}} \right)^{-1}I

Normalization:

Σ=βt(1−αˉt−1)αt(1−αˉt−1)+βtI \Sigma {=} \frac{ \beta_t(1-\bar{\alpha}_{t-1}) }{ \alpha_t(1-\bar{\alpha}_{t-1})+\beta_t } I

Because:

αˉt=αtαˉt−1 \bar{\alpha}_t=\alpha_t\bar{\alpha}_{t-1}

And:

αt+βt=1 \alpha_t+\beta_t=1

So:

αt(1−αˉt−1)+βt=1−αˉt \alpha_t(1-\bar{\alpha}_{t-1})+\beta_t {=} 1-\bar{\alpha}_t

Therefore:

Σ=βt(1−αˉt−1)1−αˉtI \Sigma {=} \frac{ \beta_t(1-\bar{\alpha}_{t-1}) }{ 1-\bar{\alpha}_t } I

i.e.:

β~t=1−αˉt−11−αˉtβt \boxed{ \tilde{\beta}_t {=} \frac{ 1-\bar{\alpha}_{t-1} }{ 1-\bar{\alpha}_t } \beta_t }

Calculate the mean again. From the linear term coefficient:

Σ−1μ=αtβtxt+αˉt−11−αˉt−1x0 \Sigma^{-1}\mu {=} \frac{\sqrt{\alpha_t}}{\beta_t}x_t + \frac{\sqrt{\bar{\alpha}_{t-1}}}{1-\bar{\alpha}_{t-1}}x_0

So:

μ=Σ(αtβtxt+αˉt−11−αˉt−1x0) \mu {=} \Sigma \left( \frac{\sqrt{\alpha_t}}{\beta_t}x_t + \frac{\sqrt{\bar{\alpha}_{t-1}}}{1-\bar{\alpha}_{t-1}}x_0 \right)

Substitute Σ\Sigma:

μ=βt(1−αˉt−1)1−αˉt(αtβtxt+αˉt−11−αˉt−1x0) \mu {=} \frac{ \beta_t(1-\bar{\alpha}_{t-1}) }{ 1-\bar{\alpha}_t } \left( \frac{\sqrt{\alpha_t}}{\beta_t}x_t + \frac{\sqrt{\bar{\alpha}_{t-1}}}{1-\bar{\alpha}_{t-1}}x_0 \right)

Obtained:

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

So:

μ~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 }

Appendix C: Deriving the Reverse Mean Formula from Prediction Noise

Known:

μθ(xt,t)=αˉt−1βt1−αˉtx0,θ(xt,t)+αt(1−αˉt−1)1−αˉtxt \mu_\theta(x_t,t) {=} \frac{ \sqrt{\bar{\alpha}_{t-1}}\beta_t }{ 1-\bar{\alpha}_t } x_{0,\theta}(x_t,t) + \frac{ \sqrt{\alpha_t}(1-\bar{\alpha}_{t-1}) }{ 1-\bar{\alpha}_t } x_t

Another one:

x0,θ(xt,t)=xt−1−αˉtϵθ(xt,t)αˉt x_{0,\theta}(x_t,t) {=} \frac{ x_t-\sqrt{1-\bar{\alpha}_t}\epsilon_\theta(x_t,t) }{ \sqrt{\bar{\alpha}_t} }

Substitute:

μθ(xt,t)=αˉt−1βt1−αˉt⋅xt−1−αˉtϵθ(xt,t)αˉt+αt(1−αˉt−1)1−αˉtxt \mu_\theta(x_t,t) {=} \frac{ \sqrt{\bar{\alpha}_{t-1}}\beta_t }{ 1-\bar{\alpha}_t } \cdot \frac{ x_t-\sqrt{1-\bar{\alpha}_t}\epsilon_\theta(x_t,t) }{ \sqrt{\bar{\alpha}_t} } + \frac{ \sqrt{\alpha_t}(1-\bar{\alpha}_{t-1}) }{ 1-\bar{\alpha}_t } x_t

Because:

αˉt=αtαˉt−1 \bar{\alpha}_t=\alpha_t\bar{\alpha}_{t-1}

So:

αˉt−1αˉt=1αt \frac{ \sqrt{\bar{\alpha}_{t-1}} }{ \sqrt{\bar{\alpha}_t} } {=} \frac{1}{\sqrt{\alpha_t}}

So:

μθ(xt,t)=βtαt(1−αˉt)xt−βtαt1−αˉtϵθ(xt,t)+αt(1−αˉt−1)1−αˉtxt \mu_\theta(x_t,t) {=} \frac{ \beta_t }{ \sqrt{\alpha_t}(1-\bar{\alpha}_t) } x_t {-} \frac{ \beta_t }{ \sqrt{\alpha_t}\sqrt{1-\bar{\alpha}_t} } \epsilon_\theta(x_t,t) + \frac{ \sqrt{\alpha_t}(1-\bar{\alpha}_{t-1}) }{ 1-\bar{\alpha}_t } x_t

Merge xtx_t coefficients:

βtαt(1−αˉt)+αt(1−αˉt−1)1−αˉt \frac{ \beta_t }{ \sqrt{\alpha_t}(1-\bar{\alpha}_t) } + \frac{ \sqrt{\alpha_t}(1-\bar{\alpha}_{t-1}) }{ 1-\bar{\alpha}_t }

Normalization:

βt+αt(1−αˉt−1)αt(1−αˉt) \frac{ \beta_t+\alpha_t(1-\bar{\alpha}_{t-1}) }{ \sqrt{\alpha_t}(1-\bar{\alpha}_t) }

Because:

βt+αt=1 \beta_t+\alpha_t=1

And:

αtαˉt−1=αˉt \alpha_t\bar{\alpha}_{t-1}=\bar{\alpha}_t

So:

βt+αt(1−αˉt−1)=βt+αt−αtαˉt−1=1−αˉt \beta_t+\alpha_t(1-\bar{\alpha}_{t-1}) {=} \beta_t+\alpha_t-\alpha_t\bar{\alpha}_{t-1} {=} 1-\bar{\alpha}_t

Therefore, the coefficient of xtx_t is:

1αt \frac{1}{\sqrt{\alpha_t}}

Final:

μθ(xt,t)=1αtxt−βtαt1−αˉtϵθ(xt,t) \mu_\theta(x_t,t) {=} \frac{1}{\sqrt{\alpha_t}}x_t {-} \frac{ \beta_t }{ \sqrt{\alpha_t}\sqrt{1-\bar{\alpha}_t} } \epsilon_\theta(x_t,t)

Extract 1αt\frac{1}{\sqrt{\alpha_t}}:

μθ(xt,t)=1αt(xt−βt1−αˉtϵθ(xt,t)) \boxed{ \mu_\theta(x_t,t) {=} \frac{1}{\sqrt{\alpha_t}} \left( x_t {-} \frac{ \beta_t }{ \sqrt{1-\bar{\alpha}_t} } \epsilon_\theta(x_t,t) \right) }