@nnx.jit
def annealed_step(score, x, key, sigma, alpha):
sigma_vec = jnp.full((len(x),), sigma)
return (x + 0.5 * alpha * score(x, sigma_vec)
+ alpha ** 0.5 * jax.random.normal(key, x.shape))
def annealed_langevin(score, x, key, sigmas, steps_per_level=100, scale=0.1):
"""Langevin dynamics at each level of the ladder, coarse to fine."""
for sigma in sigmas:
alpha = scale * sigma ** 2 # alpha_i = eps * sigma_i^2 / sigma_L^2
for _ in range(steps_per_level):
key, subkey = jax.random.split(key)
x = annealed_step(score, x, subkey, sigma, alpha)
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(float(w), 3) for w in
jnp.bincount(d2.argmin(1), length=3) / len(x)]
nearest = d2.min(1)
inside = nearest < (3 * mix.std) ** 2
return (f'mode weights {weights}, spread {nearest[inside].mean():.2f}, '
f'stray {1 - inside.mean():.3f}')
key, subkey = jax.random.split(key)
x_init = 3 * jax.random.normal(subkey, (2000, 2))
print(f'target (2000 samples of the mixture): {summarize(data)}')
for steps in (20, 100):
key, subkey = jax.random.split(key)
x = annealed_langevin(score, x_init, subkey, sigmas, steps_per_level=steps)
print(f'annealed, {steps:>3d} steps per level: {summarize(x)}')
key, subkey = jax.random.split(key)
x_single = annealed_langevin(score, x_init, subkey, sigmas[-1:],
steps_per_level=1000)
print(f'finest level only, 1000 steps: {summarize(x_single)}')