Take one step of the DDPM reverse chain: given xt and the network's predicted noise, produce xt−1.
Substituting x^0=(xt−1−αˉtε^)/αˉt into the posterior mean and simplifying gives the form Algorithm 2 of DDPM actually uses:
xt−1=αt1(xt−1−αˉtβtε^θ(xt,t))+σtz,z∼N(0,I)
The mean subtracts a small fraction of the predicted noise -- βt/1−αˉt, not all of it -- and then rescales. Removing all the noise at once would be the DDIM jump straight to x^0, not an ancestral step.
For the variance DDPM reports two choices that work about equally well:
At t=0 no noise is added at all -- the final step must output a clean sample, and β~0 is zero anyway.
Implement:
def ddpm_step(x_t, eps, t, betas, z, variance_type="posterior"):
Derive alphas and alpha_bars from betas inside the function. Return xt−1 as an array shaped like x_t. Raise ValueError for an unknown variance_type.
A NumPy array shaped like x_t.
betas = np.array([0.1, 0.2, 0.3])
x_t = np.array([0.5, -0.5])
eps = np.array([0.2, 0.1])
z = np.array([1.0, -1.0])
print(np.round(ddpm_step(x_t, eps, 1, betas, z), 4).tolist())
alpha_1 = 0.8, alpha_bar_1 = 0.72, so the mean is (x_t - 0.2/sqrt(0.28) * eps) / sqrt(0.8), and *sigma = sqrt((0.1/0.28)0.2) is added times z.
betas = np.array([0.1, 0.2, 0.3]) x_t = np.array([0.5, -0.5]) eps = np.array([0.2, 0.1]) z = np.array([1.0, -1.0]) print(np.round(ddpm_step(x_t, eps, 1, betas, z), 4).tolist())
[0.7418, -0.8685]
At t = 1: beta = 0.2, alpha = 0.8, alpha_bar = 0.72, so 1 - alpha_bar = 0.28. The bracket is x_t - (0.2/sqrt(0.28))*eps, divided by sqrt(0.8). Since t > 0, noise is added with sigma = sqrt((1-0.9)/0.28 * 0.2) = 0.2673, scaled by z = [1, -1].
0 <= t < len(betas).t == 0, return the mean with no noise term.variance_type must raise ValueError.