fix: correct biased pymc HMC sampling (target_accept) - #1908
Conversation
The hmc parametrizations of test_c2st_pymc_sampler_on_Gaussian were failing in CD (c2st ~0.62). Root cause is PyMC's HamiltonianMC default target_accept=0.65, which mixes poorly on this peaked target regardless of seed, warmup, or number of draws (NUTS is unaffected). Expose seed and target_accept on PyMCSampler and set target_accept=0.95 (c2st ~0.51-0.54 across seeds and chain counts) plus a fixed seed in the test for reproducibility.
MCMCPosterior._pymc_mcmc left PyMC's HamiltonianMC at its default target_accept=0.65, which produces biased hmc_pymc posteriors (c2st ~0.62 on a Gaussian). Default it to 0.9 for the HMC step, leaving nuts_pymc and slice_pymc unchanged. hmc_pymc now samples accurately (c2st ~0.51).
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1908 +/- ##
==========================================
+ Coverage 87.81% 87.94% +0.13%
==========================================
Files 143 143
Lines 13387 13657 +270
==========================================
+ Hits 11756 12011 +255
- Misses 1631 1646 +15
Flags with carried forward coverage won't be shown. Click here to find out more.
|
|
thanks for the review @dgedon when addressing your comments I noticed there was an inconsistency in the default values (0.9 vs 0.95 in the tests), that's fixed now. And I noticed it would be better to have the new |
dgedon
left a comment
There was a problem hiding this comment.
Maybe update the original description of the PR, so it reflects the latest changes, e.g. the 0.9 default.
I approve for now since all remaining comments are smaller details and not functional really. In that way you can merge easier @janfb
Addresses review feedback from @dgedon on #1908: - add `validate_target_accept()` to `sbi/utils/typechecks.py` and use it at all four validation sites, replacing the repeated `validate_float_range` block. The helper lives in `typechecks.py` because it has no sbi-domain imports, so `MCMCPosterior` and `MCMCPosteriorParameters` can use it without pulling in the optional `pymc` dependency. - `MCMCPosterior.sample()` now validates only the caller's argument; the fallback to `self.target_accept` is already validated in `__init__`. - name the HMC default `_DEFAULT_HMC_TARGET_ACCEPT` in `pymc_wrapper.py` instead of hardcoding `0.9`, with the rationale on the constant. - state explicitly in all docstrings that `target_accept` is used only by `hmc_pymc` and `nuts_pymc`, and ignored by `slice_pymc`, the Pyro samplers and the numpy slice samplers. - revert unrelated whitespace and variable churn in the Pyro/PyMC branches.
Validate the type and (0, 1) range directly instead of delegating to validate_float_range. Error types and messages are unchanged.
Follow-up to #1894 because CD is failing again. Once the lockfile fix let the pymc
tests run again in CD,
test_c2st_pymc_sampler_on_Gaussianfailed on thehmcparametrizations with c2st around 0.62.
However, it's not flaky: pymc's
HamiltonianMCdefaults totarget_accept=0.65,which mixes poorly on this peaked Gaussian and biases the samples, regardless of
seed, warmup, or number of draws. NUTS works fine.
To fix this:
seedandtarget_acceptonPyMCSamplerhmc_pymctotarget_accept=0.9(_DEFAULT_HMC_TARGET_ACCEPTinpymc_wrapper.py) to get accurate HMC posteriors (c2st ~0.51 vs ~0.62);nuts_pymckeeps PyMC's backend default andslice_pymcis unchangedMCMCPosterior(target_accept=...),MCMCPosterior.sample(target_accept=...)andMCMCPosteriorParameters.target_accept, validated to be in(0, 1)0.9default rather than passing its own value
target_acceptis PyMC-only:pymc.Slicedoes not take it, so it is forwardedonly for the
hmc/nutssteps, and the Pyro samplers (hmc_pyro,nuts_pyro)ignore it entirely.
Since this silently changes a default, there is a CHANGELOG entry under
v0.26.1→ Bug Fixes.Note: the c2st test is marked
slow, I will run the CD on this branch viaworkflow dispatch.