Inference execution

The RxInfer inference API supports different types of message-passing algorithms (including hybrid algorithms combining several different types). While RxInfer implements several algorithms to cater to different computational needs and scenarios, the core message-passing algorithms that form the foundation of our inference capabilities are:

Whereas belief propagation computes exact inference for the random variables of interest, the variational message passing (VMP) is an approximation method that can be applied to a larger range of models.

The inference engine itself isn't aware of different algorithm types and simply does message passing between nodes. However, during the model specification stage user may specify different factorisation constraints around factor nodes with the help of the @constraints macro. Different factorisation constraints lead to different message passing update rules. See more documentation about constraints specification in the corresponding section.

Automatic inference specification

RxInfer exports the infer function to quickly run and test your model with both static and asynchronous (real-time) datasets. See more information about the infer function on the separate documentation section:

RxInfer.infer — Function
infer(
    model; 
    data = nothing,
    datastream = nothing,
    autoupdates = nothing,
    initialization = nothing,
    constraints = nothing,
    meta = nothing,
    options = nothing,
    returnvars = nothing, 
    predictvars = nothing, 
    historyvars = nothing,
    keephistory = nothing,
    iterations = nothing,
    free_energy = false,
    free_energy_diagnostics = DefaultObjectiveDiagnosticChecks,
    showprogress = false,
    callbacks = nothing,
    annotations = nothing,
    logscales = nothing,
    postprocess = nothing,
    warn = true,
    events = nothing,
    uselock = false,
    autostart = true,
    catch_exception = false,
    benchmark = false,
    trace = false,
    session = RxInfer.default_session()
)

This function provides a generic way to perform probabilistic inference for batch/static and streamline/online scenarios. Returns either an InferenceResult (batch setting) or RxInferenceEngine (streamline setting) based on the parameters used.

Note

Before using this function, you may want to review common issues and solutions in the Sharp bits of RxInfer section of the documentation.

Arguments

