Gamma, Beta and discrete distributions

These nodes model positive quantities (the Gamma and inverse-Gamma nodes), probabilities (Beta, Dirichlet) and discrete outcomes (Bernoulli, Categorical, Poisson). Several of them form conjugate pairs: a Beta prior on a Bernoulli's probability, a Dirichlet on a Categorical's and a Gamma on a Poisson's rate.

In a conjugate pair, belief propagation is exact and in closed form. An observed outcome, a point mass, sends its parameter a message in the prior's family, such as a Beta towards a Bernoulli's p. The product of that message and the prior is the posterior. The rules towards a Gamma's shape and rate, and towards a Categorical's p, take marginals and are variational: they use expectations such as E[log out] under those marginals, so an uncertain out works as well as an observed one. Many nodes on this page serve as priors only. They have the message towards out and no rule towards their parameters, which a model then gives as constants. The coverage tables show which.

The nodes are the types of ExponentialFamily and Distributions, declared as nodes here, and all run under DefaultAlgorithm.

Example

An observed coin flip sends its bias a Beta likelihood. The message's log scale is log(1/2):

julia> using StandardMessagePassingRules, MessagePassingRulesBase, ExponentialFamily, BayesBase

julia> result = @call_message_update_rule(node = Bernoulli, target = :p, m = (out = PointMass(1.0),));

julia> getresult(result) isa Beta && all(params(getresult(result)) .≈ (2.0, 1.0))
true

julia> getlogscale(result) ≈ log(1 / 2)
true

GammaShapeRate

\[p(\mathrm{out} \mid α, β) = \frac{β^α}{Γ(α)} \mathrm{out}^{α - 1} e^{-β \mathrm{out}}, \quad \mathrm{out} > 0\]

MessagePassingRulesBase.nodespec(GammaShapeRate)
NodeSpec: GammaShapeRate
α (a, shape)β (b, rate)outGammaShapeRatestochastic
interfacealiases
out
αa, shape
βb, rate
default algorithmMessagePassingRulesBase.DefaultAlgorithm()
static inputsnone
definednodes/gamma_shape_rate.jl:1
interfacealiasesmeaningmessages and marginals its rules take
outthe variableany marginal with E[out], E[log out]
αa, shapethe shapea PointMass message, or any marginal with E[α]
βb, ratethe ratea PointMass message, or a Gamma marginal
MessagePassingRulesBase.rule_coverage(GammaShapeRate)
Rule coverage for ExponentialFamily.GammaShapeRate
DefaultAlgorithm
→ out✓×2
→ α✓
→ β✓
average energy✓×2
q(out, α, β)✓

With known parameters, the message towards out is the node's own distribution:

@call_message_update_rule(node = GammaShapeRate, target = :out, m = (α = PointMass(2.0), β = PointMass(3.0)))
RuleResult: message of GammaShapeRate towards :outbelief propagation
αβoutGammaShapeRate
Result
valueExponentialFamily.GammaShapeRate{Float64}(a=2.0, b=3.0)
typeExponentialFamily.GammaShapeRate{Float64}
log scale0 (declared)
Inputs
edgevalue
αmPointMass{Float64}(2.0)
βmPointMass{Float64}(3.0)
Rule
declared inputsm[:α]::BayesBase.PointMass, m[:β]::BayesBase.PointMass
algorithmDefaultAlgorithm()
log scale0
definedgamma_shape_rate/out.jl:1
body
args->GammaShapeRate(mean(args.m[:α]), mean(args.m[:β]))
Other rules for this target (1)
gamma_shape_rate/out.jl:8
✓algorithm MessagePassingRulesBase.DefaultAlgorithm
✗q[:α]::Any not provided
✗q[:β]::Any not provided
✗m[:α]::BayesBase.PointMass{Float64} provided but not consumed
✗m[:β]::BayesBase.PointMass{Float64} provided but not consumed

The variational message towards the rate takes the marginals of out and α. It is a GammaShapeRate with shape E[α] + 1 and rate E[out]:

