BIFM
Overview
BIFM is one time slice of a linear state-space model with known matrices, as a single deterministic node: the previous state and an input make the next state, which makes the output. A chain of them, started by BIFMHelper and ended by a flat prior on the last state, is smoothed in two passes.
The backward pass sends information-form messages, in weighted-mean and precision form, towards the earlier states. At the start of the chain BIFMHelper turns it into the forward pass, whose messages are the marginals themselves. On a chain the result is the Rauch–Tung–Striebel smoother's.
Definition
\[z_{next} = A z_{prev} + B\, u, \qquad out = C z_{next}\]
A is the state transition, B the input matrix and C the output matrix; the node's algorithm carries all three.
The backward message towards zprev, from the messages on out, in and znext, in weighted-mean and precision form:
\[\begin{aligned} \xi_z &= C^\top \xi_{out} + \xi_{znext}, & \Lambda_z &= C^\top \Lambda_{out} C + \Lambda_{znext}, \\ H &= (\Lambda_{in} + B^\top \Lambda_z B)^{-1}, & \tilde\xi_z &= \xi_z + \Lambda_z B H (-\xi_{in} - B^\top \xi_z), \\ \tilde\Lambda_z &= \Lambda_z (I - B H B^\top \Lambda_z), & m_{zprev} &= \mathcal{N}^{-1}\big(A^\top \tilde\xi_z,\; A^\top \tilde\Lambda_z A\big). \end{aligned}\]
The forward rules take the marginal of zprev, N(μ, Σ), and recompute these quantities from the messages on their own edges. With F = I - Λ_z B H Bᵀ, the marginal of znext is N(Fᵀ A μ + B (Σ_in Bᵀ ξ̃_z + μ_in), Fᵀ A Σ Aᵀ F + B H Bᵀ) and the marginal of out its image under C; the marginal of in follows Wadehn (2016), On sparsity by NUV-EM, in the dual parameterisation.
Interfaces
using BIFMMessagePassingRules, MessagePassingRulesBase, BayesBase, ExponentialFamily
MessagePassingRulesBase.nodespec(BIFM)| interface | aliases | |
|---|---|---|
out | ||
in | ||
zprev | ||
znext |
| default algorithm | MessagePassingRulesBase.DefaultAlgorithm() |
|---|---|
| static inputs | none |
| defined | src/bifm.jl:136 |
The node has four interfaces.
| name | meaning | messages the rules take |
|---|---|---|
out | the output, C znext | MvNormal |
in | the input u | MvNormal |
zprev | the previous state | TerminalProdArgument of an MvNormal, the forward pass's marginal |
znext | the next state | MvNormal, possibly with zero precision at the end of the chain |
Towards zprev the rule sends an MvNormal in weighted-mean and precision form; towards in, out and znext a TerminalProdArgument of an MvNormal, which the engine takes as the marginal without a product.
Algorithm
BIFMMessagePassingRules.BIFMSmoother — Type
BIFMSmoother(A, B, C)The algorithm of BIFM: the matrices of znext = A zprev + B in and out = C znext, converted to a common element type. It keeps nothing between calls, so one smoother may serve every node of a chain; the statistics of the input come from the message on in. The node declares no algorithm of its own, so a model must give this one.
Arguments
A: thed × dstate transition;B: thed × kinput matrix, for ak-dimensional input;C: thep × doutput matrix, for ap-dimensional output.
Throws
A DimensionMismatch when A is not square, B has not as many rows as A, or C has not as many columns as A has rows.
Examples
julia> using BIFMMessagePassingRules
julia> BIFMSmoother([1.0 0.0; 0.0 1.0], reshape([1.0, 0.0], 2, 1), [1.0 0.0]).C
1×2 Matrix{Float64}:
1.0 0.0BIFMSmoother is the node's algorithm. The node declares no algorithm of its own, so a model gives every BIFM node a BIFMSmoother. Under the default one, DefaultAlgorithm, no rule is found.
Under BIFMSmoother the node declares its dependencies, the inputs each target's rule takes:
MessagePassingRulesBase.dependencies_spec(BIFM, BIFMSmoother([1.0;;], [1.0;;], [1.0;;]))| free-energy partition | from the factorisation |
|---|
Every target takes the messages of the default scheme. The forward rules, towards out, in and znext, also read the message on their own edge, to recompute the backward quantities. They keep only working memory between the steps of one call.
Supported rules
MessagePassingRulesBase.rule_coverage(BIFM)| DefaultAlgorithm | BIFMSmoother | |
|---|---|---|
| → out | ✓ | |
| → in | ✓ | |
| → zprev | ✓ | |
| → znext | ✓ | |
| q(in, zprev, znext) | ✓ |
Every message rule is belief propagation on the messages. The marginal rule for q(in, zprev, znext), the joint that the free energy of a deterministic node reads, exists only to throw a BIFMFreeEnergyError. There is no average energy, and no free energy.
Example
The backward message of a scalar random walk observed directly: the message on out is N(1, 1), the input's N(0, 1), and nothing is known of the next state, so the message on zprev is N(1, 2).
@call_message_update_rule(
node = BIFM, target = :zprev, algorithm = BIFMSmoother([1.0;;], [1.0;;], [1.0;;]),
m = (
out = MvNormalMeanPrecision([1.0], [1.0;;]),
in = MvNormalMeanPrecision([0.0], [1.0;;]),
znext = MvNormalWeightedMeanPrecision([0.0], [0.0;;]),
),
)Result
| value | MvNormalWeightedMeanPrecision( xi: [0.5] Λ: [0.5;;] ) |
|---|---|
| type | ExponentialFamily.MvNormalWeightedMeanPrecision{Float64, Vector{Float64}, Matrix{Float64}} |
| log scale | undefined: the message rule for BIFM towards :zprev under BIFMSmoother declares no `logscale` |
Inputs
| edge | value |
|---|
Rule
| declared inputs | m[:out]::Union{ExponentialFamily.MultivariateNormalDistributionsFamily{T}, ExponentialFamily.MultivariateGaussianDistributionsFamily{T}} where T, m[:in]::Union{ExponentialFamily.MultivariateNormalDistributionsFamily{T}, ExponentialFamily.MultivariateGaussianDistributionsFamily{T}} where T, m[:znext]::Union{ExponentialFamily.MultivariateNormalDistributionsFamily{T}, ExponentialFamily.MultivariateGaussianDistributionsFamily{T}} where T |
|---|---|
| algorithm | BIFMSmoother{Float64}([1.0;;], [1.0;;], [1.0;;]) |
| scratch | (ξz = [1.0], Λz = [1.0;;], BHBt = [0.5;;], ξ̃z = [0.5], Λ̃z = [0.5;;], CtΛ = [1.0;;], BH = [0.5;;], z = [-0.5], zz = [0… |
| log scale | none |
| defined | src/bifm.jl:189 |
| body | (scratch, algo, args)->begin
s = bifm_backward!(scratch, algo, args.m[:out], args.m[:in], args.m[:znext])
A = algo.A
T = promote_samplefloattype(args.m[:out], args.m[:in], args.m[:znext])
convert(MvNormalWeightedMeanPrecision{T}, MvNormalWeightedMeanPrecision(A' * s.ξ̃z, A' * s.Λ̃z * A))
end |
@call_message_update_rule runs the rule and draws the three messages it read. The result is in weighted-mean and precision form, and getresult and mean_cov read it as N(1, 2):
julia> using BIFMMessagePassingRules, MessagePassingRulesBase, BayesBase, ExponentialFamily
julia> result = @call_message_update_rule(
node = BIFM, target = :zprev, algorithm = BIFMSmoother([1.0;;], [1.0;;], [1.0;;]),
m = (
out = MvNormalMeanPrecision([1.0], [1.0;;]),
in = MvNormalMeanPrecision([0.0], [1.0;;]),
znext = MvNormalWeightedMeanPrecision([0.0], [0.0;;]),
),
);
julia> m, V = mean_cov(getresult(result));
julia> m ≈ [1.0] && V ≈ [2.0;;]
trueLimitations
- No free energy: the joint marginal of the inputs and the average energy of
BIFMHelperthrow aBIFMFreeEnergyError. - Multivariate normal messages only, and the forward rules need the marginal of
zprevas aTerminalProdArgument, so the chain must start withBIFMHelper. - The matrices are known:
BIFMSmoothercarries them, and nothing learns them.
API
The node, and the error that asking for its free energy throws.
BIFMMessagePassingRules.BIFM — Type
BIFMThe deterministic node of a whole time slice of a linear state-space model, for backward-information-filter forward-marginal (BIFM) smoothing:
\[z_{next} = A z_{prev} + B u, \qquad out = C z_{next},\]
with the matrices A, B and C carried by its algorithm, BIFMSmoother, which the model must give each BIFM node. A chain of BIFM nodes starts with BIFMHelper.
Interfaces
out: the output,C znext, a vector;in: the inputu, a vector;zprev: the previous state;znext: the next state.
Every message the rules read is a multivariate normal; the forward rules read the message on zprev as a TerminalProdArgument wrapping one, the marginal that BIFMHelper or the previous slice sends.
Smoothing
Smoothing runs in two passes over the chain. The backward pass sends information-form messages towards zprev, from the messages on out, in and znext:
\[\begin{aligned} \xi_z &= C^\top \xi_{out} + \xi_{znext}, & \Lambda_z &= C^\top \Lambda_{out} C + \Lambda_{znext}, & H &= (\Lambda_{in} + B^\top \Lambda_z B)^{-1}, \\ \tilde\xi_z &= \xi_z + \Lambda_z B H (-\xi_{in} - B^\top \xi_z), & \tilde\Lambda_z &= \Lambda_z (I - B H B^\top \Lambda_z), & m_{zprev} &= \mathcal{N}^{-1}(A^\top \tilde\xi_z,\; A^\top \tilde\Lambda_z A). \end{aligned}\]
BIFMHelper turns it into the forward pass, whose messages towards in, out and znext are the marginals of those variables themselves, as TerminalProdArguments, so that the engine takes them as the marginals without a product. Each forward rule reads the message on its own edge, besides the others, and recomputes the backward quantities from them: the rules keep nothing between calls, so they are independent of the order they run in, and one BIFMSmoother may serve every node. On a chain the result is the Rauch–Tung–Striebel smoother's.
Limitations
- No free energy. A deterministic node's free energy reads the joint marginal of its inputs,
q(in, zprev, znext), whose rule here throws aBIFMFreeEnergyError, as does the average energy ofBIFMHelper. Run the inference without the free energy. - Multivariate normal messages only, with a
TerminalProdArgumentonzprevfor the forward rules: the chain must start withBIFMHelper. - Under the default algorithm,
DefaultAlgorithm, no rule exists: every node must be given aBIFMSmoother.
Examples
A scalar random walk observed without transformation: the message on out is N(1, 1), the input's N(0, 1), and nothing is known of the next state yet, so the backward message on zprev is N(1, 2).
julia> algorithm = BIFMSmoother([1.0;;], [1.0;;], [1.0;;]);
julia> result = @call_message_update_rule(
node = BIFM, target = :zprev, algorithm = algorithm,
m = (
out = MvNormalMeanPrecision([1.0], [1.0;;]),
in = MvNormalMeanPrecision([0.0], [1.0;;]),
znext = MvNormalWeightedMeanPrecision([0.0], [0.0;;]),
),
);
julia> m, V = mean_cov(getresult(result));
julia> m ≈ [1.0] && V ≈ [2.0;;]
trueIn a model, the chain of BIFM nodes, each under one BIFMSmoother, starts at a BIFMHelper whose input is the first state's prior, and ends at a flat prior on the last state, a normal of zero precision; the prior and the states are in separate clusters, q(z_prior) q(z). Each node's in is the input u[i] and its out the observed yt[i].
See also BIFMSmoother, BIFMHelper.
BIFMMessagePassingRules.BIFMFreeEnergyError — Type
BIFMFreeEnergyError(node::Symbol)The error thrown when the free energy of a model with BIFM or BIFMHelper is asked for, which is not supported: BIFM smoothing computes the posteriors, and its Bethe free energy is not derived. node names the node that threw, :BIFM or :BIFMHelper.