Skip to content

Learning a prior distribution ​

Neural estimators are usually trained with parameters drawn from a known prior distribution. Sometimes, however, prior knowledge comes in the form of samples: for example, parameter estimates from many previous analyses of related data, or posterior draws from an earlier analysis (e.g., Schmidli et al., 2014; Roch and Shen, 2026).

Here, we use a PriorEstimator to fit a flexible distribution to such samples (i.e., unconditional density estimation). The trained estimator can be used to sample from the learned distribution (e.g., to simulate training data for another neural estimator), to evaluate its density (e.g., as the prior of a RatioEstimator), and to map parameters to the latent space of a normalising flow, where unusual parameter vectors can be flagged.

Package dependencies ​

julia
using NeuralEstimators
using Flux
using CairoMakie
using Distributions: Chisq, quantile

To improve computational efficiency, various GPU backends are supported. Once the relevant package is loaded and a compatible GPU is available, it will be used automatically:

julia
using CUDA, cuDNN
julia
using AMDGPU
julia
using Metal
julia
using oneAPI

Samples from the target distribution ​

In practice, the samples would be, for example, existing parameter estimates, stored as a × matrix (or a NamedMatrix), where is the dimension of the parameter vector and is the number of samples. Here, for illustration, we draw them from a known distribution,   and   independently, so that the learned distribution can be compared with the truth:

julia
sampler(K) = NamedMatrix(μ = randn(K), σ = rand(K))
θ_train = sampler(100_000)
θ_val   = sampler(100_000)
d = size(θ_train, 1)  # dimension of the parameter vector

Standardising the parameters

A PriorEstimator does not standardise the parameters. When they are on very different scales, standardise each parameter before training (e.g., subtract its mean and divide by its standard deviation, both computed from the training samples), and apply the inverse transformation to samples from the learned distribution.

Constructing the estimator ​

By default, a PriorEstimator approximates the distribution with a NormalisingFlow. Other Approximate distributions can be used through the keyword argument q:

julia
estimator = PriorEstimator(d)

Training the estimator ​

We train the estimator with train, passing the training and validation samples. No data or simulator are needed:

julia
estimator = train(estimator, θ_train, θ_val; batchsize = 256)

Training progress is reported in the terminal:

If new samples can be drawn at will, one may instead pass the function that draws them, train(estimator, sampler; K = 100_000), in which case new samples are drawn during training.

The risk is the average of over the samples, where denotes the density of the learned distribution. Since the samples are drawn from the true distribution, with density , the expected risk decomposes as

where is the (differential) entropy of and    is the Kullback–Leibler (KL) divergence from to . Since does not depend on , minimising the expected risk is equivalent to minimising the KL divergence. Since the KL divergence is non-negative and zero if and only if   (almost everywhere), the expected risk is bounded below by , with equality if and only if  . Here, since and are independent, is the sum of the entropies of their distributions, and , namely   .

The risk over the course of training can be plotted using plotrisk:

julia
plotrisk()

Assessing the estimator ​

The function assess compares draws from the learned distribution with test samples from the true distribution. The resulting Assessment contains the risk, here the average negative log density of the test samples under the learned distribution, which estimates the expected risk discussed above. The draws can be visualised with plot(assessment), which compares the marginal distributions of and and their joint distribution:

julia
θ_test = sampler(100_000)               # test samples from the true distribution
assessment = assess(estimator, θ_test)  # compares 100,000 draws from the learned distribution with θ_test
risk(assessment)                        # ≈ 1.44, slightly above its lower bound H(p) ≈ 1.42
plot(assessment)

The learned distribution closely matches the true distribution. Since a normalising flow is a smooth transformation of a Gaussian distribution, it slightly smooths the sharp boundaries of the uniform distribution of : for parameters with bounded support, it can help to train on an unbounded scale (e.g., after a logit or log transformation) and to transform the samples back.

With three or more parameters, plot(assessment) gives a pairs plot, and plot(assessment; plots = :marginals) gives the marginal histograms alone. If no test samples are available, assess(estimator) can be used to visualise the learned distribution alone.

Evaluating the learned distribution ​

The density of the learned distribution is evaluated with logdensity, and forward and inverse map between the parameter space and the latent space of the normalising flow:

julia
θ = sampler(5)
logdensity(estimator, θ)   # log density of each parameter vector
U = forward(estimator, θ)  # latent representation of each parameter vector
inverse(estimator, U) ≈ θ  # the flow is invertible

For parameter vectors drawn from the learned distribution, the latent representation is standard Gaussian, so that its squared norm approximately follows a distribution. This gives a simple check for parameter vectors that are unusual under the learned distribution:

julia
θ_new = NamedMatrix(μ = [0.1, -1.2, 8.0], σ = [0.5, 0.2, 0.5])  # the third vector is unusual
U = forward(estimator, θ_new)
r² = vec(sum(abs2, U; dims = 1))
r² .> quantile(Chisq(d), 0.999)  # flags the third vector