probcomp / GenExperimental.jl

Featherweight embedded probabilistic programming language and compositional inference programming library
MIT License
17 stars 2 forks source link

Record sub-traces in a compositional generator trace #58

Closed marcoct closed 6 years ago

marcoct commented 6 years ago

Currently, the only compositional generator is a ProbabilisticProgram, and for these the only sub-regenerators are currently AtomicGenerators (i.e. "probabilistic modules"), and the value itself is stored in the program's trace instead of the (AtomicTrace) sub-trace. Note that ProbabilisticProgram can easily be wrapped in to a AtomicGenerator.

Although "probabilistic modules" are appealing because they have transient sub-traces, which are completely hidden from the outside world, it may be useful to have persistent sub-traces. This seems useful for collapsed generators that can maintain sufficient statistics (e.g. CRPState and NIGNState). Currently this state is manually added to the trace using a separate tag instruction. It would be better to have a new generator type whose trace type stores the sufficient statistics alongside the actual values. We would then need to actually store the sub-traces of sub-generators, and not just the value when it exists as is done currently.

Persistent sub-traces also allow for more flexible inference programming that breaks through the procedure abstraction boundary, although I'm not sure this is actually a good thing or not. Perhaps there is a form of 'disciplined probabilistic programming' in which model programs are written so that the model procedural abstraction can be respected by the inference program. This seems possible in some cases, and boils down to the model program exposing all latent variables of domain interest or inference interest at the top level.

marcoct commented 6 years ago

This applies to probabilistic programs as well as generator networks.

marcoct commented 6 years ago

Potential solutions should be evaluated on at least a DPMM example with CRP and NIGN, and using both collapsed and uncollapsed versions of the sub-generators.

marcoct commented 6 years ago

A CRP joint generator type (CRPJointGenerator) with a custom trace type (CRPJointTrace) that exposes the cluster assignments as addressable values, and internally maintains sufficient statistics, was added in this commit.

marcoct commented 6 years ago

Also relevant: https://github.com/probcomp/Gen.jl/issues/59

marcoct commented 6 years ago

Current plan, hopefully to be implemented this weekend:

marcoct commented 6 years ago

This is still the plan. The only change since that post was written is that now the CRP and NIGN joint generators accept arbitrary keys as addresses (instead of the strict address space of increasing integer addresses that I was using before). This greatly simplifies the address aliases for the DPMM, without violating the semantics of generate!.

There is one minor or subtle change in semantics: I was thinking that the arguments to a generator should somehow demarcate the address space of its trace. Now, with the new CRP and NIGN joint generators, the address space is all valid keys, and the argument defines which keys should be simulated during that call to generate!. For example, I can constrain address foo in sub-trace st and then later call generate!(CRPJointGenerator(Set(['bar', 123]), alpha), st), and the st will then contain calues for addresses bar and 123 and the value for foo will be retained. A subsequent call tho generate!(CRPJointGenerator(Set(), alpha), st) would then cause the values for bar and 123 to be removed from the sub-trace but the value for foo would persist. The score is always based on the constrained address not the newly generated ones (I haven't implemented propose! for these generators yet).

marcoct commented 6 years ago

Wrapping up a re-rewrite to use sub-traces, and not use aliases.

Address aliasing can be introduced later as a separate nice-to-have feature. Directly applying directives like constrain! to sub-traces, instead of simply recording them for replay later, results in a simpler mental model of what state a trace is in: e.g. a trace is a record of the program's execution, and it doesn't require a separate model for thinking about the pre-generate! (before directives have been forwarded to subtraces) and post-generate! state of a trace (after directives have been forwarded).

Propagating directives immediately to subtraces also more closely matches the approach taken in Metaprob. However, it is different from Metaprob in that sub-traces that are not AtomicTraces need to be manually added to the trace with set_subtrace!, before addresses under that sub-trace can be touched using the trace directives like constrain!. We require manually adding sub-traces because we allow custom trace type and we don't currently automatically infer what type a sub-trace will be. If there is a hierarchy of compound procedure applications, the user needs to add all of them before they can constrain the lowest on in the call-stack.

Note that during a call to generate! all necessary subtraces will be added to the trace, and do not need to be added again later in order to be constrained.

I toyed with the idea of automatically generating the needed hierarchy of a default trace types (ProgramTrace) during directive calls, and then replacing them with the actual trace types (and copying over constraints, etc.) at run-time, but this seems too complex for this early stage, and against the working philosophy of Gen, which is, I think:

"The user should have the ability to customize the system at a low-level, and should expect to do a lot of low-level programming at first. Gradually, abstractions will be built into the system, but only after lower-level APIs are used, and the need for certain abstraction is clear"

marcoct commented 6 years ago

The new ProgramTrace implementation uses subtraces.