Check the official documentation for more information about some of the arguments.

  • model: specifies a model generator, required
  • data: NamedTuple or Dict with data, required (or datastream or predictvars)
  • datastream: A stream of NamedTuple with data, required (or data)
  • autoupdates = nothing: auto-updates specification, required for streamline inference, see @autoupdates
  • initialization = nothing: initialization specification object, optional, see @initialization
  • constraints = nothing: constraints specification object, or an alias such as MeanField, optional, see @constraints
  • algorithm = nothing: the algorithm specification, which algorithm each node runs under, optional, may be required for some models, see @algorithm
  • meta = nothing: deprecated, the old name of algorithm; a @meta specification is still accepted as one
  • options = nothing: model creation options, optional, see ReactiveMPInferenceOptions
  • returnvars = nothing: return structure info, optional, defaults to return everything at each iteration
  • predictvars = nothing: return structure info, optional (exclusive for batch inference)
  • historyvars = nothing: history structure info, optional, defaults to no history (exclusive for streamline inference)
  • keephistory = nothing: history buffer size, defaults to empty buffer (exclusive for streamline inference)
  • iterations = nothing: number of iterations, optional, defaults to nothing, the inference engine does not distinguish between variational message passing, loopy belief propagation, or expectation propagation iterations. See Early stopping for an opt-in callback example, which implements early stopping.
  • free_energy = false: compute the Bethe free energy, optional, defaults to false. Can be passed a floating point type, e.g. Float64, for better efficiency, but disables automatic differentiation packages, such as ForwardDiff.jl
  • free_energy_diagnostics = DefaultObjectiveDiagnosticChecks: free energy diagnostic checks, optional, by default checks for possible NaNs and Infs. nothing disables all checks.
  • showprogress = false: show a progress bar, optional, defaults to false (exclusive for batch inference)
  • catch_exception: specifies whether exceptions during the inference procedure should be caught, optional, defaults to false (exclusive for batch inference)
  • callbacks = nothing: inference cycle callbacks, optional. Can be a NamedTuple, Dict, or any custom structure that implements ReactiveMP.handle_event. See Callbacks for a comprehensive overview, Benchmark callbacks for performance analysis, Trace callbacks for event tracing, and Early stopping for an opt-in callback example.
  • annotations = nothing: a tuple of annotation processors that attach extra information to messages and marginals during inference, such as InputArgumentsAnnotations(). When annotations are enabled, the inference results preserve the Marginal wrapper type so that annotation data remains accessible via ReactiveMP.getannotations. See ReactiveMP.jl documentation for available annotation types and how to implement custom annotation processors.
  • logscales = nothing: whether messages and marginals carry log scales, the log of the constant a rule's result leaves out; true enables them, as options = (logscales = true,) does. The Mixture node needs them, and a posterior's log scale, getlogscale(result.posteriors[:x]), is the log evidence of a model inferred exactly by belief propagation, useful for Bayes factors and model comparison. The inference results then preserve the Marginal wrapper, which carries the log scale.
  • postprocess = nothing: inference results postprocessing step, optional. By default, uses UnpackMarginalPostprocess when neither annotations nor logscales is enabled (strips the Marginal wrapper), and NoopPostprocess otherwise (preserves the wrapper). See Inference results postprocessing for details on implementing custom strategies.
  • events = nothing: inference cycle events, optional (exclusive for streamline inference)
  • uselock = false: specifies whether to use a lock structure for the inference; if set to true, uses Base.Threads.SpinLock. Accepts a custom AbstractLock. (exclusive for streamline inference)
  • autostart = true: specifies whether to call RxInfer.start on the created engine automatically or not (exclusive for streamline inference)
  • warn = true: enables/disables warnings
  • benchmark = false: when set to true, automatically merges a RxInferBenchmarkCallbacks instance with the user-provided callbacks. The benchmark results are accessible via result.model.metadata[:benchmark]. See Benchmark callbacks.
  • trace = false: when set to true, automatically merges a RxInferTraceCallbacks instance with the user-provided callbacks. Can also be a Tuple of Symbols to trace only specific event types (e.g., trace = (:before_iteration, :after_iteration)). The trace results are accessible via result.model.metadata[:trace]. See Trace callbacks.
  • session = RxInfer.default_session(): current logging session for the RxInfer invokes, see Session for more details, pass nothing to disable logging

Error hints

By default, RxInfer provides helpful error hints with documentation links, solutions, and troubleshooting guidance.

Use RxInfer.disable_inference_error_hint!() to disable error hints or RxInfer.enable_inference_error_hint!() to enable them. Note that changes to error hint settings require a Julia session restart to take effect.

See also: RxInfer.disable_inference_error_hint!, RxInfer.enable_inference_error_hint!

source

Note on NamedTuples

When passing NamedTuple as a value for some argument, make sure you use a trailing comma for NamedTuples with a single entry. The reason is that Julia treats returnvars = (x = KeepLast()) and returnvars = (x = KeepLast(), ) expressions differently. This first expression creates (or overwrites!) new local/global variable named x with contents KeepLast(). The second expression (note trailing comma) creates NamedTuple with x as a key and KeepLast() as a value assigned for this key.

(x = KeepLast()) # defines a variable `x` with the value `KeepLast()`
KeepLast()
(x = KeepLast(), ) # defines a NamedTuple with `x` as one of the keys and value `KeepLast()`
(x = KeepLast(),)
  • model

Also read the Model Specification section.

The model argument accepts a model specification as its input. The easiest way to create the model is to use the @model macro. For example:

@model function beta_bernoulli(y, a, b)
    x  ~ Beta(a, b)
    y .~ Bernoulli(x)
end

result = infer(
    model = beta_bernoulli(a = 1, b = 1),
    data  = (y = [ true, false, false ], )
)

result.posteriors[:x]
Beta{Float64}(α=2.0, β=3.0)
Note

The model keyword argument does not accept a ProbabilisticModel instance as a value, as it needs to inject constraints and algorithm during the inference procedure.

  • data

Either data or datastream keyword argument are required. Specifying both data and datastream is not supported and will result in an error.

Note

