1. Background Knowledge
In DDPM (Denoising Diffusion Probabilistic Models), the reverse process q(xt−1∣xt,x0) is a Gaussian whose mean and variance are derived from the forward diffusion schedule. The variance of this posterior, often denoted β~t, is given by:
β~t=1−αˉt1−αˉt−1βt
where αˉt=∏s=1tαs and αt=1−βt. This β~t is the theoretically optimal variance for the reverse step. However, Improved DDPM (Nichol & Dhariwal, 2021) observes that letting the network learn the variance can improve sample quality. Instead of fixing the variance to β~t or βt, the model outputs a scalar v∈[−1,1] per dimension, which is mapped to an interpolation factor f∈[0,1].
The learned variance is computed in log-space to ensure positivity and numerical stability:
logΣt=flogβt+(1−f)logβ~t
This is a geometric interpolation between the two bounds. When f=0 (i.e., v=−1), Σt=β~t; when f=1 (i.e., v=+1), Σt=βt. The mapping f=2v+1 linearly transforms the network output from [−1,1] to [0,1].
2. Algorithm Approach
The problem is a direct element-wise computation with no iterative or search-based logic. For each dimension:
- Compute the posterior variance β~t from the given schedule parameters.
- Map the raw network output v to the interpolation factor f.
- Compute the log-interpolated variance and exponentiate to get Σt.
- Round to 6 decimal places.
This is a vectorizable operation: all dimensions share the same βt, αˉt, and αˉt−1, so β~t is computed once, and the per-dimension work is just the f-mapping and log-interpolation.
3. Step-by-Step Strategy
- Compute β~t: Use the formula β~t=1−αˉt1−αˉt−1βt. This is a single scalar value shared across all dimensions.
- Precompute logs: Calculate logβt and logβ~t once. These are constants for the entire list.
- Loop over each vi:
- Compute fi=2vi+1.
- Compute logΣi=fi⋅logβt+(1−fi)⋅logβ~t.
- Compute Σi=exp(logΣi).
- Round Σi to 6 decimal places.
- Collect results into a list and return.
import math
def learned_variance(v, beta_t, alpha_bar_t, alpha_bar_prev):
beta_tilde = (1 - alpha_bar_prev) / (1 - alpha_bar_t) * beta_t
log_beta = math.log(beta_t)
log_beta_tilde = math.log(beta_tilde)
result = []
for vi in v:
f = (vi + 1) / 2.0
log_sigma = f * log_beta + (1 - f) * log_beta_tilde
sigma = math.exp(log_sigma)
result.append(round(sigma, 6))
return result
4. Common Pitfalls
- Confusing αˉt and αˉt−1: The posterior formula uses αˉt−1 in the numerator and αˉt in the denominator. Swapping them gives an incorrect β~t.
- Forgetting to exponentiate: The interpolation is done in log-space. If you forget the final exp(⋅), you return log-variances instead of variances.
- Incorrect f mapping: The mapping is f=2v+1, not f=v or f=2v−1. Verify with the boundary conditions: v=−1⇒f=0, v=+1⇒f=1.
- Rounding too early: Round only the final variance, not intermediate values like f or logΣt. Premature rounding can accumulate error.
- Division by zero: If αˉt=1, the denominator 1−αˉt is zero. In practice, αˉt<1 for all finite t, but be aware of this edge case.
5. Time & Space Complexity
- Time: O(n) where n=len(v). Each dimension requires a constant number of arithmetic operations (one division, one multiplication, one addition, one exponential). The precomputation of β~t and its log is O(1).
- Space: O(n) for the output list. No additional data structures are needed beyond the input and output.