AL for surrogate LnL (linear regression test)#

Test to run inference on some line data with a surrogate lnl likelihood trained on the line data’s Gaussian Likelihood

import os
from typing import Callable

import bilby
import matplotlib.pyplot as plt
import numpy as np

from compas_surrogate.surrogate.models import SklearnGPModel
from compas_surrogate.surrogate.surrogate_likelihood import SurrogateLikelihood

np.random.seed(0)

SIGMA = 0.25
M_RANGE = (0, 5)
C_RANGE = (-2, 2)
OUTDIR = "out_inference_run"


def get_priors():
    priors = dict()
    priors["m"] = bilby.core.prior.Uniform(*M_RANGE, "m")
    priors["c"] = bilby.core.prior.Uniform(*C_RANGE, "c")
    return bilby.core.prior.PriorDict(priors)


def get_prior_samples_in_grid(n_grid_cells=20):
    m, c = np.meshgrid(
        np.linspace(*M_RANGE, n_grid_cells),
        np.linspace(*C_RANGE, n_grid_cells),
    )
    m, c = m.ravel(), c.ravel()
    return np.array([m, c]).T


def generate_observed_data(
    func: Callable, func_kwargs, n=50, sigma=SIGMA
) -> np.ndarray:
    x = np.random.uniform(0, 1, n)
    x = np.sort(x)
    y = func(x, **func_kwargs) + np.random.normal(0, sigma, n)
    X = [[i] for i in x]
    Y = [[i] for i in y]
    return np.array([X, Y])


def get_likelihood_fn(x_data, obs_y_data, model) -> Callable:
    return bilby.core.likelihood.GaussianLikelihood(
        x_data, obs_y_data, model, sigma=SIGMA
    )


def get_likelihood_surrogate(lnl, clean=False) -> bilby.likelihood.Likelihood:
    model_path = f"{OUTDIR}/surr_model"
    if clean or not os.path.exists(model_path):
        print("Training surrogate model")
        mc_samples = get_prior_samples_in_grid(n_grid_cells=10)
        lnl = generate_lnl_data(lnl, mc_samples[:, 0], mc_samples[:, 1])
        lnl = lnl.reshape(-1, 1)
        model = SklearnGPModel()
        model.train(mc_samples, lnl, verbose=True)
        model.save(model_path)
        print("Surrogate model saved")
    else:
        model = SklearnGPModel.load(model_path)

    likelihood = SurrogateLikelihood(model, ["m", "c"])
    return likelihood


def generate_lnl_data(lnl, m, c) -> np.ndarray:
    n = len(m)
    lnl_vals = np.zeros(n)
    if isinstance(lnl, SurrogateLikelihood):
        _, lnl_vals, _ = lnl.surr(np.array([m, c]).T)
        lnl_vals = lnl_vals.ravel()
    else:
        for i, (m_i, c_i) in enumerate(zip(m, c)):
            lnl.parameters = {"m": m_i, "c": c_i, "sigma": SIGMA}
            lnl_vals[i] = lnl.log_likelihood()
    return lnl_vals


def plot_lnl_surface(
    lnl,
    true_vals,
    label="true",
    cmap="viridis",
    fig=None,
    savefn=None,
    kwgs={},
):
    samples = get_prior_samples_in_grid(n_grid_cells=20)
    m, c = samples[:, 0], samples[:, 1]
    lnl_vals = generate_lnl_data(lnl, m, c)

    if fig is None:
        fig, ax = plt.subplots(1, 1, figsize=(4, 3))
    else:
        ax = fig.get_axes()[0]

    levels = np.array([-50, -30, -20, -10, 0])
    # contour plot
    cmap = plt.get_cmap(
        cmap,
    )
    cset = ax.contour(
        m.reshape(20, 20),
        c.reshape(20, 20),
        lnl_vals.reshape(20, 20),
        cmap=cmap,
        levels=levels,
        alpha=0.5,
        **kwgs,
    )
    if "ls" in kwgs:
        for c in cset.collections:
            c.set_linestyle(kwgs["ls"])
    if "lw" in kwgs:
        for c in cset.collections:
            c.set_linewidth(kwgs["lw"])

    ax.plot(-100, -100, label=label, color=cmap(100))

    ax.set_xlabel("m")
    ax.set_xlim(*M_RANGE)
    ax.set_ylabel("c")
    ax.set_ylim(*C_RANGE)

    if savefn is not None:
        ax.axvline(true_vals["m"], color="k", linestyle="--")
        plt.axhline(true_vals["c"], color="k", linestyle="--")
        plt.title("LnL Contours")
        plt.tight_layout()
        plt.legend(frameon=False)
        fig.savefig(savefn, dpi=300)
    return fig


def plot_corner(result: bilby.result.Result, color="k", fig=None, savefn=None):
    if fig is None:
        fig = result.plot_corner(color=color)
    else:
        result.plot_corner(fig=fig, color=color)

    if savefn is not None:
        fig.savefig(savefn, dpi=300)
    return fig


def plot_posterior_predictive_check(
    obs,
    model,
    posterior,
    label="true",
    color="tab:blue",
    fig=None,
    savefn=None,
    **kwargs,
):
    # take 100 random samples from the posterior
    post = posterior.sample(100).to_numpy().T
    m, c = post[0], post[1]
    ys = np.array(model(obs[0], m, c))
    y_low, y_mean, y_up = np.quantile(ys, [0.05, 0.5, 0.95], axis=1)

    if fig is None:
        fig, ax = plt.subplots(1, 1, figsize=(4, 3))
    else:
        ax = fig.get_axes()[0]
    ax.scatter(obs[0].flatten(), obs[1].flatten(), color="k", zorder=10)
    ax.plot(obs[0], y_mean, label=label, color=color)
    ax.fill_between(obs[0].flatten(), y_low, y_up, alpha=0.1, color=color)
    ax.set_xlim(obs[0].min(), obs[0].max())
    if savefn is not None:
        plt.title("Posterior predictive check")
        plt.legend(frameon=False)
        plt.tight_layout()
        fig.savefig(savefn, dpi=300)
    return fig


def test_inference_run(clean=False):
    model = lambda x, m, c: x * m + c
    true_parameters = dict(m=3, c=0.2)
    obs_data = generate_observed_data(model, true_parameters, n=20)
    prior = get_priors()

    true_lnl = get_likelihood_fn(obs_data[0], obs_data[1], model)
    normal_result = bilby.run_sampler(
        likelihood=true_lnl,
        priors=prior,
        sampler="emcee",
        injection_parameters=true_parameters,
        nwalkers=50,
        outdir=OUTDIR,
        label="true_lnl",
        clean=clean,
    )

    surrogate_lnl = get_likelihood_surrogate(true_lnl, clean=clean)
    surr_result = bilby.run_sampler(
        likelihood=surrogate_lnl,
        priors=prior,
        sampler="emcee",
        injection_parameters=true_parameters,
        nwalkers=50,
        outdir=OUTDIR,
        label="surr_lnl",
    )

    lnl_surf_fig = plot_lnl_surface(
        true_lnl, true_parameters, label="true", cmap="Blues"
    )
    plot_lnl_surface(
        surrogate_lnl,
        true_parameters,
        cmap="Reds",
        label="surrogate",
        fig=lnl_surf_fig,
        savefn=f"{OUTDIR}/lnl_surf.png",
        kwgs=dict(ls="--", lw=2),
    )
    ppc = plot_posterior_predictive_check(
        obs_data,
        model,
        normal_result.posterior,
        label="Analytical",
        color="tab:red",
    )
    plot_posterior_predictive_check(
        obs_data,
        model,
        surr_result.posterior,
        label="Surrogate",
        color="tab:blue",
        fig=ppc,
        savefn=f"{OUTDIR}/ppc.png",
    )

    crner = plot_corner(normal_result, color="tab:red")
    plot_corner(surr_result, color="tab:blue", fig=crner, savefn=f"{OUTDIR}/corner.png")


if __name__ == "__main__":
    test_inference_run()
WARNING: Skipping /usr/local/lib/python3.11/site-packages/numpy-1.25.2-py3.11.egg-info due to invalid metadata entry 'name'
WARNING: Skipping /usr/local/lib/python3.11/site-packages/numpy-1.25.2-py3.11.egg-info due to invalid metadata entry 'name'
20:09 bilby INFO    : Analysis priors:
20:09 bilby INFO    : m=Uniform(minimum=0, maximum=5, name='m', latex_label='m', unit=None, boundary=None)
20:09 bilby INFO    : c=Uniform(minimum=-2, maximum=2, name='c', latex_label='c', unit=None, boundary=None)
20:09 bilby INFO    : Analysis likelihood class: <class 'bilby.core.likelihood.GaussianLikelihood'>
20:09 bilby INFO    : Analysis likelihood noise evidence: nan
20:09 bilby INFO    : Single likelihood evaluation took 7.495e-05 s
20:09 bilby INFO    : Using sampler Emcee with kwargs {'nwalkers': 50, 'a': 2, 'args': [], 'kwargs': {}, 'postargs': None, 'pool': None, 'live_dangerously': False, 'runtime_sortingfn': None, 'lnprob0': None, 'rstate0': None, 'blobs0': None, 'iterations': 100, 'thin': 1, 'storechain': True, 'mh_proposal': None}
20:09 bilby INFO    : Checkpointing sampler to file out_inference_run/emcee_true_lnl/sampler.pickle
20:09 bilby INFO    : Max autocorr time = 23
20:09 bilby INFO    : Discarding 69 steps for burn-in
20:09 bilby INFO    : Sampling time: 0:00:01.867138
20:09 bilby INFO    : Summary of results:
nsamples: 1550
ln_noise_evidence:    nan
ln_evidence:    nan +/-    nan
ln_bayes_factor:    nan +/-    nan

2023-08-23 20:09:31.230 | INFO     | compas_surrogate.surrogate.models.model:_preprocess_and_split_data:112 - Training surrogate In((80, 2))-->Out((80, 1)) [testing:20]
|COMPAS-SUR|23/08 20:09:31|INFO| Training surrogate In((80, 2))-->Out((80, 1)) [testing:20] 
Training surrogate model
20:09 bilby INFO    : Running for label 'surr_lnl', output will be saved to 'out_inference_run'
Surrogate model saved
WARNING: Skipping /usr/local/lib/python3.11/site-packages/numpy-1.25.2-py3.11.egg-info due to invalid metadata entry 'name'
WARNING: Skipping /usr/local/lib/python3.11/site-packages/numpy-1.25.2-py3.11.egg-info due to invalid metadata entry 'name'
WARNING: Skipping /usr/local/lib/python3.11/site-packages/numpy-1.25.2-py3.11.egg-info due to invalid metadata entry 'name'
20:09 bilby INFO    : Analysis priors:
20:09 bilby INFO    : m=Uniform(minimum=0, maximum=5, name='m', latex_label='m', unit=None, boundary=None)
20:09 bilby INFO    : c=Uniform(minimum=-2, maximum=2, name='c', latex_label='c', unit=None, boundary=None)
20:09 bilby INFO    : Analysis likelihood class: <class 'compas_surrogate.surrogate.surrogate_likelihood.SurrogateLikelihood'>
20:09 bilby INFO    : Analysis likelihood noise evidence: nan
20:09 bilby INFO    : Single likelihood evaluation took 3.864e-04 s
20:09 bilby INFO    : Using sampler Emcee with kwargs {'nwalkers': 50, 'a': 2, 'args': [], 'kwargs': {}, 'postargs': None, 'pool': None, 'live_dangerously': False, 'runtime_sortingfn': None, 'lnprob0': None, 'rstate0': None, 'blobs0': None, 'iterations': 100, 'thin': 1, 'storechain': True, 'mh_proposal': None}
20:09 bilby INFO    : Checkpointing sampler to file out_inference_run/emcee_surr_lnl/sampler.pickle
20:09 bilby INFO    : Max autocorr time = 20
20:09 bilby INFO    : Discarding 60 steps for burn-in
20:09 bilby INFO    : Sampling time: 0:00:03.123357
20:09 bilby INFO    : Summary of results:
nsamples: 2000
ln_noise_evidence:    nan
ln_evidence:    nan +/-    nan
ln_bayes_factor:    nan +/-    nan
../_images/3934c8607eb8a72396d603614d218bc0b049f347fc12d09e004a45f8da1f6ae5.png ../_images/f8ccb534fb253827951321b0db35a593a28ccf806931e6af9d5a0cdc9b69f696.png
20:09 bilby INFO    : Running for label 'true_lnl', output will be saved to 'out_inference_run'
WARNING: Skipping /usr/local/lib/python3.11/site-packages/numpy-1.25.2-py3.11.egg-info due to invalid metadata entry 'name'
WARNING: Skipping /usr/local/lib/python3.11/site-packages/numpy-1.25.2-py3.11.egg-info due to invalid metadata entry 'name'
WARNING: Skipping /usr/local/lib/python3.11/site-packages/numpy-1.25.2-py3.11.egg-info due to invalid metadata entry 'name'
20:09 bilby INFO    : Analysis priors:
20:09 bilby INFO    : m=Uniform(minimum=0, maximum=5, name='m', latex_label='m', unit=None, boundary=None)
20:09 bilby INFO    : c=Uniform(minimum=-2, maximum=2, name='c', latex_label='c', unit=None, boundary=None)
20:09 bilby INFO    : Analysis likelihood class: <class 'bilby.core.likelihood.GaussianLikelihood'>
20:09 bilby INFO    : Analysis likelihood noise evidence: nan
20:09 bilby INFO    : Single likelihood evaluation took 9.462e-05 s
20:09 bilby WARNING : Using cached result
20:09 bilby INFO    : Summary of results:
nsamples: 1550
ln_noise_evidence:    nan
ln_evidence:    nan +/-    nan
ln_bayes_factor:    nan +/-    nan

20:09 bilby INFO    : Running for label 'surr_lnl', output will be saved to 'out_inference_run'
WARNING: Skipping /usr/local/lib/python3.11/site-packages/numpy-1.25.2-py3.11.egg-info due to invalid metadata entry 'name'
WARNING: Skipping /usr/local/lib/python3.11/site-packages/numpy-1.25.2-py3.11.egg-info due to invalid metadata entry 'name'
WARNING: Skipping /usr/local/lib/python3.11/site-packages/numpy-1.25.2-py3.11.egg-info due to invalid metadata entry 'name'
20:09 bilby INFO    : Analysis priors:
20:09 bilby INFO    : m=Uniform(minimum=0, maximum=5, name='m', latex_label='m', unit=None, boundary=None)
20:09 bilby INFO    : c=Uniform(minimum=-2, maximum=2, name='c', latex_label='c', unit=None, boundary=None)
20:09 bilby INFO    : Analysis likelihood class: <class 'compas_surrogate.surrogate.surrogate_likelihood.SurrogateLikelihood'>
20:09 bilby INFO    : Analysis likelihood noise evidence: nan
20:09 bilby INFO    : Single likelihood evaluation took 4.298e-04 s
20:09 bilby WARNING : Using cached result
20:09 bilby INFO    : Summary of results:
nsamples: 2000
ln_noise_evidence:    nan
ln_evidence:    nan +/-    nan
ln_bayes_factor:    nan +/-    nan
../_images/3934c8607eb8a72396d603614d218bc0b049f347fc12d09e004a45f8da1f6ae5.png ../_images/b224c975e050265c2bcdd20e5f6a1f98842d00a37af0c551c7fff7ed0e593641.png