Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 1

Forward Diffusion Process Simulation and Verification

Recommended resources

Hello! Welcome to your first lesson on diffusion models.

Over the next few weeks, we'll be diving deep into the theory and practice of these powerful generative models, moving from the fundamentals all the way to advanced applications in 2D and 3D generation.

In this lesson, we'll start at the beginning by focusing on the forward diffusion process. You'll learn what it is, explore its key mathematical properties, and—most importantly—implement a simulation of it yourself. While you've implemented a basic DDPM before, this lesson will serve as a rigorous "brush-up" to solidify your understanding of the foundational mechanics. Mastering the forward process is the essential first step before we can tackle the reverse (denoising) process, which is where the generation actually happens.

1. The Forward Process: An Intuitive Overview

At its core, the forward diffusion process is conceptually simple: it's a fixed procedure that systematically and gradually destroys structure in data by adding noise. We start with a clean image, , and over a series of timesteps, we add a small amount of Gaussian noise at each step. This continues until, at the final timestep , the image is indistinguishable from pure random noise.

Forward Diffusion Process Illustration
This image illustrates the forward process, a Markov chain that progressively adds Gaussian noise to an image \(\mathbf{x}_0\) until it becomes pure noise \(\mathbf{x}_T\). The process is defined by the conditional probability \(q(\mathbf{x}_t|\mathbf{x}_{t-1})\).

This is a fixed process, meaning we don't need a neural network to learn it. The amount of noise added at each step is determined by a pre-defined variance schedule. The real goal of a diffusion model, which we'll explore in the next lesson, is to learn how to reverse this process.


2. The Mathematics of Gradual Noising

The forward process is mathematically defined as a Markov chain, where the state at timestep , denoted , depends only on the state at the previous timestep, .

The distribution of given is a Gaussian distribution:

Let's break this down:

  • : This is the variance schedule. It's a set of pre-defined small positive constants, , that control how much noise is added at each step . A common choice is a linear schedule, where increases from a small value (e.g., 0.0001) to a larger one (e.g., 0.02).
  • Mean (): The mean of the distribution is the previous image , scaled down slightly.
  • Variance (): The variance determines the "strength" of the noise we add. is the identity matrix, meaning we add independent noise to each pixel.

To get a more intuitive feel for this process and the role of the variance schedule, watch the following segment.

Diffusion models from scratch in PyTorch

The video 'Diffusion models from scratch in PyTorch' by DeepFindr provides a clear explanation of the mathematical formulation of the forward process.

Watch the section 'The Forward Process' from 6:45 to 12:08. Pay close attention to how eta_t controls the rate of convergence to pure noise and the introduction of \alpha_t.


3. The "Nice Property": Jumping to Any Timestep

The step-by-step Markovian process is intuitive, but it's computationally inefficient. If we want to generate a noisy sample at timestep , we would need to perform 500 iterative sampling steps. This is impractical for training.

Fortunately, a key property of this process is that we can sample directly from the original image in a single step. This is possible because the sum of Gaussian distributions is also a Gaussian. By defining and (the cumulative product of ), we can derive a closed-form expression for .

The derivation involves recursively expanding the expression for . If you're interested in the step-by-step math, the following resource provides an excellent walkthrough.

Code the Maths - A deep dive into DDPMs

The blog post 'Code the Maths - A deep dive into DDPMs' by The Latent explains the reparameterization trick that allows for efficient sampling.

Read the section '2. Reparameterization' to see the mathematical derivation. Focus on how the recursive expansion leads to the final closed-form equation involving ar{\alpha}_t.

The final result of this derivation is the most important equation for the forward process:

This formula tells us that any noised sample can be expressed as a simple combination of the original image and some Gaussian noise. Using the reparameterization trick, we can write this as a direct sampling equation:

where is a random noise tensor with the same dimensions as .

This equation is powerful. It shows that is just a weighted sum of the original "signal" () and "noise" (). The term controls the balance between the two.

Test your understanding!

Consider the direct sampling equation: .

As , what do and approach? What about as ?

