A normal node ties a variable out to a mean and a spread: a variance, a precision, a covariance matrix or a precision matrix. The page covers the univariate and multivariate normals in each of their parametrisations, and the half-normal.
With the spread known, the rules towards out and the mean are exact belief propagation: a normal message on the mean, convolved with the noise, gives a normal message on out, and the same holds the other way. With the spread unknown, belief propagation towards it gives no closed-form message for NormalMeanVariance, and the other normals have no such rule at all. The rules towards the spread are therefore variational: they take marginals and return a Gamma, a Wishart or their inverses. In practice, you choose a factorisation that puts an unknown spread in a cluster of its own, such as q(out, μ) q(τ) or the mean fieldq(out) q(μ) q(τ).
The nodes are ExponentialFamily's types, declared as nodes here. They all run under DefaultAlgorithm, so a model never names an algorithm for them.
In the interface tables below, a rule takes messages from the interfaces in its target's own cluster, and it takes marginals from the interfaces in other clusters. "Normal" means any univariate or multivariate normal of ExponentialFamily in any parametrisation. "Any" means a marginal with the expectations the rule needs.
The mean-field message towards the mean of a NormalMeanPrecision node takes the marginals of out and of the precision. It is a normal around E[out] with precision E[τ]:
m[:μ]::Union{ExponentialFamily.UnivariateNormalDistributionsFamily{T}, ExponentialFamily.UnivariateGaussianDistributionsFamily{T}} where T, m[:v]::BayesBase.PointMass
algorithm
DefaultAlgorithm()
log scale
0
defined
normal_mean_variance/out.jl:8
body
args->begin
(μ_mean, μ_var) = mean_var(args.m[:μ])
NormalMeanVariance(μ_mean, μ_var + mean(args.m[:v]))
end
m[:μ]::Union{ExponentialFamily.UnivariateNormalDistributionsFamily{T}, ExponentialFamily.UnivariateGaussianDistributionsFamily{T}} where T got ExponentialFamily.NormalMeanVariance{Float64}
✗
q[:v]::Any not provided
✗
m[:v]::BayesBase.PointMass{Float64} provided but not consumed
m[:μ]::Union{ExponentialFamily.UnivariateNormalDistributionsFamily{T}, ExponentialFamily.UnivariateGaussianDistributionsFamily{T}} where T got ExponentialFamily.NormalMeanVariance{Float64}
✗
q[:v]::BayesBase.PointMass not provided
✗
m[:v]::BayesBase.PointMass{Float64} provided but not consumed
The variational message takes the marginals of μ and v. It keeps the mean of q(μ), ignores its variance, and uses 1/E[1/v] = 0.5 of the Gamma marginal as the variance:
m[:μ]::Union{ExponentialFamily.UnivariateNormalDistributionsFamily{T}, ExponentialFamily.UnivariateGaussianDistributionsFamily{T}} where T not provided
✗
m[:v]::BayesBase.PointMass not provided
✗
q[:v]::ExponentialFamily.GammaShapeRate{Float64} provided but not consumed
✗
q[:μ]::ExponentialFamily.NormalMeanVariance{Float64} provided but not consumed
m[:μ]::Union{ExponentialFamily.UnivariateNormalDistributionsFamily{T}, ExponentialFamily.UnivariateGaussianDistributionsFamily{T}} where T not provided
m[:μ]::Union{ExponentialFamily.UnivariateNormalDistributionsFamily{T}, ExponentialFamily.UnivariateGaussianDistributionsFamily{T}} where T not provided
q[:μ]::ExponentialFamily.NormalMeanVariance{Float64} provided but not consumed
Towards out and μ, the node has belief propagation, variational and structured rules. Towards v, belief propagation from normal or known out and μ gives a log-density on the half line, a ContinuousUnivariateLogPdf, which has no closed-form family. The variational rule towards v gives an unchecked GammaInverse with shape -1/2, which is a likelihood rather than a distribution. The average energy covers the mean field and q(out, μ) q(v).
m[:μ]::Union{ExponentialFamily.UnivariateNormalDistributionsFamily{T}, ExponentialFamily.UnivariateGaussianDistributionsFamily{T}} where T, m[:τ]::BayesBase.PointMass
algorithm
DefaultAlgorithm()
log scale
0
defined
normal_mean_precision/out.jl:8
body
args->begin
(μ_mean, μ_var) = mean_var(args.m[:μ])
NormalMeanPrecision(μ_mean, inv(μ_var + inv(mean(args.m[:τ]))))
end
m[:μ]::Union{ExponentialFamily.UnivariateNormalDistributionsFamily{T}, ExponentialFamily.UnivariateGaussianDistributionsFamily{T}} where T got ExponentialFamily.NormalMeanPrecision{Float64}
✗
q[:τ]::Any not provided
✗
m[:τ]::BayesBase.PointMass{Float64} provided but not consumed
The variational message towards τ takes the marginals of out and μ, and it is a Gamma:
q[:out]::BayesBase.PointMass{Float64} provided but not consumed
✗
q[:μ]::ExponentialFamily.NormalMeanPrecision{Float64} provided but not consumed
The rules towards τ take q(out) q(μ) or q(out, μ). There is no belief propagation rule towards τ, so an unknown precision needs a factorisation that separates it from out and μ. Asking for one throws a RuleNotFoundError, which lists the rules that do exist:
RuleNotFoundError: no message rule for NormalMeanPrecision towards :τ under MessagePassingRulesBase.DefaultAlgorithm() takes the inputs (m[:out]::BayesBase.PointMass{Float64}, m[:μ]::ExponentialFamily.NormalMeanPrecision{Float64})
no rule consumes this set of inputs under this algorithm (no rule of this shape)
what to try: the factorisation decides which inputs a rule receives (messages from the target's cluster, marginals of the other clusters): choose one that delivers the inputs a rule below takes (in RxInfer, `@constraints`), or define a rule for these inputs
near misses:
rule at ~/work/StandardMessagePassingRules.jl/StandardMessagePassingRules.jl/src/rules/normal_mean_precision/precision.jl:1
✓ algorithm MessagePassingRulesBase.DefaultAlgorithm
✗ q[:out]::Any not provided
✗ q[:μ]::Any not provided
✗ m[:out]::BayesBase.PointMass{Float64} provided but not consumed
✗ m[:μ]::ExponentialFamily.NormalMeanPrecision{Float64} provided but not consumed
rule at ~/work/StandardMessagePassingRules.jl/StandardMessagePassingRules.jl/src/rules/normal_mean_precision/precision.jl:10
✓ algorithm MessagePassingRulesBase.DefaultAlgorithm
✗ q[:out, :μ]::Any not provided
✗ m[:out]::BayesBase.PointMass{Float64} provided but not consumed
✗ m[:μ]::ExponentialFamily.NormalMeanPrecision{Float64} provided but not consumed
m[:μ]::Union{ExponentialFamily.MultivariateNormalDistributionsFamily{T}, ExponentialFamily.MultivariateGaussianDistributionsFamily{T}} where T, m[:Σ]::BayesBase.PointMass
algorithm
DefaultAlgorithm()
log scale
0
defined
mv_normal_mean_covariance/out.jl:10
body
args->begin
(μ, V) = mean_cov(args.m[:μ])
MvNormalMeanCovariance(μ, V + mean(args.m[:Σ]))
end
m[:μ]::Union{ExponentialFamily.MultivariateNormalDistributionsFamily{T}, ExponentialFamily.MultivariateGaussianDistributionsFamily{T}} where T got ExponentialFamily.MvNormalMeanCovariance{Float64, Vector{Float64}, Matrix{Float64}}
✗
q[:Σ]::Any not provided
✗
m[:Σ]::BayesBase.PointMass{Matrix{Float64}} provided but not consumed
The rules towards Σ are variational only, and they give an inverse-Wishart likelihood. There is no belief propagation rule towards Σ.
Rule coverage for ExponentialFamily.MvNormalMeanPrecision
DefaultAlgorithm
→ out
✓×7
→ μ
✓×7
→ Λ
✓×2
average energy
✓×2
q(out, μ)
✓×3
q(out, μ, Λ)
✓×3
The rules towards Λ are variational only, and they give a Wishart likelihood. They apply the context's matrix_correction to its scale, and none by default. There is no belief propagation rule towards Λ.
Rule coverage for ExponentialFamily.MvNormalWeightedMeanPrecision
DefaultAlgorithm
→ out
✓×2
→ ξ
→ Λ
average energy
✓
q(out, ξ, Λ)
✓
Limitations. The node has the message towards out and the joint marginal with known parameters only. There is no rule towards ξ or Λ: they must be known, or have their marginals from elsewhere.
Its interfaces are out and v, with the aliases var and σ²; it runs under DefaultAlgorithm.
Rules. Only the message towards out, from a known variance (a PointMass marginal of v): a Normal(0, √v) truncated to [0, ∞). There is no rule towards v, so v must be known, and no marginal rule. The average energy takes any marginals q_out with a mean and a var, and q_v with mean(log, ·) and mean(inv, ·).
Examples
The average energy of a known out = 1 under a known variance v = 1, -log p(1 | 1):
julia> energy = getresult(@call_average_energy(node = HalfNormal, q = (out = PointMass(1.0), v = PointMass(1.0))));
julia> energy ≈ (log(π / 2) + 1) / 2
true