-
Notifications
You must be signed in to change notification settings - Fork 230
Improve maybe_link! for MH sampler
#1582
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Somehow this feels a bit wrong - wouldn't it be better to extract the different proposals from the NamedTuple and link only the variables that should be linked? |
Yep. But isn't this when you should be using |
|
Also tests are now failiing o.O |
Ah crap. My definition is too general and will also catch |
But the docstrings explicitly explain how to set different proposals for different variables without Gibbs, so it should work it seems. I still don't understand what the underlying problem is here but it seems wrong to fail or emit warning here, and it seems suboptimal to link everything. |
|
Ideally, the linking would be more transparent and less confusing. There are so many bugs and hours of debugging... IIRC when moving to AbstractMCMC 2 we discussed that in general it would be good if one could avoid to link the samples only in the beginning and at the end of sampling since it leads to surprising results when one uses the iteration interface. |
I agree! But not clear to me how to go about doing that without additional implementations in DynamicPPL 😕
100% agree.
Wait, you want to NOT only link at beginning and end of sampling? |
Ah, so when we do |
|
Btw, one simple way of allowing the desired behavior is to add a function link!(vi::TypedVarInfo, spl::AbstractSampler)
return link!(vi, spl, Val(getspace(spl)))
end
function link!(vi::TypedVarInfo, spl::AbstractSampler, spaceval::Val)
vns = _getvns(vi, spl)
return _link!(vi.metadata, vi, vns, spaceval)
endCombined with function maybe_link!(varinfo, sampler, proposal::NamedTuple{(), Tuple{}})
return varinfo
end
function maybe_link!(
varinfo,
sampler,
proposal::NamedTuple{Syms, Tuple{<:AdvancedMH.RandomWalkProposal}}
) where {Syms}
return link!(varinfo, sampler, Val(Syms))
end
function maybe_link!(varinfo, sampler, proposal::NamedTuple)
maybe_link!(varinfo, sampler, first(proposal))
return maybe_link!(varinfo, sampler, Iterators.tail(proposal))
endwe get what we want. |
Yep, maybe it would be better to just apply the bijective mapping in Lines 251 to 263 in 2eab753
Line 362 in 2eab753
dist_val_tuple (in Line 351 in 2eab753
Line 378 in 2eab753
Line 389 in 2eab753
As I said above, I am always confused by the linking. In general, IMO it is problematic if the samples that are returned in the intermediate steps return the transformed samples. I am not completely sure if this happens here but it is one reason for why I think the current design is a bit suboptimal. Also, if I understand correctly, the linking only makes it easier to set and retrieve the transformed samples (which could be moved to the other functions mentioned above) but when the model is executed the variables still have to be converted to the original anyway, so I wonder if and to what extent it actually improves performance. Maybe generally it would be cleaner to always store the samples in the original space in the VarInfo object and to map them on the fly if needed (or at least not modify the VarInfo object in-place since this e.g. makes it impossible to implement bijections from a submanifold in a higher dimensional space to some lower dimensional space where the dimensions and potentially the type changes). |
I disagree with this right now, simply because it diverges from the current approach of the rest of the samplers. BUT I do agree that the current approach is suboptimal; just feels like we should address that head on rather than approach it differently for different samplers.
I see what you're saying and I remember the conversation we had about potential issues this causes when using the iterator interface for AbstractMCMC.jl. And yes, it does happen here. We only
Currently we do: for i = 1:num_samples
# Propose in unconstrained space
ϕ_new = step(ϕ)
# Transform to constrained
θ_new = f(ϕ)
# Evaluate logpdf
logpdf(model, θ_new)
# Update sample
ϕ = θ_new
endbut if we made it so that it should be transformed on the fly, we'd get for i = 1:num_samples
# ADDITIONAL STEP
# Transform previous sample to unconstrained
ϕ = f⁻¹(θ)
# Propose in unconstrained space
ϕ_new = step(ϕ)
# Transform to constrained
θ_new = f(ϕ)
# Evaluate logpdf
logpdf(model, θ_new)
# Update sample
ϕ = θ_new
endSo it def helps performance-wise in the case where the linking is actual a significant part of the computation.
It does have a performance cost though, as mentioned above. But I do think it's an important point, and for certain bijectors we could possibly even gain some performance by doing what you mention. With all this being said, I do agree that the implicit transformation is both:
I'm also of the opinion that we should exploit Bijectors.jl more. This would also make it possible to construct completely arbitrary transformations, use different bijectors than default if that's wanted, etc., by simply calling |
|
Also, we should take this discussion to an issue over at DPPL 👍 In addition, open an issue regarding how MH is a bit disingenuous when it comes to what it claims and what it can actually do wrt. unconstrained variables. |
The unconstrained sample could just be part of the state. But whatever, I agree that the general design should be discussed (and possibly changed) separately and not in this PR 👍 |
Pull Request Test Coverage Report for Build 736531705
💛 - Coveralls |
Codecov Report
@@ Coverage Diff @@
## master #1582 +/- ##
==========================================
+ Coverage 78.08% 78.16% +0.07%
==========================================
Files 23 23
Lines 1424 1429 +5
==========================================
+ Hits 1112 1117 +5
Misses 312 312
Continue to review full report at Codecov.
|
devmotion
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good but it seems it doesn't handle the case where we use not only random walk proposals in the NamedTuple, does it?
And I wonder if we should add specific tests or it's sufficient that the tests don't fail anymore?
Correct; IMO we should fix the general case through TuringLang/DynamicPPL.jl#231.
Well, technically the tests to catch this were already present 😅 Issue was that they only did so every now and then, depending on the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, given that tests pass. Can you also update the version number?
Makes it possible to only link/invlink a subset of the space of a sampler *for static models only* (as there's no equivalent for `UntypedVarInfo`). Is non-breaking since this is just adding an intermediate method to an existing implementation, giving increased flexbility. Probably don't want to encourage use of this, but it can be useful in cases such as the MH sampler TuringLang/Turing.jl#1582 (comment).
Fixes issues as discussed in #1579 regarding failing tests by making
maybe_link!also link ifproposalis aNamedTupleof proposals which all should be linked. If only one of the proposals does not require linking, we don't link.It might be better to not dispatch here, but instead check at runtime and warn the user that there's a mix between proposals that are by default linked but don't link any?