Getting Started
On this page we will cover the basic syntax of GraphPPL
and will work towards a basic coin-toss example.
Installation
GraphPPL.jl
is a registered Julia package. To install it, run the following command in the Julia REPL:
julia> using Pkg
julia> Pkg.add("GraphPPL")
This guide assumes you have GraphPPL.jl
installed, as well as Distributions.jl
to work with standard probability distributions as building blocks in our probabilistic programs.
using GraphPPL
using Distributions
Creating a model
In GraphPPL
, we can specify a model with the @model
macro. The @model
macro takes a function as an argument, and registers the blueprint of creating this model. The model macro is not exported by default by GraphPPL
to allow inference packages to define their own @model
macro on top of the GraphPPL.jl
. For demonstration purposes we will import GraphPPL.@model
explicitly:
import GraphPPL: @model
Syntax
In general, we can write probabilistic programs in GraphPPL
using the ~
operator. The x ~ X
expression can be read as $x$ sampled from $X$. For example, if we want to define a random variable x
that is distributed according to a normal distribution with mean 0 and variance 1, we can write:
@model function example()
x ~ Normal(0, 1)
end
We can also define multiple random variables in the same model:
@model function example()
x ~ Normal(0, 1)
y ~ Normal(0, 1)
end
or use our newly defined random variables as parameters for other distributions:
@model function example()
x ~ Normal(0, 1)
y ~ Normal(x, 1)
end
We can also use the :=
operator to define deterministic relations:
@model function example()
x ~ Normal(0, 1)
y ~ Normal(x, 1)
z := x + y
end
Note that a deterministic function, when called with known parameters, will not materialize in the factor graph, but will instead compile out and return the result. To illustrate this:
@model function example()
μ := 1 + 2
x ~ Normal(μ, 1)
y ~ Normal(x, 1)
z := x + y
end
In the above example, the μ := 1 + 2
line will not materialize in the factor graph, and instead will instantiate the variable μ
with the value 3
. However, since x
and y
are random variables, the +
operator will materialize in the factor graph.
Inputs and interfaces
In GraphPPL
, we can feed data and interfaces into the model through the function arguments. For example, if we want to define a model that takes in a vector of observations x
that are all distributed according to a normal distribution with mean 0 and variance 1, we can write:
@model function example(x)
for i in eachindex(x)
x[i] ~ Normal(0, 1)
end
end
Alternatively, we can use the broadcasting syntax from Julia, extended to work with the ~
operator:
@model function example(x)
x .~ Normal(0, 1)
end
Note that interfaces do not need to be random variables; this distinction will be made during model construction. Until a variable is used as an input to a stochastic node, it will be treated as a regular variable. This allows us to write models that take in both data and parameters:
@model function recursive_model(x, depth)
if depth == 0
x ~ Normal(0, 1)
else
x ~ recursive_model(depth = depth - 1)
end
end
Here, x
is treated as a random variable since it is connected to a Normal
node. However, depth
is only used as a hyperparameter to define model structure and is not connected to any stochastic nodes, so it is treated as a regular variable. In this recursive model we also get to see nested models in action: the recursive_model
is used as a submodel of itself. More on this in the Nested Models section.
Models defined with the @model
macro have no positional arguments. All the arguments are converted to the keyword arguments. This also means that the models cannot use multiple dispatch, since multiple dispatch on keyword arguments is not supported in Julia.
Bayesian Coin-Toss (Beta-Bernoulli model)
Now that we have a grasp on the basic syntax and semantics of GraphPPL
, let's try to write a simple coin-toss model a.k.a Beta-Bernoulli model. We will start with a model that takes in a series of observations x
that are i.i.d. distributed according to a Bernoulli distribution with parameter θ
, where we put a Beta prior on θ
:
@model function coin_toss(x)
θ ~ Beta(1, 1)
x .~ Bernoulli(θ)
end
Instantiating the model
To instantiate the model we need to pass the data x
. We can do that with the GraphPPL.create_model
function in combination with GraphPPL.datalabel
.
data_for_x = [ 1.0, 0.0, 0.0, 1.0 ]
model = GraphPPL.create_model(coin_toss()) do model, context
return (;
# This expression creates data handle for `x` in the model using the `xdata` as the underlying collection
x = GraphPPL.datalabel(model, context, GraphPPL.NodeCreationOptions(kind = GraphPPL.VariableKindData), :x, data_for_x)
)
end
Visualizing the model
GraphPPL
exports a simple visualization function that can be used to visualize the factor graph of a model. This requires the GraphPlot
and Cairo
packages to be installed. To visualize the coin_toss
model, we can run:
using GraphPlot, Cairo
GraphPlot.gplot(model)
Syntax Guide
To dive into the specifics of the syntax of the model specification within GraphPPL.jl
read the Syntax Guide.