Sampling with the Score
Once you have \(s_\theta(x) \approx \nabla_x \log p(x)\) , sample via Langevin dynamics:
\[ x_{t+1} = x_t + \frac{\eta}{2} s_\theta(x_t) + \sqrt{\eta}\, \xi_t, \quad \xi_t \sim \mathcal{N}(0, I) \]
As \(\eta \to 0\) and \(T \to \infty\) , \(x_T \sim p_\theta(x)\) .
For multi-scale: use annealed Langevin dynamics — start with high noise score, gradually decrease.
How to Sample
- Start from random noise: \(x_0 \sim \mathcal{N}(0, I)\)
- Iteratively update:
\[
x_{t+1} = x_t + \frac{\eta}{2} s_\theta(x_t) + \sqrt{\eta}\, \xi_t, \quad \xi_t \sim \mathcal{N}(0, I)
\]
- The score \(s_\theta(x_t)\) pushes \(x\) toward high-density regions
- The noise \(\xi_t\) ensures exploration of the full distribution
- As \(\eta \to 0\) and \(T \to \infty\), samples converge to \(p(x)\)
Annealed Langevin Dynamics (practical version)
- Use decreasing noise levels \(\sigma_1 > \sigma_2 > \cdots > \sigma_L\)
- At each level, run a few Langevin steps using the score trained at that noise level
- High-noise scores provide a coarse landscape to avoid getting stuck in low-density regions early on
Test: Sample from a Known Gaussian
import torch def langevin_sample(score_fn, n_steps=1000, eta=0.01, dim=2, n_samples=64): x = torch.randn(n_samples, dim) for _ in range(n_steps): x = x + (eta / 2) * score_fn(x) + torch.sqrt(torch.tensor(eta)) * torch.randn_like(x) return x # Score of N(mu, I) is -(x - mu) mu = torch.tensor([3.0, -2.0]) score_fn = lambda x: -(x - mu) samples = langevin_sample(score_fn, n_steps=2000, eta=0.01, dim=2, n_samples=1000) print(f"Mean: {samples.mean(0)}") # should be close to [3, -2] print(f"Std: {samples.std(0)}") # should be close to [1, 1]
Mean: tensor([ 2.9596, -1.9918]) Std: tensor([1.0213, 0.9915])