pavi-pyro 0.1.0__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.
- pavi_pyro-0.1.0/LICENSE +21 -0
- pavi_pyro-0.1.0/PKG-INFO +226 -0
- pavi_pyro-0.1.0/README.md +196 -0
- pavi_pyro-0.1.0/pyproject.toml +137 -0
- pavi_pyro-0.1.0/pyproject.toml.orig +103 -0
- pavi_pyro-0.1.0/src/pavi/__init__.py +8 -0
- pavi_pyro-0.1.0/src/pavi/core/__init__.py +12 -0
- pavi_pyro-0.1.0/src/pavi/core/encoding_manager.py +436 -0
- pavi_pyro-0.1.0/src/pavi/core/pavi_autoguide.py +380 -0
- pavi_pyro-0.1.0/src/pavi/core/transform_scheme.py +377 -0
- pavi_pyro-0.1.0/src/pavi/core/utils.py +27 -0
- pavi_pyro-0.1.0/src/pavi/data/__init__.py +0 -0
- pavi_pyro-0.1.0/src/pavi/data/generic.py +193 -0
- pavi_pyro-0.1.0/src/pavi/distributions/__init__.py +64 -0
- pavi_pyro-0.1.0/src/pavi/distributions/_pyro_base.py +27 -0
- pavi_pyro-0.1.0/src/pavi/distributions/affine_modules.py +605 -0
- pavi_pyro-0.1.0/src/pavi/distributions/householder_modules.py +115 -0
- pavi_pyro-0.1.0/src/pavi/distributions/power_spherical.py +272 -0
- pavi_pyro-0.1.0/src/pavi/distributions/pyro_factories.py +62 -0
- pavi_pyro-0.1.0/src/pavi/distributions/zuco_modules.py +96 -0
- pavi_pyro-0.1.0/src/pavi/inference/__init__.py +1 -0
- pavi_pyro-0.1.0/src/pavi/inference/callbacks.py +289 -0
- pavi_pyro-0.1.0/src/pavi/inference/lightning_module.py +275 -0
- pavi_pyro-0.1.0/src/pavi/inference/utils.py +45 -0
- pavi_pyro-0.1.0/src/pavi/py.typed +0 -0
pavi_pyro-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexandre Le Bris
|
|
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.
|
pavi_pyro-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pavi-pyro
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Plate-amortized variational inference (PAVI) for large hierarchical Bayesian models, built on Pyro
|
|
5
|
+
Keywords: variational inference,hierarchical Bayesian models,pyro,normalizing flows,neuroimaging
|
|
6
|
+
Author: Alexandre Le Bris
|
|
7
|
+
Author-email: Alexandre Le Bris <alexandrelebris@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Dist: numpy>=2.5.3
|
|
20
|
+
Requires-Dist: pyro-ppl>=1.9.1
|
|
21
|
+
Requires-Dist: pytorch-lightning>=2.6.5
|
|
22
|
+
Requires-Dist: torch>=2.15.0
|
|
23
|
+
Requires-Dist: zuko>=1.6.0
|
|
24
|
+
Requires-Python: >=3.14
|
|
25
|
+
Project-URL: Homepage, https://github.com/Dubris/pavi-pyro
|
|
26
|
+
Project-URL: Documentation, https://dubris.github.io/pavi-pyro/
|
|
27
|
+
Project-URL: Repository, https://github.com/Dubris/pavi-pyro
|
|
28
|
+
Project-URL: Issues, https://github.com/Dubris/pavi-pyro/issues
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# pavi — plate-amortized variational inference in Pyro
|
|
32
|
+
|
|
33
|
+
**Scalable Bayesian inference for hierarchical models with millions of latent variables.**
|
|
34
|
+
|
|
35
|
+
[](https://dubris.github.io/pavi-pyro/)
|
|
36
|
+
[](https://github.com/Dubris/pavi-pyro/blob/main/pyproject.toml)
|
|
37
|
+
[](https://pyro.ai)
|
|
38
|
+
[](https://github.com/Dubris/pavi-pyro/blob/main/LICENSE)
|
|
39
|
+
|
|
40
|
+
<p align="center">
|
|
41
|
+
<img src="https://raw.githubusercontent.com/Dubris/pavi-pyro/main/docs/figures/parcellations_group_vs_individuals.jpg" width="85%" alt="Population-level and individual parcellations of the cortex into 17 functional networks, estimated with PAVI on the HCP dataset">
|
|
42
|
+
<br>
|
|
43
|
+
<em>Population-level (top) and individual (bottom) parcellations of the cortex into 17 functional networks, inferred with PAVI from HCP resting-state fMRI (<a href="https://hal.science/hal-04506561">Le Bris et al., 2024</a>).</em>
|
|
44
|
+
</p>
|
|
45
|
+
|
|
46
|
+
`pavi` implements **PAVI** ([Rouillard et al., 2023](#citation)) for [Pyro](https://pyro.ai), the probabilistic programming library built on PyTorch. You describe your data with a hierarchical Bayesian model; `pavi` approximates the posterior distribution of its latent variables, even when there are millions of them. In particular, a main application of the algorithm and this implementation was estimating individualized functional networks over the cortex from fMRI data (see the figure above and [Le Bris et al., 2024](https://hal.science/hal-04506561)).
|
|
47
|
+
|
|
48
|
+
## Why PAVI?
|
|
49
|
+
|
|
50
|
+
Hierarchical models repeat the same variable across plates[^plate], and the number of latent variables quickly explodes. In the brain application, about 1,000 subjects × 60,000 cortical vertices give **60 million** latent variables. With a classical mean-field Gaussian approximation over about 20 functional networks, this requires **more than 2 billion** variational parameters.
|
|
51
|
+
|
|
52
|
+
PAVI builds the variational family with **normalizing flows**: a simple base distribution is mapped to a complex one by an invertible, learnable transform $f$. For an instance $n$ of a plate,
|
|
53
|
+
|
|
54
|
+
$$z^n = f(u^n;\ \phi, E^n), \qquad u^n \sim \text{base distribution}.$$
|
|
55
|
+
|
|
56
|
+
- The parameters $\phi$ of the transform are **shared** by all the instances of the plate. They capture what instances have in common and act as a group-level regularization.
|
|
57
|
+
- Each instance only keeps a small **encoding** $E^n \in \mathbb{R}^m$, which encodes the statistics of its posterior (hence the name).
|
|
58
|
+
|
|
59
|
+
The number of parameters still grows with the number of instances $N$, but much more slowly: for large $N$ it is reduced by a factor $O(m/d)$ compared to the mean-field approximation, where $d$ is the size of each variable and $m \ll d$. The mean-field approximation itself is the special case of a standard normal base and a diagonal affine transform. New subjects can be inferred by **refitting only their encodings**.
|
|
60
|
+
|
|
61
|
+
<p align="center">
|
|
62
|
+
<img src="https://raw.githubusercontent.com/Dubris/pavi-pyro/main/docs/figures/pavi_plate_amortization.png" width="45%" alt="(A) Generative model and PAVI variational family: posteriors of z_1 are parameterized by a shared phi_1 and per-instance encodings E_1^n. (B) Each posterior is a base distribution mapped by an invertible transform f.">
|
|
63
|
+
</p>
|
|
64
|
+
|
|
65
|
+
Learn more in [Concepts](https://dubris.github.io/pavi-pyro/concepts/).
|
|
66
|
+
|
|
67
|
+
## Features
|
|
68
|
+
|
|
69
|
+
- **Bring your own generative model**, implemented with Pyro and PyTorch.
|
|
70
|
+
- **An off-the-shelf implementation of PAVI**, with training utilities: plate-aware mini-batch sampler, PyTorch Lightning inference module, KL annealing and logging callbacks.
|
|
71
|
+
- **Many customization options**:
|
|
72
|
+
- a wide choice of parameterized invertible [transforms](https://dubris.github.io/pavi-pyro/transforms/);
|
|
73
|
+
- several strategies to manage the [encodings](https://dubris.github.io/pavi-pyro/encodings/);
|
|
74
|
+
- several strategies for the base distribution, called [schemes](https://dubris.github.io/pavi-pyro/schemes/).
|
|
75
|
+
|
|
76
|
+
## Installation
|
|
77
|
+
|
|
78
|
+
`pavi` requires Python ≥ 3.14. It is published on PyPI as `pavi-pyro` and imported as `pavi`:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install pavi-pyro # or: uv add pavi-pyro
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
See [Getting started](https://dubris.github.io/pavi-pyro/getting-started/) to install from source.
|
|
85
|
+
|
|
86
|
+
## Quickstart
|
|
87
|
+
|
|
88
|
+
A three-level hierarchical model: a global mean $z_2$, the means $z_1^i$ of 5 groups (plate `P_1`), and 50 observations $X^{i,j}$ per group (plate `P_0`).
|
|
89
|
+
|
|
90
|
+
**1. Write the generative model in Pyro.** The model receives the data and, optionally, the indices of a mini-batch (`idx`).
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import pyro
|
|
94
|
+
import pyro.distributions as dist
|
|
95
|
+
import torch
|
|
96
|
+
|
|
97
|
+
from pavi.core import JointEM, NormalizingFlowScheme, PAVIGuide
|
|
98
|
+
from pavi.distributions import affine, conditional_affine
|
|
99
|
+
|
|
100
|
+
NUM_GROUPS, NUM_OBS, DIM = 5, 50, 2
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def model(data=None, idx=None):
|
|
104
|
+
idx = idx or {}
|
|
105
|
+
z_2 = pyro.sample("z_2", dist.Normal(torch.zeros(DIM), 1.0).to_event(1))
|
|
106
|
+
with pyro.plate("P_1", NUM_GROUPS, subsample=idx.get("P_1"), dim=-1):
|
|
107
|
+
z_1 = pyro.sample("z_1", dist.Normal(z_2, 5.0).to_event(1))
|
|
108
|
+
with pyro.plate("P_0", NUM_OBS, subsample=idx.get("P_0"), dim=-2):
|
|
109
|
+
pyro.sample("X", dist.Normal(z_1, 1.0).to_event(1), obs=data)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# Simulate a dataset from the model
|
|
113
|
+
pyro.set_rng_seed(0)
|
|
114
|
+
X = pyro.poutine.trace(model).get_trace().nodes["X"]["value"]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**2. Build the PAVI guide[^guide].** Choose a transform for each latent site[^site], an encoding manager and a scheme.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
guide = PAVIGuide(
|
|
121
|
+
model,
|
|
122
|
+
transform_modules={
|
|
123
|
+
"z_2": [affine(scale_init=1.0)], # global variable
|
|
124
|
+
"z_1": [conditional_affine(hidden_layers=[32, 32])], # plate P_1: conditioned on encodings
|
|
125
|
+
},
|
|
126
|
+
transform_scheme=NormalizingFlowScheme,
|
|
127
|
+
encoding_manager=JointEM(encoding_size={"P_1": 8}),
|
|
128
|
+
hidden_sites=["X"], # observed sites are not inferred
|
|
129
|
+
device="cpu",
|
|
130
|
+
data=X,
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**3. Fit the guide** by maximizing the evidence lower bound (ELBO).
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
elbo = pyro.infer.Trace_ELBO(num_particles=8)
|
|
138
|
+
optimizer = torch.optim.Adam(guide.parameters(), lr=1e-2)
|
|
139
|
+
for step in range(2000):
|
|
140
|
+
optimizer.zero_grad()
|
|
141
|
+
loss = elbo.differentiable_loss(model, guide, X)
|
|
142
|
+
loss.backward()
|
|
143
|
+
optimizer.step()
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**4. Sample from the approximate posterior.**
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
with torch.no_grad():
|
|
150
|
+
posterior_sample = guide(X)
|
|
151
|
+
print(posterior_sample["z_1"]) # one sample of the 5 group means
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The [toy-model notebook](https://dubris.github.io/pavi-pyro/examples/toymodel_hgm/) compares the result with the exact posterior.
|
|
155
|
+
|
|
156
|
+
## How it works
|
|
157
|
+
|
|
158
|
+
```mermaid
|
|
159
|
+
flowchart LR
|
|
160
|
+
M["Pyro model<br/>with plates"] -->|"inspected once"| S["latent sites<br/>and their plates"]
|
|
161
|
+
S --> EM["encoding manager<br/>encodings E"]
|
|
162
|
+
S --> T["transforms<br/>per site"]
|
|
163
|
+
EM -->|"conditions"| T
|
|
164
|
+
T --> SC["scheme<br/>base distribution"]
|
|
165
|
+
SC --> Q["approximate posterior q"]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
When it is created, the guide runs the model once to list its latent sites and their plates. The encoding manager then creates the encodings, and each site gets its chain of transforms, conditioned on the encodings when the site lives in a plate. The scheme chooses the base distribution that the transforms are applied to. See [The PAVI guide](https://dubris.github.io/pavi-pyro/guide/).
|
|
169
|
+
|
|
170
|
+
## Application: individual brain functional networks
|
|
171
|
+
|
|
172
|
+
PAVI makes it possible to map the **functional networks of each individual brain** at the resolution of the cortical mesh, from resting-state fMRI ([Le Bris et al., 2024](https://hal.science/hal-04506561)). A hierarchical model explains the functional connectivity of each vertex as a mixture of 17 networks, whose spatial organization varies across individuals around a population-level atlas.
|
|
173
|
+
|
|
174
|
+
Instead of a discrete atlas, the model yields a **continuous, probabilistic map of each network**, at the population level (below) and for each individual:
|
|
175
|
+
|
|
176
|
+
<p align="center">
|
|
177
|
+
<img src="https://raw.githubusercontent.com/Dubris/pavi-pyro/main/docs/figures/probabilistic_networks_1.jpg" width="48%" alt="Population-level probabilistic maps of functional networks VIN-1 to CON-2">
|
|
178
|
+
<img src="https://raw.githubusercontent.com/Dubris/pavi-pyro/main/docs/figures/probabilistic_networks_2.jpg" width="48%" alt="Population-level probabilistic maps of functional networks CON-3 to DMN-4">
|
|
179
|
+
</p>
|
|
180
|
+
|
|
181
|
+
These individual maps are reliable, reflect individual-specific organization, predict behavior, and transfer to small datasets through the population-level priors. See [the brain application example](https://dubris.github.io/pavi-pyro/examples/brain-functional-networks/).
|
|
182
|
+
|
|
183
|
+
## Documentation
|
|
184
|
+
|
|
185
|
+
| Page | Content |
|
|
186
|
+
| --- | --- |
|
|
187
|
+
| [Getting started](https://dubris.github.io/pavi-pyro/getting-started/) | installation and a first model |
|
|
188
|
+
| [Concepts](https://dubris.github.io/pavi-pyro/concepts/) | hierarchical models, variational inference, normalizing flows, plate amortization |
|
|
189
|
+
| [The PAVI guide](https://dubris.github.io/pavi-pyro/guide/) | the arguments of `PAVIGuide` |
|
|
190
|
+
| [Encodings](https://dubris.github.io/pavi-pyro/encodings/) · [Transforms](https://dubris.github.io/pavi-pyro/transforms/) · [Schemes](https://dubris.github.io/pavi-pyro/schemes/) | the building blocks and their options |
|
|
191
|
+
| [Training](https://dubris.github.io/pavi-pyro/training/) | ELBOs, enumeration, mini-batches, Lightning, KL annealing |
|
|
192
|
+
| [Examples](https://dubris.github.io/pavi-pyro/examples/) | toy model, brain functional networks |
|
|
193
|
+
|
|
194
|
+
The documentation is published at <https://dubris.github.io/pavi-pyro/>. To browse it locally, run `uv run --group docs mkdocs serve`.
|
|
195
|
+
|
|
196
|
+
## Citation
|
|
197
|
+
|
|
198
|
+
If you use `pavi`, please cite:
|
|
199
|
+
|
|
200
|
+
```bibtex
|
|
201
|
+
@article{rouillard2023pavi,
|
|
202
|
+
title = {{PAVI}: Plate-Amortized Variational Inference},
|
|
203
|
+
author = {Rouillard, Louis and Le Bris, Alexandre and Moreau, Thomas and Wassermann, Demian},
|
|
204
|
+
journal = {Transactions on Machine Learning Research},
|
|
205
|
+
year = {2023}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
For the brain functional network application:
|
|
210
|
+
|
|
211
|
+
```bibtex
|
|
212
|
+
@unpublished{lebris2024transfer,
|
|
213
|
+
title = {Improving Individual-Specific Functional Parcellation through Transfer Learning},
|
|
214
|
+
author = {Le Bris, Alexandre and Rouillard, Louis and Wassermann, Demian},
|
|
215
|
+
year = {2024},
|
|
216
|
+
url = {https://hal.science/hal-04506561}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
|
|
222
|
+
[MIT](https://github.com/Dubris/pavi-pyro/blob/main/LICENSE)
|
|
223
|
+
|
|
224
|
+
[^plate]: **Plate**: a set of random variables that are conditionally independent given their parents, e.g. one variable per subject or per brain vertex. Declared with `pyro.plate`.
|
|
225
|
+
[^guide]: **Guide**: in Pyro, the parameterized family of distributions $q$ (the *variational family*) that is fitted to approximate the posterior. `pavi` provides the guide; you provide the model.
|
|
226
|
+
[^site]: **Site**: a random variable of a Pyro model, declared with `pyro.sample("name", distribution)`. Latent sites are inferred; observed sites hold the data.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# pavi — plate-amortized variational inference in Pyro
|
|
2
|
+
|
|
3
|
+
**Scalable Bayesian inference for hierarchical models with millions of latent variables.**
|
|
4
|
+
|
|
5
|
+
[](https://dubris.github.io/pavi-pyro/)
|
|
6
|
+
[](https://github.com/Dubris/pavi-pyro/blob/main/pyproject.toml)
|
|
7
|
+
[](https://pyro.ai)
|
|
8
|
+
[](https://github.com/Dubris/pavi-pyro/blob/main/LICENSE)
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<img src="https://raw.githubusercontent.com/Dubris/pavi-pyro/main/docs/figures/parcellations_group_vs_individuals.jpg" width="85%" alt="Population-level and individual parcellations of the cortex into 17 functional networks, estimated with PAVI on the HCP dataset">
|
|
12
|
+
<br>
|
|
13
|
+
<em>Population-level (top) and individual (bottom) parcellations of the cortex into 17 functional networks, inferred with PAVI from HCP resting-state fMRI (<a href="https://hal.science/hal-04506561">Le Bris et al., 2024</a>).</em>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
`pavi` implements **PAVI** ([Rouillard et al., 2023](#citation)) for [Pyro](https://pyro.ai), the probabilistic programming library built on PyTorch. You describe your data with a hierarchical Bayesian model; `pavi` approximates the posterior distribution of its latent variables, even when there are millions of them. In particular, a main application of the algorithm and this implementation was estimating individualized functional networks over the cortex from fMRI data (see the figure above and [Le Bris et al., 2024](https://hal.science/hal-04506561)).
|
|
17
|
+
|
|
18
|
+
## Why PAVI?
|
|
19
|
+
|
|
20
|
+
Hierarchical models repeat the same variable across plates[^plate], and the number of latent variables quickly explodes. In the brain application, about 1,000 subjects × 60,000 cortical vertices give **60 million** latent variables. With a classical mean-field Gaussian approximation over about 20 functional networks, this requires **more than 2 billion** variational parameters.
|
|
21
|
+
|
|
22
|
+
PAVI builds the variational family with **normalizing flows**: a simple base distribution is mapped to a complex one by an invertible, learnable transform $f$. For an instance $n$ of a plate,
|
|
23
|
+
|
|
24
|
+
$$z^n = f(u^n;\ \phi, E^n), \qquad u^n \sim \text{base distribution}.$$
|
|
25
|
+
|
|
26
|
+
- The parameters $\phi$ of the transform are **shared** by all the instances of the plate. They capture what instances have in common and act as a group-level regularization.
|
|
27
|
+
- Each instance only keeps a small **encoding** $E^n \in \mathbb{R}^m$, which encodes the statistics of its posterior (hence the name).
|
|
28
|
+
|
|
29
|
+
The number of parameters still grows with the number of instances $N$, but much more slowly: for large $N$ it is reduced by a factor $O(m/d)$ compared to the mean-field approximation, where $d$ is the size of each variable and $m \ll d$. The mean-field approximation itself is the special case of a standard normal base and a diagonal affine transform. New subjects can be inferred by **refitting only their encodings**.
|
|
30
|
+
|
|
31
|
+
<p align="center">
|
|
32
|
+
<img src="https://raw.githubusercontent.com/Dubris/pavi-pyro/main/docs/figures/pavi_plate_amortization.png" width="45%" alt="(A) Generative model and PAVI variational family: posteriors of z_1 are parameterized by a shared phi_1 and per-instance encodings E_1^n. (B) Each posterior is a base distribution mapped by an invertible transform f.">
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
Learn more in [Concepts](https://dubris.github.io/pavi-pyro/concepts/).
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- **Bring your own generative model**, implemented with Pyro and PyTorch.
|
|
40
|
+
- **An off-the-shelf implementation of PAVI**, with training utilities: plate-aware mini-batch sampler, PyTorch Lightning inference module, KL annealing and logging callbacks.
|
|
41
|
+
- **Many customization options**:
|
|
42
|
+
- a wide choice of parameterized invertible [transforms](https://dubris.github.io/pavi-pyro/transforms/);
|
|
43
|
+
- several strategies to manage the [encodings](https://dubris.github.io/pavi-pyro/encodings/);
|
|
44
|
+
- several strategies for the base distribution, called [schemes](https://dubris.github.io/pavi-pyro/schemes/).
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
`pavi` requires Python ≥ 3.14. It is published on PyPI as `pavi-pyro` and imported as `pavi`:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install pavi-pyro # or: uv add pavi-pyro
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
See [Getting started](https://dubris.github.io/pavi-pyro/getting-started/) to install from source.
|
|
55
|
+
|
|
56
|
+
## Quickstart
|
|
57
|
+
|
|
58
|
+
A three-level hierarchical model: a global mean $z_2$, the means $z_1^i$ of 5 groups (plate `P_1`), and 50 observations $X^{i,j}$ per group (plate `P_0`).
|
|
59
|
+
|
|
60
|
+
**1. Write the generative model in Pyro.** The model receives the data and, optionally, the indices of a mini-batch (`idx`).
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import pyro
|
|
64
|
+
import pyro.distributions as dist
|
|
65
|
+
import torch
|
|
66
|
+
|
|
67
|
+
from pavi.core import JointEM, NormalizingFlowScheme, PAVIGuide
|
|
68
|
+
from pavi.distributions import affine, conditional_affine
|
|
69
|
+
|
|
70
|
+
NUM_GROUPS, NUM_OBS, DIM = 5, 50, 2
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def model(data=None, idx=None):
|
|
74
|
+
idx = idx or {}
|
|
75
|
+
z_2 = pyro.sample("z_2", dist.Normal(torch.zeros(DIM), 1.0).to_event(1))
|
|
76
|
+
with pyro.plate("P_1", NUM_GROUPS, subsample=idx.get("P_1"), dim=-1):
|
|
77
|
+
z_1 = pyro.sample("z_1", dist.Normal(z_2, 5.0).to_event(1))
|
|
78
|
+
with pyro.plate("P_0", NUM_OBS, subsample=idx.get("P_0"), dim=-2):
|
|
79
|
+
pyro.sample("X", dist.Normal(z_1, 1.0).to_event(1), obs=data)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Simulate a dataset from the model
|
|
83
|
+
pyro.set_rng_seed(0)
|
|
84
|
+
X = pyro.poutine.trace(model).get_trace().nodes["X"]["value"]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**2. Build the PAVI guide[^guide].** Choose a transform for each latent site[^site], an encoding manager and a scheme.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
guide = PAVIGuide(
|
|
91
|
+
model,
|
|
92
|
+
transform_modules={
|
|
93
|
+
"z_2": [affine(scale_init=1.0)], # global variable
|
|
94
|
+
"z_1": [conditional_affine(hidden_layers=[32, 32])], # plate P_1: conditioned on encodings
|
|
95
|
+
},
|
|
96
|
+
transform_scheme=NormalizingFlowScheme,
|
|
97
|
+
encoding_manager=JointEM(encoding_size={"P_1": 8}),
|
|
98
|
+
hidden_sites=["X"], # observed sites are not inferred
|
|
99
|
+
device="cpu",
|
|
100
|
+
data=X,
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**3. Fit the guide** by maximizing the evidence lower bound (ELBO).
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
elbo = pyro.infer.Trace_ELBO(num_particles=8)
|
|
108
|
+
optimizer = torch.optim.Adam(guide.parameters(), lr=1e-2)
|
|
109
|
+
for step in range(2000):
|
|
110
|
+
optimizer.zero_grad()
|
|
111
|
+
loss = elbo.differentiable_loss(model, guide, X)
|
|
112
|
+
loss.backward()
|
|
113
|
+
optimizer.step()
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**4. Sample from the approximate posterior.**
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
with torch.no_grad():
|
|
120
|
+
posterior_sample = guide(X)
|
|
121
|
+
print(posterior_sample["z_1"]) # one sample of the 5 group means
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The [toy-model notebook](https://dubris.github.io/pavi-pyro/examples/toymodel_hgm/) compares the result with the exact posterior.
|
|
125
|
+
|
|
126
|
+
## How it works
|
|
127
|
+
|
|
128
|
+
```mermaid
|
|
129
|
+
flowchart LR
|
|
130
|
+
M["Pyro model<br/>with plates"] -->|"inspected once"| S["latent sites<br/>and their plates"]
|
|
131
|
+
S --> EM["encoding manager<br/>encodings E"]
|
|
132
|
+
S --> T["transforms<br/>per site"]
|
|
133
|
+
EM -->|"conditions"| T
|
|
134
|
+
T --> SC["scheme<br/>base distribution"]
|
|
135
|
+
SC --> Q["approximate posterior q"]
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
When it is created, the guide runs the model once to list its latent sites and their plates. The encoding manager then creates the encodings, and each site gets its chain of transforms, conditioned on the encodings when the site lives in a plate. The scheme chooses the base distribution that the transforms are applied to. See [The PAVI guide](https://dubris.github.io/pavi-pyro/guide/).
|
|
139
|
+
|
|
140
|
+
## Application: individual brain functional networks
|
|
141
|
+
|
|
142
|
+
PAVI makes it possible to map the **functional networks of each individual brain** at the resolution of the cortical mesh, from resting-state fMRI ([Le Bris et al., 2024](https://hal.science/hal-04506561)). A hierarchical model explains the functional connectivity of each vertex as a mixture of 17 networks, whose spatial organization varies across individuals around a population-level atlas.
|
|
143
|
+
|
|
144
|
+
Instead of a discrete atlas, the model yields a **continuous, probabilistic map of each network**, at the population level (below) and for each individual:
|
|
145
|
+
|
|
146
|
+
<p align="center">
|
|
147
|
+
<img src="https://raw.githubusercontent.com/Dubris/pavi-pyro/main/docs/figures/probabilistic_networks_1.jpg" width="48%" alt="Population-level probabilistic maps of functional networks VIN-1 to CON-2">
|
|
148
|
+
<img src="https://raw.githubusercontent.com/Dubris/pavi-pyro/main/docs/figures/probabilistic_networks_2.jpg" width="48%" alt="Population-level probabilistic maps of functional networks CON-3 to DMN-4">
|
|
149
|
+
</p>
|
|
150
|
+
|
|
151
|
+
These individual maps are reliable, reflect individual-specific organization, predict behavior, and transfer to small datasets through the population-level priors. See [the brain application example](https://dubris.github.io/pavi-pyro/examples/brain-functional-networks/).
|
|
152
|
+
|
|
153
|
+
## Documentation
|
|
154
|
+
|
|
155
|
+
| Page | Content |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| [Getting started](https://dubris.github.io/pavi-pyro/getting-started/) | installation and a first model |
|
|
158
|
+
| [Concepts](https://dubris.github.io/pavi-pyro/concepts/) | hierarchical models, variational inference, normalizing flows, plate amortization |
|
|
159
|
+
| [The PAVI guide](https://dubris.github.io/pavi-pyro/guide/) | the arguments of `PAVIGuide` |
|
|
160
|
+
| [Encodings](https://dubris.github.io/pavi-pyro/encodings/) · [Transforms](https://dubris.github.io/pavi-pyro/transforms/) · [Schemes](https://dubris.github.io/pavi-pyro/schemes/) | the building blocks and their options |
|
|
161
|
+
| [Training](https://dubris.github.io/pavi-pyro/training/) | ELBOs, enumeration, mini-batches, Lightning, KL annealing |
|
|
162
|
+
| [Examples](https://dubris.github.io/pavi-pyro/examples/) | toy model, brain functional networks |
|
|
163
|
+
|
|
164
|
+
The documentation is published at <https://dubris.github.io/pavi-pyro/>. To browse it locally, run `uv run --group docs mkdocs serve`.
|
|
165
|
+
|
|
166
|
+
## Citation
|
|
167
|
+
|
|
168
|
+
If you use `pavi`, please cite:
|
|
169
|
+
|
|
170
|
+
```bibtex
|
|
171
|
+
@article{rouillard2023pavi,
|
|
172
|
+
title = {{PAVI}: Plate-Amortized Variational Inference},
|
|
173
|
+
author = {Rouillard, Louis and Le Bris, Alexandre and Moreau, Thomas and Wassermann, Demian},
|
|
174
|
+
journal = {Transactions on Machine Learning Research},
|
|
175
|
+
year = {2023}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
For the brain functional network application:
|
|
180
|
+
|
|
181
|
+
```bibtex
|
|
182
|
+
@unpublished{lebris2024transfer,
|
|
183
|
+
title = {Improving Individual-Specific Functional Parcellation through Transfer Learning},
|
|
184
|
+
author = {Le Bris, Alexandre and Rouillard, Louis and Wassermann, Demian},
|
|
185
|
+
year = {2024},
|
|
186
|
+
url = {https://hal.science/hal-04506561}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
[MIT](https://github.com/Dubris/pavi-pyro/blob/main/LICENSE)
|
|
193
|
+
|
|
194
|
+
[^plate]: **Plate**: a set of random variables that are conditionally independent given their parents, e.g. one variable per subject or per brain vertex. Declared with `pyro.plate`.
|
|
195
|
+
[^guide]: **Guide**: in Pyro, the parameterized family of distributions $q$ (the *variational family*) that is fitted to approximate the posterior. `pavi` provides the guide; you provide the model.
|
|
196
|
+
[^site]: **Site**: a random variable of a Pyro model, declared with `pyro.sample("name", distribution)`. Latent sites are inferred; observed sites hold the data.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pavi-pyro"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Plate-amortized variational inference (PAVI) for large hierarchical Bayesian models, built on Pyro"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
keywords = [
|
|
9
|
+
"variational inference",
|
|
10
|
+
"hierarchical Bayesian models",
|
|
11
|
+
"pyro",
|
|
12
|
+
"normalizing flows",
|
|
13
|
+
"neuroimaging",
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
requires-python = ">=3.14"
|
|
27
|
+
dependencies = [
|
|
28
|
+
"numpy>=2.5.3",
|
|
29
|
+
"pyro-ppl>=1.9.1",
|
|
30
|
+
"pytorch-lightning>=2.6.5",
|
|
31
|
+
"torch>=2.15.0",
|
|
32
|
+
"zuko>=1.6.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[[project.authors]]
|
|
36
|
+
name = "Alexandre Le Bris"
|
|
37
|
+
email = "alexandrelebris@gmail.com"
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/Dubris/pavi-pyro"
|
|
41
|
+
Documentation = "https://dubris.github.io/pavi-pyro/"
|
|
42
|
+
Repository = "https://github.com/Dubris/pavi-pyro"
|
|
43
|
+
Issues = "https://github.com/Dubris/pavi-pyro/issues"
|
|
44
|
+
|
|
45
|
+
[build-system]
|
|
46
|
+
requires = ["uv_build>=0.11.25,<0.13.0"]
|
|
47
|
+
build-backend = "uv_build"
|
|
48
|
+
|
|
49
|
+
[tool.uv.build-backend]
|
|
50
|
+
module-name = "pavi"
|
|
51
|
+
|
|
52
|
+
[tool.ruff]
|
|
53
|
+
line-length = 140
|
|
54
|
+
include = [
|
|
55
|
+
"*.py",
|
|
56
|
+
"*.pyi",
|
|
57
|
+
"**/__init__.py",
|
|
58
|
+
"*.ipynb",
|
|
59
|
+
]
|
|
60
|
+
exclude = [
|
|
61
|
+
".git",
|
|
62
|
+
".venv",
|
|
63
|
+
"__pycache__",
|
|
64
|
+
"build",
|
|
65
|
+
"dist",
|
|
66
|
+
]
|
|
67
|
+
extend-exclude = [
|
|
68
|
+
"*.interactive",
|
|
69
|
+
"untitled:*",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
[tool.ruff.lint]
|
|
73
|
+
select = [
|
|
74
|
+
"E",
|
|
75
|
+
"F",
|
|
76
|
+
"W",
|
|
77
|
+
"I",
|
|
78
|
+
"B",
|
|
79
|
+
"UP",
|
|
80
|
+
"NPY",
|
|
81
|
+
"C4",
|
|
82
|
+
]
|
|
83
|
+
ignore = [
|
|
84
|
+
"E501",
|
|
85
|
+
"B008",
|
|
86
|
+
]
|
|
87
|
+
extend-select = [
|
|
88
|
+
"TD",
|
|
89
|
+
"FIX",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
[tool.vscode.settings]
|
|
93
|
+
"jupyter.notebookFileRoot" = "${workspaceFolder}"
|
|
94
|
+
|
|
95
|
+
[tool.mypy]
|
|
96
|
+
files = ["src"]
|
|
97
|
+
python_version = "3.14"
|
|
98
|
+
|
|
99
|
+
[[tool.mypy.overrides]]
|
|
100
|
+
module = ["zuko.*"]
|
|
101
|
+
ignore_missing_imports = true
|
|
102
|
+
|
|
103
|
+
[tool.pytest.ini_options]
|
|
104
|
+
addopts = "-s"
|
|
105
|
+
testpaths = ["test"]
|
|
106
|
+
|
|
107
|
+
[dependency-groups]
|
|
108
|
+
dev = [
|
|
109
|
+
"mypy>=2.4.1",
|
|
110
|
+
"pytest>=9.1.1",
|
|
111
|
+
"pytest-mock>=3.15.1",
|
|
112
|
+
]
|
|
113
|
+
docs = [
|
|
114
|
+
"markdown-callouts>=0.4.0",
|
|
115
|
+
"mkdocs-jupyter>=0.27.0",
|
|
116
|
+
"mkdocs-material>=9.8.0",
|
|
117
|
+
"mkdocstrings[python]>=1.0.8",
|
|
118
|
+
]
|
|
119
|
+
experiments = [
|
|
120
|
+
"hcp-utils>=0.1.0",
|
|
121
|
+
"psutil>=7.2.2",
|
|
122
|
+
"hydra-core==1.3.5",
|
|
123
|
+
"ipykernel>=7.3.0",
|
|
124
|
+
"nibabel>=5.4.2",
|
|
125
|
+
"nilearn>=0.13.1",
|
|
126
|
+
"omegaconf>=2.3.1",
|
|
127
|
+
"seaborn>=0.13.2",
|
|
128
|
+
"xarray>=2026.7.0",
|
|
129
|
+
"lightning>=2.6.5",
|
|
130
|
+
"mlflow>=3.15.2",
|
|
131
|
+
"hydra-zen>=0.16.0",
|
|
132
|
+
"beartype>=0.22.9",
|
|
133
|
+
"einops>=0.8.2",
|
|
134
|
+
"brainspace>=0.2.1",
|
|
135
|
+
"typer>=0.27.2",
|
|
136
|
+
"graphviz>=0.21",
|
|
137
|
+
]
|