Show answer
  • As , . The equation becomes . The image is mostly signal.
  • As , (since it's a product of many numbers less than 1). The equation becomes . The image is almost pure noise.

This confirms our intuition: the schedule smoothly transitions the image from pure signal to pure noise.


4. Implementation and Verification in PyTorch

Now, let's translate this theory into code. Your task is to implement the forward diffusion process and visualize its effects on an image. We'll use PyTorch.

Follow these steps to build your simulation:

Step 1: Define the Variance Schedule ()

First, create a linear variance schedule for a chosen number of timesteps, T. This schedule will contain the values of for .

import torch
import torchvision
import matplotlib.pyplot as plt

def linear_beta_schedule(timesteps, start=0.0001, end=0.02):
    return torch.linspace(start, end, timesteps)

# Example usage
T = 300
betas = linear_beta_schedule(timesteps=T)

Step 2: Pre-compute Coefficients

Using the betas tensor, calculate the and values. The cumulative product function torch.cumprod is perfect for calculating .

alphas = 1. - betas
alphas_cumprod = torch.cumprod(alphas, axis=0)

The resource Denoising Diffusion Probabilistic Models (DDPM) from labml.ai provides a clean implementation of this setup within a class structure, which is a common practice. You can find these lines of code in the __init__ method.

Step 3: Implement the q_sample Function

This is the core of your implementation. Write a function that takes an initial image x0, a timestep t, and the pre-computed alphas_cumprod tensor, and returns the noised image using the direct sampling formula.

def q_sample(x0, t, alphas_cumprod):
    """
    Sample a noisy image x_t from x_0 at a specific timestep t.
    """
    # Get the value of alpha_bar_t for the given timestep t
    # Note: t is a tensor of timesteps
    alpha_bar_t = alphas_cumprod[t].view(-1, 1, 1, 1) # Reshape for broadcasting
    
    # Generate random noise
    epsilon = torch.randn_like(x0)
    
    # Apply the formula
    mean = torch.sqrt(alpha_bar_t) * x0
    std_dev = torch.sqrt(1. - alpha_bar_t)
    
    xt = mean + std_dev * epsilon
    
    return xt

This implementation is central to any DDPM. For a detailed code walkthrough that links each line back to the mathematical formulas, check out the following videos.

Ultimate Guide to Diffusion Models | ML Coding Series | Denoising Diffusion Probabilistic Models

First, Aleksa Gordić's 'Ultimate Guide to Diffusion Models' shows how the different coefficients are calculated and then used in the q_sample function.

Watch the segment from 39:00 to 42:54 to see the pre-computation of coefficients. Then, watch from 52:05 to 55:45 to see the q_sample function implementation and explanation. Notice how he explicitly maps the code to the paper's equations.

Diffusion models from scratch in PyTorch

Next, the 'Diffusion models from scratch in PyTorch' video provides a concise implementation of the same logic within a practical training context.

Watch from 12:08 to 16:04. Focus on the forward_diffusion_sample function and how the pre-calculated tensors are used. This reinforces the concepts you just saw.

Step 4: Visualize the Process

Finally, let's verify that your implementation works as expected. Load an image, apply your q_sample function for several different timesteps, and plot the results.

# (Assuming you have defined T, betas, alphas, alphas_cumprod)

# Load an image (e.g., from torchvision)
# Note: You'll need to preprocess it to the range [-1, 1]
# For simplicity, we'll create a dummy tensor here.
# In a real scenario, you'd load an image.
from PIL import Image
import requests
from io import BytesIO
import torchvision.transforms as transforms

# Let's use an example image URL
url = 'https://upload.wikimedia.org/wikipedia/commons/5/57/A_cat_on_a_motorcycle_in_the_medina_of_Tunis_2017.jpg'
response = requests.get(url)
img = Image.open(BytesIO(response.content))

# Preprocess the image
img_size = 64
transform = transforms.Compose([
    transforms.Resize((img_size, img_size)),
    transforms.ToTensor(), # Scales to [0, 1]
    transforms.Lambda(lambda t: (t * 2) - 1) # Scales to [-1, 1]
])
x0 = transform(img).unsqueeze(0) # Add a batch dimension

# Visualize at different timesteps
timesteps_to_show = [0, 50, 100, 150, 200, 299]
fig, axes = plt.subplots(1, len(timesteps_to_show), figsize=(15, 3))

for i, t_int in enumerate(timesteps_to_show):
    t = torch.tensor([t_int])
    xt = q_sample(x0, t, alphas_cumprod)
    
    # Reverse the transformation for plotting
    img_plot = (xt.squeeze(0).clamp(-1, 1) + 1) / 2
    axes[i].imshow(img_plot.permute(1, 2, 0))
    axes[i].set_title(f"Timestep {t_int}")
    axes[i].axis('off')

plt.show()

Running this code should produce a series of images, starting with your original image and progressively becoming noisier, eventually looking like static. This visually confirms that you have successfully implemented the forward diffusion process.


Conclusion

In this lesson, you've established the theoretical and practical foundation of the diffusion process. Here are the key takeaways:

  • The forward process is a fixed Markov chain that gradually adds Gaussian noise to data according to a variance schedule .
  • A crucial "nice property" allows us to sample a noised image directly from the original using the formula .
  • This direct sampling equation is efficient and forms the basis for training diffusion models. You've now implemented and verified it in PyTorch.

You're now perfectly positioned for the next lesson, where we'll tackle the more complex and exciting part: the reverse process. We will introduce a neural network designed to predict the noise that was added, and by doing so, learn to reverse the diffusion process one step at a time to generate new, high-quality images from pure noise.