Cosine Noise Schedule
Problem Statement
Implement the cosine noise schedule from Improved Denoising Diffusion Probabilistic Models (Nichol & Dhariwal, 2021), which is defined on αˉ directly and only afterwards converted back into betas.
Background
The linear schedule destroys information too fast: by the middle of the trajectory the image is already almost pure noise, so the later steps contribute little. The cosine schedule instead defines
f(t)=cos2(1+st/T+s⋅2π),αˉt=f(0)f(t)
for t=0,1,…,T, where the small offset s=0.008 keeps β1 from becoming vanishingly small. By construction αˉ0=1 (no noise) and αˉT≈0 (pure noise).
The betas are then recovered from the ratio of consecutive cumulative products:
βt=1−αˉt−1αˉt
Near t=T this ratio goes to zero and βt approaches 1, which makes the sampler unstable, so the paper clips every beta to at most 0.999.
Your Task
Implement:
def cosine_schedule(T, s=0.008):
Return (alpha_bars, betas), both 1-D NumPy arrays of length T covering t=1,…,T (0-based index i is t=i+1). alpha_bars must be the unclipped αˉt values; betas must be clipped to the range [0, 0.999].
Input Format
- T (int): number of diffusion steps, T >= 1.
- s (float): the small offset, default 0.008.
Output Format
A tuple (alpha_bars, betas) of NumPy arrays of length T.
Sample
ab, b = cosine_schedule(4)
print(np.round(ab, 4).tolist())
print(np.round(b, 4).tolist())
With T = 4 the cosine curve drops from just below 1 down to essentially 0, and the betas grow monotonically, with the final one hitting the 0.999 clip.
Example:
ab, b = cosine_schedule(4) print(np.round(ab, 4).tolist()) print(np.round(b, 4).tolist())
[0.847, 0.4938, 0.1443, 0.0] [0.153, 0.417, 0.7079, 0.999]
With T = 4 and s = 0.008 the offset grid is t/T = 0, 0.25, 0.5, 0.75, 1. Each maps through cos^2(((t/T + s)/(1+s)) * pi/2), and dividing by the t = 0 value normalises alpha_bar_0 to 1. Dropping that first entry leaves the four returned alpha-bars, which decay smoothly to ~0. Each beta is one minus the ratio of consecutive alpha-bars; the last ratio is essentially 0, so the raw beta would be ~1 and the 0.999 clip takes effect.
Constraints:
- Evaluate f on the T+1 grid points t=0,1,…,T, then divide by f(0).
- Return only the
Tentries for t≥1. - βt uses the ratio to the previous αˉ, with αˉ0=1.
- Clip betas to
[0, 0.999]. Do not clipalpha_bars. - No rounding inside the function.
1. Background Knowledge
Diffusion models operate by gradually adding Gaussian noise to data over a fixed number of steps T. The core of this process is the noise schedule, which dictates how much noise is added at each step. The parameter βt represents the variance of the noise added at step t, while αˉt represents the cumulative product of the signal retention factors up to step t. Specifically, αˉt=∏i=1t(1−βi). In the forward process, the noisy data xt is related to the original data x0 by xt=αˉtx0+1−αˉtϵ, where ϵ is standard Gaussian noise.
The linear noise schedule simply sets βt to increase linearly from a small value to a larger one. However, this causes information to be lost too quickly in the early stages, leaving the later stages with little signal to recover. The cosine schedule, introduced in Improved Denoising Diffusion Probabilistic Models (Nichol & Dhariwal, 2021), addresses this by defining αˉt directly using a cosine function. This ensures that the signal remains strong for a longer portion of the trajectory, allowing the model to learn more effectively from the intermediate steps. The schedule is defined as αˉt=f(0)f(t), where f(t)=cos2(1+st/T+s⋅2π) and s is a small offset to prevent β1 from being too small.
Once αˉt is computed for all t, the individual noise levels βt are derived from the ratio of consecutive cumulative alphas: βt=1−αˉt−1αˉt. This relationship stems from the definition αˉt=αˉt−1(1−βt). Since αˉt approaches 0 as t approaches T, the ratio αˉt−1αˉt can become very small, causing βt to approach 1. To maintain numerical stability during sampling, βt is clipped to a maximum value of 0.999.
2. Algorithm Approach
The problem requires implementing a specific mathematical function to generate two arrays: alpha_bars and betas. The approach involves vectorized computation using NumPy to efficiently calculate the cosine schedule for all time steps simultaneously.
- Generate Time Steps: Create an array of time indices t from 1 to T.
- Compute αˉt: Apply the cosine formula directly to these time steps to get the cumulative signal retention factors. Ensure normalization by f(0).
- Compute βt: Use the relationship between consecutive αˉ values to derive βt. This involves shifting the alpha_bars array to align αˉt−1 with αˉt.
- Clip βt: Apply the clipping constraint to ensure no βt exceeds 0.999.
- Return Results: Return the unclipped alpha_bars and the clipped betas.
The key insight is that αˉt is computed independently for each t, while βt depends on the ratio of adjacent αˉ values. Vectorization allows this to be done without explicit loops, leveraging NumPy's broadcasting and slicing capabilities.
3. Step-by-Step Strategy
- Define Time Array: Create a NumPy array t containing integers from 1 to T. This represents the diffusion steps.
- Calculate f(t): Compute the cosine squared function for each t. The argument to the cosine is 1+st/T+s⋅2π. Use np.cos and square the result.
- Normalize to get αˉt: Calculate f(0) by substituting t=0 into the formula for f(t). Divide the array f(t) by f(0) to obtain alpha_bars. This ensures αˉ0=1 (though we only return t=1…T).
- Prepare for βt Calculation: To compute βt=1−αˉt−1αˉt, you need access to αˉt−1. Create a shifted version of alpha_bars where the first element is αˉ0=1 and the rest are alpha_bars[:-1]. Let's call this alpha_bars_prev.
- Compute Raw Betas: Calculate betas_raw = 1 - alpha_bars / alpha_bars_prev. This gives the unclipped noise levels.
- Clip Betas: Use np.clip(betas_raw, 0, 0.999) to ensure all values are within the valid range. This prevents numerical instability in the reverse process.
- Return: Return the tuple (alpha_bars, betas_clipped).
4. Common Pitfalls
- Indexing Errors: The problem specifies that the output arrays should cover t=1,…,T. Be careful with 0-based indexing in NumPy. When computing βt, you need αˉt−1. For t=1, this is αˉ0, which is not in the alpha_bars array (since it starts at t=1). You must explicitly handle αˉ0=1.
- Division by Zero: Although αˉ0=1 and αˉt>0 for t<T, ensure that your implementation of f(0) is correct. If s is not handled correctly, f(0) might not be 1, leading to incorrect normalization.
- Clipping Order: The problem states that alpha_bars must be unclipped, while betas must be clipped. Do not clip alpha_bars. Also, ensure that the clipping is applied to betas after they are computed from the ratio, not before.
- Floating Point Precision: When t is close to T, αˉt becomes very small. The ratio αˉt−1αˉt might suffer from precision issues, but the clipping to 0.999 mitigates the impact on the final result. Ensure you use float64 precision if necessary, though float32 is often sufficient.
- Formula Misinterpretation: The formula for f(t) involves cos2, which means you should square the result of np.cos(), not pass a squared argument to np.cos().
5. Time & Space Complexity
- Time Complexity: The algorithm involves vectorized operations on arrays of size T. Computing f(t), normalizing, shifting, and clipping all take O(T) time. Thus, the overall time complexity is O(T).
- Space Complexity: The algorithm stores several arrays of size T: t, f(t), alpha_bars, alpha_bars_prev, betas_raw, and betas_clipped. Each of these requires O(T) space. Therefore, the overall space complexity is O(T).
This efficiency makes the cosine schedule suitable for large T values, such as T=1000 or T=2000, commonly used in diffusion models.