PIXELBANKv9.1.0
Menu

DDPM Posterior Mean and Variance

Problem Statement

Compute the exact reverse-process posterior q(xt−1∣xt,x0)q(x_{t-1} \mid x_t, x_0) -- the distribution the denoising network is trained to imitate.

Background

The reverse of the forward chain is intractable in general, but if you are also told x0x_0 it is available in closed form and it is Gaussian:

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

μ~t(xt,x0)=αˉt−1 βt1−αˉt x0  +  αt (1−αˉt−1)1−αˉt xt\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

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

with the convention αˉ−1=1\bar{\alpha}_{-1} = 1 for the very first step.

Both coefficients are positive, so the mean sits between x0x_0 and xtx_t: early in the reverse trajectory (large tt) almost all the weight is on xtx_t and the posterior barely moves, while near t=0t = 0 it swings onto x0x_0. (They sum to slightly less than 1, not exactly 1 -- the forward process shrinks the signal a little at every step.) At t=0t = 0 the convention αˉ−1=1\bar{\alpha}_{-1} = 1 makes the coefficients exactly (1,0)(1, 0) and β~0=0\tilde{\beta}_0 = 0: knowing x0x_0 pins x−1=x0x_{-1} = x_0 with no uncertainty left. Handling that boundary is the part implementations get wrong.

Your Task

Implement:

def posterior(x0, x_t, t, betas):

Derive alphas and alpha_bars from betas inside the function. Return (mean, var) where mean is an array shaped like x0 and var is a Python float.

Input Format

  • x0, x_t: NumPy arrays of the same shape.
  • t (int): 0-based timestep index, 0 <= t < len(betas).
  • betas: 1-D NumPy array of the noise schedule.

Output Format

A tuple (mean, var): a NumPy array shaped like x0, and a float.

Sample

betas = np.array([0.1, 0.2, 0.3])
x0 = np.array([1.0, -1.0])
x_t = np.array([0.4, 0.2])
m, v = posterior(x0, x_t, 1, betas)
print(np.round(m, 4).tolist())
print(round(v, 4))

Here alpha_bar_1 = 0.72, alpha_bar_0 = 0.9, so the coefficients are *sqrt(0.9)0.2/0.28 on x0 and *sqrt(0.8)0.1/0.28 on x_t, and the variance is *(0.1/0.28)0.2.

Example:

Input:
betas = np.array([0.1, 0.2, 0.3])
x0 = np.array([1.0, -1.0])
x_t = np.array([0.4, 0.2])
m, v = posterior(x0, x_t, 1, betas)
print(np.round(m, 4).tolist())
print(round(v, 4))
Output:
[0.8054, -0.6137]
0.0714
Reasoning:

alphas = [0.9, 0.8, 0.7], so alpha_bar_0 = 0.9 and alpha_bar_1 = 0.72, giving 1 - alpha_bar_1 = 0.28. The x0 coefficient is sqrt(0.9)*0.2/0.28 = 0.6776 and the x_t coefficient is sqrt(0.8)*0.1/0.28 = 0.3194, so the mean is 0.6776*1.0 + 0.3194*0.4 = 0.8054 and 0.6776*(-1.0) + 0.3194*0.2 = -0.6137. The variance is (0.1/0.28)*0.2 = 0.0714.

Constraints:

  • 0 <= t < len(betas).
  • Use αˉ−1=1\bar{\alpha}_{-1} = 1 when t == 0; the mean is then exactly x0 and the variance exactly 0.0.
  • The x0x_0 coefficient uses the cumulative αˉt−1\bar{\alpha}_{t-1}, while the xtx_t coefficient uses the per-step αt\alpha_t together with 1−αˉt−11-\bar{\alpha}_{t-1}; mixing the two up is the usual bug.
  • Return var as a plain Python float.
  • Do not round inside the function.
solution.py

Test Results

0/0
Run code to see test results.
DDPM Posterior Mean and Variance - Medium | PixelBank