Transform parameters#

A positive parameter can be represented by an unconstrained source while the likelihood still receives its positive value. Use a bijection when you want to preserve a prior already defined on the positive scale.

Keep positive values#

Starting with model from Build your first model, copy it and transform its variance:

import jax
import jax.numpy as jnp
import tensorflow_probability.substrates.jax.bijectors as tfb
import tensorflow_probability.substrates.jax.distributions as tfd

import liesel.goose as gs
import liesel.model as lsl
transformed = model.copy()
variance = transformed.vars["variance"]

variance.biject(tfb.Exp(), name="log_variance")
log_variance = variance.bijected_var
(log_variance.name, float(log_variance.value), float(variance.value))
('log_variance', 0.0, 1.0)
transformed.plot(width=8, height=5, legend=False)
Strong log_variance supplies weak positive variance, then sigma and y.

The new strong parameter is log_variance. The original variance is now weak and calculates exp(log_variance). The exponential bijector’s forward direction goes from the unconstrained value to the positive value; its inverse computes the initial log variance. The graph gains that value dependency. Its colors and edges follow the key in the first tutorial.

biject returns the original variable, which is useful for chaining. Read .bijected_var to obtain the new parameter. transform() returns the transformed variable instead. biject("auto") selects the distribution’s default event-space bijector; an explicit bijector makes your choice visible.

Preserve the density#

The prior moves to the unconstrained source and includes the change-of-variables Jacobian. If the original prior is on variance \(v\) and \(u=\log(v)\), the transformed log density is \(\log p(\exp(u)) + u\). The parameter flag moves to the source as well.

By comparison, new_calc(jnp.exp, log_variance) only creates a calculation. A normal prior placed directly on log_variance defines a log-normal prior on variance; it does not automatically transform an arbitrary prior on variance. The second tutorial uses this direct modeling choice for a log-scale predictor.

Density modes depend on the parameterization. Even with the correct Jacobian, optimizing the transformed density need not give the transform of the original density’s mode.

Choose parameters#

For an existing distribution, biject_parameters() can transform its strong parameter inputs. For example, this selects the scale input of a normal likelihood:

scale = lsl.Var.new_param(
    1.0,
    dist=lsl.Dist(tfd.LogNormal, loc=0.0, scale=0.5),
    name="scale",
)

likelihood = lsl.Dist(tfd.Normal, loc=0.0, scale=scale)

likelihood.biject_parameters({"scale": tfb.Exp()});
scale.bijected_var.name
'h(scale)'

These keys name distribution arguments, not model variables. For precise control over the source name and its inference configuration, use Var.biject.

Set up inference#

If a variable already has inference settings, supply settings for the new source or explicitly drop the old ones with inference="drop". For an untransformed variance prepared for NUTS, the corresponding call is:

prepared = model.copy()
prepared.vars["variance"].biject(
    tfb.Exp(),
    name="log_variance",
    inference=gs.MCMCSpec(gs.NUTSKernel),
);

Sample or optimize log_variance and interpret the back-transformed variance. Configure the other parameters too before running inference. See Sample your first posterior for a complete MCMC example and Choose optimizers for parameter selection in optimization.