BinomialPolya

Overview

BinomialPolya is a binomial regression through the logistic: y successes out of n trials, with success probability σ(xᵀβ) for the covariates x and the weights β. With n = 1 it is logistic regression. y, x and n are observed; the node learns β, whose message is normal by the Pólya-Gamma augmentation.

Definition

\[p(y \mid x, n, \beta) = \binom{n}{y} \sigma(x^\top \beta)^{y} \big(1 - \sigma(x^\top \beta)\big)^{n - y}, \qquad \sigma(\psi) = \frac{1}{1 + e^{-\psi}}\]

The binomial coefficient counts the orders in which y successes can occur. The logistic function σ maps the linear predictor xᵀβ to a probability.

Interfaces

using PolyaMessagePassingRules, MessagePassingRulesBase, BayesBase, ExponentialFamily
MessagePassingRulesBase.nodespec(BinomialPolya)
NodeSpec: BinomialPolya
xnβyBinomialPolyastochastic
interfacealiases
y
x
n
β
default algorithmBinomialPolyaApproximation{Nothing}(nothing, 32)
static inputsnone
definedsrc/binomial_polya.jl:101

The node has four interfaces. The table lists the marginals and messages the rules read on each.

namemeaningmessages and marginals the rules take
ythe number of successesPointMass marginal
xthe covariates, a vector, or a number for a scalar βPointMass marginal
nthe number of trialsPointMass marginal (any marginal with a mean, towards y)
βthe weightsnormal message on β towards β; normal marginal towards y and for the energy

The rule towards β sends a normal in weighted-mean and precision form, MvNormal for a vector x; the rule towards y sends a Binomial. There is no rule towards x or n.

Algorithm

PolyaMessagePassingRules.BinomialPolyaApproximation — Type
BinomialPolyaApproximation(; samples = nothing, points = 32)

The algorithm of BinomialPolya, and its default: a model names it only to sample or to change points.

Keywords

  • samples: nothing or a number of draws. Default nothing. With nothing, the rules use means: the Pólya-Gamma mean at xᵀ times the mean of β, and σ(xᵀβ) at the mean of β. With a number k, they average over k draws of β, the rule towards β over one Pólya-Gamma draw for each, from the rule context's generator, ctx.rng. An engine supplies it; a call by hand passes ctx = MessagePassingRulesBase.RuleContext(rng = …).
  • points: the number of Gauss–Hermite points with which the average energy computes ⟨softplus(ψ)⟩. Default 32. The messages do not depend on it.

The average energy does not sample under either: xᵀβ is normal under a normal q(β), so its expectation is computed by Gauss–Hermite cubature.

Examples

julia> BinomialPolyaApproximation().samples === nothing
true

julia> BinomialPolyaApproximation(samples = 100).samples
100

julia> BinomialPolyaApproximation(points = 64).points
64
source

BinomialPolyaApproximation() is the node's default algorithm, so a model names it only to sample, as BinomialPolyaApproximation(samples = 100). Sampling draws from the rule context's generator, a service the engine supplies.

The node declares its dependencies, the inputs each target's rule takes, for both variants of the algorithm:

MessagePassingRulesBase.dependencies_spec(BinomialPolya, BinomialPolyaApproximation())
DependenciesSpec: BinomialPolyaunder BinomialPolyaApproximation
defaultyBinomialPolyadefaultxBinomialPolyadefaultnBinomialPolyadefaultm[:β]βBinomialPolya
━▶ target─▶ message m··· the default scheme's inputs
free-energy partitionfrom the factorisation

Every target takes the inputs of the default scheme. The rule towards β also reads the message on β, the current estimate at which it takes the Pólya-Gamma mean.

Supported rules

MessagePassingRulesBase.rule_coverage(BinomialPolya)
Rule coverage for BinomialPolya
BinomialPolyaApproximation{Nothing}BinomialPolyaApproximation{Int64}BinomialPolyaApproximation{<:Union{Nothing, Int64}}
→ y✓✓
→ x
→ n
→ β✓✓
average energy✓

