Getting started

Installation

inference-tools is available from PyPI, so can be easily installed using pip: as follows:

pip install inference-tools

If pip is not available, you can clone from the GitHub source repository or download the files from PyPI directly.

Jupyter notebook demos

In addition to the example code present in the online documentation for each class/function in the package, there is also a set of Jupyter notebook demos which can be found in the /demos/ directory of the source code.

Example - Gaussian peak fitting

Suppose we have the following dataset:

x_data = [0.00, 0.80, 1.60, 2.40, 3.20, 4.00, 4.80, 5.60,
          6.40, 7.20, 8.00, 8.80, 9.60, 10.4, 11.2, 12.0]

y_data = [2.473, 1.329, 2.370, 1.135, 5.861, 7.045, 9.942, 7.335,
          3.329, 5.348, 1.462, 2.476, 3.096, 0.784, 3.342, 1.877]

y_error = [1., 1., 1., 1., 1., 1., 1., 1.,
           1., 1., 1., 1., 1., 1., 1., 1.]
_images/gaussian_data.png

Our model \(F(x)\) for this data is a Gaussian plus a constant background such that

\[F(x) = \frac{A}{w\sqrt{2\pi}}\exp{\left(-\frac{1}{2}\frac{(x-c)^2}{w^2}\right)} + b\]

where our model parameters are the area under the gaussian \(A\), the width \(w\), center \(c\) and background \(b\).

In this example we’re going to:

  • Construct a likelihood distribution using the model and our dataset.

  • Construct a prior distribution for the model parameters.

  • Combine the likelihood and prior to create a posterior distribution.

  • Sample from the posterior using MCMC.

  • Use the sample to make inferences about the model parameters.

Constructing the likelihood

The first step is to implement our model. For simple models like this one this can be done using just a function, but as models become more complex it is becomes useful to build them as classes. Here we implement the model as the PeakModel class, where by defining the __call__ instance method, an instance of PeakModel can be called as a function to return a prediction of the y-data values.

class PeakModel:
    def __init__(self, x_data):
        """
        The __init__ should be used to pass in any data which is required
        by the model to produce predictions of the y-data values.
        """
        self.x = x_data

    def __call__(self, theta):
        return self.forward_model(self.x, theta)

    @staticmethod
    def forward_model(x, theta):
        """
        The forward model must make a prediction of the experimental data we would
        expect to measure given a specific set model parameters 'theta'.
        """
        # unpack the model parameters
        area, width, center, background = theta
        # calculate and return the prediction of the y-data values
        z = (x - center) / width
        gaussian = exp(-0.5 * z**2) / (sqrt(2 * pi) * width)
        return area * gaussian + background

Inference-tools has a variety of likelihood classes which allow you to easily construct a likelihood function given the measured data and your forward-model.

As in this example we assume the errors on the y-data values to be Gaussian, we use the GaussianLikelihood class from the likelihoods module:

from inference.likelihoods import GaussianLikelihood
likelihood = GaussianLikelihood(
    y_data=y_data,
    sigma=y_error,
    forward_model=PeakModel(x_data)
)

Instances of the likelihood classes can be called as functions, and return the log-likelihood when passed a vector of model parameters.

Constructing the prior

In the common case that the prior distribution for each model variable is independent of the others (i.e. the prior over all variables can be written as a product of priors over each individual variable) the inference.priors module has tools which allow us to build a prior easily.

Which model parameters are assigned to a given prior is specified using the indices of those parameters (i.e. the position they hold in the parameter vector as defined in the PeakModel class we wrote earlier).

Suppose we want the area, width and background parameters of the model to each have an exponential prior. The indices of the area, width and background parameters are [0, 1, 3] respectively, and we pass these indices to the ExponentialPrior class via the variable_indices argument:

from inference.priors import ExponentialPrior
exp_prior = ExponentialPrior(beta=[50., 20., 20.], variable_indices=[0, 1, 3])

We can assign the ‘center’ parameter a uniform distribution in the same way using the UniformPrior class:

from inference.priors import UniformPrior
uni_prior = UniformPrior(lower=0., upper=12., variable_indices=[2])

Now we use the JointPrior class to combine the various components into a single prior distribution which covers all the model parameters:

from inference.priors import JointPrior
prior_components = [exp_prior, uni_prior]
prior = JointPrior(components=prior_components, n_variables=4)

Sampling from the posterior

The likelihood and prior can be easily combined into a posterior distribution using the Posterior class:

from inference.posterior import Posterior
posterior = Posterior(likelihood=likelihood, prior=prior)

Now we have constructed a posterior distribution, we can sample from it using Markov-chain Monte-Carlo (MCMC).

The inference.mcmc module contains implementations of various MCMC sampling algorithms. Here we import the PcaChain class and use it to create a Markov-chain object:

from inference.mcmc import PcaChain
chain = PcaChain(posterior=posterior, start=initial_guess)

We generate samples by advancing the chain by a chosen number of steps using the advance method:

chain.advance(25000)

We can check the status of the chain using the plot_diagnostics method:

chain.plot_diagnostics()
_images/plot_diagnostics_example.png

The burn-in (how many samples from the start of the chain are discarded) can be passed as an argument to methods which access or plot sampling results:

burn = 5000

Using the sample to infer the model parameters

We can get a overview of the posterior using the matrix_plot method of chain objects, which plots all possible 1D & 2D marginal distributions of the full parameter set (or a chosen sub-set).

labels = ['area', 'width', 'center', 'background']
chain.matrix_plot(labels=labels, burn=burn)
_images/matrix_plot_example.png

We can easily estimate 1D marginal distributions for any parameter using the get_marginal method:

area_pdf = chain.get_marginal(0, burn=burn)
area_pdf.plot_summary(label='Gaussian area')
_images/pdf_summary_example.png

We can assess the level of uncertainty in the model predictions by passing each sample through the forward-model and observing the distribution of model expressions that result:

# generate an axis on which to evaluate the model
x_fits = linspace(-1, 13, 500)
# get the sample
sample = chain.get_sample(burn=burn)
# pass each through the forward model
curves = array([PeakModel.forward_model(x_fits, theta) for theta in sample])

We could plot the predictions for each sample all on a single graph, but this is often cluttered and difficult to interpret.

A better option is to use the hdi_plot function from the plotting module to plot highest-density intervals for each point where the model is evaluated:

_images/prediction_uncertainty_example.png