@call_message_update_rule(node = GammaShapeRate, target = :β, q = (out = GammaShapeRate(4.0, 2.0), α = PointMass(2.0)))
RuleResult: message of GammaShapeRate towards :βvariational
outαβGammaShapeRate
Result
valueExponentialFamily.GammaShapeRate{Float64}(a=3.0, b=2.0)
typeExponentialFamily.GammaShapeRate{Float64}
log scaleundefined: the message rule for GammaShapeRate towards :β under MessagePassingRulesBase.DefaultAlgorithm declares no `logscale`
Inputs
edgevalue
outqExponentialFamily.GammaShapeRate{Float64}(a=4.0, b=2.0)
αqPointMass{Float64}(2.0)
Rule
declared inputsq[:out]::Any, q[:α]::Any
algorithmDefaultAlgorithm()
log scalenone
definedgamma_shape_rate/b.jl:1
body
args->GammaShapeRate(1 + mean(args.q[:α]), mean(args.q[:out]))

The rules towards α and β are variational only. Towards β the message is a GammaShapeRate. Towards α it is a GammaShapeLikelihood, and the rule needs a Gamma marginal of β. The average energy takes a PointMass or a Gamma marginal of α. Gamma and GammaShapeRate are different nodes: this one is parametrised by the rate.

Gamma

\[p(\mathrm{out} \mid α, θ) = \frac{\mathrm{out}^{α - 1} e^{-\mathrm{out} / θ}}{Γ(α) θ^α}, \quad \mathrm{out} > 0\]

interfacealiasesmeaningmessages and marginals its rules take
outthe variableany marginal (average energy)
αshapethe shapea PointMass message, or any marginal
θscalethe scalea PointMass message, or any marginal
MessagePassingRulesBase.rule_coverage(Gamma)
Rule coverage for Distributions.Gamma
DefaultAlgorithm
→ out✓×2
→ α
→ θ
average energy✓×2
q(out, α, θ)✓

Limitations. No rules towards α or θ. The variational message towards out is Gamma(E[α], E[θ]), with the mean of the scale.

GammaInverse

GammaInverse is ExponentialFamily's name for Distributions' InverseGamma: the two names refer to one type, and a message of that type prints as InverseGamma.

\[p(\mathrm{out} \mid α, θ) = \frac{θ^α}{Γ(α)} \mathrm{out}^{-α - 1} e^{-θ / \mathrm{out}}, \quad \mathrm{out} > 0\]

interfacealiasesmeaningmessages and marginals its rules take
outthe variablea GammaInverse marginal (average energy)
αshapethe shapea PointMass message, or any marginal
θscalethe scalea PointMass message, or any marginal
MessagePassingRulesBase.rule_coverage(GammaInverse)
Rule coverage for Distributions.InverseGamma
DefaultAlgorithm
→ out✓×2
→ α
→ θ
average energy✓
q(out, α, θ)✓

Limitations. No rules towards α or θ. The average energy needs both known.

Beta

\[p(\mathrm{out} \mid a, b) = \frac{\mathrm{out}^{a - 1} (1 - \mathrm{out})^{b - 1}}{B(a, b)}, \quad 0 < \mathrm{out} < 1\]

MessagePassingRulesBase.nodespec(Beta)
NodeSpec: Beta
a (α)b (β)outBetastochastic
interfacealiases
out
aα
bβ
default algorithmMessagePassingRulesBase.DefaultAlgorithm()
static inputsnone
definednodes/beta.jl:1
interfacealiasesmeaningmessages and marginals its rules take
outthe variablea Beta message (marginal rule), any marginal (average energy)
aαthe first shapea PointMass
bβthe second shapea PointMass
MessagePassingRulesBase.rule_coverage(Beta)
Rule coverage for Distributions.Beta
DefaultAlgorithm
→ out✓×2
→ a
→ b
average energy✓
q(out, a, b)✓

As a prior with known shapes, Beta sends its own distribution towards out, with log scale zero:

@call_message_update_rule(node = Beta, target = :out, m = (a = PointMass(2.0), b = PointMass(3.0)))
RuleResult: message of Beta towards :outbelief propagation
aboutBeta
Result
valueDistributions.Beta{Float64}(α=2.0, β=3.0)
typeDistributions.Beta{Float64}
log scale0 (declared)
Inputs
edgevalue
amPointMass{Float64}(2.0)
bmPointMass{Float64}(3.0)
Rule
declared inputsm[:a]::BayesBase.PointMass, m[:b]::BayesBase.PointMass
algorithmDefaultAlgorithm()
log scale0
definedbeta/out.jl:1
body
args->Beta(mean(args.m[:a]), mean(args.m[:b]))
Other rules for this target (1)
beta/out.jl:8
✓algorithm MessagePassingRulesBase.DefaultAlgorithm
✗q[:a]::BayesBase.PointMass not provided
✗q[:b]::BayesBase.PointMass not provided
✗m[:a]::BayesBase.PointMass{Float64} provided but not consumed
✗m[:b]::BayesBase.PointMass{Float64} provided but not consumed