The three columns are the algorithm's variants:

  • BinomialPolyaApproximation{Nothing}, the means, the default;
  • BinomialPolyaApproximation{Int}, sampling;
  • the type itself, under which the average energy serves both.

The rules read marginals of the observed interfaces and, towards β, the message on β. They fit a mean-field factorisation, in which y, x and n are data. With the average energy, the Bethe free energy is available.

Example

The message towards β for one observation, at a message on β centred on zero, where the Pólya-Gamma mean is n / 4:

@call_message_update_rule(
    node = BinomialPolya, target = :β,
    q = (y = PointMass(3), x = PointMass([0.1, 0.2]), n = PointMass(5)),
    m = (β = MvNormalWeightedMeanPrecision(zeros(2), [1.0 0.0; 0.0 1.0]),),
)
RuleResult: message of BinomialPolya towards :βmessages and marginals
yxnββBinomialPolya
Result
valueMvNormalWeightedMeanPrecision( xi: [0.05, 0.1] Λ: [0.0125 0.025; 0.025 0.05] )
typeExponentialFamily.MvNormalWeightedMeanPrecision{Float64, Vector{Float64}, Matrix{Float64}}
log scaleundefined: the message rule for BinomialPolya towards :β under BinomialPolyaApproximation{Nothing} declares no `logscale`
Inputs
edgevalue
yqPointMass{Int64}(3)
xqPointMass{Vector{Float64}}([0.1, 0.2])
nqPointMass{Int64}(5)
βmMvNormalWeightedMeanPrecision( xi: [0.0, 0.0] Λ: [1.0 0.0; 0.0 1.0] ) its own edge
Rule
declared inputsq[:y]::BayesBase.PointMass, q[:x]::BayesBase.PointMass, q[:n]::BayesBase.PointMass, m[:β]::Union{ExponentialFamily.NormalDistributionsFamily{T}, ExponentialFamily.GaussianDistributionsFamily{T}} where T
algorithmBinomialPolyaApproximation{Nothing}(nothing, 32)
log scalenone
definedsrc/binomial_polya.jl:125
body
args->begin
        ω = mean(PolyaGammaHybridSampler(mean(args.q[:n]), dot(mean(args.q[:x]), mean(args.m[:β]))))
        binomial_polya_towards_β(ω, args.q[:y], args.q[:x], args.q[:n], args.m[:β])
    end
Other rules for this target (1)
src/binomial_polya.jl:134
✗algorithm BinomialPolyaApproximation{Int64}
✓q[:y]::BayesBase.PointMass got BayesBase.PointMass{Int64}
✓q[:x]::BayesBase.PointMass got BayesBase.PointMass{Vector{Float64}}
✓q[:n]::BayesBase.PointMass got BayesBase.PointMass{Int64}
✓m[:β]::Union{ExponentialFamily.NormalDistributionsFamily{T}, ExponentialFamily.GaussianDistributionsFamily{T}} where T got ExponentialFamily.MvNormalWeightedMeanPrecision{Float64, Vector{Float64}, Matrix{Float64}}

@call_message_update_rule runs the rule and draws its inputs: the three observed marginals and the message on β, the target's own edge. The weighted mean is (y - n/2) x and the precision (n/4) x xᵀ, which getresult confirms:

julia> using PolyaMessagePassingRules, MessagePassingRulesBase, BayesBase, ExponentialFamily

julia> result = @call_message_update_rule(
           node = BinomialPolya, target = :β,
           q = (y = PointMass(3), x = PointMass([0.1, 0.2]), n = PointMass(5)),
           m = (β = MvNormalWeightedMeanPrecision(zeros(2), [1.0 0.0; 0.0 1.0]),),
       );

julia> weightedmean(getresult(result)) ≈ (3 - 5 / 2) * [0.1, 0.2]
true

julia> precision(getresult(result)) ≈ (5 / 4) * [0.1, 0.2] * [0.1, 0.2]'
true

