Encoding Spatial Priors with VAEs (PriorVAE)
Bayesian disease mapping is slow for a frustrating reason: the prior, not the likelihood. A Gaussian-process or ICAR prior over a spatial field couples every location to every other, so each MCMC step pays for an expensive, badly-conditioned evaluation of a high-dimensional correlated prior, and the sampler mixes poorly through the resulting cramped geometry. PriorVAE, introduced by Semenova and colleagues, sidesteps this with a trick that is obvious only in hindsight: spend the cost once, offline, by training a variational autoencoder to imitate the spatial prior, then reuse its decoder — a cheap, fixed neural network — as a drop-in prior during inference. The correlated field is then generated from a handful of independent standard-normal latents, which MCMC handles beautifully.
The problem PriorVAE solves#
A spatial model puts a correlated prior on a latent field at locations or areal units, for example the Gaussian process with covariance , and links it to observed counts through a likelihood such as .
The spatial correlation in Figure 2 is the whole point of the prior — it says nearby places should have similar rates — but it is also what makes inference expensive. Two costs make this painful inside a sampler. Evaluating the prior density needs the inverse and determinant of the matrix — an Cholesky at worst — repeated at every step. Worse, the strong correlations warp the posterior into a shape (a “funnel”, a thin ridge) that Hamiltonian samplers traverse slowly, so you need many steps and each step is expensive. Basis-function approximations and INLA attack the first cost; PriorVAE attacks both, and it works for priors — irregular ICAR graphs, aggregated fields, non-Gaussian processes — that those methods handle less naturally.
The PriorVAE recipe#
The method has four steps, split cleanly into an expensive offline stage and a cheap inference stage — the workflow on the right of the figure.
- Sample the prior. Draw many realizations from the spatial prior evaluated at the fixed set of locations you will model. This uses the slow machinery, but only once, and it is embarrassingly parallel.
- Train a VAE on the draws. Fit a variational autoencoder whose “data” are these prior draws, learning an encoder and a decoder with a low-dimensional latent , . The ELBO forces the decoder to reproduce the prior’s spatial structure from a standard-normal code.
- Freeze the decoder. Keep fixed. It is now a compact, differentiable surrogate for “a draw from the spatial prior”, parameterized by the independent latents .
- Infer over the latents. Replace the spatial prior with the decoder in the Bayesian model: put a prior on , set , and run MCMC. The sampler now explores an uncorrelated, low-dimensional, well-conditioned standard-normal space, and the decoder maps each proposal to a valid correlated field.
The model that goes to the sampler is simply with no , no , and geometry as friendly as a standard normal.
Why a decoder can stand in for the prior#
The key fact is that the decoder learns the prior’s distribution, not one draw from it. Because the ELBO trains so that pushing a standard normal through it reproduces the family of prior samples, the induced distribution of matches the target spatial prior — the same marginal variances and spatial correlations — up to the VAE’s approximation error. A useful special case makes this concrete and is worth running: if the decoder is linear, , then is Gaussian with covariance , and choosing from the top eigenvectors of scaled by the square roots of its eigenvalues (the truncated Karhunen–Loève expansion) makes . That linear map is a low-rank generative model of a Gaussian prior — a linear PriorVAE — and it already delivers the inference speed-up. PriorVAE’s advance is to let be nonlinear, so the same recipe works when the prior is not Gaussian: an ICAR field on an irregular county graph, a log-Gaussian Cox intensity, or a field defined on shifting administrative boundaries.
Freezing the decoder fixes the prior’s hyperparameters (its lengthscale, its variance) at training time. To keep them inferable, PriorCVAE conditions the decoder on the hyperparameters, and aggVAE extends the idea to spatial aggregation and change-of-support — for instance mapping malaria prevalence across redrawn district boundaries in Kenya. The closely related πVAE encodes a stochastic-process prior over functions the same way.
A worked example#
Suppose the spatial prior is a Gaussian process on points with a smooth kernel, and its Karhunen–Loève expansion shows that the first components already capture almost all of the prior variance.
The spectrum in Figure 3 is why this works: the correlated field lives, to good approximation, in a six-dimensional subspace spanned by the leading spatial modes. Encoding the prior means keeping those six basis vectors ; a standard-normal then decodes to a full 25-dimensional field whose covariance reproduces the GP. Inference that would have sampled a correlated 25-dimensional field now samples six independent unit normals, so the posterior geometry is a plain sphere and the sampler mixes fast — the payoff PriorVAE brings to the far larger fields (hundreds to thousands of areal units) of real disease maps.
In code#
Python#
The runnable version uses a linear decoder — the Karhunen–Loève special case — so it executes here with numpyro, while faithfully showing PriorVAE’s inference stage: sample the prior, encode it into a low-dimensional basis, then run MCMC over independent latents that decode to the field.
import numpy as np, jax, jax.numpy as jnp
import numpyro, numpyro.distributions as dist
from numpyro.infer import MCMC, NUTS
numpyro.set_host_device_count(1)
rng = np.random.default_rng(0)
s = np.linspace(0, 1, 25) # spatial locations
K = np.exp(-0.5 * (s[:, None] - s[None, :]) ** 2 / 0.15 ** 2) + 1e-6 * np.eye(25)
vals, vecs = np.linalg.eigh(K) # "encode" the GP prior:
idx = np.argsort(vals)[::-1][:6] # keep the top 6 KL modes
B = jnp.asarray(vecs[:, idx] * np.sqrt(vals[idx])) # decoder f = B z, z~N(0,I)
f_true = np.asarray(B) @ rng.standard_normal(6) # a field to recover
y = f_true + 0.1 * rng.standard_normal(25) # noisy observations
def model(y):
z = numpyro.sample("z", dist.Normal(jnp.zeros(6), 1.0)) # independent latents
numpyro.sample("y", dist.Normal(B @ z, 0.1), obs=jnp.asarray(y))
mcmc = MCMC(NUTS(model), num_warmup=300, num_samples=300, num_chains=1,
progress_bar=False)
mcmc.run(jax.random.PRNGKey(0), y=y)
f_hat = np.asarray(B) @ np.asarray(mcmc.get_samples()["z"]).mean(0)
print(f"sampled {6} independent latents in place of a correlated {25}-dim field")
print(f"posterior field RMSE vs truth: {np.sqrt(np.mean((f_hat - f_true) ** 2)):.3f}")
sampled 6 independent latents in place of a correlated 25-dim field
posterior field RMSE vs truth: 0.063
The recovery in Figure 4 is the payoff: the posterior mean follows the truth to an RMSE of , with honest uncertainty everywhere, at a fraction of the sampling cost of the full correlated prior.
The full PriorVAE replaces the linear basis with a nonlinear decoder, trained offline (step 2) to reproduce draws from the spatial prior; here a small flax VAE learns the GP’s structure from 4000 draws, and its frozen decoder would then slot into the same inference model as did above:
import flax.linen as fnn, optax
L = jnp.linalg.cholesky(jnp.asarray(K)) # sample the GP prior (step 1)
key = jax.random.PRNGKey(0)
draws = (L @ jax.random.normal(key, (25, 4000))).T # 4000 draws, each a 25-dim field
class PriorVAE(fnn.Module): # nonlinear encoder + decoder
@fnn.compact
def __call__(self, x, key):
h = fnn.tanh(fnn.Dense(32)(x))
mu, logvar = fnn.Dense(6)(h), fnn.Dense(6)(h)
z = mu + jnp.exp(0.5 * logvar) * jax.random.normal(key, mu.shape)
return fnn.Dense(25)(fnn.tanh(fnn.Dense(32)(z))), mu, logvar
vae = PriorVAE()
params = vae.init(key, draws[:1], key)
opt = optax.adam(2e-3); state = opt.init(params)
def elbo_loss(p, x, k):
xh, mu, lv = vae.apply(p, x, k)
recon = jnp.mean(jnp.sum((xh - x) ** 2, 1))
kl = jnp.mean(-0.5 * jnp.sum(1 + lv - mu ** 2 - jnp.exp(lv), 1))
return recon + 0.01 * kl
@jax.jit
def train_step(p, state, x, k):
loss, grads = jax.value_and_grad(elbo_loss)(p, x, k)
updates, state = opt.update(grads, state)
return optax.apply_updates(p, updates), state, loss
for _ in range(400): # train the VAE offline (step 2)
key, k = jax.random.split(key)
params, state, loss = train_step(params, state, draws, k)
print(f"nonlinear PriorVAE trained on GP draws: reconstruction loss {float(loss):.1f}")
nonlinear PriorVAE trained on GP draws: reconstruction loss 1.0
The nonlinear decoder is what lets the same recipe handle priors the linear basis cannot — an ICAR field on an irregular graph, or an aggregated/change-of-support prior.
R#
# Train the VAE offline (torch/keras), export the decoder weights, then do the
# Bayesian inference in a probabilistic language. Here the numpyro-style model
# expressed with cmdstanr, feeding a frozen decoder as generated quantities.
library(cmdstanr)
# Stan model sketch:
# parameters { vector[d] z; }
# model { z ~ std_normal();
# y ~ poisson_log(log_expected + decoder(z)); } // decoder = fixed NN
Julia#
# Flux to train the decoder offline; Turing.jl for inference over the latents.
using Turing, Flux
@model function priorvae(y, expected, decoder)
z ~ MvNormal(zeros(d), I) # independent latents
f = decoder(z) # frozen VAE decoder -> spatial field
@. y ~ Poisson(expected * exp(f))
end
Why it matters#
PriorVAE is a template for a broader move: amortize an expensive prior into a neural network so that Bayesian inference stays cheap and honest. For spatial epidemiology this means disease maps over many small areas — prevalence, incidence, mortality — with the full uncertainty quantification of a hierarchical Bayesian model but a fraction of the sampling cost, and with the flexibility to encode priors that resist closed-form treatment. More broadly it joins deep generative modelling to principled inference: the neural network does not replace the Bayesian model or invent the prior, it compresses a prior you already trust into a form the sampler likes, keeping the interpretability and calibrated uncertainty that make a model useful for public-health decisions. The same idea generalizes beyond space — to priors over time series, functions, and mechanistic-model outputs — wherever a trusted but intractable prior is the bottleneck.