ReactiveMP.jl

A reactive message passing engine for Bayesian inference on factor graphs.

ReactiveMP.jl runs message passing on a factor graph. It runs exact belief propagation, variational message passing under a factorisation, and the approximations between them, with the Bethe free energy as the objective. It builds no schedule in advance. Messages and marginals are streams, and each new observation propagates through the graph, recomputing only what depends on it. This suits streaming and online inference as well as batch inference.

ReactiveMP — Module
ReactiveMP

A reactive message passing engine for Bayesian inference on factor graphs. A graph is built from variables (randomvar, datavar, constvar) and factor nodes (factornode), and activated: messages and marginals are then streams, and each new observation (new_observation!) propagates through them. A factor node computes each outbound message with the update rule MessagePassingRulesBase finds for it, from the latest messages and marginals it depends on; a variable multiplies its inbound messages into its marginal; bethe_free_energy is the stream of the variational objective.

The engine defines no node and no rule: they come from the rule packages, such as StandardMessagePassingRules, which depend on the base package only. Most models are written with RxInfer, which builds and runs the graph on this engine.

Examples

A normal prior on x and one observation y ~ N(x, 1), with a normal node declared for the example, as a rule package would declare it:

julia> using MessagePassingRulesBase, BayesBase, ExponentialFamily

julia> import ReactiveMP: activate!, FactorNodeActivationOptions, get_stream_of_marginals

julia> struct Gaussian end   # N(out | μ, v), for a known variance v

julia> @define_factor_node(node = Gaussian, type = Stochastic, interfaces = [:out, :μ, :v])

julia> @define_message_update_rule(
           node = Gaussian, target = :out, args = (m[:μ]::PointMass, m[:v]::PointMass),
           body = (args) -> NormalMeanVariance(mean(args.m[:μ]), mean(args.m[:v])),
       )

julia> @define_message_update_rule(
           node = Gaussian, target = :μ, args = (m[:out]::PointMass, m[:v]::PointMass),
           body = (args) -> NormalMeanVariance(mean(args.m[:out]), mean(args.m[:v])),
       )

julia> x, y = randomvar(), datavar();

julia> prior = factornode(Gaussian, [(:out, x), (:μ, constvar(0.0)), (:v, constvar(10.0))]);

julia> likelihood = factornode(Gaussian, [(:out, y), (:μ, x), (:v, constvar(1.0))]);

julia> activate!(x, RandomVariableActivationOptions()); activate!(y, DataVariableActivationOptions());

julia> foreach(node -> activate!(node, FactorNodeActivationOptions()), (prior, likelihood));

julia> posterior = Ref{Any}(); subscribe!(get_stream_of_marginals(x), (q) -> posterior[] = q);

julia> new_observation!(y, 2.0)

julia> mean(posterior[]) ≈ 20 / 11 && var(posterior[]) ≈ 10 / 11
true
source

Who this site is for

The engine is the computational core of the RxInfer ecosystem. Most users never call it directly: RxInfer's @model and infer build the graph and run it on this engine. This site is for you if you want to:

  • build a factor graph by hand and run inference on it;
  • write your own inference loop, or observe and change what the engine does, through callbacks, stream postprocessors and annotations;
  • work on the engine itself.

If you want to specify and fit a model, start with RxInfer's documentation.

What the engine is and is not

The engine creates variables and factor nodes, connects them, and computes the messages, the marginals and the free energy as data arrives.

The engine defines no node and no rule. A factor node and its rules are declared with MessagePassingRulesBase, and the nodes a model uses come from rule packages, such as StandardMessagePassingRules (see The ecosystem). Loading a rule package is enough for the engine to find its rules. This site's examples run on one small node, declared in the file that The example node shows.

The engine has no model syntax. You create each variable and node with a function call. GraphPPL turns a @model into a graph, and RxInfer builds that graph on this engine.

Ideas and principles

Reactive message passing creates no message passing schedule in advance. It reacts to changes in the data, hence reactive. The PhD dissertation of Dmitry Bagaev, Reactive Probabilistic Programming for Scalable Bayesian Inference (also here, and its sources), explains the ideas behind it. Tutorials and example models are in the RxInfer documentation.

The site