Limitations

  • The rule towards β needs an initial message on β.
  • y, x and n must be observed: there are no rules towards x or n, and the average energy takes PointMass marginals of all three.
  • The average energy computes ⟨softplus(ψ)⟩ by Gauss–Hermite cubature with BinomialPolyaApproximation(points = …) points, 32 by default. It is exact to rounding while ψ = xᵀβ is narrow, a variance up to about 1, and loses accuracy as ψ broadens, since softplus bends sharply on the cubature's scale: about 1e-4 relative at a variance of 25 and 1e-2 at 400, and more points help slowly. The free energy of a model with broad priors is approximate in its first iterations; the messages do not use the cubature.

API

The node:

PolyaMessagePassingRules.BinomialPolya — Type
BinomialPolya

The stochastic node of a binomial regression through the logistic,

\[p(y \mid x, n, \beta) = \binom{n}{y} \sigma(x^\top \beta)^{y} \big(1 - \sigma(x^\top \beta)\big)^{n - y}, \qquad \sigma(\psi) = \frac{1}{1 + e^{-\psi}},\]

with its weights β Pólya-Gamma augmented so that their message is normal. With n = 1 it is logistic regression.

Interfaces

  • y: the number of successes, observed, a PointMass;
  • x: the covariates, observed, a PointMass holding a vector (or a number, for a scalar β);
  • n: the number of trials, observed, a PointMass;
  • β: the weights, with a normal message and marginal, MvNormal for a vector x.

Augmentation

For a count y out of b trials with log-odds ψ, the Pólya-Gamma identity

\[\frac{(e^{\psi})^{y}}{(1 + e^{\psi})^{b}} = 2^{-b} e^{\kappa \psi} \int_0^\infty e^{-\omega \psi^2 / 2} \, \mathrm{PG}(\omega \mid b, 0) \, d\omega, \qquad \kappa = y - b / 2,\]

makes the likelihood Gaussian in ψ given the auxiliary ω ~ PG(b, ψ): proportional to exp(κψ - ωψ²/2). The rules replace ω by its expectation, E[ω] = b / (2c) · tanh(c / 2) for PG(b, c), at the current estimate c of ψ, so the message towards the weights is a normal in weighted-mean and precision form.

With ψ = xᵀβ and b = n, the message towards β has the weighted mean (y - n/2) x and the precision ω x xᵀ, where ω is the Pólya-Gamma mean at xᵀ times the mean of the message on β. The message towards y is Binomial(n, σ(xᵀ mᵦ)), mᵦ the mean of q(β). Both rules average over draws of β instead when BinomialPolyaApproximation is given a number of samples.

The average energy is that of the binomial likelihood itself, not of the augmented one: -log C(n, y) - y ⟨ψ⟩ + n ⟨softplus(ψ)⟩, with ⟨softplus(ψ)⟩ under the normal ψ = xᵀβ by Gauss–Hermite cubature with the algorithm's points.

Limitations

  • The rule towards β reads the message on β itself, besides the marginals its factorisation gives it, so a model must initialise that message.
  • y, x and n must be observed: their rules and the average energy take PointMass marginals only, and there is no rule towards x or n.
  • The Gauss–Hermite cubature of ⟨softplus(ψ)⟩ is exact to rounding while ψ is narrow, a variance up to about 1, and loses accuracy as ψ broadens, since softplus bends sharply on the cubature's scale: about 1e-4 relative at a variance of 25 and 1e-2 at 400 with 32 points. More points help slowly. The free energy of a model with broad priors is therefore approximate in its first iterations; the messages do not use the cubature.

Examples

julia> result = @call_message_update_rule(
           node = BinomialPolya, target = :y,
           q = (x = PointMass([1.0, 2.0]), n = PointMass(10), β = MvNormalMeanCovariance([0.0, 0.0], [1.0 0.0; 0.0 1.0])),
       );

julia> mean(getresult(result)) ≈ 5.0
true

At β = 0 the success probability is σ(0) = 1/2, so the message is Binomial(10, 1/2).

See also BinomialPolyaApproximation, MultinomialPolya.

source