|
| 1 | +""" |
| 2 | +This is to replicate the Figure 7 in Maximal Couplings of the Metropolis-Hastings. |
| 3 | +As discussed with authors, there was a mistake in their code so that P_{MR} works better than P_C. |
| 4 | +
|
| 5 | +""" |
| 6 | + |
| 7 | +from functools import partial |
| 8 | + |
| 9 | +import jax |
| 10 | +import jax.numpy as jnp |
| 11 | +import numpy as np |
| 12 | +import tqdm |
| 13 | +import matplotlib.pyplot as plt |
| 14 | +from coupled_rejection_sampling.mvn import reflection_maximal |
| 15 | +from coupled_rejection_sampling.reflected_coupled_mh import reflection_coupled_mh |
| 16 | + |
| 17 | +DS = np.arange(1, 21, dtype=int) |
| 18 | +M = 1_000 |
| 19 | +JAX_KEY = jax.random.PRNGKey(42) |
| 20 | + |
| 21 | + |
| 22 | +@partial(jax.jit, static_argnums=(0, 1)) |
| 23 | +def experiment(D, kernel): |
| 24 | + |
| 25 | + @jax.vmap |
| 26 | + def get_meeting_time(key): |
| 27 | + def cond(carry): |
| 28 | + return ~carry[-1] |
| 29 | + |
| 30 | + def body(carry): |
| 31 | + curr_k, x, y, i, *_ = carry |
| 32 | + curr_k, op_k = jax.random.split(curr_k) |
| 33 | + x, y, coupled = kernel(op_k, x, y) |
| 34 | + return curr_k, x, y, i + 1, coupled |
| 35 | + |
| 36 | + loop_key, init_key = jax.random.split(key, 2) |
| 37 | + x0, y0 = jax.random.normal(init_key, (2, D)) |
| 38 | + |
| 39 | + *_, coupling_time, _ = jax.lax.while_loop(cond, body, (loop_key, x0, y0, 0, False)) |
| 40 | + return coupling_time |
| 41 | + |
| 42 | + return get_meeting_time(jax.random.split(JAX_KEY, M)) |
| 43 | + |
| 44 | + |
| 45 | +@jax.jit |
| 46 | +def status_quo_kernel(key, x, y, chol): |
| 47 | + proposal_key, acceptance_key = jax.random.split(key) |
| 48 | + x_prop, y_prop, coupled_prop = reflection_maximal(proposal_key, 1, x, y, chol) |
| 49 | + x_prop, y_prop, coupled_prop = x_prop[0], y_prop[0], coupled_prop[0] |
| 50 | + |
| 51 | + log_alpha_x = -0.5 * jnp.sum(x_prop ** 2) + 0.5 * jnp.sum(x ** 2) |
| 52 | + log_alpha_y = -0.5 * jnp.sum(y_prop ** 2) + 0.5 * jnp.sum(y ** 2) |
| 53 | + |
| 54 | + log_u = jnp.log(jax.random.uniform(acceptance_key)) |
| 55 | + accept_x = log_u < log_alpha_x |
| 56 | + accept_y = log_u < log_alpha_y |
| 57 | + |
| 58 | + x = jax.lax.select(accept_x, x_prop, x) |
| 59 | + y = jax.lax.select(accept_y, y_prop, y) |
| 60 | + |
| 61 | + coupled = accept_x & accept_y & coupled_prop |
| 62 | + return x, y, coupled |
| 63 | + |
| 64 | + |
| 65 | +@jax.jit |
| 66 | +def reflected_mh_kernel(key, x, y, chol): |
| 67 | + target_logpdf = lambda z: -0.5 * jnp.sum(z ** 2) |
| 68 | + proposal = lambda k, z: z + chol @ jax.random.normal(k, z.shape) |
| 69 | + proposal_logpdf = lambda z, z_prime: 0. # Symmetric proposal, so doesn't matter |
| 70 | + step = reflection_coupled_mh(proposal, proposal_logpdf, target_logpdf) |
| 71 | + (x, _), (y, _), coupled = step(key, x, y) |
| 72 | + return x, y, coupled |
| 73 | + |
| 74 | + |
| 75 | +if __name__ == "__main__": |
| 76 | + status_quo_res = np.empty((len(DS), M)) |
| 77 | + reflected_res = np.empty((len(DS), M)) |
| 78 | + |
| 79 | + for i, d in enumerate(tqdm.tqdm(DS)): |
| 80 | + proposal_chol = 2.38 * np.eye(d) / d ** 0.5 |
| 81 | + status_quo_res[i] = experiment(d, partial(status_quo_kernel, chol=proposal_chol)) |
| 82 | + reflected_res[i] = experiment(d, partial(reflected_mh_kernel, chol=proposal_chol)) |
| 83 | + |
| 84 | + plt.plot(DS, np.mean(status_quo_res, -1), label="$P_{SQ}$ with $Q_{MR}$", c="tab:blue") |
| 85 | + plt.fill_between(DS, |
| 86 | + np.percentile(status_quo_res, 5, -1), |
| 87 | + np.percentile(status_quo_res, 95, -1), |
| 88 | + color="tab:blue", alpha=0.5) |
| 89 | + plt.plot(DS, np.mean(reflected_res, -1), label="$P_{MR}$", c="tab:orange") |
| 90 | + plt.fill_between(DS, |
| 91 | + np.percentile(reflected_res, 5, -1), |
| 92 | + np.percentile(reflected_res, 95, -1), |
| 93 | + color="tab:orange", alpha=0.5) |
| 94 | + plt.legend() |
| 95 | + plt.show() |
0 commit comments