The behavior of the data keyword argument depends on the inference setting (batch or streamline).

The data keyword argument must be a NamedTuple (or Dict) where keys (of Symbol type) correspond to some arguments defined in the model specification. For example, if a model defines y in its argument list

@model function beta_bernoulli(y, a, b)
    x  ~ Beta(a, b)
    y .~ Bernoulli(x)
end

and you want to condition on this argument, then the data field must have an :y key (of Symbol type) which holds the data. The values in the data must have the exact same shape as its corresponding variable container. E.g. in the example above y is being used in the broadcasting operation, thus it must be a collection of values. The a and b arguments, however, could be just single numbers:

result = infer(
    model = beta_bernoulli(),
    data  = (y = [ true, false, false ], a = 1, b = 1)
)

result.posteriors[:x]
Beta{Float64}(α=2.0, β=3.0)
  • datastream

Also read the Streamlined Inference section.

The datastream keyword argument must be an observable that supports subscribe! and unsubscribe! functions (e.g., streams from the Rocket.jl package). The elements of the observable must be of type NamedTuple where keys (of Symbol type) correspond to input arguments defined in the model specification, except for those which are listed in the @autoupdates specification. For example, if a model defines y as its argument (which is not part of the @autoupdates specification) the named tuple from the observable must have an :y key (of Symbol type). The values in the named tuple must have the exact same shape as the corresponding variable container.

  • initialization

Also read the Initialization section.

For specific types of inference algorithms, such as variational message passing, it might be required to initialize (some of) the marginals before running the inference procedure in order to break the dependency loop. If this is not done, the inference algorithm will not be executed due to the lack of information and message and/or marginals will not be updated. In order to specify these initial marginals and messages, you can use the initialization argument in combination with the @initialization macro, such as

init = @initialization begin
    # initialize the marginal distribution of x as a vague Normal distribution
    # if x is a vector, then it simply uses the same value for all elements
    # However, it is also possible to provide a vector of distributions to set each element individually
    q(x) = vague(NormalMeanPrecision)
end
Initial state: 
  q(x) = NormalMeanPrecision{Float64}(μ=0.0, w=1.0e-12)
  • algorithm

Also read the Algorithm specification section.

Every rule of a node belongs to an algorithm, which selects the rules that run and carries their parameters. Most nodes run under the default algorithm, and need nothing. Some need a choice, such as the approximation method of a deterministic node or the order of an autoregressive node. The algorithm argument accepts an @algorithm specification, which gives algorithms to the nodes of the model by their function and variables:

@model function nonlinear_model(y)
    x ~ Normal(mean = 0.0, variance = 1.0)
    z := exp(x)
    y ~ Normal(mean = z, variance = 0.1)
end

result = infer(
    model     = nonlinear_model(),
    data      = (y = 2.0,),
    algorithm = @algorithm(exp() -> DeltaApproximation(method = Linearization())),
)

result.posteriors[:x]
NormalWeightedMeanPrecision{Float64}(xi=9.999999999999996, w=10.999999999999996)

A single node can also take its algorithm in the model, as z := exp(x) where { algorithm = ... }. The meta argument is the old name of algorithm, and it still works with a deprecation warning.

  • returnvars

returnvars specifies latent variables of interest and their posterior updates. Its behavior depends on the inference type: streamline or batch.

Batch inference:

  • Accepts a NamedTuple or Dict of return variable specifications.
  • Two specifications available: KeepLast (saves the last update) and KeepEach (saves all updates).
  • When iterations is set, returns every update for each iteration (equivalent to KeepEach()); if nothing, saves the last update (equivalent to KeepLast()).
  • Use iterations = 1 to force KeepEach() for a single iteration or set returnvars = KeepEach() manually.
@model function normal_gamma(y)
    x ~ Normal(mean = 0.0, variance = 10.0)
    τ ~ Gamma(shape = 1.0, rate = 1.0)
    y .~ Normal(mean = x, precision = τ)
end

