Nodes implementation
In the message passing framework, one of the most important concepts is a factor node. A factor node represents a local function in a factorised representation of a generative model.
To quickly check the list of all available factor nodes that can be used in the model specification language, call ?make_node
or Base.doc(make_node)
.
Adding a custom node
ReactiveMP.jl
exports the @node
macro that allows for quick definition of a factor node with a fixed number of edges. The interface is the following:
struct MyNewCustomNode end
@node MyNewCustomNode Stochastic [ x, y, z ]
# ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^
# Node's tag/name Node's type A fixed set of edges
# Another possible The very first edge (in this example `x`) is considered
# value is to be the output of the node
# `Deterministic`
This expression registers a new node that can be used with the inference engine. Note howeve, that the @node
macro does not generate any message passing update rules. These must be defined using the @rule
macro.
Node types
We distinguish different types of factor nodes in order to have better control over Bethe Free Energy computation. Each factor node has either the Deterministic
or Stochastic
functional form type.
ReactiveMP.Deterministic
— TypeDeterministic
Deterministic
object used to parametrize factor node object with determinstic type of relationship between variables.
See also: Stochastic
, isdeterministic
, isstochastic
, sdtype
ReactiveMP.Stochastic
— TypeStochastic
Stochastic
object used to parametrize factor node object with stochastic type of relationship between variables.
See also: Deterministic
, isdeterministic
, isstochastic
, sdtype
ReactiveMP.isdeterministic
— Functionisdeterministic(node)
Function used to check if factor node object is deterministic or not. Returns true or false.
See also: Deterministic
, Stochastic
, isstochastic
, sdtype
ReactiveMP.isstochastic
— Functionisstochastic(node)
Function used to check if factor node object is stochastic or not. Returns true or false.
See also: Deterministic
, Stochastic
, isdeterministic
, sdtype
ReactiveMP.sdtype
— Functionsdtype(object)
Returns either Deterministic
or Stochastic
for a given object (if defined).
See also: Deterministic
, Stochastic
, isdeterministic
, isstochastic
For example the +
node has the Deterministic
type:
plus_node = make_node(+)
println("Is `+` node deterministic: ", isdeterministic(plus_node))
println("Is `+` node stochastic: ", isstochastic(plus_node))
Is `+` node deterministic: true
Is `+` node stochastic: false
On the other hand, the Bernoulli
node has the Stochastic
type:
bernoulli_node = make_node(Bernoulli)
println("Is `Bernoulli` node deterministic: ", isdeterministic(bernoulli_node))
println("Is `Bernoulli` node stochastic: ", isstochastic(bernoulli_node))
Is `Bernoulli` node deterministic: false
Is `Bernoulli` node stochastic: true
To get an actual instance of the type object we use sdtype
function:
println("sdtype() of `+` node is ", sdtype(plus_node))
println("sdtype() of `Bernoulli` node is ", sdtype(bernoulli_node))
sdtype() of `+` node is Deterministic()
sdtype() of `Bernoulli` node is Stochastic()
Node functional dependencies pipeline
The generic implementation of factor nodes in ReactiveMP supports custom functional dependency pipelines. Briefly, the functional dependencies pipeline defines what dependencies are need to compute a single message. As an example, consider the belief-propagation message update equation for a factor node $f$ with three edges: $x$, $y$ and $z$:
\[\mu(x) = \int \mu(y) \mu(z) f(x, y, z) \mathrm{d}y \mathrm{d}z\]
Here we see that in the standard setting for the belief-propagation message out of edge $x$, we need only messages from the edges $y$ and $z$. In contrast, consider the variational message update rule equation with mean-field assumption:
\[\mu(x) = \exp \int q(y) q(z) \log f(x, y, z) \mathrm{d}y \mathrm{d}z\]
We see that in this setting, we do not need messages $\mu(y)$ and $\mu(z)$, but only the marginals $q(y)$ and $q(z)$. The purpose of a functional dependencies pipeline is to determine functional dependencies (a set of messages or marginals) that are needed to compute a single message. By default, ReactiveMP.jl
uses so-called DefaultFunctionalDependencies
that correctly implements belief-propagation and variational message passing schemes (including both mean-field and structured factorisations). The full list of built-in pipelines is presented below:
ReactiveMP.DefaultFunctionalDependencies
— TypeDefaultFunctionalDependencies
This pipeline translates directly to enforcing a variational message passing scheme. In order to compute a message out of some edge, this pipeline requires messages from edges within the same edge-cluster and marginals over other edge-clusters.
See also: ReactiveMP.RequireMessageFunctionalDependencies
, ReactiveMP.RequireMarginalFunctionalDependencies
, ReactiveMP.RequireEverythingFunctionalDependencies
ReactiveMP.RequireMessageFunctionalDependencies
— TypeRequireMessageFunctionalDependencies(indices::Tuple, start_with::Tuple)
The same as DefaultFunctionalDependencies
, but in order to compute a message out of some edge also requires the inbound message on the this edge.
Arguments
indices
::Tuple, tuple of integers, which indicates what edges should require inbound messagesstart_with::Tuple
, tuple ofnothing
or<:Distribution
, which specifies the initial inbound messages for edges inindices
Note: start_with
uses setmessage!
mechanism, hence, it can be visible by other listeners on the same edge. Explicit call to setmessage!
overwrites whatever has been passed in start_with
.
@model
macro accepts a simplified construction of this pipeline:
@model function some_model()
# ...
y ~ NormalMeanVariance(x, τ) where {
pipeline = RequireMessage(x = vague(NormalMeanPrecision), τ)
# ^^^ ^^^
# request 'inbound' for 'x' we may do the same for 'τ',
# and initialise with `vague(...)` but here we skip initialisation
}
# ...
end
Deprecation warning: RequireInboundFunctionalDependencies
has been deprecated in favor of RequireMessageFunctionalDependencies
.
See also: ReactiveMP.DefaultFunctionalDependencies
, ReactiveMP.RequireMarginalFunctionalDependencies
, ReactiveMP.RequireEverythingFunctionalDependencies
ReactiveMP.RequireMarginalFunctionalDependencies
— TypeRequireMarginalFunctionalDependencies(indices::Tuple, start_with::Tuple)
Similar to DefaultFunctionalDependencies
, but in order to compute a message out of some edge also requires the posterior marginal on that edge.
Arguments
indices
::Tuple, tuple of integers, which indicates what edges should require their own marginalsstart_with::Tuple
, tuple ofnothing
or<:Distribution
, which specifies the initial marginal for edges inindices
Note: start_with
uses the setmarginal!
mechanism, hence it can be visible to other listeners on the same edge. Explicit calls to setmarginal!
overwrites whatever has been passed in start_with
.
@model
macro accepts a simplified construction of this pipeline:
@model function some_model()
# ...
y ~ NormalMeanVariance(x, τ) where {
pipeline = RequireMarginal(x = vague(NormalMeanPrecision), τ)
# ^^^ ^^^
# request 'marginal' for 'x' we may do the same for 'τ',
# and initialise with `vague(...)` but here we skip initialisation
}
# ...
end
Note: The simplified construction in @model
macro syntax is only available in GraphPPL.jl
of version >2.2.0
.
See also: ReactiveMP.DefaultFunctionalDependencies
, ReactiveMP.RequireMessageFunctionalDependencies
, ReactiveMP.RequireEverythingFunctionalDependencies
ReactiveMP.RequireEverythingFunctionalDependencies
— TypeRequireEverythingFunctionalDependencies
This pipeline specifies that in order to compute a message of some edge update rules request everything that is available locally. This includes all inbound messages (including on the same edge) and marginals over all local edge-clusters (this may or may not include marginals on single edges, depends on the local factorisation constraint).
See also: DefaultFunctionalDependencies
, RequireMessageFunctionalDependencies
, RequireMarginalFunctionalDependencies
Node traits
Each factor node has to define the ReactiveMP.as_node_functional_form
trait function and to specify a ReactiveMP.ValidNodeFunctionalForm
singleton as a return object. By default ReactiveMP.as_node_functional_form
returns ReactiveMP.UndefinedNodeFunctionalForm
. Objects that do not specify this property correctly cannot be used in model specification.
@node
macro does that automatically
ReactiveMP.ValidNodeFunctionalForm
— TypeValidNodeFunctionalForm
Trait specification for an object that can be used in model specification as a factor node.
See also: ReactiveMP.as_node_functional_form
, ReactiveMP.UndefinedNodeFunctionalForm
ReactiveMP.UndefinedNodeFunctionalForm
— TypeUndefinedNodeFunctionalForm
Trait specification for an object that can not be used in model specification as a factor node.
See also: ReactiveMP.as_node_functional_form
, ReactiveMP.ValidNodeFunctionalForm
ReactiveMP.as_node_functional_form
— Functionas_node_functional_form(object)
Determines object
node functional form trait specification. Returns either ValidNodeFunctionalForm()
or UndefinedNodeFunctionalForm()
.
See also: ReactiveMP.ValidNodeFunctionalForm
, ReactiveMP.UndefinedNodeFunctionalForm