Trace algorithms build on sites, traces and policies; numerical methods require explicit block capabilities. Custom population programs compose weighting, selection, Markov moves and transport under scoped ownership contracts; see Inference Programs. A common result interface does not remove each method's model requirements.
You can extend the system at each level. From least to most involved:
Implement dist/Distribution — see
distributions.
A group of latents with a log density and its gradient, for HMC — see the blocks notebook and, for compiled densities, foerster-raster.
A policy decides every site of a run. foerster.runtime.trace/policy builds one
from options, and the particle methods take it as :policy (all but BBVI,
whose guide decides the latent sites):
| Option | Effect |
|---|---|
:constraints {address value} | fix those sites; their density enters the weight (conditioning) |
:interventions {selector-or-address transform} | replace a site's mechanism: {:do v} and {:policy (fn [choices])} fix the value and score nothing; {:dist d} and {:shift δ} replace the law, and the site is then decided and scored under it like any other |
:draw (fn [savepoint old-entry]) | a custom proposal for latent sites: return nil (not my site), {:value v :log-proposal lq}, or {:value v :symmetric? true}; the weight gets log p(v) − lq |
:keep? | reuse the values of the replayed trace, rescored |
:noise {address u} | counterfactual noise for mechanisms |
:strict? true with :noise | refuse a counterfactual site without compatible factual noise |
:init? | start sites at their :init option (a chain's first state) |
:out-of-support :keep | with :keep?, keep a value outside its site's new support, scored −∞ (Gen's update) |
:simulate-observed? | observed sites draw a fresh value instead of scoring the data (predictive runs) |
:temperature β | observations and factors count β·log p (the tempered target p(x)·L(x)^β) |
:else | the policy for every other site (default: its payload) |
A policy that only fixes sites (:constraints) or intervenes on them keeps
SMC's evidence :unbiased (foerster.contract): PMMH relies on this for
its θ. A policy with any other option is the caller's responsibility, and
the evidence is :unverified.
;; a proposal that knows where the data is
(infer/importance-sampling (model) 1000
{:proposal (proposal/only #{:mu} (proposal/distribution (dist/normal 7.0 1.0)))})
A :proposal (foerster.proposal, algorithms)
is chosen from outside the program, per inference call; it becomes the
policy's :draw, which also takes a raw (fn [savepoint old-entry]) for
internal policies. A proposal the program itself computes — from its data, or
an amortized guide — is a sample site's :proposal option instead
(language); it is weighed the same way, and a :draw
that answers for a site takes precedence over it.
Inference programs expose scoped initialization, target
advancement, resampling, invariant moves and final projection over complete
traces or persistent numerical values. Their operations compose inside ordinary
spin control flow. They keep population mass separate from selection weights
and release owned sessions before returning.
(trace/mh-step trace opts) is one Metropolis–Hastings move over a trace;
(trace/mh-chain trace n opts) runs n of them. Both are CPS operations.
| Option | |
|---|---|
:select (fn [trace iteration]) | {:targets #{address} :log-selection (fn [trace])} — which sites to move and the log probability of selecting them (default: one latent uniformly) |
:propose | a foerster.proposal for the target (default proposal/prior; proposal/random-walk for a random walk); the reverse move is scored with its log-q, so asymmetric proposals are exact |
:step | for mh-chain: a step function replacing mh-step (e.g. hmc/within-gibbs) |
:on-step | for mh-chain: (fn [{:keys [trace accepted?]}]) called after every step |
:first-iteration | for mh-chain: the number of its first move; a move's randomness is keyed by its number. Without it the numbers continue from a counter the trace's world keeps, so calling mh-chain again on the same trace draws afresh |
A proposal program — (gfi/mh-proposal trace proposal & args) is
Gen's metropolis_hastings(trace, proposal, args): proposal is
(fn [choices & args]) returning a spin whose sample sites are named by
the model addresses it proposes, any number of them, jointly and correlated
as the program likes. The move runs the proposal forward, updates the model
with what it drew, and scores the reverse move by assessing the proposal on
the replaced values under the new choices; accepted on
w(update) + log q(old | new) − log q(new | old). The proposal must be able
to propose the reverse move.
(defn along-the-ridge [{:keys [x y]}] ; x and y move together
(spin (let [x' (sample (dist/normal x 0.6) :id :x)]
(sample (dist/normal (- y (- x' x)) 0.15) :id :y))))
((gfi/mh-proposal trace along-the-ridge) on-step on-error) ; {:trace :accepted?}
For moves that are not "redraw these sites" — scaling, swapping, splitting —
use involutive MCMC (foerster.moves.involutive/step) and the generative function
interface (foerster.runtime.gfi), shown in the
programmable inference notebook.
An involution may change the model's dimension (reversible jump): it sets
the new sites through :choices and names under :removed the sites whose
values it moved into the auxiliary variables, as in a split of one
component into two and the matching merge
(test/…/reversible_jump_test.clj).
A proposal (foerster.proposal) draws a latent site's value. from-fn
makes one from a function of the site:
(def near-data
(proposal/from-fn
(fn [{:keys [address dist old trace]}] ; old: the value being moved, if any
(when (= :mu address) ; nil: the site's own law decides
(let [q (dist/normal 7.0 1.0) v (dist/draw q)]
{:value v :log-q (dist/logpdf q v)})))
;; optional: the density of proposing v, which an MH move scores its
;; reverse with
(fn [_ v] (dist/logpdf (dist/normal 7.0 1.0) v))))
(infer/smc-infer (model) 100 {:proposal near-data})
(:trace site) is a delay of the particle's choices so far. Implementing
PProposal (propose, log-q, verified?) directly works as well;
verified? says whether log-q is the density propose draws from by
construction, which decides whether SMC's evidence is :unbiased.
A proposal is not a Markov kernel. Markov-chain descriptions implement
PMarkovKernel (transition-id) and are compiled by
foerster.moves.transition. They are combined with k/cycle and k/mixture
(algorithms).
k/kernel makes a Markov kernel of your own from a step over traces. It
composes with the built-in kernels, runs as chains (kernel-infer), and
rejuvenates SMC (:rejuvenate {:kernel k}):
(def mine
(k/kernel 1000
(fn [t context] ; context: target, replay frontier,
(trace/mh-step t (assoc context ; ownership, :iteration
:select my-select
:propose (proposal/random-walk 0.3))))
{:retains? true :samples :all :burn 100}))
(infer/kernel-infer (model) (k/cycle 500 [mine (k/single-site-mh-kernel 1)]) 4)
Passing context on to mh-step keeps the target, the replay frontier
(SMC's moves stop at the particle's barrier) and the ownership options;
declare :retains? true when it does, and :tempering? true when the step
targets a temperature below one. Leaving the target invariant is your
obligation: a result whose moves include your kernel says so
(:unverified evidence).
A steered program (foerster.inference.steer/model) records each step's state and its
reward in the trace, and foerster.amortized.learn reads them back as training data:
(learn/trajectories measure) gives every particle's
{:states :reward :weight :log-weight}, and (learn/draws measure n)
resamples n of them by weight into unweighted draws from the target. A
value estimate trained on them is the next run's :value; a proposal
trained on them returns its steps as (steer/weighted state log-w), with
log-w = log p − log q of the step, so the target does not change
(algorithms). Training
itself is outside foerster.
Every algorithm here is a handler of spindel savepoints: open a session on a
world, install handlers for :inference/choose and :inference/factor, and
resume, fork, copy or abandon the savepoints they receive.
foerster.inference.smc is the reference; the design guide explains the
structure and spindel's savepoints
the primitive.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |