Samplers comparison
License
rosenbrock_simple
Sun Apr 02 21:13:00 2023
Copyright 2023
Sandro Dias Pinto Vitenti vitenti@uel.br ___ rosenbrock_simple
Copyright (C) 2023 Sandro Dias Pinto Vitenti vitenti@uel.br
numcosmo is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
numcosmo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
import sys
from numcosmo_py import Ncm
import matplotlib.pyplot as plt
import numpy as np
import getdist
import getdist.plots
from numcosmo_py.sampling.apes import APES
from numcosmo_py.sampling.catalog import Catalog
from numcosmo_py.plotting.tools import set_rc_params_article
import emcee
import zeus
from pyhmc import hmc
Initialize NumCosmo and sampling parameters
In this notebook, we will compare the performance of four different samplers: APES, Emcee, Zeus and PyHMC. To start, we will use the new Python interface for NumCosmo.
Next, we will define the sampling configuration. Our goal is to generate a total of 600,000 points across all three samplers, with each sampler using 300 walkers except for PyHMC that generates a single chain. This translates to 2000 steps on each sampler to reach our desired sample size. By comparing the performance of these three samplers using the NumCosmo Python interface, we can evaluate their respective capabilities and identify any advantages or limitations for our specific problem.
Ncm.cfg_init()
Ncm.cfg_set_log_handler(lambda msg: sys.stdout.write(msg) and sys.stdout.flush())
ssize = 600000
nwalkers = 300
burin_steps = 500
verbose = False
Probability definition
In the cell below, we will define the unnormalized Rosenbrock distribution, which is known to be a difficult distribution to sample from. The Rosenbrock distribution is often used as a benchmark for testing the performance of sampling algorithms. By using this challenging distribution, we can better understand how well the samplers perform under difficult sampling conditions.
We will generate a single initial sample point with random normal realizations having a mean of zero and a standard deviation of one. This initial point will be used as the starting point for each of the samplers, ensuring that they all start at the same point.
It’s worth noting that this initial sample point is very different from a sample from an actual Rosenbrock distribution. However, this is intentional, as we want to see how each sampler performs under challenging conditions.
def log_prob(x, ivar):
return -0.5 * (100.0 * (x[1] - x[0] * x[0]) ** 2 + (1.0 - x[0]) ** 2) * 1.0e-1
def log_prob_grad(x, ivar):
logp = -0.5 * (100.0 * (x[1] - x[0] * x[0]) ** 2 + (1.0 - x[0]) ** 2) * 1.0e-1
grad = [
-0.5
* (100.0 * 2.0 * (x[1] - x[0] * x[0]) * (-2.0 * x[0]) - 2.0 * (1.0 - x[0]))
* 1.0e-1,
-0.5 * (100.0 * 2.0 * (x[1] - x[0] * x[0])) * 1.0e-1,
]
return logp, np.array(grad)
ndim, nwalkers = 2, nwalkers
p0 = np.random.randn(nwalkers, ndim)
Running NumComo’s APES
In the cell below, we will run NumCosmo’s APES algorithm using the configuration defined above.
sampler_apes = APES(
nwalkers=nwalkers, ndim=ndim, model=None, log_prob=log_prob, args=()
)
sampler_apes.run_mcmc(p0, ssize // nwalkers)
mcat_apes = sampler_apes.get_catalog()
mcat_apes.trim(burin_steps)
Running Emcee
In the cell below, we will run the Emcee algorithm with the same initial
point p0
generated previously. We will generate a chain of samples
using Emcee and store the resulting chain in a Catalog
object. This
will allow us to apply the same tests to all algorithms and compare
their performance on an even footing.
sampler_emcee = emcee.EnsembleSampler(nwalkers, ndim, log_prob, args=[0])
state_emcee = sampler_emcee.run_mcmc(p0, ssize // nwalkers)
chain_emcee = sampler_emcee.get_chain(flat=True)
log_prob_emcee = sampler_emcee.get_log_prob(flat=True)
mcat_emcee = Catalog(ndim=ndim, nwalkers=nwalkers, run_type="EMCEE")
mcat_emcee.add_points_m2lnp(chain_emcee, -2.0 * log_prob_emcee)
mcat_emcee.trim(burin_steps)
Running Zeus
In the cell below, we will run the Zeus algorithm with the same initial
point p0
generated previously. We will generate a chain of samples
using Zeus and store the resulting chain in a Catalog
object. One
difference from Emcee is that Zeus output chains are not interweaved, so
we need to inform the Catalog
object of this fact to ensure that
autocorrelation estimates are correct.
sampler_zeus = zeus.EnsembleSampler(nwalkers, ndim, log_prob, args=[0], verbose=verbose)
sampler_zeus.run_mcmc(p0, ssize // nwalkers, progress=False)
chain_zeus = sampler_zeus.get_chain(flat=True)
log_prob_zeus = sampler_zeus.get_log_prob(flat=True)
mcat_zeus = Catalog(ndim=ndim, nwalkers=nwalkers, run_type="ZEUS")
mcat_zeus.add_points_m2lnp(chain_zeus, -2.0 * log_prob_zeus, interweaved=False)
mcat_zeus.trim(burin_steps)
Running pyhmc: Hamiltonian Monte Carlo
In the cell below, we will run the pyhmc algorithm with the same initial
point p0
generated previously, however since pyhmc is not an ensemble
sampler we use only the first point. We will generate a chain of samples
using pyhmc and store the resulting chain in a Catalog
object.
Moreover, here we need to remove a longer burn-in period of 1000
samples, since pyhmc is not an ensemble sampler.
chain_pyhmc, log_prob_pyhmc = hmc(
log_prob_grad,
x0=np.array(p0[0]),
args=(np.array([0]),),
n_samples=ssize,
return_logp=True,
)
mcat_pyhmc = Catalog(ndim=ndim, nwalkers=1, run_type="PyHMC")
mcat_pyhmc.add_points_m2lnp(chain_pyhmc, -2.0 * log_prob_zeus)
mcat_pyhmc.trim(ssize // 2)
Analyzing results
In the cell below, we will create two arrays containing the true values
for the mean and standard deviation of the Rosenbrock distribution. We
will then call print_status
on each catalog to obtain the current
mean, standard deviation, mean standard deviation, and autocorrelation
time τ. Based on the results, we can see that APES has the lowest
autocorrelation time τ for all parameters, being between 50-100 times
smaller than the autocorrelation time obtained by Emcee and Zeus.
This indicates that APES was able to explore the parameter space more efficiently and produce less correlated samples. Additionally, we can see that the mean, standard deviation and mean standard deviation obtained by all samplers are close to the true values of the Rosenbrock distribution. However, it is important to note that the Emcee and Zeus chains may not have fully converged yet, so it is possible that their mean and variance estimates, as well as their autocorrelation times, could improve with further sampling.
sigma = np.sqrt([10.0, 2401 / 10])
mean = np.array([1.0, 11.0])
Ncm.cfg_msg_sepa()
mcat_apes.print_status()
Ncm.cfg_msg_sepa()
mcat_emcee.print_status()
Ncm.cfg_msg_sepa()
mcat_zeus.print_status()
Ncm.cfg_msg_sepa()
mcat_pyhmc.print_status()
#----------------------------------------------------------------------------------
# NcmMSetCatalog: Current mean: 2.0118 1.0021 11.103
# NcmMSetCatalog: Current msd: 0.0079207 0.012742 0.074897
# NcmMSetCatalog: Current sd: 1.9888 3.1781 15.276
# NcmMSetCatalog: Current var: 3.9551 10.1 233.36
# NcmMSetCatalog: Current tau: 7.138 7.234 10.817
#----------------------------------------------------------------------------------
# NcmMSetCatalog: Current mean: 1.5951 0.60027 6.0177
# NcmMSetCatalog: Current msd: 0.021953 0.09409 0.3077
# NcmMSetCatalog: Current sd: 1.6006 2.3792 7.3684
# NcmMSetCatalog: Current var: 2.5621 5.6608 54.294
# NcmMSetCatalog: Current tau: 84.648 703.76 784.71
#----------------------------------------------------------------------------------
# NcmMSetCatalog: Current mean: 1.7673 0.97194 8.5758
# NcmMSetCatalog: Current msd: 0.028349 0.046361 0.30815
# NcmMSetCatalog: Current sd: 1.7064 2.7625 11.019
# NcmMSetCatalog: Current var: 2.912 7.6313 121.41
# NcmMSetCatalog: Current tau: 124.2 126.74 351.94
#----------------------------------------------------------------------------------
# NcmMSetCatalog: Current mean: 1.7126 0.53742 2.8218
# NcmMSetCatalog: Current msd: 0.028136 0.13137 0.45803
# NcmMSetCatalog: Current sd: 2.0903 1.5917 3.3861
# NcmMSetCatalog: Current var: 4.3695 2.5336 11.465
# NcmMSetCatalog: Current tau: 54.353 2043.4 5489.3
Comparing the mean estimates
Below, we compare the mean estimates obtained by each sampler and print the relative error on each estimate. As we can see, APES is the only sampler that is able to recover the true means within 1% - 2% error, while Emcee and Zeus exhibit much larger errors.
print(np.abs(mcat_apes.get_mean() / mean - 1.0))
print(np.abs(mcat_emcee.get_mean() / mean - 1.0))
print(np.abs(mcat_zeus.get_mean() / mean - 1.0))
print(np.abs(mcat_pyhmc.get_mean() / mean - 1.0))
[0.0020966 0.00939155]
[0.39972599 0.45293618]
[0.02806083 0.2203813 ]
[0.46258321 0.74347242]
Comparing the standard deviation estimates
Similar to the previous comparison, we now look at the relative error on the standard deviation estimates. Once again, APES performs the best among the samplers, with estimates within 1% - 4% of the true values.
print(np.abs(np.sqrt(np.diagonal(mcat_apes.get_covar())) / sigma - 1.0))
print(np.abs(np.sqrt(np.diagonal(mcat_emcee.get_covar())) / sigma - 1.0))
print(np.abs(np.sqrt(np.diagonal(mcat_zeus.get_covar())) / sigma - 1.0))
print(np.abs(np.sqrt(np.diagonal(mcat_pyhmc.get_covar())) / sigma - 1.0))
[0.00499343 0.01413589]
[0.24761925 0.52446852]
[0.12642799 0.28888953]
[0.49665275 0.78147664]
Using getdist
on the chains
In this next cell, we convert the catalog of MCMC chains for each
algorithm into MCSamples
objects using the getdist
package. This
allows us to easily plot the joint and marginal posterior distributions
of the parameters using the getdist
plotter
.
mcs_apes = mcat_apes.get_mcsamples("APES")
mcs_emcee = mcat_emcee.get_mcsamples("EMCEE")
mcs_zeus = mcat_zeus.get_mcsamples("ZEUS")
mcs_pyhmc = mcat_pyhmc.get_mcsamples("PyHMC")
Removed no burn in
Removed no burn in
Removed no burn in
Removed no burn in
Corner plot
Finally, we create a joint corner plot of the samples using getdist
.
We can see that the other samplers (Emcee and Zeus) have more difficulty
in sampling the tails, leading to the 1D marginals being more
concentrated and reporting smaller variance. This is reflected in the
larger relative errors in the mean and standard deviation estimates we
saw earlier.
set_rc_params_article(ncol=2)
g = getdist.plots.get_subplot_plotter(width_inch=plt.rcParams["figure.figsize"][0])
g.settings.linewidth = 0.01
g.triangle_plot([mcs_apes, mcs_emcee, mcs_zeus, mcs_pyhmc], shaded=True)
auto bandwidth for theta_0 very small or failed (h=0.0005832638183669094,N_eff=38411.13458893739). Using fallback (h=0.022408377246589863)
auto bandwidth for theta_1 very small or failed (h=0.0006114885736063465,N_eff=112997.872249284). Using fallback (h=0.003704573608527487)
auto bandwidth for theta_1 very small or failed (h=0.0014299159644314198,N_eff=14883.212703556037). Using fallback (h=0.007191755509401264)
fine_bins_2D not large enough for optimal density: theta_0, theta_1
fine_bins_2D not large enough for optimal density: theta_0, theta_1
fine_bins_2D not large enough for optimal density: theta_0, theta_1