bamojax 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bamojax-0.4.2/LICENSE +21 -0
- bamojax-0.4.2/MANIFEST.in +4 -0
- bamojax-0.4.2/PKG-INFO +264 -0
- bamojax-0.4.2/README.md +229 -0
- bamojax-0.4.2/bamojax/__init__.py +2 -0
- bamojax-0.4.2/bamojax/_version.py +21 -0
- bamojax-0.4.2/bamojax/base.py +632 -0
- bamojax-0.4.2/bamojax/examples/__init__.py +0 -0
- bamojax-0.4.2/bamojax/gwishart/__init__.py +6 -0
- bamojax-0.4.2/bamojax/gwishart/gwishart.py +361 -0
- bamojax-0.4.2/bamojax/inference.py +794 -0
- bamojax-0.4.2/bamojax/marginal_likelihoods/__init__.py +0 -0
- bamojax-0.4.2/bamojax/marginal_likelihoods/bayesian_quadrature.py +55 -0
- bamojax-0.4.2/bamojax/marginal_likelihoods/bridge_sampling.py +180 -0
- bamojax-0.4.2/bamojax/marginal_likelihoods/importance_sampling.py +82 -0
- bamojax-0.4.2/bamojax/marginal_likelihoods/laplace.py +40 -0
- bamojax-0.4.2/bamojax/marginal_likelihoods/naive_monte_carlo.py +55 -0
- bamojax-0.4.2/bamojax/marginal_likelihoods/thames.py +122 -0
- bamojax-0.4.2/bamojax/marginal_likelihoods/utility.py +54 -0
- bamojax-0.4.2/bamojax/model_search/__init__.py +1 -0
- bamojax-0.4.2/bamojax/model_search/anchored_rjsmc.py +236 -0
- bamojax-0.4.2/bamojax/modified_blackjax/__init__.py +0 -0
- bamojax-0.4.2/bamojax/modified_blackjax/modified_elliptical_slice_nd.py +323 -0
- bamojax-0.4.2/bamojax/modified_blackjax/more_proposals.py +66 -0
- bamojax-0.4.2/bamojax/more_distributions.py +556 -0
- bamojax-0.4.2/bamojax/samplers.py +435 -0
- bamojax-0.4.2/bamojax.egg-info/PKG-INFO +264 -0
- bamojax-0.4.2/bamojax.egg-info/SOURCES.txt +38 -0
- bamojax-0.4.2/bamojax.egg-info/dependency_links.txt +1 -0
- bamojax-0.4.2/bamojax.egg-info/requires.txt +20 -0
- bamojax-0.4.2/bamojax.egg-info/top_level.txt +1 -0
- bamojax-0.4.2/pyproject.toml +83 -0
- bamojax-0.4.2/setup.cfg +11 -0
- bamojax-0.4.2/setup.py +8 -0
- bamojax-0.4.2/tests/test_base.py +63 -0
- bamojax-0.4.2/tests/test_inference.py +214 -0
- bamojax-0.4.2/tests/test_more_distributions.py +42 -0
- bamojax-0.4.2/tests/test_samplers_and_engines.py +374 -0
- bamojax-0.4.2/versioneer.py +2277 -0
bamojax-0.4.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Max Hinne, Uncertainty in complex systems
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
bamojax-0.4.2/PKG-INFO
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bamojax
|
|
3
|
+
Version: 0.4.2
|
|
4
|
+
Summary: Bayesian Modelling with JAX
|
|
5
|
+
Author: Max Hinne
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/UncertaintyInComplexSystems/bamojax
|
|
8
|
+
Project-URL: Issues, https://github.com/UncertaintyInComplexSystems/bamojax/issues
|
|
9
|
+
Project-URL: Documentation, https://mhinne.github.io/uncertainty-in-complex-systems/bamojax_demo.html
|
|
10
|
+
Keywords: bayesian modelling,jax,probabilistic,mcmc,smc,inference
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: jax>=0.10.0
|
|
17
|
+
Requires-Dist: jaxlib>=0.10.0
|
|
18
|
+
Requires-Dist: blackjax>=1.6
|
|
19
|
+
Requires-Dist: numpyro>=0.22
|
|
20
|
+
Requires-Dist: jaxtyping
|
|
21
|
+
Requires-Dist: optax
|
|
22
|
+
Requires-Dist: tqdm
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-cov>=5; extra == "dev"
|
|
26
|
+
Requires-Dist: ipython>=8.0; extra == "dev"
|
|
27
|
+
Provides-Extra: docs
|
|
28
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
29
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
30
|
+
Requires-Dist: mkdocstrings[python]; extra == "docs"
|
|
31
|
+
Requires-Dist: pymdown-extensions; extra == "docs"
|
|
32
|
+
Requires-Dist: mkdocs-gen-files; extra == "docs"
|
|
33
|
+
Requires-Dist: mkdocs-literate-nav; extra == "docs"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
[](https://github.com/uncertaintyincomplexsystems/bamojax/actions/workflows/test.yml) [](https://doi.org/10.21105/joss.08642)
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
# Welcome to bamojax!
|
|
41
|
+
|
|
42
|
+
Welcome to **bamojax**, the Bayesian modelling and model comparison toolbox implemented using the JAX coding universe. **bamojax** is a probabilistic programming language (PPL), similar to Numpyro, PyMC, Stan, JAGS, and BUGS, as well as a set of analyses for model comparison and averaging. **bamojax** relies on [Blackjax](https://blackjax-devs.github.io/blackjax/) for approximate inference, and on [Numpyro](https://num.pyro.ai/en/stable/) for probability distributions and their essential operations.
|
|
43
|
+
|
|
44
|
+
## What sets bamojax apart?
|
|
45
|
+
|
|
46
|
+
Existing PPLs, such as PyMC, can export their log density function so it can be sampled using Blackjax. However, this has two downsides:
|
|
47
|
+
|
|
48
|
+
1. Not all existing probabilistic programming easily allow you to update variables in your model using Gibbs Markov chain Monte Carlo (MCMC) algorithms. For example, if one wants to approximate the posterior over a latent Gaussian process (GP) and its hyperparameters, it can be more efficient to use elliptical slice sampling for the GP, than to apply the No-U-Turn Hamiltonian Monte Carlo Sampler [(Hoffman and Gelman, 2014)](https://jmlr.org/papers/v15/hoffman14a.html) to all variables at once. This effect is even more pronounced when embedding MCMC sampling within Sequential Monte Carlo (see [Hinne, 2025](https://link.springer.com/article/10.3758/s13428-025-02642-1) for more details).
|
|
49
|
+
2. It is harder to embed this logdensity into tempered Sequential Monte Carlo algorithms, as for this one needs access to the prior and likelihood separately.
|
|
50
|
+
|
|
51
|
+
By implementing your own models and samplers using Blackjax, these problems can be circumvented. However, this is a labour-intensive and error-prone process. Therefore, **bamojax** provides a user-friendly interface around Blackjax, that allows for easy model construction and Gibbs sampling.
|
|
52
|
+
|
|
53
|
+
Once a Bayesian model has been determined, the next goal is often to compare it with competing hypotheses. This can, for example, be done by computing the marginal likelihoods of different models, and taking their ratio, a quantity known as the _Bayes factor_. However, computing marginal likelihoods and therefore Bayes factors is computationally challenging, and several approximation strategies exist. **bamojax** makes this process easier by implementing the following marginal likelihood approximations:
|
|
54
|
+
|
|
55
|
+
- Importance sampling
|
|
56
|
+
- Sequential Monte Carlo
|
|
57
|
+
- The Laplace approximation
|
|
58
|
+
- Bridge sampling (Gronau et al., 2017; Meng & Wong, 1996)
|
|
59
|
+
- The THAMES estimator (Metodiev et al., 2025)
|
|
60
|
+
|
|
61
|
+
More comparison features are being added to the toolbox continuously.
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
Install **bamojax** using:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
pip install git+https://github.com/UncertaintyInComplexSystems/bamojax#egg=bamojax
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Please note that if you want to use hardware acceleration, such as using GPUs, you might want to install `JAX` yourself. Please see https://docs.jax.dev/en/latest/installation.html for instructions. If no JAX installation is present, **bamojax** will install it as a dependency.
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## Quick tutorial
|
|
75
|
+
|
|
76
|
+
Let's estimate the latent probability $\theta=p(x_1=\text{heads} \mid \theta)$ that a coin lands up heads, given a set of observations $x_1, \ldots, x_n$:
|
|
77
|
+
|
|
78
|
+
#### Generate data
|
|
79
|
+
```
|
|
80
|
+
import jax.numpy as jnp
|
|
81
|
+
import jax.random as jrnd
|
|
82
|
+
from bamojax.base import Model
|
|
83
|
+
from bamojax.inference import SMCInference
|
|
84
|
+
from bamojax.samplers import mcmc_sampler
|
|
85
|
+
import numpyro as npr
|
|
86
|
+
import numpyro.distributions as dist
|
|
87
|
+
import blackjax as bjx
|
|
88
|
+
|
|
89
|
+
key = jrnd.PRNGKey(0)
|
|
90
|
+
key, key_data, key_inference = jrnd.split(key, 3)
|
|
91
|
+
|
|
92
|
+
true_theta = 0.3
|
|
93
|
+
n = 10000
|
|
94
|
+
x = jrnd.bernoulli(key_data, p=true_theta, shape=(n, ))
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### Define Bayesian generative model
|
|
98
|
+
|
|
99
|
+
Under the good, **bamojax** represents the a Bayesian model using a Directed Acyclic Graph (DAG) structure, and automatically derives model priors, likelihoods, and Gibbs inference schemes. These are then combined with the fast inference algorithms implemented in Blackjax. Bayesian models can be instantiated in **bamojax** using:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
from bamojax.base import Model
|
|
103
|
+
my_model = Model('The name of the model')
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Subsequently, variables can be added to this model using for example:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
def link_fn(probs):
|
|
110
|
+
return {'probs': probs}
|
|
111
|
+
|
|
112
|
+
#
|
|
113
|
+
latent_theta = my_model.add_node('theta',
|
|
114
|
+
distribution=dist.Beta(concentration1=1, concentration0=1))
|
|
115
|
+
observations = my_model.add_node('x',
|
|
116
|
+
distribution=dist.Bernoulli,
|
|
117
|
+
parents=dict(probs=latent_theta),
|
|
118
|
+
link_fn=link_fn,
|
|
119
|
+
observations=x)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Perform approximate inference
|
|
123
|
+
|
|
124
|
+
The next step is to perform inference. Here, we use Adaptive-Tempered Sequential Monte Carlo, as implemented by Blackjax:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
num_particles = 1_000
|
|
128
|
+
num_mcmc_steps = 100
|
|
129
|
+
num_chains = 1
|
|
130
|
+
|
|
131
|
+
stepsize = 0.01
|
|
132
|
+
|
|
133
|
+
mcmc_params = dict(sigma=stepsize*jnp.eye(my_model.get_model_size()))
|
|
134
|
+
rmh = mcmc_sampler(my_model,
|
|
135
|
+
mcmc_kernel=bjx.normal_random_walk,
|
|
136
|
+
mcmc_parameters=mcmc_params)
|
|
137
|
+
|
|
138
|
+
key, key_inference = jrnd.split(key)
|
|
139
|
+
|
|
140
|
+
engine = SMCInference(model=my_model,
|
|
141
|
+
mcmc_kernel=rmh,
|
|
142
|
+
num_particles=num_particles,
|
|
143
|
+
num_mutations=num_mcmc_steps,
|
|
144
|
+
num_chains=num_chains)
|
|
145
|
+
result = engine.run(key_inference)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
print(jnp.mean(result['final_state'].particles['theta']))
|
|
149
|
+
|
|
150
|
+
>>> 0.29956531188263924
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Modelling in bamojax
|
|
154
|
+
|
|
155
|
+
**bamojax** aims to make Bayesian modelling and inference with JAX and the Blackjax library more user-friendly, while retaining the speed and flexibility of the latter. To use the framework, a user is required to specify the directed acyclic graph that defines a Bayesian probabilistic model, as well as
|
|
156
|
+
* Distributions,
|
|
157
|
+
* Transformations,
|
|
158
|
+
* Link functions, and
|
|
159
|
+
* Observations.
|
|
160
|
+
|
|
161
|
+
### The model
|
|
162
|
+
|
|
163
|
+
To perform these steps, one first initializes the `Model`:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
my_model = Model('The name of my model')
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
This effectively creates an empty DAG.
|
|
170
|
+
|
|
171
|
+
### The variables
|
|
172
|
+
|
|
173
|
+
Next, one adds nodes to the DAG one by one, or multiple at once in the case of iteration / plate notation: For instance, the following adds a latent random variable with a Gaussian distribution assigned to it, $x\sim \mathcal{N}(0,1)$:
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
a_new_node = my_model.add_node('variable name', distribution=dist.Normal, parents=dict(loc=0.0, scale=1.0))
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Depending on whether a user provides values for the `distribution=`, and/or `observations=` arguments in the `add_node()` method, **bamojax** derives whether the variable is:
|
|
180
|
+
1. Stochastic and latent,
|
|
181
|
+
2. Stochastic and observed,
|
|
182
|
+
3. Deterministic and observed.
|
|
183
|
+
|
|
184
|
+
The `parents` argument expects a dictionary, in which the keys must correspond to either the arguments of a Numpyro `dist.Distribution` object or the arguments of a link function.
|
|
185
|
+
|
|
186
|
+
Nodes can furthermore take any Numpyro bijector using the `bijector=` argument, for example to transform a real variable to a bounded domain or vice versa. In many cases, the same goal can be achieved by the link function, but sometimes a bijector is simpler to use.
|
|
187
|
+
|
|
188
|
+
### Link functions
|
|
189
|
+
|
|
190
|
+
An important 'feature' of **bamojax** is that it is straightforward to add any deterministic transformation from the values of a parent variable to the inputs of a child variable. For example, imagine a variable $\theta$ representing a coin flip probability. A typical prior would be the beta distribution, typically parametrized with pseudo-counts $\alpha$ and $\beta$, so that $\theta \sim \text{Beta}(\alpha,\beta)$. However, we may want to specify a hierarchical prior on the _mode_ and _precision_ of this distribution, rather than on the pseudo-counts. With a link function we can express this:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
def beta_link_fn(mode, conc):
|
|
194
|
+
a = mode*(conc-2) + 1
|
|
195
|
+
b = (1 - mode)*(conc-2) + 1
|
|
196
|
+
return {'concentration1': a, 'concentration0': b}
|
|
197
|
+
|
|
198
|
+
omega = my_model.add_node('omega', distribution=dist.Beta, parents=dict(concentration1=1.0, concentration0=1.0))
|
|
199
|
+
theta = my_model.add_node('theta', distribution=dist.Beta, parents=dict(mode=omega, conc=15), link_fn=beta_link_fn)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The link function `beta_link_fn` takes the mode (given by another node `omega`) and the concentration (given by a scalar, which is implicitly converted to a deterministic and observed node), and returns the standard arguments $\alpha$ and $\beta$ which the `dist.Beta` distribution object recognizes as valid parameters.
|
|
203
|
+
|
|
204
|
+
Because link functions are written in plain Python, they can be of arbitrary complexity. For example, in [bamojax/examples/bnn/bnn_mpl.ipynb](https://github.com/UncertaintyInComplexSystems/bamojax/blob/main/bamojax/examples/bnn/bnn_mpl.ipynb), we use [Flax Linen](https://flax.readthedocs.io/en/latest/) to set up a multilayer perceptron as a link function.
|
|
205
|
+
|
|
206
|
+
### Inference
|
|
207
|
+
|
|
208
|
+
**bamojax** does not have its own inference engine, but provides an interface to [Blackjax](https://blackjax-devs.github.io/blackjax/), in addition to some quality-of-life features. Ultimately, control is left entirely to the user. Here is an example where we use Gibbs MCMC:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
step_fns = dict(beta=normal_random_walk, sigma=normal_random_walk)
|
|
212
|
+
step_fn_params = dict(omega=dict(sigma=0.05), theta=dict(sigma=0.5))
|
|
213
|
+
gibbs_kernel = gibbs_sampler(my_model, step_fns=step_fns, step_fn_params=step_fn_params)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
In Gibbs sampling, we specify a kernel for each variable. In this case, we use Gaussian proposal distributions for both $\omega$ and $\theta$.
|
|
217
|
+
|
|
218
|
+
Now we can set up our inference engine:
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
engine = MCMCInference(model=my_model, num_chains=4, num_samples=100_000, num_burn=100_000, num_thin=50, mcmc_kernel=gibbs_kernel, return_diagnostics=True)
|
|
222
|
+
result = engine.run(jrnd.PRNGKey(0))
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
This returns a dictionary with a `states` value containing a dictionary samples for each variable, for the requested number of chains and samples, discarding the specified number of burn-in samples, and storing only every 50th sample. By setting `return_diagnostics=True`, information such as acceptance rates are provided as well. For large models, turning this off can conserve memory consumption.
|
|
226
|
+
|
|
227
|
+
Alternative inference engines include Sequential Monte Carlo, Variational Inference, and Stochastic-Gradient MCMC methods. Examples on how to use these can be found in the `examples/` folder, as well as on the [Uncertainty in Complex Systems website](https://mhinne.github.io/uncertainty-in-complex-systems).
|
|
228
|
+
|
|
229
|
+
### Predictions
|
|
230
|
+
|
|
231
|
+
**bamojax** supports each combination of sampling from the prior or posterior, and the latent variables or the predictive distribution, using any of the following:
|
|
232
|
+
|
|
233
|
+
| |Prior |Posterior |
|
|
234
|
+
|---------------------|----------------------------------------------|----------------------------------------------------------------------------------|
|
|
235
|
+
|**Latent variables** |`my_model.sample_prior(key)` |Using `InferenceEngine` |
|
|
236
|
+
|**Predictive** |`my_model.sample_prior_predictive(key)` |`my_model.sample_posterior_predictive(key, posterior_samples, input_variables)` |
|
|
237
|
+
|
|
238
|
+
When sampling from the posterior predictive, the parameter `input_variables=` can be used to provide for example predictor values, such as when sampling from a regression model or a neural network; $p(y^* \mid X, Y, x^*)$.
|
|
239
|
+
|
|
240
|
+
## API
|
|
241
|
+
|
|
242
|
+
The full **bamojax** API can be found here: https://uncertaintyincomplexsystems.github.io/bamojax/.
|
|
243
|
+
|
|
244
|
+
## Citing bamojax
|
|
245
|
+
|
|
246
|
+
If you use Bamojax in your work, please cite the following:
|
|
247
|
+
|
|
248
|
+
Max Hinne. (2025). Bamojax: Bayesian Modelling with JAX (Version 0.3.11). Journal of Open Source Software, 114. https://doi.org/10.21105/joss.08642.
|
|
249
|
+
|
|
250
|
+
BibTeX:
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
@article{hinne2025bamojax,
|
|
254
|
+
author = {Hinne, Max},
|
|
255
|
+
title = {Bamojax: Bayesian Modelling with JAX},
|
|
256
|
+
journal = {Journal of Open Source Software},
|
|
257
|
+
volume = 114
|
|
258
|
+
year = 2025,
|
|
259
|
+
publisher = {Open Journals},
|
|
260
|
+
version = {0.3.11},
|
|
261
|
+
doi = {10.21105/joss.08642},
|
|
262
|
+
url = {https://joss.theoj.org/papers/10.21105/joss.08642}
|
|
263
|
+
}
|
|
264
|
+
```
|
bamojax-0.4.2/README.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
[](https://github.com/uncertaintyincomplexsystems/bamojax/actions/workflows/test.yml) [](https://doi.org/10.21105/joss.08642)
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
# Welcome to bamojax!
|
|
6
|
+
|
|
7
|
+
Welcome to **bamojax**, the Bayesian modelling and model comparison toolbox implemented using the JAX coding universe. **bamojax** is a probabilistic programming language (PPL), similar to Numpyro, PyMC, Stan, JAGS, and BUGS, as well as a set of analyses for model comparison and averaging. **bamojax** relies on [Blackjax](https://blackjax-devs.github.io/blackjax/) for approximate inference, and on [Numpyro](https://num.pyro.ai/en/stable/) for probability distributions and their essential operations.
|
|
8
|
+
|
|
9
|
+
## What sets bamojax apart?
|
|
10
|
+
|
|
11
|
+
Existing PPLs, such as PyMC, can export their log density function so it can be sampled using Blackjax. However, this has two downsides:
|
|
12
|
+
|
|
13
|
+
1. Not all existing probabilistic programming easily allow you to update variables in your model using Gibbs Markov chain Monte Carlo (MCMC) algorithms. For example, if one wants to approximate the posterior over a latent Gaussian process (GP) and its hyperparameters, it can be more efficient to use elliptical slice sampling for the GP, than to apply the No-U-Turn Hamiltonian Monte Carlo Sampler [(Hoffman and Gelman, 2014)](https://jmlr.org/papers/v15/hoffman14a.html) to all variables at once. This effect is even more pronounced when embedding MCMC sampling within Sequential Monte Carlo (see [Hinne, 2025](https://link.springer.com/article/10.3758/s13428-025-02642-1) for more details).
|
|
14
|
+
2. It is harder to embed this logdensity into tempered Sequential Monte Carlo algorithms, as for this one needs access to the prior and likelihood separately.
|
|
15
|
+
|
|
16
|
+
By implementing your own models and samplers using Blackjax, these problems can be circumvented. However, this is a labour-intensive and error-prone process. Therefore, **bamojax** provides a user-friendly interface around Blackjax, that allows for easy model construction and Gibbs sampling.
|
|
17
|
+
|
|
18
|
+
Once a Bayesian model has been determined, the next goal is often to compare it with competing hypotheses. This can, for example, be done by computing the marginal likelihoods of different models, and taking their ratio, a quantity known as the _Bayes factor_. However, computing marginal likelihoods and therefore Bayes factors is computationally challenging, and several approximation strategies exist. **bamojax** makes this process easier by implementing the following marginal likelihood approximations:
|
|
19
|
+
|
|
20
|
+
- Importance sampling
|
|
21
|
+
- Sequential Monte Carlo
|
|
22
|
+
- The Laplace approximation
|
|
23
|
+
- Bridge sampling (Gronau et al., 2017; Meng & Wong, 1996)
|
|
24
|
+
- The THAMES estimator (Metodiev et al., 2025)
|
|
25
|
+
|
|
26
|
+
More comparison features are being added to the toolbox continuously.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
Install **bamojax** using:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
pip install git+https://github.com/UncertaintyInComplexSystems/bamojax#egg=bamojax
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Please note that if you want to use hardware acceleration, such as using GPUs, you might want to install `JAX` yourself. Please see https://docs.jax.dev/en/latest/installation.html for instructions. If no JAX installation is present, **bamojax** will install it as a dependency.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Quick tutorial
|
|
40
|
+
|
|
41
|
+
Let's estimate the latent probability $\theta=p(x_1=\text{heads} \mid \theta)$ that a coin lands up heads, given a set of observations $x_1, \ldots, x_n$:
|
|
42
|
+
|
|
43
|
+
#### Generate data
|
|
44
|
+
```
|
|
45
|
+
import jax.numpy as jnp
|
|
46
|
+
import jax.random as jrnd
|
|
47
|
+
from bamojax.base import Model
|
|
48
|
+
from bamojax.inference import SMCInference
|
|
49
|
+
from bamojax.samplers import mcmc_sampler
|
|
50
|
+
import numpyro as npr
|
|
51
|
+
import numpyro.distributions as dist
|
|
52
|
+
import blackjax as bjx
|
|
53
|
+
|
|
54
|
+
key = jrnd.PRNGKey(0)
|
|
55
|
+
key, key_data, key_inference = jrnd.split(key, 3)
|
|
56
|
+
|
|
57
|
+
true_theta = 0.3
|
|
58
|
+
n = 10000
|
|
59
|
+
x = jrnd.bernoulli(key_data, p=true_theta, shape=(n, ))
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### Define Bayesian generative model
|
|
63
|
+
|
|
64
|
+
Under the good, **bamojax** represents the a Bayesian model using a Directed Acyclic Graph (DAG) structure, and automatically derives model priors, likelihoods, and Gibbs inference schemes. These are then combined with the fast inference algorithms implemented in Blackjax. Bayesian models can be instantiated in **bamojax** using:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
from bamojax.base import Model
|
|
68
|
+
my_model = Model('The name of the model')
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Subsequently, variables can be added to this model using for example:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
def link_fn(probs):
|
|
75
|
+
return {'probs': probs}
|
|
76
|
+
|
|
77
|
+
#
|
|
78
|
+
latent_theta = my_model.add_node('theta',
|
|
79
|
+
distribution=dist.Beta(concentration1=1, concentration0=1))
|
|
80
|
+
observations = my_model.add_node('x',
|
|
81
|
+
distribution=dist.Bernoulli,
|
|
82
|
+
parents=dict(probs=latent_theta),
|
|
83
|
+
link_fn=link_fn,
|
|
84
|
+
observations=x)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### Perform approximate inference
|
|
88
|
+
|
|
89
|
+
The next step is to perform inference. Here, we use Adaptive-Tempered Sequential Monte Carlo, as implemented by Blackjax:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
num_particles = 1_000
|
|
93
|
+
num_mcmc_steps = 100
|
|
94
|
+
num_chains = 1
|
|
95
|
+
|
|
96
|
+
stepsize = 0.01
|
|
97
|
+
|
|
98
|
+
mcmc_params = dict(sigma=stepsize*jnp.eye(my_model.get_model_size()))
|
|
99
|
+
rmh = mcmc_sampler(my_model,
|
|
100
|
+
mcmc_kernel=bjx.normal_random_walk,
|
|
101
|
+
mcmc_parameters=mcmc_params)
|
|
102
|
+
|
|
103
|
+
key, key_inference = jrnd.split(key)
|
|
104
|
+
|
|
105
|
+
engine = SMCInference(model=my_model,
|
|
106
|
+
mcmc_kernel=rmh,
|
|
107
|
+
num_particles=num_particles,
|
|
108
|
+
num_mutations=num_mcmc_steps,
|
|
109
|
+
num_chains=num_chains)
|
|
110
|
+
result = engine.run(key_inference)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
print(jnp.mean(result['final_state'].particles['theta']))
|
|
114
|
+
|
|
115
|
+
>>> 0.29956531188263924
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Modelling in bamojax
|
|
119
|
+
|
|
120
|
+
**bamojax** aims to make Bayesian modelling and inference with JAX and the Blackjax library more user-friendly, while retaining the speed and flexibility of the latter. To use the framework, a user is required to specify the directed acyclic graph that defines a Bayesian probabilistic model, as well as
|
|
121
|
+
* Distributions,
|
|
122
|
+
* Transformations,
|
|
123
|
+
* Link functions, and
|
|
124
|
+
* Observations.
|
|
125
|
+
|
|
126
|
+
### The model
|
|
127
|
+
|
|
128
|
+
To perform these steps, one first initializes the `Model`:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
my_model = Model('The name of my model')
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
This effectively creates an empty DAG.
|
|
135
|
+
|
|
136
|
+
### The variables
|
|
137
|
+
|
|
138
|
+
Next, one adds nodes to the DAG one by one, or multiple at once in the case of iteration / plate notation: For instance, the following adds a latent random variable with a Gaussian distribution assigned to it, $x\sim \mathcal{N}(0,1)$:
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
a_new_node = my_model.add_node('variable name', distribution=dist.Normal, parents=dict(loc=0.0, scale=1.0))
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Depending on whether a user provides values for the `distribution=`, and/or `observations=` arguments in the `add_node()` method, **bamojax** derives whether the variable is:
|
|
145
|
+
1. Stochastic and latent,
|
|
146
|
+
2. Stochastic and observed,
|
|
147
|
+
3. Deterministic and observed.
|
|
148
|
+
|
|
149
|
+
The `parents` argument expects a dictionary, in which the keys must correspond to either the arguments of a Numpyro `dist.Distribution` object or the arguments of a link function.
|
|
150
|
+
|
|
151
|
+
Nodes can furthermore take any Numpyro bijector using the `bijector=` argument, for example to transform a real variable to a bounded domain or vice versa. In many cases, the same goal can be achieved by the link function, but sometimes a bijector is simpler to use.
|
|
152
|
+
|
|
153
|
+
### Link functions
|
|
154
|
+
|
|
155
|
+
An important 'feature' of **bamojax** is that it is straightforward to add any deterministic transformation from the values of a parent variable to the inputs of a child variable. For example, imagine a variable $\theta$ representing a coin flip probability. A typical prior would be the beta distribution, typically parametrized with pseudo-counts $\alpha$ and $\beta$, so that $\theta \sim \text{Beta}(\alpha,\beta)$. However, we may want to specify a hierarchical prior on the _mode_ and _precision_ of this distribution, rather than on the pseudo-counts. With a link function we can express this:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
def beta_link_fn(mode, conc):
|
|
159
|
+
a = mode*(conc-2) + 1
|
|
160
|
+
b = (1 - mode)*(conc-2) + 1
|
|
161
|
+
return {'concentration1': a, 'concentration0': b}
|
|
162
|
+
|
|
163
|
+
omega = my_model.add_node('omega', distribution=dist.Beta, parents=dict(concentration1=1.0, concentration0=1.0))
|
|
164
|
+
theta = my_model.add_node('theta', distribution=dist.Beta, parents=dict(mode=omega, conc=15), link_fn=beta_link_fn)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The link function `beta_link_fn` takes the mode (given by another node `omega`) and the concentration (given by a scalar, which is implicitly converted to a deterministic and observed node), and returns the standard arguments $\alpha$ and $\beta$ which the `dist.Beta` distribution object recognizes as valid parameters.
|
|
168
|
+
|
|
169
|
+
Because link functions are written in plain Python, they can be of arbitrary complexity. For example, in [bamojax/examples/bnn/bnn_mpl.ipynb](https://github.com/UncertaintyInComplexSystems/bamojax/blob/main/bamojax/examples/bnn/bnn_mpl.ipynb), we use [Flax Linen](https://flax.readthedocs.io/en/latest/) to set up a multilayer perceptron as a link function.
|
|
170
|
+
|
|
171
|
+
### Inference
|
|
172
|
+
|
|
173
|
+
**bamojax** does not have its own inference engine, but provides an interface to [Blackjax](https://blackjax-devs.github.io/blackjax/), in addition to some quality-of-life features. Ultimately, control is left entirely to the user. Here is an example where we use Gibbs MCMC:
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
step_fns = dict(beta=normal_random_walk, sigma=normal_random_walk)
|
|
177
|
+
step_fn_params = dict(omega=dict(sigma=0.05), theta=dict(sigma=0.5))
|
|
178
|
+
gibbs_kernel = gibbs_sampler(my_model, step_fns=step_fns, step_fn_params=step_fn_params)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
In Gibbs sampling, we specify a kernel for each variable. In this case, we use Gaussian proposal distributions for both $\omega$ and $\theta$.
|
|
182
|
+
|
|
183
|
+
Now we can set up our inference engine:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
engine = MCMCInference(model=my_model, num_chains=4, num_samples=100_000, num_burn=100_000, num_thin=50, mcmc_kernel=gibbs_kernel, return_diagnostics=True)
|
|
187
|
+
result = engine.run(jrnd.PRNGKey(0))
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
This returns a dictionary with a `states` value containing a dictionary samples for each variable, for the requested number of chains and samples, discarding the specified number of burn-in samples, and storing only every 50th sample. By setting `return_diagnostics=True`, information such as acceptance rates are provided as well. For large models, turning this off can conserve memory consumption.
|
|
191
|
+
|
|
192
|
+
Alternative inference engines include Sequential Monte Carlo, Variational Inference, and Stochastic-Gradient MCMC methods. Examples on how to use these can be found in the `examples/` folder, as well as on the [Uncertainty in Complex Systems website](https://mhinne.github.io/uncertainty-in-complex-systems).
|
|
193
|
+
|
|
194
|
+
### Predictions
|
|
195
|
+
|
|
196
|
+
**bamojax** supports each combination of sampling from the prior or posterior, and the latent variables or the predictive distribution, using any of the following:
|
|
197
|
+
|
|
198
|
+
| |Prior |Posterior |
|
|
199
|
+
|---------------------|----------------------------------------------|----------------------------------------------------------------------------------|
|
|
200
|
+
|**Latent variables** |`my_model.sample_prior(key)` |Using `InferenceEngine` |
|
|
201
|
+
|**Predictive** |`my_model.sample_prior_predictive(key)` |`my_model.sample_posterior_predictive(key, posterior_samples, input_variables)` |
|
|
202
|
+
|
|
203
|
+
When sampling from the posterior predictive, the parameter `input_variables=` can be used to provide for example predictor values, such as when sampling from a regression model or a neural network; $p(y^* \mid X, Y, x^*)$.
|
|
204
|
+
|
|
205
|
+
## API
|
|
206
|
+
|
|
207
|
+
The full **bamojax** API can be found here: https://uncertaintyincomplexsystems.github.io/bamojax/.
|
|
208
|
+
|
|
209
|
+
## Citing bamojax
|
|
210
|
+
|
|
211
|
+
If you use Bamojax in your work, please cite the following:
|
|
212
|
+
|
|
213
|
+
Max Hinne. (2025). Bamojax: Bayesian Modelling with JAX (Version 0.3.11). Journal of Open Source Software, 114. https://doi.org/10.21105/joss.08642.
|
|
214
|
+
|
|
215
|
+
BibTeX:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
@article{hinne2025bamojax,
|
|
219
|
+
author = {Hinne, Max},
|
|
220
|
+
title = {Bamojax: Bayesian Modelling with JAX},
|
|
221
|
+
journal = {Journal of Open Source Software},
|
|
222
|
+
volume = 114
|
|
223
|
+
year = 2025,
|
|
224
|
+
publisher = {Open Journals},
|
|
225
|
+
version = {0.3.11},
|
|
226
|
+
doi = {10.21105/joss.08642},
|
|
227
|
+
url = {https://joss.theoj.org/papers/10.21105/joss.08642}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
# This file was generated by 'versioneer.py' (0.29) from
|
|
3
|
+
# revision-control system data, or from the parent directory name of an
|
|
4
|
+
# unpacked source archive. Distribution tarballs contain a pre-generated copy
|
|
5
|
+
# of this file.
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
version_json = '''
|
|
10
|
+
{
|
|
11
|
+
"date": "2026-10-26T21:35:56+0100",
|
|
12
|
+
"dirty": false,
|
|
13
|
+
"error": null,
|
|
14
|
+
"full-revisionid": "7b6576685f711fc94c568bb32813d45e67e2a861",
|
|
15
|
+
"version": "0.4.2"
|
|
16
|
+
}
|
|
17
|
+
''' # END VERSION_JSON
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_versions():
|
|
21
|
+
return json.loads(version_json)
|