Data, priors, losses and optimisers are all nodes in the graph. This is not a stylistic choice — it is forced by AutoBayes Remark 24.
Sources: original to this vault (design and analysis; no single paper).
Theory (CT-ML wiki): Bayesian Inversion · Statistical Game · Parametric Lens · Variational Free Energy · Lens · Open Model · Gradient Based Learning with Parametric Lenses
The argument from Remark 24
Definition 20’s loss for a pure game with energy and Shannon entropy is
which is not the variational free energy of Proposition 18 — it is missing . The paper’s own fix is to make the prior a factor:
Given a prior distribution , we can turn it into a statistical game by first equipping it with a trivial inversion, then setting , and finally noting that must be . Composing after the resulting yields a composite with .
The prior is not part of the model. It is a separate node you compose with. Once that is true of priors it is true of everything else that contributes a term to the objective.
The four structural factors
| node | arity | edges | learnable? | energy | entropy |
|---|---|---|---|---|---|
DataFactor(ch, v) | 1 | Emitting | no | ||
PriorFactor(ch, b, nlogp) | 1 | Emitting | optional | ||
LossFactor(chs, f) | Absorbing | no | |||
OptimiserFactor(ch, rule) | 1 | Bidirectional | no (holds state) | ||
RelayFactor(a, b) | 2 | Bidirectional | no |
Data is a factor, not a variable
README says “our training data are fixed variables in our factor graph”. The refinement
that falls out of Remark 24 is: data is a DataFactor of arity one attached to a variable.
A variable is a wire and has no content; data is evidence, and evidence contributes to the
objective. A DataFactor is precisely the categorical cup and the
hard clamp of Channels and Polarity — and because it is a factor, it
can be softened (a finite precision, a noisy observation) without changing the graph’s
structure, only the node’s type.
Losses are sinks
A LossFactor supports zero polarities: it absorbs on every channel and emits nothing. It
is a third case beyond unidirectional (one polarity) and bidirectional (two or more), and it
corresponds exactly to Cruttwell et al.’s learning-rate cap — the lens
that terminates a wire (Learning Components as Parametric Lenses §3.3).
Note also that Definition 3.3 makes the label the loss map’s parameter. In a graph the label is just another absorbed channel, and the graph does the bookkeeping. Same statement, less machinery.
A loss factor never tells its input variable anything in belief terms. What a loss “tells” its input is a cotangent, and cotangents travel by the
AbstractGradientCouplingon each edge, not by the message scheduler. This is why the tree schedule prunes messages at loss nodes — see Schedules §“Pruning”.
Optimisers are reparametrisations, and a wire is a variable
Cruttwell et al. present an optimiser as a reparametrisation — a box sitting above the parameter wire, a lens (Learning Components as Parametric Lenses §3.4).
In a factor graph a wire is a variable, so a box above it is a factor. The optimiser
becomes an ordinary node, bidirectional by nature: it emits the current parameter
(rule_get ) and absorbs the update (rule_put ).
This only works because a factor may expose its parameters as channels instead of hiding
them in ps. That is exactly the move AutoBayes Example 3 (VBEM) makes when
“migrates from the parameter space into the wire” (AutoBayes Examples as Factor Graphs).
Parameter exposure is a graph-level decision, not a factor-level one
The same factor can be used with hidden parameters (Lux style,
psthreaded through) or exposed ones (a variable, with an optimiser or a hyperprior attached). Hidden gives you maximum-likelihood; exposed gives you a posterior over weights — i.e. Bayesian deep learning (Example 5). The difference between the two is one edge.
The rules implemented are Cruttwell’s table verbatim:
| rule | get | put |
|---|---|---|
GradientDescent(η) | ||
Momentum(η,γ) | ; | |
Nesterov(η,γ) | as momentum |
Nesterov is the one with a non-trivial get, and it is the reason optimisers must be
lenses rather than functions. “Evaluate the gradient at the look-ahead point” is “the
forward part of the optimiser lens is not the identity”. Asserted in the test suite, because
it is the claim most worth checking.
What this buys
The entire Cruttwell et al. pipeline — model, loss, learning rate, optimiser (Learning Components as Parametric Lenses Figure 4) — becomes one graph with no privileged machinery. A training loop is a schedule over it. There is no separate “optimiser API”, no separate “loss API”, no separate “data loader”: there are factors, and there is a message schedule.
That is also the answer to README’s note that Lenticulum “cannot rely on traditional data
handling like done with MLUtils.jl”. Batching is not a data-loader concern; it is a question of
which DataFactors are attached to the graph this step.
The optimiser-as-factor is half of a larger idea
Making the optimiser a node in the graph is the per-factor case of attaching a graph to the graph — one whose variables are the base graph’s parameters. Its counterpart, a graph over the base graph’s messages, does not exist, and
LenticulumCore.AmortisedInversionis its per-factor case. See The Inferencer and the Optimizer.
Related: The Inferencer and the Optimizer, Factor Graphs, Learning Components as Parametric Lenses, AutoBayes Examples as Factor Graphs, factors