TuringLang / TuringLang/DynamicPPL.jl
VNTs and submodels
Nobody has claimed this yet.
- Dominant language
- Julia
- Stars
- 286
- Forks
- 41
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 34
Description
There is a nasty interaction here:
@model inner() = a ~ Normal()
@model function f()
x ~ to_submodel(inner())
end
model = f() | (@varname(x.a) => 2.0)
OK. In the past, what would happen is that the conditioned values would be stored as Dict(@varname(x.a) => 2.0). When we got to x ~ to_submodel(...), we check whether x was conditioned on. If so, we call tilde_observe!!, if not, we call tilde_assume!!.
tilde_observe!! on submodels used to error. The reason for this is because we want to avoid two scenarios:
- People thinking that they can condition the return value of the submodels, e.g.
model = f() | (@varname(x) => myretval). - People putting the return value of the submodel as a model argument, e.g.
@model function f(x) ... x ~ to_submodel(...).
Fundamentally this is because the return value of the submodel can't be conditioned on.
What's the problem now then?
Well, if conditioned values are stored as VNTs, then the problem is that VarNamedTuple(x = VarNamedTuple(a = 1)) DOES actually contain a value for x. The value for x is the inner VNT.
So when we check whether x is conditioned on, that returns true, and we go down tilde_observe!! and that errors.
In #1238 I changed tilde_observe!! to no longer error. I also put in a patch inside the compiler itself for fixed variables, which have their own code path. That is a hotfix for the current situation, however, it also means that the previous cases where we used to error now run fine. The current behaviour will be that the submodel will be evaluated as if the left-hand side was a completely new variable (i.e. everything is assumed). So, for example
@model function f2(x)
x ~ to_submodel(inner())
end
model = f2(3.0)
will behave exactly the same as model = f().
Fundamentally the reason why we have this problem is that we can't differentiate between the latent variables (which can be conditioned on) and the return value (which can't). There is not enough information to tell whether somebody wanted to condition on x.a (valid), or whether somebody wanted to condition on the return value x (invalid).
In contrast, if we had
retval, latent ~ to_submodel(inner())
then it would be trivial to error if somebody tried to condition on retval, but evaluate the submodel normally if somebody conditioned on latent.x.
I think, then, in the next version of DPPL, it's time to finally do this properly, enforce a separation between the retval and the latent variables, to avoid all this horrendous behaviour which is frankly 1.5 or 2 year old technical debt arising from the to_submodel change.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing to_submodel, tilde_observe!!, and the compiler path for fixed variables described in the issue. Done means submodels clearly separate return values from latent variables, valid latent conditioning still works, and conditioning on or passing the return value is rejected.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- julia
- Domain
- machine-learning
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100