Limitations. No rules towards a or b. The average energy accepts any marginals of a and b but uses only their means, which is exact for known shapes.

Bernoulli

\[p(\mathrm{out} \mid p) = p^{\mathrm{out}} (1 - p)^{1 - \mathrm{out}}, \quad \mathrm{out} \in \{0, 1\}\]

MessagePassingRulesBase.nodespec(Bernoulli)
NodeSpec: Bernoulli
p (θ)outBernoullistochastic
interfacealiases
out
pθ
default algorithmMessagePassingRulesBase.DefaultAlgorithm()
static inputsnone
definednodes/bernoulli.jl:1
interfacealiasesmeaningmessages and marginals its rules take
outthe outcomea PointMass message; a PointMass, Bernoulli or two-category Categorical marginal
pθthe probability of 1a Beta or PointMass message; any marginal with E[log p], E[log(1 - p)]
MessagePassingRulesBase.rule_coverage(Bernoulli)
Rule coverage for Distributions.Bernoulli
DefaultAlgorithm
→ out✓×4
→ p✓×4
average energy✓
q(out, p)✓×2

Towards p, the message is the likelihood Beta(1 + r, 2 - r) of an observed outcome r. Its log scale, log(1/2), normalises the likelihood:

@call_message_update_rule(node = Bernoulli, target = :p, m = (out = PointMass(1.0),))
RuleResult: message of Bernoulli towards :pbelief propagation
outpBernoulli
Result
valueDistributions.Beta{Float64}(α=2.0, β=1.0)
typeDistributions.Beta{Float64}
log scaleloghalf = -0.693147180559... (declared)
Inputs
edgevalue
outmPointMass{Float64}(1.0)
Rule
declared inputsm[:out]::BayesBase.PointMass
algorithmDefaultAlgorithm()
log scaleloghalf
definedbernoulli/p.jl:5
body
args->bernoulli_likelihood(mean(args.m[:out]))
Other rules for this target (3)
bernoulli/p.jl:12
✓algorithm MessagePassingRulesBase.DefaultAlgorithm
✗q[:out]::BayesBase.PointMass not provided
✗m[:out]::BayesBase.PointMass{Float64} provided but not consumed
bernoulli/p.jl:19
✓algorithm MessagePassingRulesBase.DefaultAlgorithm
✗q[:out]::Distributions.Bernoulli not provided
✗m[:out]::BayesBase.PointMass{Float64} provided but not consumed
bernoulli/p.jl:25
✓algorithm MessagePassingRulesBase.DefaultAlgorithm
✗q[:out]::Distributions.Categorical{P} where P<:Real not provided
✗m[:out]::BayesBase.PointMass{Float64} provided but not consumed

Towards out, the message is a Bernoulli. By belief propagation from a Beta(2, 3) message, its probability is the mean 2/5. The variational rule from a Beta(2, 3) marginal uses exp E[log p] instead, normalised against exp E[log(1 - p)], which gives a smaller value:

@call_message_update_rule(node = Bernoulli, target = :out, q = (p = Beta(2.0, 3.0),))
RuleResult: message of Bernoulli towards :outvariational
poutBernoulli
Result
valueDistributions.Bernoulli{Float64}(p=0.377541)
typeDistributions.Bernoulli{Float64}
log scaleundefined: the message rule for Bernoulli towards :out under MessagePassingRulesBase.DefaultAlgorithm declares no `logscale`
Inputs
edgevalue
pqDistributions.Beta{Float64}(α=2.0, β=3.0)
Rule
declared inputsq[:p]::Any
algorithmDefaultAlgorithm()
log scalenone
definedbernoulli/out.jl:25
body
args->begin
        (rho_1, rho_2) = (mean(log, args.q[:p]), mean(mirrorlog, args.q[:p]))
        m = max(rho_1, rho_2)
        tmp = exp(rho_1 - m)
        Bernoulli(clamp(tmp / (tmp + exp(rho_2 - m)), tiny, one(m)))
    end
