blackjax.mcmc.laplace_marginal#
Adjoint-differentiated Laplace marginal log-density.
Provides a differentiable approximation to the marginal log-density obtained by integrating out latent Gaussian variables via the Laplace approximation. Intended for use in hierarchical models where sampling the joint posterior over latent variables and hyperparameters is geometrically difficult.
Typical model structure:
phi ~ p(phi) # hyperparameters (small dimension)
theta ~ N(0, K(phi)) # latent Gaussian variables (large dimension)
y ~ p(y | theta, phi) # observations (any C³ likelihood)
laplace_marginal_factory returns a LaplaceMarginal object whose
__call__ method evaluates the Laplace-approximated marginal log-density
log p̂(phi | y) with correct gradients via the implicit function theorem.
References
Margossian et al., “Hamiltonian Monte Carlo using an adjoint-differentiated Laplace approximation”, NeurIPS 2020. arXiv:2004.12550.
Margossian, “General adjoint-differentiated Laplace approximation”, 2023. arXiv:2306.14976.
Classes#
Info returned by any |
|
Bundle of pure functions for the Laplace-approximated marginal density. |
Functions#
|
Estimated total inner L-BFGS gradient evaluations for one kernel step. |
|
Build a Laplace-approximated marginal log-density over hyperparameters. |
Module Contents#
- class LaplaceHMCInfo[source]#
Info returned by any
laplace_*hmckernel step.Contains all standard
HMCInfofields for backward compatibility, plus L-BFGS convergence diagnostics from thetheta*refresh that occurs after each accept/reject step.The L-BFGS diagnostics reflect the post-accept-reject warm-started solve — i.e., the single explicit
solve_theta_with_info()call at the end of each kernel step, not the leapfrog-interior solves (those happen insidejax.lax.custom_rootand are not directly accessible).- momentum
Momentum sampled at the start of the trajectory.
- acceptance_rate
Metropolis acceptance probability for this transition.
- is_accepted
Whether the proposed position was accepted.
- is_divergent
Whether the energy difference exceeded the divergence threshold.
- energy
Total energy (kinetic + potential) of the transition.
- proposal
The proposed integrator state (position + momentum at trajectory end).
- num_integration_steps
Number of leapfrog steps taken.
- lbfgs_iter_num
Number of L-BFGS iterations at the post-accept
theta*refresh.Note
Using this field for gradient-evaluation accounting
The leapfrog-interior L-BFGS solves happen inside
jax.lax.custom_root()and are not directly observable.lbfgs_iter_num(the post-accept refresh) is the best available proxy for the per-leapfrog inner iteration count, because warm-started solves from nearbyphivalues converge in a similar number of iterations.To estimate total inner gradient evaluations per kernel step (for a grad-count denominator), use
laplace_lbfgs_grad_evals():total_inner_grads = laplace_lbfgs_grad_evals(info) # ≈ (num_integration_steps + 1) × lbfgs_iter_num
The
+1accounts for the post-accept refresh itself.- lbfgs_error
Final gradient norm
||∇f(theta*)||₂at the post-accept refresh. Large values (>>gtol) indicate a non-converged inner solve.- lbfgs_converged
Trueifflbfgs_error <= gtol. May beFalsefor well-behaved warm-started solves that land near (but not below)gtol; preferlbfgs_hit_maxiteras the primary non-convergence alarm.- lbfgs_hit_maxiter
Trueiff the L-BFGS solver exhausted its iteration budget (iter_num >= maxiter). This is the direct signal for the silent-non-convergence bug diagnosed in blackjax issue #925. WhenTrue,theta*may be a poor MAP estimate and the Laplace log-marginal (and its gradient) is unreliable for this step.
- laplace_lbfgs_grad_evals(info: LaplaceHMCInfo) blackjax.types.Array[source]#
Estimated total inner L-BFGS gradient evaluations for one kernel step.
The Laplace-HMC family requires an inner L-BFGS solve at each leapfrog step to find
theta*(phi)and at the post-accepttheta*refresh. The leapfrog-interior solves run insidejax.lax.custom_root()and are not directly observable;info.lbfgs_iter_num(the post-accept refresh) is used as a proxy because warm-started solves from nearbyphivalues converge in a similar number of iterations.The formula is:
total_inner_grads ≈ (num_integration_steps + 1) × lbfgs_iter_num
where the
+1accounts for the post-accept refresh itself.- Parameters:
info –
LaplaceHMCInforeturned by anylaplace_*hmckernel step.- Returns:
Scalar JAX array with the estimated inner gradient evaluation count for this step.
- Return type:
Array
Notes
Use this as the
grad_count_per_stepcallable in benchmark harnesses to replace the heuristicnum_integration_steps × 5. The measuredlbfgs_iter_numwill differ per recipe depending onmaxiter,gtol, model curvature, and step size. Mean values across a chain can be extracted fromchain_stats["lbfgs_iter_num"]when the harness captures that field.
- class LaplaceMarginal[source]#
Bundle of pure functions for the Laplace-approximated marginal density.
Each attribute is a plain callable, testable and reusable independently. The dataclass is a named container — there is no mutable state.
The five callables are:
solve_theta(phi, theta_prev=None) -> theta_star: finds the mode ofp(theta | phi, y)via L-BFGS. No custom VJP; useful for warm-starting.solve_theta_with_info(phi, theta_prev=None) -> (theta_star, LBFGSDiagnostics): same assolve_thetabut also returns per-call L-BFGS diagnostics (iter_num,error,converged,hit_maxiter). Used inside the laplace kernel to populateLaplaceHMCInfo.get_theta_star(phi, theta_prev=None) -> theta_star: same assolve_thetabut wrapped injax.lax.custom_rootfor IFT gradients.log_marginal(phi, theta_prev=None) -> (lp, theta_star): evaluates the Laplace log-marginal and returnstheta_staras auxiliary output. Use withjax.value_and_grad(..., has_aux=True).sample_theta(rng_key, phi, theta_star) -> theta_sample: draws one sample fromp(theta | phi, y) ≈ N(theta_star, H(phi)^{-1}).
- laplace_marginal_factory(log_joint_fn: Callable, theta_init: blackjax.types.ArrayLikeTree, **optimizer_kwargs) LaplaceMarginal[source]#
Build a Laplace-approximated marginal log-density over hyperparameters.
For a model
log_joint_fn(theta, phi) = log p(theta, phi, y), returns aLaplaceMarginalwhose__call__evaluates the Laplace approximation:log p̂(phi | y) ≈ log p(theta*(phi), phi, y) - 1/2 log|det(-H(theta*(phi), phi))| + d/2 log(2π)where
theta*(phi) = argmax_theta log_joint_fn(theta, phi)is found via L-BFGS andH = d²/dtheta² log_joint_fnis the Hessian at the mode.Gradients w.r.t.
phiare computed via the implicit function theorem (jax.lax.custom_root): the L-BFGS iterations are not unrolled. The log-determinant gradient uses JAX’s built-in VJP forjnp.linalg.slogdet.- Parameters:
log_joint_fn –
(theta, phi) -> float. Boththetaandphimay be arbitrary PyTrees. Must be at least C³ smooth intheta.theta_init – Initial guess for
theta. Fixes the PyTree structure and shape of the latent variable space for all subsequent calls. Used as cold-start fallback whentheta_prev=None.**optimizer_kwargs – Passed through to
blackjax.optimizers.lbfgs.minimize_lbfgs. Useful keys:maxiter(default 30),gtol,ftol,maxls.
- Return type:
A
LaplaceMarginalinstance.
Examples
def log_joint(theta, phi): log_p_phi = jax.scipy.stats.halfnorm.logpdf(phi, 0, 1) log_p_theta = jax.scipy.stats.norm.logpdf(theta, 0, phi).sum() log_lik = jax.scipy.stats.norm.logpdf(y_obs, theta, 1).sum() return log_p_phi + log_p_theta + log_lik laplace = laplace_marginal_factory(log_joint, jnp.zeros(n)) # Evaluate with gradient (for use in any sampler): (lp, theta_star), grad = jax.value_and_grad( laplace, has_aux=True )(phi) # Individual components are testable in isolation: theta_star = laplace.solve_theta(phi, theta_prev=prev_theta_star)
Notes
Applicability:
The Laplace approximation is accurate when
p(theta | phi, y)is approximately Gaussian (unimodal, log-concave near the mode).The Hessian
-d²/dtheta² log_joint_fnmust be positive-definite attheta*(phi)for allphiencountered during sampling.Memory is O(d²) and log-determinant computation is O(d³) where
d = dim(theta).