result = infer(
    model          = normal_gamma(),
    data           = (y = [0.9, 1.1, 1.3, 0.8, 1.0],),
    constraints    = MeanField(),
    initialization = @initialization(q(τ) = Gamma(1.0, 1.0)),
    iterations     = 5,
    returnvars     = (
        x = KeepLast(),
        τ = KeepEach()
    )
)

result.posteriors[:τ]
5-element Vector{GammaShapeRate{Float64}}:
 GammaShapeRate{Float64}(a=3.5, b=1.5651960784313728)
 GammaShapeRate{Float64}(a=3.5, b=1.2958216933834892)
 GammaShapeRate{Float64}(a=3.5, b=1.2578972450186254)
 GammaShapeRate{Float64}(a=3.5, b=1.2525496216627718)
 GammaShapeRate{Float64}(a=3.5, b=1.2517954017984472)

Shortcut for setting the same option for all variables:

result = infer(
    model          = normal_gamma(),
    data           = (y = [0.9, 1.1, 1.3, 0.8, 1.0],),
    constraints    = MeanField(),
    initialization = @initialization(q(τ) = Gamma(1.0, 1.0)),
    iterations     = 5,
    returnvars     = KeepLast()  # or KeepEach()
)

result.posteriors[:τ]
GammaShapeRate{Float64}(a=3.5, b=1.2517954017984472)

Streamline inference:

  • For each symbol in returnvars, infer creates an observable stream of posterior updates.
  • Agents can subscribe to these updates using the Rocket.jl package.
@model function gaussian_filter(y, x_mean, x_var)
    x ~ Normal(mean = x_mean, variance = x_var)
    y ~ Normal(mean = x, variance = 1.0)
end

my_autoupdates = @autoupdates begin
    x_mean, x_var = mean_var(q(x))
end

datastream   = RecentSubject(Float64)
observations = labeled(Val((:y,)), combineLatest(datastream))

engine = infer(
    model          = gaussian_filter(),
    datastream     = observations,
    autoupdates    = my_autoupdates,
    initialization = @initialization(q(x) = NormalMeanVariance(0.0, 10.0)),
    returnvars     = (:x,),
    autostart      = false
)
RxInferenceEngine:
  Posteriors stream    | enabled for (x)
  Free Energy stream   | disabled
  Posteriors history   | unavailable
  Free Energy history  | unavailable
  Enabled events       | [  ]
RxInfer.KeepLast — Type

Instructs the inference engine to keep only the last marginal update and disregard intermediate updates.

source
RxInfer.KeepEach — Type

Instructs the inference engine to keep each marginal update for all intermediate iterations.

source
  • predictvars

predictvars specifies the variables which should be predicted. Similar to returnvars, predictvars accepts a NamedTuple or Dict. There are two specifications:

  • KeepLast: saves the last update for a variable, ignoring any intermediate results during iterations
  • KeepEach: saves all updates for a variable for all iterations
result = infer(
    model          = normal_gamma(),
    data           = (y = [0.9, 1.1, 1.3, 0.8, missing],),
    constraints    = MeanField(),
    initialization = @initialization(q(τ) = Gamma(1.0, 1.0)),
    iterations     = 5,
    predictvars    = (y = KeepLast(),)
)

result.predictions[:y][end]
NormalMeanPrecision{Float64}(μ=1.0141213250047167, w=2.330525834820038)
Note

The predictvars argument is exclusive for batch setting.

  • historyvars

Also read the Keeping the history of posteriors.

historyvars specifies the variables of interests and the amount of information to keep in history about the posterior updates when performing streamline inference. The specification is similar to the returnvars when applied in batch setting. The historyvars requires keephistory to be greater than zero.

historyvars accepts a NamedTuple or Dict or return var specification. There are two specifications:

  • KeepLast: saves the last update for a variable, ignoring any intermediate results during iterations
  • KeepEach: saves all updates for a variable for all iterations
engine = infer(
    model          = gaussian_filter(),
    datastream     = observations,
    autoupdates    = my_autoupdates,
    initialization = @initialization(q(x) = NormalMeanVariance(0.0, 10.0)),
    historyvars    = (
        x = KeepLast(),
    ),
    keephistory    = 10
)

