nic hoffs

Langevin Sampling

# demo of langevin sampling
# https://yang-song.net/blog/2021/score/

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation

np.random.seed(42)

TIMESTEPS = 100
NUM_SAMPLES = 100

std = 0.1

mean1 = 0
mean2 = 1

mixing_coefficient = 0.5

# plotting setup
fig, ax = plt.subplots()
ax.set_xlim(-1, 2)
ax.set_ylim(-1, 2)

# initialize chain from arbitrary distribution, let's say uniform
initial = np.random.uniform(-1, 2, (NUM_SAMPLES, 2))

step_size = 1e-3


def gradient_log_likelihood(x):
    def score_component(x, mu):
        return -(x - mu) / (std**2)

    def density_component(x, mu):
        return np.exp(-np.sum((x - mu) ** 2, axis=1) / (2 * std**2))

    p1 = density_component(x, mu=np.array([mean1, mean1]))
    p2 = density_component(x, mu=np.array([mean2, mean2]))

    score1 = score_component(x, mu=np.array([mean1, mean1]))
    score2 = score_component(x, mu=np.array([mean2, mean2]))

    numerator = (
        mixing_coefficient * p1[:, None] * score1
        + (1 - mixing_coefficient) * p2[:, None] * score2
    )
    denominator = (mixing_coefficient * p1 + (1 - mixing_coefficient) * p2)[:, None]

    return numerator / denominator


def update_particles(previous):
    gll = gradient_log_likelihood(previous)
    noise = np.sqrt(2 * step_size) * np.random.normal(0, 1, (NUM_SAMPLES, 2))
    return previous + step_size * gll + noise


current = initial.copy()
scat = ax.scatter(initial[:, 0], initial[:, 1], alpha=0.3, label="Particles")


def update(frame):
    global current
    current = update_particles(current)
    scat.set_offsets(current)
    return (scat,)


ax.scatter(np.array([0, 1]), np.array([0, 1]), label="Ground-Truth Means")

ani = FuncAnimation(fig, update, frames=TIMESTEPS, interval=50, blit=True, repeat=False)
plt.legend()
plt.title("Langevin Sampling on Mixture of Two Gaussians")
ani.save("langevin.gif", writer="ffmpeg", fps=20)

langevin