v0.37.0
DynamicPPL v0.37.0
DynamicPPL 0.37 comes with a substantial reworking of its internals.
Fundamentally, there is no change to the actual modelling syntax: if you are a Turing.jl user, for example, this release will not affect you too much (apart from the changes to @addlogprob!
).
Any such changes will be covered separately in the Turing.jl changelog when a release is made.
However, if you are a package developer or someone who uses DynamicPPL's functionality directly, you will notice a number of changes.
To avoid overwhelming the reader, we begin by listing the most important, user-facing changes, before explaining the changes to the internals in more detail.
Note that virtually all changes listed here are breaking.
Public-facing changes
Submodel macro
The @submodel
macro is fully removed; please use to_submodel
instead.
DynamicPPL.TestUtils.AD.run_ad
The three keyword arguments, test
, reference_backend
, and expected_value_and_grad
have been merged into a single test
keyword argument.
Please see the API documentation for more details.
(The old test=true
and test=false
values are still valid, and you only need to adjust the invocation if you were explicitly passing the reference_backend
or expected_value_and_grad
arguments.)
There is now also an rng
keyword argument to help seed parameter generation.
Finally, instead of specifying value_atol
and grad_atol
, you can now specify atol
and rtol
which are used for both value and gradient.
Their semantics are the same as in Julia's isapprox
; two values are equal if they satisfy either atol
or rtol
.
DynamicPPL.TestUtils.check_model
You now need to explicitly pass a VarInfo
argument to check_model
and check_model_and_trace
.
Previously, these functions would generate a new VarInfo for you (using an optionally provided rng
).
Evaluating model log-probabilities in more detail
Previously, during evaluation of a model, DynamicPPL only had the capability to store a single log probability (logp
) field.
DefaultContext
, PriorContext
, and LikelihoodContext
were used to control what this field represented: they would accumulate the log joint, log prior, or log likelihood, respectively.
In this version, we have overhauled this quite substantially.
The technical details of exactly how this is done is covered in the 'Accumulators' section below, but the upshot is that the log prior, log likelihood, and log Jacobian terms (for any linked variables) are separately tracked.
Specifically, you will want to use the following functions to access these log probabilities:
getlogprior(varinfo)
to get the log prior. Note: This version introduces new, more consistent behaviour for this function, in that it always returns the log-prior of the values in the original, untransformed space, even if thevarinfo
has been linked.getloglikelihood(varinfo)
to get the log likelihood.getlogjoint(varinfo)
to get the log joint probability. Note: Similar togetlogprior
, this function now always returns the log joint of the values in the original, untransformed space, even if thevarinfo
has been linked.
If you are using linked VarInfos (e.g. if you are writing a sampler), you may find that you need to obtain the log probability of the variables in the transformed space.
To this end, you can use:
getlogjac(varinfo)
to get the log Jacobian of the link transforms for any linked variables.getlogprior_internal(varinfo)
to get the log prior of the variables in the transformed space.getlogjoint_internal(varinfo)
to get the log joint probability of the variables in the transformed space.
Since transformations only apply to random variables, the likelihood is unaffected by linking.
Removal of PriorContext
and LikelihoodContext
Following on from the above, a number of DynamicPPL's contexts have been removed, most notably PriorContext
and LikelihoodContext
.
Although these are not the only exported contexts, we consider unlikely that anyone was using other contexts manually: if you have a question about contexts other than these, please continue reading the 'Internals' section below.
If you were evaluating a model with PriorContext
, you can now just evaluate it with DefaultContext
, and instead of calling getlogp(varinfo)
, you can call getlogprior(varinfo)
(and similarly for the likelihood).
If you were constructing a LogDensityFunction
with PriorContext
, you can now stick to DefaultContext
.
LogDensityFunction
now has an extra field, called getlogdensity
, which represents a function that takes a VarInfo
and returns the log density you want.
Thus, if you pass getlogprior_internal
as the value of this parameter, you will get the same behaviour as with PriorContext
.
(You should consider whether your use case needs the log prior in the transformed space, or the original space, and use (respectively) getlogprior_internal
or getlogprior
as needed.)
The other case where one might use PriorContext
was to use @addlogprob!
to add to the log prior.
Previously, this was accomplished by manually checking __context__ isa DynamicPPL.PriorContext
.
Now, you can write @addlogprob (; logprior=x, loglikelihood=y)
to add x
to the log-prior and y
to the log-likelihood.
Removal of order
and num_produce
The VarInfo
type used to carry with it:
num_produce
, an integer which recorded the number of observe tilde-statements that had been evaluated so far; andorder
, an integer perVarName
which recorded the value ofnum_produce
at the time that the variable was seen.
These fields were used in particle samplers in Turing.jl.
In DynamicPPL 0.37, these fields and the associated functions have been removed:
get_num_produce
set_num_produce!!
reset_num_produce!!
increment_num_produce!!
set_retained_vns_del!
setorder!!
Because this is one of the more arcane features of DynamicPPL, some extra explanation is warranted.
num_produce
and order
, along with the del
flag in VarInfo
, were used to control whether new values for variables were sampled during model execution.
For example, the particle Gibbs method has a reference particle, for which variables are never resampled.
However, if the reference particle is forked (i.e., if the reference particle is selected by a resampling step multiple times and thereby copied), then the variables that have not yet been evaluated must be sampled anew to ensure that the new particle is independent of the reference particle.
Previousy, this was accomplished by setting the del
flag in the VarInfo
object for all variables with order
greater or equal to than num_produce
.
Note that setting the del
flag does not itself trigger a new value to be sampled; rather, it indicates that a new value should be sampled if the variable is encountered again.
This Turing.jl PR changes the implementation to set the del
flag for all variables in the VarInfo
.
Since the del
flag only makes a difference when encountering a variable, this approach is entirely equivalent as long as the same variable is not seen multiple times in the model.
The interested reader is referred to that PR for more details.
Internals
Accumulators
This release overhauls how VarInfo objects track variables such as the log joint probability. The new approach is to use what we call accumulators: Objects that the VarInfo carries on it that may change their state at each tilde_assume!!
and tilde_observe!!
call based on the value of the variable in question. They replace both variables that were previously hard-coded in the VarInfo
object (logp
and num_produce
) and some contexts. This brings with it a number of breaking changes:
PriorContext
andLikelihoodContext
no longer exist. By default, aVarInfo
tracks both the log prior and the log likelihood separately, and they can be accessed withgetlogprior
andgetloglikelihood
. If you want to execute a model while only accumulating one of the two (to save clock cycles), you can do so by creating aVarInfo
that only has one accumulator in it, e.g.varinfo = setaccs!!(varinfo, (LogPriorAccumulator(),))
.MiniBatchContext
does not exist anymore. It can be replaced by creating and using a custom accumulator that replaces the defaultLikelihoodContext
. We may introduce such an accumulator in DynamicPPL in the future, but for now you'll need to do it yourself.tilde_observe
andobserve
have been removed.tilde_observe!!
still exists, and any contexts should modify its behaviour. We may further rework the call stack undertilde_observe!!
in the near future.tilde_assume
no longer returns the log density of the current assumption as its second return value. We may further rework thetilde_assume!!
call stack as well.- For literal observation statements like
0.0 ~ Normal(blahblah)
we used to calltilde_observe!!
without thevn
argument. This method no longer exists. Rather we calltilde_observe!!
withvn
set tonothing
. @addlogprob!
now always adds to the log likelihood. Previously it added to the log probability that the execution context specified, e.g. the log prior when usingPriorContext
.getlogp
now returns aNamedTuple
with keyslogprior
andloglikelihood
. If you want the log joint probability, which is whatgetlogp
used to return, usegetlogjoint
.- Correspondingly
setlogp!!
andacclogp!!
should now be called with aNamedTuple
with keyslogprior
andloglikelihood
. Theacclogp!!
method with a single scalar value has been deprecated and falls back onaccloglikelihood!!
, and the single scalar version ofsetlogp!!
has been removed. Corresponding setter/accumulator functions exist for the log prior as well.
Evaluation contexts
Historically, evaluating a DynamicPPL model has required three arguments: a model, some kind of VarInfo, and a context.
It's less known, though, that since DynamicPPL 0.14.0 the model itself actually contains a context as well.
This version therefore excises the context argument, and instead uses model.context
as the evaluation context.
The upshot of this is that many functions that previously took a context argument now no longer do.
There were very few such functions where the context argument was actually used (most of them simply took DefaultContext()
as the default value).
evaluate!!(model, varinfo, ext_context)
is removed, and broadly speaking you should replace calls to that with new_model = contextualize(model, ext_context); evaluate!!(new_model, varinfo)
.
If the 'external context' ext_context
is a parent context, then you should wrap model.context
appropriately to ensure that its information content is not lost.
If, on the other hand, ext_context
is a DefaultContext
, then you can just drop the argument entirely.
To aid with this process, contextualize
is now exported from DynamicPPL.
The main situation where one did want to specify an additional evaluation context was when that context was a SamplingContext
.
Doing this would allow you to run the model and sample fresh values, instead of just using the values that existed in the VarInfo object.
Thus, this release also introduces the unexported function evaluate_and_sample!!
.
Essentially, evaluate_and_sample!!(rng, model, varinfo, sampler)
is a drop-in replacement for evaluate!!(model, varinfo, SamplingContext(rng, sampler))
.
Do note that this is an internal method, and its name or semantics are liable to change in the future without warning.
There are many methods that no longer take a context argument, and listing them all would be too much.
However, here are the more user-facing ones:
LogDensityFunction
no longer has a context field (or type parameter)DynamicPPL.TestUtils.AD.run_ad
no longer uses a context (and the returnedADResult
object no longer has a context field)VarInfo(rng, model, sampler)
and other VarInfo constructors / functions that made VarInfos (e.g.typed_varinfo
) from a model(::Model)(args...)
: specifically, this now only takesrng
andvarinfo
arguments (with both being optional)- If you are using the
__context__
special variable inside a model, you will now have to use__model__.context
instead
And a couple of more internal changes:
- Just like
evaluate!!
, the other functions_evaluate!!
,evaluate_threadsafe!!
, andevaluate_threadunsafe!!
now no longer accept context arguments evaluate!!
no longer takes rng and sampler (if you used this, you should useevaluate_and_sample!!
instead, or construct your ownSamplingContext
)- The model evaluation function,
model.f
for somemodel::Model
, no longer takes a context as an argument - The internal representation and API dealing with submodels (i.e.,
ReturnedModelWrapper
,Sampleable
,should_auto_prefix
,is_rhs_model
) has been simplified. If you need to check whether something is a submodel, just usex isa DynamicPPL.Submodel
. Note that the public API i.e.to_submodel
remains completely untouched.
Merged pull requests:
- Release v0.37 (#901) (@mhauru)
- Accumulators stage 2 (#925) (@mhauru)
- VariableOrderAccumulator (#940) (@mhauru)
- DebugAccumulator (plus tiny bits and pieces) (#976) (@penelopeysm)
- issue 737 and issue 789 (#977) (@mosesmakola)
InitContext
, part 2 - Movehasvalue
andgetvalue
to AbstractPPL; enforce key type ofAbstractDict
(#980) (@penelopeysm)- Implement more consistent tracking of logp components via
LogJacobianAccumulator
(#998) (@penelopeysm) - Accumulator miscellanea: Subset, merge, acclogp, and LogProbAccumulator (#999) (@mhauru)
- Fix behaviour of
set_retained_vns_del!
fornum_produce == 0
(#1000) (@penelopeysm) - Remove VariableOrderAccumulator and subset and merge on accs (#1005) (@mhauru)
- Remove DataStructures docs dep (#1007) (@penelopeysm)
- Make
DynamicPPL.TestUtils.run_ad
return both the primal and gradient benchmarks (#1009) (@penelopeysm) resetaccs!!
before evaluating (#1013) (@penelopeysm)- move mutable struct outside loop in test (#1014) (@penelopeysm)
- Fix stack overflow in
make_chain_from_prior
(#1020) (@penelopeysm)
Closed issues:
- Improve
@addlogprob!
(#390) TypedVarInfo
failing for certain models over empty vectors (#428)- Conditioning with Turing Chains
name_map
(#478) - Broken
init
forMatrixDistribution
andCholeskyVariate
with multiple samples (#493) - get log-density of prior components (#662)
- Add default implementations of
assume
anddot_assume
(#682) - Track
:=
variables in resulting chains (#693) - These tests should be in AbstractPPL (if they aren't already) (#737)
- Fix
truncated
invocations (#789) - key type of SimpleVarInfo is underfined (#791)
- Combine Condition and FixedContext? (#894)
- Does
model.f
really need to take a context? (#951) - move mutable struct definition outside of loop (#954)
run_ad
needs rng argument (#962)run_ad
should test gradients using elementwise rtol, instead of atol (#963)- Convert
DebugContext
to accumulator (#974) logjac
accumulator (#992)- accumulator docstring is out of date, needs to mention
copy
(#996)