for y in (0.9, 1.1, 1.3)
    next!(datastream, y)
end

engine.history[:x]
3-element DataStructures.CircularBuffer{Any}:
 NormalWeightedMeanPrecision{Float64}(xi=0.9, w=1.1)
 NormalWeightedMeanPrecision{Float64}(xi=2.0, w=2.1)
 NormalWeightedMeanPrecision{Float64}(xi=3.3, w=3.1)

It is also possible to set either historyvars = KeepLast() or historyvars = KeepEach() that acts as an alias and sets the given option for all random variables in the model.

engine = infer(
    model          = gaussian_filter(),
    datastream     = observations,
    autoupdates    = my_autoupdates,
    initialization = @initialization(q(x) = NormalMeanVariance(0.0, 10.0)),
    historyvars    = KeepLast(),
    keephistory    = 10
)
  • keephistory

Specifies the buffer size for the updates history both for the historyvars and the free_energy buffers in streamline inference.

Note

The historyvars and keephistory arguments are exclusive for streamlined setting.

  • iterations

Specifies the number of variational (or loopy belief propagation) iterations. By default set to nothing, which is equivalent of doing 1 iteration. However, if set explicitly to 1 the default setting for returnvars changes from KeepLast to KeepEach.

See Early stopping for an opt-in callback example that implements early stopping criterion.

  • free_energy

Batch inference:

Specifies if the infer function should return Bethe Free Energy (BFE) values.

  • Optionally accepts a floating-point type (e.g., Float64) for improved BFE computation performance, but restricts the use of automatic differentiation packages.

Streamline inference:

Specifies if the infer function should create an observable stream of Bethe Free Energy (BFE) values, computed at each VMP iteration.

  • When free_energy = true and keephistory > 0, additional fields are exposed in the engine for accessing the history of BFE updates.
    • engine.free_energy_history: Averaged BFE history over VMP iterations.
    • engine.free_energy_final_only_history: BFE history of values computed in the last VMP iterations for each observation.
    • engine.free_energy_raw_history: Raw BFE history.
  • free_energy_diagnostics

This settings specifies either a single or a tuple of diagnostic checks for Bethe Free Energy values stream. By default checks for NaNs and Infs. See also RxInfer.ObjectiveDiagnosticCheckNaNs and RxInfer.ObjectiveDiagnosticCheckInfs. Pass nothing to disable any checks.

  • options

RxInfer.ReactiveMPInferenceOptions — Type
ReactiveMPInferenceOptions(; kwargs...)

Creates model inference options object. The list of available options is present below.

Options

  • limit_stack_depth: limits the stack depth for computing messages; helps with StackOverflowError for some huge models, but reduces the performance of the inference backend. Accepts an integer argument that specifies the maximum recursion depth. Lower is better for stack overflow errors, but worse for performance.
  • warn: (optional) flag to suppress warnings. Warnings are not displayed if set to false. Defaults to true.
  • force_marginal_computation: (optional) flag to force computation of marginals even when not explicitly requested. Defaults to false.

Advanced options

  • stream_postprocessors: changes the postprocessor of reactive streams, see ReactiveMP.jl for more info, defaults to nothing, unless the limit_stack_depth option is set, in which case will be set to ReactiveMP.ScheduleOnStreamPostprocessor together with RxInfer.LimitStackScheduler.
  • diagnostics: the engine's audits of the rules the model runs, a ReactiveMP.EngineDiagnostics (check_everything_pure, check_everything_inplace, checked_buffers), all off by default; or, shorter, a NamedTuple of the audits to switch on, (check_everything_pure = true,), which builds one.
  • context: services for the rules, a NamedTuple merged over the engine's defaults: rng, the random number generator the rules draw from, the task's own by default; matrix_correction, the correction rules apply to the matrices they build, each rule's own by default; and any service a rule declares.
  • rulefallback: the message where no rule matches, such as NodeFunctionRuleFallback(); by default none, and a missing rule is an error that lists the closest candidates.
  • logscales: whether messages and marginals carry log scales, the log of the constant a rule's result leaves out (see ReactiveMP.getlogscale); false by default. The Mixture node reads them, and a posterior's log scale is the log evidence of a model inferred exactly by belief propagation.

