Learned Interpolated Posterior Variance
Problem Statement
Improved-DDPM lets the network learn the reverse variance by interpolating, in log space, between the two natural bounds beta_t and the posterior beta_tilde_t. Implement that interpolation from the network's raw output.
Background
The model emits a value v per dimension in [-1, 1] (after a tanh, say). The reverse variance is
Σt=exp(flogβt+(1−f)logβ~t),f=2v+1
where f maps v from [-1, 1] to [0, 1]. The posterior lower bound is beta_tilde_t = (1 - alpha_bar_prev)/(1 - alpha_bar_t) * beta_t. At v = -1 the variance is exactly beta_tilde_t; at v = +1 it is beta_t.
Your Task
Implement:
def learned_variance(v, beta_t, alpha_bar_t, alpha_bar_prev):
- v: list of per-dimension model outputs in [-1, 1].
Return the list of variances rounded to 6 decimals.
Input Format
- v: list of floats in [-1, 1].
- beta_t, alpha_bar_t, alpha_bar_prev (float).
Output Format
- A list of floats rounded to 6 decimals.
Sample
print(learned_variance([1.0, -1.0, 0.0], 0.2, 0.5, 0.64))
Output:
[0.2, 0.144, 0.169706]
Example:
print(learned_variance([1.0, -1.0, 0.0], 0.2, 0.5, 0.64))
[0.2, 0.144, 0.169706]
- Compute the posterior lower bound β~t using the given parameters: β~t=1−0.51−0.64×0.2=0.50.36×0.2=0.72×0.2=0.144.
- Determine the interpolation factor f for each dimension in v=[1.0,−1.0,0.0] using f=2v+1, yielding f=[1.0,0.0,0.5].
- Calculate the log-variance for each dimension via logΣt=flogβt+(1−f)logβ~t:
- For v=1.0 (f=1): logΣ=1⋅log(0.2)+0⋅log(0.144)=log(0.2).
- For v=−1.0 (f=0): logΣ=0⋅log(0.2)+1⋅log(0.144)=log(0.144).
- For v=0.0 (f=0.5): logΣ=0.5⋅log(0.2)+0.5⋅log(0.144)=log(0.2×0.144).
- Exponentiate the log-variances to obtain the final variances:
- Σ1=exp(log(0.2))=0.2.
- Σ2=exp(log(0.144))=0.144.
- Σ3=0.2×0.144=0.0288≈0.1697056.
- The final output is [0.2, 0.144, 0.169706]
Constraints:
v[i]in[-1, 1]; schedule values in(0, 1).beta_tilde = (1 - alpha_bar_prev)/(1 - alpha_bar_t)*beta_t.- Interpolate in log space with
f = (v+1)/2; round to 6 decimals.
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βtwhere αˉ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β~tThis 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.