Multi-Parameter Bayesian Inference Using Markov Chain Monte Carlo (MCMC) Sampling and the Metropolis-Hastings Algorithm
-
The code has been written in plain vanilla C++ and tested using g++ 8.1.0 (MinGW-W64).
-
Likelihood (pdf) can be defined as an arbitrary function with any number of independent parameters.
-
Prior functions are defined using an array of structures, and can be any pdf from function prior_dist in file Metropolis.cpp (other priors can be easily added).
-
Jumps in the Metropolis-Hastings algorithm are proposed using a normal distribution of the parameters.
-
Function random_number in file Metropolis.cpp can be used to generate random numbers from any arbitrary pdf.
-
Results are passed back using a structure.
-
Usage: test.exe example.
example
Name of the example to run.
likelihood
Name of the likelihood function.
par
Array with the parameters of the likelihood function.
n_data
Number of data to be sampled from the likelihood function.
data
Array with the data sampled from the likelihood function.
a0
, b0
Support interval for the likelihood function.
priors
Array of structures with the priors. Each prior is assigned to one of the likelihood parameter following the same order as in par
.
samples
Number of jumps to perform in the Metropolis-Hastings algorithm.
par_init
Initial value for the parameters in the Metropolis-Hastings algorithm.
width_prop
Standard deviation of the normal distribution used to search the neighboroud of a parameter. A good value should give about 50% of accepted jumps.
i0
Index specifying the burn-in/warm-up amount.
posterior
Array containing the jumps (accepted or rejected) of all parameters.
jumps
Number of jumps actually accepted.
res
Structure with the results returned by the Metropolis-Hastings algorithm.
save_res
Name of the file where to save the results. If empty, results are not saved.
There are three examples: Coin, Normal, and Random (see test.cpp for the specific parameters and results). A brief description is given below. Plots similar to the ones in the Python version (see the Readme) can be easily generated using the results saved in the file specified by save_res
.
Coin:
One parameter (theta), Bernoulli distribution as likelihood, beta distribution as prior, admit an analytical solution.
// Numerical results:
// - accepted jumps = 53.9%
// - <theta> mean and std = 0.332, 0.057
// Analytical results:
// - <theta> mean and std = 0.333, 0.058
Normal:
Two parameters (mean and standard deviation), normal distribution as likelihood, normal distribution as prior for the mean, gamma distribution as prior for the standard deviation.
// Numerical results:
// - accepted jumps = 52.7%
// - <mu> mean and std = -1.207, 0.170
// - <sigma> mean and std = 0.728, 0.150
// Numerical (pymc3) results:
// - <mu> mean and std = -1.210, 0.171
// - <sigma> mean and std = 0.730, 0.156
The pymc3
results have been obtained loading the same random data generated by the C++ version (array data
) in the Python version.
Random:
A generic pdf (a piece-wise function in the example) is correctly approximated using 50000 randomly generated numbers.
// Piece-wise function:
//
// pdf = 0.0 in [-inf, 0]
// pdf = 0.3 * x in (0, 1]
// pdf = -0.2 * x + 0.5 in (1, 2]
// pdf = 0.1 in (2, 3]
// pdf = 0.1 * x - 0.2 in (3, 4]
// pdf = 0.2 in (4, 5]
// pdf = -0.1 * x + 0.7 in (5, 7]
// pdf = 0.0 in (7, +inf]
//
// The integral in [-inf, +inf] is 1.
-
Wikipedia, "Metropolis-Hastings Algorithm".
-
Wikipedia, "Markov Chain Monte Carlo".
-
"Bayesian Statistics", Chapter 2 in "Advanced Algorithmic Trading", by M. Halls-Moore.