See also: infer

source
  • catch_exception

The catch_exception keyword argument specifies whether exceptions during the batch inference procedure should be caught in the error field of the result. By default, if exception occurs during the inference procedure the result will be lost. Set catch_exception = true to obtain partial result for the inference in case if an exception occurs. Use RxInfer.issuccess and RxInfer.iserror function to check if the inference completed successfully or failed. If an error occurs, the error field will store a tuple, where first element is the exception itself and the second element is the caught backtrace. Use the stacktrace function with the backtrace as an argument to recover the stacktrace of the error. Use Base.showerror function to display the error.

  • callbacks

The inference function and underlying reactive message passing procedure both have their own lifecycle. The user is free to provide some (or none) of the callbacks to inject extra logic during the inference procedure. Callbacks can be a NamedTuple, Dict, or any custom structure that implements ReactiveMP.handle_event. For example:

@model function beta_bernoulli(y)
    x ~ Beta(1.0, 1.0)
    y .~ Bernoulli(x)
end

result = infer(
    model     = beta_bernoulli(),
    data      = (y = [true, false, true],),
    callbacks = (
        on_marginal_update = (event) -> println("$(event.variable_name) has been updated: $(event.update)"),
        after_inference    = (event) -> println("Inference has been completed")
    )
)
x has been updated: Marginal(Beta{Float64}(α=3.0, β=2.0))
Inference has been completed

For the full list of available events, supported callback types, model metadata, and built-in callback handlers, see the Callbacks section. See also Early stopping and Benchmark callbacks.

  • annotations

Sets a tuple of annotation processors that attach extra information to messages and marginals during inference, such as InputArgumentsAnnotations(). When annotations are enabled, the inference results preserve the Marginal wrapper type so that annotation data remains accessible via ReactiveMP.getannotations. See ReactiveMP.jl documentation for available annotation types and how to implement custom annotation processors.

Automatically changes the default value of the postprocess argument to NoopPostprocess.

  • logscales

logscales = true makes messages and marginals carry log scales: the log of the constant each rule's result leaves out, where the rule can compute it, combined through every product of messages. The Mixture node needs them, and in a model inferred exactly by belief propagation a posterior's log scale, getlogscale(result.posteriors[:x]), is the log evidence of the data, which is useful for Bayes factors and model comparison. Where a log scale cannot be computed, for instance after a variational rule, it is a ReactiveMP.UndefinedLogScale that says why. It can also be given as options = (logscales = true,).

Automatically changes the default value of the postprocess argument to NoopPostprocess, so the results keep the Marginal wrapper that carries the log scale.

  • postprocess

Also read the Inference results postprocessing section.

The postprocess keyword argument controls whether the inference results must be modified in some way before exiting the inference function. By default, the inference function removes the Marginal wrapper type from the results. Change this setting to NoopPostprocess if you would like to keep the Marginal wrapper type. If the annotations or logscales argument has been used, automatically changes the default strategy value to NoopPostprocess.

  • Error hints

By default, RxInfer provides helpful error hints when an error occurs during inference. This, for example, includes links to relevant documentation, common solutions and troubleshooting steps, information about where to get help, and suggestions for providing good bug reports.

Use RxInfer.disable_inference_error_hint! to disable error hints or RxInfer.enable_inference_error_hint! to enable them. Note that the change requires a Julia session restart to take effect.

RxInfer.enable_inference_error_hint! — Function
enable_inference_error_hint!()

Enable error hints that are shown when an error occurs during inference.

The change requires a Julia session restart to take effect. When enabled, errors during the inference call will include:

  • Links to relevant documentation
  • Common solutions and troubleshooting steps
  • Information about where to get help

See also: disable_inference_error_hint!, infer

source

Where to go next?

Read more explanation about the other keyword arguments in the Streamlined (online) inference section or check out the Static Inference section or check some more advanced examples.