[ANN]: RxInfer.jl 2.0 Julia package for automated Bayesian inference on a factor graph with reactive message passing

Thank you, that is very helpful! So if I wanted to model a Gaussian Markov random field, say for example this simple one with four latent nodes (a-d) and two observations (x and y):

x
|
a---b
|   |
c---d---y

what would be the correct way to specify it in the @model function?

Hi @ElOceanografo!

To give a tiny bit of background, RxInfer.jl uses a particular type of factor graphs, which establishes the connection between the random variables. I will not go into details about the computational graph behind the package. Still, it technically means that if I want to implement your model, I need to add additional nodes (functions) in your graph representation.

For example, you can specify the graph as follows:

x
|
(f_x)
|
a---(f_a)---b
|           |
(f_a)       |
|           |   
c--------(f_cb)--d--(f_d)--y

The difference with your specification is that I explicitly show the functional dependence between your variables. The graph I showed is still incomplete, though I hope it will help you to understand what’s missing from the RxInfer.jl perspective when implementing your graph.

Now, depending on the functional form of fs (let’s say Gaussians, i.e., f_i(i, x) = N(i|x, 1.0) i in {x, a, d} and f_cb=N(d|c+b, 1.0)) you can write the model and inference in RxIner.jl as:

using RxInfer

@model function loopy_model()

    y   = datavar(Float64)
    x_0 = datavar(Float64)

    # variances are chosen arbitrary
    x ~ Normal(mean = x_0, var = 1.0)
    a ~ Normal(mean = x, var = 1.0)   # f_x
    b ~ Normal(mean = a, var = 1.0)   # f_a
    c ~ Normal(mean = a, var = 1.0)   # f_a
    d ~ Normal(mean = c+b, var = 1.0) # f_cb

    y ~ Normal(mean = d, var = 1.0)
end

result = inference(
    model = loopy_model(),
    data = (y = 1.0, x_0=1.0),
    initmessages = (
        a = vague(NormalMeanPrecision),
    ),
    iterations = 50,
    free_energy = true
)

@model macro lets you specify the model in a probabilistic way, RxInfer.jl handles the graphical equivalent of the model under the hood for fast computations.

2 Likes

So if I’m understanding the modeling language correctly, the left-hand side of each ~ is a node, and each node can only appear in one ~ statement. The right-hand side of each ~ corresponds to a factor. If a node has more than one edge coming into it, it needs to be explicitly written as a function of all the connected nodes. So a graph like this

x---a   b---y
     \ /
      d
      |
      c
      |
      z

could be specified like this:

x = datavar(Float64)
y = datavar(Float64)
z = datavar(Float64)
# v is arbitrary
a ~ Normal(x, v)
b ~ Normal(y, v)
c ~ Normal(z, v)
d ~ Normal((a + b + c) / 3, v)

Is that right?

RxInfer.jl uses Forney-style Factor Graphs (FFGs) as the graphical representation of the generative model. We use edges to represent variables and nodes for factors/functions. Indeed, you can think of the left-hand side of ~ as a variable defined by an edge (or a node in your description/ it really doesn’t matter for now). The right-hand side of ~ is indeed a factor.

If a node has more than one edge coming into it (in the case of a multi-argument function), you would need to pass all the required variables inside the function.

For example, if you have a gaussian factor node N(y|x, z), it has three edges for out y, mean x, and variance z. In your model definition, you would have to write something like this:

...
y ~ Normal(mean=x, variance=z)
...

As for the model you have specified, yes, that could be an option.