Module Norn.Dist
Probability distributions over tensors.
A distribution describes one whole tensor value: its log density is a scalar, and iid adds independent copies. Each family has one parameterisation, named by labels; parameters are tensors and broadcast. Event axes belong to the family: normal has none, dirichlet's and mvn's value is a vector along the last axis, lkj_cholesky's a matrix along the last two.
let prior = Norn.Dist.normal ~loc:(f64 0.) ~scale:(f64 5.) let lp = Norn.Dist.log_density prior (f64 1.2)
Validation on use. Constructors are total: they check shapes, raising Invalid_argument naming the function, and record their parameters' domain checks. Every eliminator runs them with Nx.check, as a check does: at once on the host, at the next wait on another set, eagerly or in a compiled function. A parameter outside its domain raises Invalid_argument naming its index and value, as in Norn.Dist.log_density: normal: scale at [3] is -1, not in (0, inf). A value outside the support has density -inf and raises nothing. valid reads the same checks as data, for a density that must be total:
let ok = Norn.Dist.valid d in let d = Norn.Dist.check ~unless:(Nx.scalar Nx.bool true) "" d in Nx.where ok (Norn.Dist.log_density d x) (Nx.scalar dt Float.neg_infinity)
type ('x, 'f, 'd) tThe type for distributions over values 'x whose log densities have element type 'f. 'x is (float, 'f, 'd) Nx.t for continuous families, 'd Nx.int32_t for counts, 'd Nx.int64_t for categories and 'd Nx.bool_t for bernoulli.
type ('x, 'f, 'd) kind = The type for the values of a distribution: a witness of 'x.
Continuous families
The log densities of gamma, inverse_gamma, beta, dirichlet and student_t are Poisson, binomial and multinomial masses at real counts, rescaled, in Loader's saddle-point form (discrete families): no term grows faster than the logarithm of a parameter. A log density f is within ε (16 |f| + 48 Λ + 12 M) of the exact one, ε the epsilon of the parameters' dtype, where Λ sums the magnitudes of the logarithms the family takes and M measures how far its counts lie from their means:
gamma:Λ = 1 + |log α| + |log x|andM = |rate x - α|;inverse_gamma: the sameΛandM = |scale / x - α|;dirichlet, ofKconcentrationsα_ksumming toA:Λ = K + |log A| + Σ_k (|log α_k| + |log x_k|)andM = Σ_k |α_k - A x_k|;betais the dirichlet of(a, b)at(x, 1 - x);student_t:Λ = 1 + |log df| + |log scale|andM = 0.
ε M is near the change in f that rounding the point by ε makes. A dtype narrower than float32 computes at float32 and rounds once: within the float32 bound plus half its own ulp. At float64 the densities' derivatives in the parameters are finite wherever the exact ones are, at concentrations and degrees of freedom from 1e-300.
val normal :
loc:(float, 'f, 'd) Nx.t ->
scale:(float, 'f, 'd) Nx.t ->
((float, 'f, 'd) Nx.t, 'f, 'd) tnormal ~loc ~scale has density exp (-z²/2) / (scale sqrt (2π)), z = (x - loc) / scale. scale is in (0, inf) and loc finite.
half_normal ~scale is the distribution of |x|, x normal with location 0, on (0, inf).
val lognormal :
loc:(float, 'f, 'd) Nx.t ->
scale:(float, 'f, 'd) Nx.t ->
((float, 'f, 'd) Nx.t, 'f, 'd) tlognormal ~loc ~scale is the distribution of exp x, x normal.
val student_t :
df:(float, 'f, 'd) Nx.t ->
loc:(float, 'f, 'd) Nx.t ->
scale:(float, 'f, 'd) Nx.t ->
((float, 'f, 'd) Nx.t, 'f, 'd) tstudent_t ~df ~loc ~scale is Student's t with df degrees of freedom, in (0, inf), located and scaled.
val cauchy :
loc:(float, 'f, 'd) Nx.t ->
scale:(float, 'f, 'd) Nx.t ->
((float, 'f, 'd) Nx.t, 'f, 'd) tcauchy ~loc ~scale has density 1 / (π scale (1 + z²)).
half_cauchy ~scale is the distribution of |x|, x Cauchy with location 0, on (0, inf).
val laplace :
loc:(float, 'f, 'd) Nx.t ->
scale:(float, 'f, 'd) Nx.t ->
((float, 'f, 'd) Nx.t, 'f, 'd) tlaplace ~loc ~scale has density exp (-|z|) / (2 scale).
val logistic :
loc:(float, 'f, 'd) Nx.t ->
scale:(float, 'f, 'd) Nx.t ->
((float, 'f, 'd) Nx.t, 'f, 'd) tlogistic ~loc ~scale has density exp (-z) / (scale (1 + exp (-z))²).
exponential ~rate has density rate exp (-rate x) on (0, inf).
val gamma :
concentration:(float, 'f, 'd) Nx.t ->
rate:(float, 'f, 'd) Nx.t ->
((float, 'f, 'd) Nx.t, 'f, 'd) tgamma ~concentration ~rate has density proportional to x^(α - 1) exp (-rate x) on (0, inf), α the concentration.
val inverse_gamma :
concentration:(float, 'f, 'd) Nx.t ->
scale:(float, 'f, 'd) Nx.t ->
((float, 'f, 'd) Nx.t, 'f, 'd) tinverse_gamma ~concentration ~scale is the distribution of 1 / x, x gamma with rate scale.
beta ~a ~b has density proportional to x^(a - 1) (1 - x)^(b - 1) on (0, 1).
val uniform :
low:(float, 'f, 'd) Nx.t ->
high:(float, 'f, 'd) Nx.t ->
((float, 'f, 'd) Nx.t, 'f, 'd) tuniform ~low ~high has density 1 / (high - low) on (low, high). Each element of high - low is in (0, inf).
dirichlet ~concentration is over vectors along the last axis of concentration, of positive components summing to one, with density proportional to prod_k x_k^(α_k - 1). A matrix of concentrations is independent rows. A vector whose sum is within 4 K ε of one, K its length, is read as x / Σ x, the point of the simplex on its ray, so the rounding that keeps it off the simplex does not move its density. Draws are Nx.Rng.dirichlet's, made at float32 for a narrower dtype, each component at least the dtype's least positive value.
Raises Invalid_argument if the last axis has fewer than two components.
val mvn :
loc:(float, 'f, 'd) Nx.t ->
scale_tril:(float, 'f, 'd) Nx.t ->
((float, 'f, 'd) Nx.t, 'f, 'd) tmvn ~loc ~scale_tril is the multivariate normal of vectors along the last axis, with mean loc and covariance L Lᵀ, L the lower triangle of scale_tril. L is finite, its diagonal in (0, inf).
Raises Invalid_argument if scale_tril's last two axes are not square or not of loc's length.
val von_mises :
loc:(float, 'f, 'd) Nx.t ->
concentration:(float, 'f, 'd) Nx.t ->
((float, 'f, 'd) Nx.t, 'f, 'd) tvon_mises ~loc ~concentration is the distribution of angles on [-π, π] with density exp (κ cos (x - loc)) / (2π I₀(κ)), κ the concentration, in [0, inf): uniform at κ = 0, near a normal of variance 1 / κ about loc for a large κ. The log density is -2κ sin² ((x - loc) / 2) - log (2π i0e κ) (Nx.i0e), finite at every finite κ. loc is finite and may lie off [-π, π]: the density is periodic in it. Draws are Nx.Rng.von_mises's, Best and Fisher's rejection, moved by loc and wrapped onto [-π, π].
lkj_cholesky n ~concentration is the distribution of the Cholesky factor L of an n × n correlation matrix C = L Lᵀ whose density is proportional to det C^(η - 1), η the concentration in (0, inf) (Lewandowski, Kurowicka and Joe, Generating random correlation matrices based on vines and extended onion method, 2009): uniform over correlation matrices at η = 1, concentrated about the identity above. Over L's strict lower triangle the density is Π_(i = 2)^n L_ii^(n - i + 2η - 2) over their normalising constant, whose factors are formed from beta densities at their mode, which keep their digits at a large η. A matrix of concentrations' leading axes are batch axes, the value's last two axes the matrix. Draws follow the onion method; each off-diagonal C_ij has (C_ij + 1) / 2 beta of shapes η - 1 + n / 2 and η - 1 + n / 2. A matrix whose rows' squared norms are within 4 n ε of one counts as in the support.
Raises Invalid_argument if n < 2.
Discrete families
The log masses of poisson, binomial and neg_binomial follow Loader's saddle-point form (Fast and Accurate Computation of Binomial Probabilities, 2000), whose terms do not cancel. At a count x of mean m, a log mass f is within ε (c_f |f| + c_m |x - m|) of the exact one, and half the least subnormal where it underflows, ε the epsilon of the parameters' dtype, for x and trials below 2{^24} at float32: c_f = 24 and c_m = 1 for poisson, 24 and 9 for binomial, 64 and 6 for neg_binomial. The second term is the rounding of the means the family computes from its parameters, or of x / m. A dtype narrower than float32 computes at float32 and rounds once: within the float32 bound plus half its own ulp. The masses' derivatives in the parameters are finite wherever the exact ones are, at means and dispersions down to the least subnormal.
bernoulli ~logits is true with probability sigmoid logits. A logit may be infinite.
val poisson : rate:(float, 'f, 'd) Nx.t -> ('d Nx.int32_t, 'f, 'd) tpoisson ~rate is the count with probability rate^x exp (-rate) / x!, rate non-negative and finite.
val neg_binomial :
mean:(float, 'f, 'd) Nx.t ->
dispersion:(float, 'f, 'd) Nx.t ->
('d Nx.int32_t, 'f, 'd) tneg_binomial ~mean ~dispersion is the count of mean mean and variance mean + mean² / dispersion, a Poisson whose rate is gamma with concentration dispersion. mean is non-negative and finite, dispersion in (0, inf).
val binomial :
trials:'d Nx.int32_t ->
logits:(float, 'f, 'd) Nx.t ->
('d Nx.int32_t, 'f, 'd) tbinomial ~trials ~logits is the number of successes in trials independent trials, each a success with probability p = sigmoid logits: the count x in {0, ..., trials} with probability C(trials, x) p^x (1 - p)^(trials - x). trials is non-negative. A logit may be infinite: -inf puts all mass at 0, inf at trials. Draws are Nx.Rng.binomial's, within the limits it states.
val categorical : logits:(float, 'f, 'd) Nx.t -> ('d Nx.int64_t, 'f, 'd) tcategorical ~logits is the index k with probability softmax logits along the last axis. A logit may be -inf, a category of probability zero.
Raises Invalid_argument if logits is a scalar or its last axis is empty.
Combinators
iid s d is s independent draws of d, its value of shape s followed by d's.
sorted n d is n independent draws of the scalar d sorted along a new last axis, with the exact density n! times the product of d's on nondecreasing vectors.
Raises Invalid_argument if d is not over scalars or n < 1.
val transform :
('f, 'd) Bij.t ->
((float, 'f, 'd) Nx.t, 'f, 'd) t ->
((float, 'f, 'd) Nx.t, 'f, 'd) ttransform b d is the distribution of b's image of a draw of d, with the density of d at the preimage less b's log-determinant there. A value outside b's image has density -inf.
val truncated :
?low:(float, 'f, 'd) Nx.t ->
?high:(float, 'f, 'd) Nx.t ->
((float, 'f, 'd) Nx.t, 'f, 'd) t ->
((float, 'f, 'd) Nx.t, 'f, 'd) ttruncated ?low ?high d is d conditioned on lying between low and high, elementwise, a missing bound leaving its side as d's: the density of d over its mass F(high) - F(low) there, -inf outside. The bounds are finite, broadcast against d's shape, and clip to d's support. The mass is a difference of upper tails where low lies above d's median and of lower tails otherwise, each held as logarithms, so the mass between bounds far in one tail keeps its digits; where the two tails agree within a factor e^(1/4) it is the density integrated over the window by eight-point Gauss-Legendre quadrature. The mass must be positive, a check like a parameter's. The log density is d's less the log mass: where both are large, their difference is within ε times their magnitude, as at bounds a million standard deviations out at float32. Draws and the quantile invert d at (1 - p) F(low) + p F(high), read from the smaller tail, within that inverse's precision, about ε min (F, 1 - F) / f in the value: a window narrower than that is not resolved. Its coordinates map onto the interval, or the half-line, between the bounds and d's support.
Raises Invalid_argument if neither bound is given, or if d is not normal, half_normal, lognormal, cauchy, half_cauchy, laplace, logistic, exponential, gamma, inverse_gamma, beta, uniform or iid of one: the families whose distribution function and its inverses nx computes.
mixture ~logits d draws one component k with probability softmax logits for the whole value, then the value from d_k, the slice of d at k on its leading axis: the log density is logsumexp_k (log_softmax logits_k + log_density d_k x). A component per point is iid [| n |] (mixture ~logits d). Its coordinates map onto the hull of its components' supports, read on the host.
Raises Invalid_argument if logits is not a vector or d's leading axis is not its length.
val hmm :
initial:(float, 'f, 'd) Nx.t ->
transition:(float, 'f, 'd) Nx.t ->
int ->
('x, 'f, 'd) t ->
('x, 'f, 'd) thmm ~initial ~transition n d is n observations of a hidden Markov chain over k states, its states summed out: state z_1 is j with probability softmax initial, state z_(t+1) is j with probability softmax transition_i after z_t = i, and observation t is a draw of d_(z_t), the slice of d at z_t on its leading axis. The value has shape [n] followed by d's components'. The log density is the forward algorithm's in logs (Rabiner, A Tutorial on Hidden Markov Models, 1989, §III-A), logsumexp_j α_n(j), its n - 1 products of k × k matrices multiplied pairwise in ⌈log₂ n⌉ rounds. membership gives each step's state's posterior from the forward and backward variables, scanned in 2 log₂ n rounds (Nx.associative_scan). A logit may be -inf, a state or a transition of probability zero. Its coordinates are those of a mixture of d.
Raises Invalid_argument if initial is not a vector of k, transition not a k × k matrix, d's leading axis not k, or n < 1; its eliminators raise on a value whose step axis is not of n.
Eliminators
log_density d x is Nx.sum (factors d x), a scalar.
factors d x is the log density of each independent unit of x: one per element for the elementwise families, per row for dirichlet and mvn, per matrix for lkj_cholesky, one for sorted, mixture and hmm, with iid's axes in front. x broadcasts against the parameters. A unit outside the support has factor -inf.
val sample : 'd Nx.Rng.key -> ('x, 'f, 'd) t -> 'xsample k d is a draw of d from the key k. It is reparameterised, differentiable in the parameters, wherever rune differentiates the Nx.Rng sampler it uses.
quantile d p is the value whose CDF is p, elementwise, read from the smaller tail: above 1/2 it inverts the survival at 1 - p, which is exact there. For gamma and inverse_gamma it is 0. at p = 0. and inf at p = 1.; for beta, Nx.betaincinv, 0. at p = 0. and 1. at p = 1..
Rune differentiates it in p and in d's parameters. For gamma, inverse_gamma and beta the first derivatives are finite wherever the exact ones are; inverse_gamma's higher derivatives can overflow where the quantile passes about 2^512.
Raises Invalid_argument for a family whose quantile nx does not compute: student_t, von_mises, the vector and matrix families and the combinators but iid and truncated.
coords d is the bijector onto d's support, from coordinates in which samplers and optimisers move: Bij.identity on the reals, Bij.exp on (0, inf), Bij.interval for beta, uniform and von_mises, Bij.simplex for dirichlet, Bij.cholesky_corr for lkj_cholesky, Bij.interval, Bij.greater or Bij.less for truncated, and for sorted d's bijector after Bij.ordered.
membership d x is the posterior probability of each value of d's discrete latent variables given x: of shape [k], each component's, for a mixture of k components; of shape [n; k], each step's state's, for an hmm of n steps over k states, from the forward and backward variables (γ_t ∝ α_t β_t, Rabiner 1989, eq. 27); and s followed by either for iid s of one.
Raises Invalid_argument if d is not one of those.
Observers
family d is d's family, such as "normal". iid keeps its argument's.
dtype d is the dtype of d's log densities, and of its values for a continuous d.
val placement : ('x, 'f, 'd) t -> 'd Nx.Placement.tsupport d is the set of d's values. Bounds given by parameters are read on the host, as the interval holding every element's support. Inside a function Rune.jit compiles, reading one raises Invalid_argument, and inside a mapped function, reading one that depends on the function's arguments does. bounds reads nothing.
bounds d is the least and greatest value each element of d's values may take, of d's shape: -inf and inf where unbounded, the support's interval for an element of a vector. It reads nothing on the host, so it serves inside compiled and mapped functions.
pp ppf d formats d's family and its parameters' dtypes and shapes, as normal(loc: float64 [], scale: float64 [8]).
Checks
check ?unless what d is d whose parameters are checked now, and whose eliminators then check none: a caller that computes parameters names itself in the refusal. A parameter outside its domain raises Invalid_argument with the message what: family: param at [i] is v, not in domain. Where unless holds, a parameter is taken whatever its value: unless broadcasts against the parameters.