Other rules for this target (3)
bernoulli/out.jl:1
✓algorithm MessagePassingRulesBase.DefaultAlgorithm
✗m[:p]::Distributions.Beta not provided
✗q[:p]::Distributions.Beta{Float64} provided but not consumed
bernoulli/out.jl:10
✓algorithm MessagePassingRulesBase.DefaultAlgorithm
✗m[:p]::BayesBase.PointMass not provided
✗q[:p]::Distributions.Beta{Float64} provided but not consumed
bernoulli/out.jl:17
✓algorithm MessagePassingRulesBase.DefaultAlgorithm
✗q[:p]::BayesBase.PointMass got Distributions.Beta{Float64}

A Categorical marginal of out with other than two categories is a RuleInputError.

Categorical

\[p(\mathrm{out} \mid p) = \prod_{k=1}^K p_k^{\mathrm{out}_k}, \quad \mathrm{out} \text{ one-hot}\]

MessagePassingRulesBase.nodespec(Categorical)
NodeSpec: Categorical
poutCategoricalstochastic
interfacealiases
out
p
default algorithmMessagePassingRulesBase.DefaultAlgorithm()
static inputsnone
definednodes/categorical.jl:1
interfacealiasesmeaningmessages and marginals its rules take
outthe one-hot outcomea Categorical or one-hot vector PointMass marginal
pthe probability vectora Dirichlet or PointMass, as message or marginal
MessagePassingRulesBase.rule_coverage(Categorical)
Rule coverage for Distributions.Categorical{P} where P<:Real
DefaultAlgorithm
→ out✓×4
→ p✓×3
average energy✓
q(out, p)✓×2

An observed one-hot outcome sends p the likelihood Dirichlet(1 .+ out), with one count on the observed category:

@call_message_update_rule(node = Categorical, target = :p, q = (out = PointMass([0.0, 1.0, 0.0]),))
RuleResult: message of Categorical towards :pvariational
outpCategorical
Result
valueDistributions.Dirichlet{Float64, Vector{Float64}, Float64}(alpha=[1.0, 2.0, 1.0])
typeDistributions.Dirichlet{Float64, Vector{Float64}, Float64}
log scale-1.791759469228055 (computed from the inputs)
Inputs
edgevalue
outqPointMass{Vector{Float64}}([0.0, 1.0, 0.0])
Rule
declared inputsq[:out]::BayesBase.PointMass{<:AbstractVector{<:Real}}
algorithmDefaultAlgorithm()
log scalea function of the inputs
checksisonehot(mean(args.q[:out])) || lazy"`q(out)` must be one-hot; got $(mean(args.q[:out]))"
definedcategorical/p.jl:19
body
args->begin
        probs = mean(args.q[:out])
        Dirichlet(probs .+ one(eltype(probs)))
    end
Other rules for this target (2)
categorical/p.jl:9
✓algorithm MessagePassingRulesBase.DefaultAlgorithm
✗q[:out]::Distributions.Categorical{P} where P<:Real got BayesBase.PointMass{Vector{Float64}}
categorical/p.jl:30
✓algorithm MessagePassingRulesBase.DefaultAlgorithm
✓q[:out]::Any got BayesBase.PointMass{Vector{Float64}}

The variational message towards out from a Dirichlet marginal of p normalises exp E[log pₖ]:

@call_message_update_rule(node = Categorical, target = :out, q = (p = Dirichlet([2.0, 3.0, 5.0]),))
RuleResult: message of Categorical towards :outvariational
poutCategorical
Result
valueDistributions.Categorical{Float64, Vector{Float64}}(support=Base.OneTo(3), p=[0.178468, 0.294245, 0.527287])
typeDistributions.Categorical{Float64, Vector{Float64}}
log scaleundefined: the message rule for Categorical towards :out under MessagePassingRulesBase.DefaultAlgorithm declares no `logscale`
Inputs
edgevalue
pqDistributions.Dirichlet{Float64, Vector{Float64}, Float64}(alpha=[2.0, 3.0, 5.0])
Rule
declared inputsq[:p]::Distributions.Dirichlet
algorithmDefaultAlgorithm()
log scalenone
definedcategorical/out.jl:10
body
args->begin
        rho = max.(exp.(mean(BroadcastFunction(log), args.q[:p])), tiny)
        Categorical(rho ./ sum(rho))
    end
