def annealed_langevin(score, x, sigmas, steps_per_level=100, scale=0.1):
"""Langevin dynamics at each level of the ladder, coarse to fine."""
with torch.no_grad():
for sigma in sigmas:
alpha = scale * sigma ** 2 # alpha_i = eps * sigma_i^2 / sigma_L^2
sigma_vec = torch.full((len(x),), sigma.item())
for _ in range(steps_per_level):
x = (x + 0.5 * alpha * score(x, sigma_vec)
+ alpha ** 0.5 * torch.randn_like(x))
return x
def summarize(x):
"""Mode weights, within-mode spread, and the fraction of stray chains."""
d2 = ((x[:, None, :] - mix.means[None]) ** 2).sum(-1)
weights = [round(w, 3) for w in
(torch.bincount(d2.argmin(1), minlength=3) / len(x)).tolist()]
nearest = d2.min(1).values
inside = nearest < (3 * mix.std) ** 2
return (f'mode weights {weights}, spread {nearest[inside].mean():.2f}, '
f'stray {1 - inside.float().mean():.3f}')
torch.manual_seed(2)
x_init = 3 * torch.randn(2000, 2)
print(f'target (2000 samples of the mixture): {summarize(data)}')
for steps in (20, 100):
x = annealed_langevin(score, x_init, sigmas, steps_per_level=steps)
print(f'annealed, {steps:>3d} steps per level: {summarize(x)}')
x_single = annealed_langevin(score, x_init, sigmas[-1:], steps_per_level=1000)
print(f'finest level only, 1000 steps: {summarize(x_single)}')