Inspect a model#
The examples below use model from
Build your first model, with beta, variance,
sigma, mu, x, and y. Inspect the model at its current values
before fitting it.
Find inputs#
import jax
import jax.numpy as jnp
import tensorflow_probability.substrates.jax.distributions as tfd
import liesel.model as lsl
sorted(model.parameters)
['beta', 'variance']
sorted(model.observed)
['x', 'y']
response = model.vars["y"]
response.dist_node["scale"].name
'sigma'
model.vars["sigma"].value_node[0].name
'variance'
The parameters are beta and variance; the observed variables are x
and y. Their order in the mappings is not significant. The two input
lookups return sigma and variance. "scale" is the distribution’s
argument name, whereas "sigma" is the variable’s name. Use an integer for
an input passed positionally and its argument name for an input passed by keyword.
Strong variables receive their values directly; weak variables calculate them from other variables. Observed and parameter flags describe a separate choice: the statistical role of a variable. A weak variable can also carry a likelihood or prior; see Use distributions.
Read log densities#
response.log_prob.shape
(80,)
{
"likelihood": round(float(model.log_lik), 3),
"prior": round(float(model.log_prior), 3),
"total": round(float(model.log_prob), 3),
}
{'likelihood': -87.078, 'prior': -4.296, 'total': -91.374}
model.vars["x"].log_prob
0.0
The response contributes one log density per observation in this scalar normal
example. model.log_lik sums densities on observed variables;
model.log_prior sums densities on parameters. Here their sum is
model.log_prob. Variables without a distribution, such as x, contribute
zero. A parameter without a distribution has a constant prior contribution;
on an unbounded domain this is an improper prior, with no distribution to sample.
The total normally sums all distribution nodes. A distribution not assigned an observed or parameter role can therefore break the likelihood/prior decomposition. Custom aggregate nodes can also change it. See Use distributions before using such a model for optimization.
Trace invalid values#
Probe a copy so the original model keeps its valid values:
probe = model.copy()
probe.vars["variance"].value = -1.0
diagnostics = probe.diagnose().sort_values("name")
diagnostics[["name", "value_n_nan", "log_prob_n_nan", "log_prob_n_inf"]]
| name | value_n_nan | log_prob_n_nan | log_prob_n_inf | |
|---|---|---|---|---|
| 4 | beta | 0 | 0 | 0 |
| 5 | mu | 0 | NaN | NaN |
| 2 | prior_scale | 0 | NaN | NaN |
| 3 | sigma | 1 | NaN | NaN |
| 0 | variance | 0 | 1 | 0 |
| 1 | x | 0 | NaN | NaN |
| 6 | y | 0 | 80 | 0 |
probe.vars["variance"].value = 1.0
The negative variance makes its square root invalid. The table lets you trace
the NaN from sigma into the response density. Missing entries (NaN) mean that the
quantity does not apply, for example a log density on a variable without a
distribution. An infinite log density can instead signal a value outside a
distribution’s support; check the distribution and its inputs rather than
replacing every non-finite result with a number.
Use plot() to follow dependencies and
plot_nodes() when you need to see the underlying value
and distribution nodes separately. Finite values establish neither a suitable
statistical model nor convergence of an inference algorithm.