Other rules for this target (3)
categorical/out.jl:1
✓algorithm MessagePassingRulesBase.DefaultAlgorithm
✗m[:p]::Distributions.Dirichlet not provided
✗q[:p]::Distributions.Dirichlet{Float64, Vector{Float64}, Float64} provided but not consumed
categorical/out.jl:22
✓algorithm MessagePassingRulesBase.DefaultAlgorithm
✗m[:p]::BayesBase.PointMass not provided
✗q[:p]::Distributions.Dirichlet{Float64, Vector{Float64}, Float64} provided but not consumed
categorical/out.jl:29
✓algorithm MessagePassingRulesBase.DefaultAlgorithm
✗q[:p]::BayesBase.PointMass got Distributions.Dirichlet{Float64, Vector{Float64}, Float64}

Limitations. The rules towards p take the marginal of out only, a Categorical or a one-hot PointMass, and give a Dirichlet. Any other marginal is an ArgumentError, and a PointMass that is not one-hot a RuleInputError. There is no rule towards p from a message on out. The variational message towards out keeps every probability above tiny.

Dirichlet

\[p(\mathrm{out} \mid a) = \frac{Γ(\sum_k a_k)}{\prod_k Γ(a_k)} \prod_{k=1}^K \mathrm{out}_k^{a_k - 1}\]

interfacealiasesmeaningmessages and marginals its rules take
outthe probability vectora Dirichlet
athe concentrationsa vector PointMass
MessagePassingRulesBase.rule_coverage(Dirichlet)
Rule coverage for Distributions.Dirichlet
DefaultAlgorithm
→ out✓×2
→ a
average energy✓
q(out, a)✓

Limitations. No rule towards a, which must be known.

DirichletCollection

A collection of independent Dirichlets, one for each slice along the first dimension of out and of a:

\[p(\mathrm{out} \mid a) = \prod_{j} \mathrm{Dir}(\mathrm{out}_{:, j} \mid a_{:, j})\]

interfacealiasesmeaningmessages and marginals its rules take
outthe array of probability vectorsa DirichletCollection
athe array of concentrationsa PointMass
MessagePassingRulesBase.rule_coverage(DirichletCollection)
Rule coverage for ExponentialFamily.DirichletCollection
DefaultAlgorithm
→ out✓×2
→ a
average energy✓
q(out, a)✓

Limitations. No rule towards a, which must be known.

Poisson

\[p(\mathrm{out} \mid l) = \frac{l^{\mathrm{out}} e^{-l}}{\mathrm{out}!}, \quad \mathrm{out} \in \{0, 1, 2, \dots\}\]

interfacealiasesmeaningmessages and marginals its rules take
outthe counta PointMass message, or any marginal with E[out]
lλthe ratea PointMass or a Gamma message, or a Gamma marginal
MessagePassingRulesBase.rule_coverage(Poisson)
Rule coverage for Distributions.Poisson
DefaultAlgorithm
→ out✓×2
→ l✓×2
average energy✓×2
q(out, l)✓

Towards l, the message is the likelihood Gamma(out + 1, 1) of an observed count. Towards out, it is a Poisson.

Limitations. For an out that is not a point mass, the average energy approximates E[log out!] by a series in E[out]. Beyond E[out] = 110 it is an error.

Uniform

\[p(\mathrm{out} \mid a, b) = \frac{1}{b - a}, \quad a \le \mathrm{out} \le b\]

interfacealiasesmeaningmessages and marginals its rules take
outthe variablea Beta marginal (average energy)
aα, leftthe lower bounda PointMass, as message or marginal
bβ, rightthe upper bounda PointMass, as message or marginal
MessagePassingRulesBase.rule_coverage(Uniform)
Rule coverage for Distributions.Uniform
DefaultAlgorithm
→ out✓×4
→ a
→ b
average energy✓

Limitations. No rules towards a or b, and no marginal rule. The average energy is defined only for Uniform(0, 1) with a Beta marginal of out, and is a RuleInputError for other bounds. The package defines the product of Uniform(0, 1) and a Beta, two types of other packages, as the Beta. For any other bounds the product is an ArgumentError.