Bayesian inference for a logistic regression model in various languages
Python
43
251 commits
updated Jul 12, 2023
This repo contains code supporting a series of blog posts I'm currently writing. Start at Part 1: the basics.
This repo contains code for MCMC-based fully Bayesian inference for a logistic regression model using R, Python, Scala, Haskell, Dex, and C, using bespoke hand-coded samplers (random walk Metropolis, unadjusted Langevin algorithm, MALA, and HMC), and samplers constructed with the help of libraries such as JAGS, Stan, JAX, BlackJAX, NumPyro, PyMC3, and Spark.
I intend to add similar examples using one or two other libraries. At some point I'd also like to switch to a much bigger dataset, that better illustrates some of the scalability issues of the different languages and libraries.
Here we will conduct fully Bayesian inference for the typical Bayesian logistic regression model for a binary outcome based on some covariates. The $i$th observation will be 1 with probability $p_i$, and the logit of $p_i$ will depend linearly on predictors. This leads to a log-likelihood function
$$l(b; y) = -\mathbb{1}'[\log(\mathbb{1} + \exp[-(2y - \mathbb{1})\circ(Xb)])]$$
where $y$ is a binary vector of responses, $X$ is an $n\times p$ matrix of covariates and $b$ is the $p$-vector of parameters of inferential interest.
JAX can auto-diff likelihoods like this, but for comparison purposes, we can also use hard-coded gradients for MALA and HMC:
$$\nabla l(b) = X'(y-p), \quad \text{where}\quad p = (\mathbb{1} + \exp[-Xb])^{-1}.$$
For a fully Bayesian analysis, we also need a prior distribution. Here we will assume independent normal priors on the elements of $b$. That is, $b_i \sim N(0, v_i)$. Note that the gradient of the log of this prior is
$$\nabla \pi(b) = -b\circ v^{-1}.$$
We will be analysing the "Pima" training dataset, with 200 observations and 7 predictors. Including an intercept as the first covariate gives a parameter vector of length $p=8$. The prior standard deviation for the intercept is 10, and for the other covariates is 1.
Please read: The code in this repo should not be used for any kind of serious performance or benchmarking exercise. I have deliberately tried to use a reasonably consistent simple style of implementation across all of the languages. I have not made any attempt to optimise any of the implementations. Indeed, I have deliberately chosen not to optimise any of the implementations. Clearly all of the implementations could be optimised, and the nature of the optimisation would differ greatly between languages. Moreover, benchmarking on a small toy dataset such as the one considered here would be very uninteresting. The interesting scaling issues only become apparent on larger datasets.
Note that these scripts use pacman to download and install any missing dependencies.
MASS::Pima.tr dataset, exported from R in parquet format (rather than CSV, as it's now the 21st Century, but also save in a simple text format for languages that can't easily read parquet...).These scripts assume a Python installation with NumPy and SciPy. The later scripts require JAX. The BlackJAX scripts require BlackJAX, the NumPyro script requires NumPyro, and the PyMC3 script requires PyMC3. These can be pip installed for basic use. See the websites for more detailed information.
The Scala examples just require a recent JVM and sbt. sbt will look after other dependencies (including Scala itself). See the Readme in the Scala directory for further info.
The Spark example requires a Spark installation in addition to sbt. See the Readme in the Scala directory for further info.
The Haskell examples use stack to build and run and manage dependencies. See the readme in the Haskell/lr directory for further details.
The Dex examples rely only on a basic Dex installation. See the readme in the Dex directory for further details. Note that Dex is an early-stage research project lacking many of the tools and libraries one would normally expect. It's also rather lacking documentation. However, it's interesting, pure functional, strongly typed, differentiable, and fast.
The C examples assume a Unix-like development environment. See the Readme in the C directory for further info.
Copyright (C) 2022, Darren J Wilkinson, but released under a GPL-3.0 license
251 commits
Python
35.9%
Haskell
28.6%
Scala
16.7%
R
13.1%
C
3.3%
Makefile
2.5%
Bayesian inference for a logistic regression model in various languages
Python
43
251 commits
updated Jul 12, 2023
This repo contains code supporting a series of blog posts I'm currently writing. Start at Part 1: the basics.
This repo contains code for MCMC-based fully Bayesian inference for a logistic regression model using R, Python, Scala, Haskell, Dex, and C, using bespoke hand-coded samplers (random walk Metropolis, unadjusted Langevin algorithm, MALA, and HMC), and samplers constructed with the help of libraries such as JAGS, Stan, JAX, BlackJAX, NumPyro, PyMC3, and Spark.
I intend to add similar examples using one or two other libraries. At some point I'd also like to switch to a much bigger dataset, that better illustrates some of the scalability issues of the different languages and libraries.
Here we will conduct fully Bayesian inference for the typical Bayesian logistic regression model for a binary outcome based on some covariates. The $i$th observation will be 1 with probability $p_i$, and the logit of $p_i$ will depend linearly on predictors. This leads to a log-likelihood function
$$l(b; y) = -\mathbb{1}'[\log(\mathbb{1} + \exp[-(2y - \mathbb{1})\circ(Xb)])]$$
where $y$ is a binary vector of responses, $X$ is an $n\times p$ matrix of covariates and $b$ is the $p$-vector of parameters of inferential interest.
JAX can auto-diff likelihoods like this, but for comparison purposes, we can also use hard-coded gradients for MALA and HMC:
$$\nabla l(b) = X'(y-p), \quad \text{where}\quad p = (\mathbb{1} + \exp[-Xb])^{-1}.$$
For a fully Bayesian analysis, we also need a prior distribution. Here we will assume independent normal priors on the elements of $b$. That is, $b_i \sim N(0, v_i)$. Note that the gradient of the log of this prior is
$$\nabla \pi(b) = -b\circ v^{-1}.$$
We will be analysing the "Pima" training dataset, with 200 observations and 7 predictors. Including an intercept as the first covariate gives a parameter vector of length $p=8$. The prior standard deviation for the intercept is 10, and for the other covariates is 1.
Please read: The code in this repo should not be used for any kind of serious performance or benchmarking exercise. I have deliberately tried to use a reasonably consistent simple style of implementation across all of the languages. I have not made any attempt to optimise any of the implementations. Indeed, I have deliberately chosen not to optimise any of the implementations. Clearly all of the implementations could be optimised, and the nature of the optimisation would differ greatly between languages. Moreover, benchmarking on a small toy dataset such as the one considered here would be very uninteresting. The interesting scaling issues only become apparent on larger datasets.
Note that these scripts use pacman to download and install any missing dependencies.
MASS::Pima.tr dataset, exported from R in parquet format (rather than CSV, as it's now the 21st Century, but also save in a simple text format for languages that can't easily read parquet...).These scripts assume a Python installation with NumPy and SciPy. The later scripts require JAX. The BlackJAX scripts require BlackJAX, the NumPyro script requires NumPyro, and the PyMC3 script requires PyMC3. These can be pip installed for basic use. See the websites for more detailed information.
The Scala examples just require a recent JVM and sbt. sbt will look after other dependencies (including Scala itself). See the Readme in the Scala directory for further info.
The Spark example requires a Spark installation in addition to sbt. See the Readme in the Scala directory for further info.
The Haskell examples use stack to build and run and manage dependencies. See the readme in the Haskell/lr directory for further details.
The Dex examples rely only on a basic Dex installation. See the readme in the Dex directory for further details. Note that Dex is an early-stage research project lacking many of the tools and libraries one would normally expect. It's also rather lacking documentation. However, it's interesting, pure functional, strongly typed, differentiable, and fast.
The C examples assume a Unix-like development environment. See the Readme in the C directory for further info.
Copyright (C) 2022, Darren J Wilkinson, but released under a GPL-3.0 license
251 commits
Python
35.9%
Haskell
28.6%
Scala
16.7%
R
13.1%
C
3.3%
Makefile
2.5%