Adam scale adapter implementation - #132
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #132 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 11 11
Lines 620 661 +41
=========================================
+ Hits 620 661 +41 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
matt-graham
left a comment
There was a problem hiding this comment.
Thanks @jennajiali - this looks all good to me. I've added one point asking for clarification about a comment in the documentation for scale_adapter about passing through algorithmic parameters but otherwise I think it's ready to merge.
Fix inaccurate docstring that the wrapper only exposes learning_rate for the Adam adapter. In fact, '...' passes through to the underlying adapter constructor, so any of its arguments can be set via the wrapper. Addresses review comment on UCL#132.
…ter, and update the manual file
… adam-scale-adapter
|
Thanks @matt-graham for the review – I have updated the |
matt-graham
left a comment
There was a problem hiding this comment.
Thanks @jennajiali this looks all good to me now, will merge this in!
Summary
Adds a third scale-adaptation algorithm to
rmcmc, alongside the existing Robbins–Monro (stochastic_approximation) and Nesterov dual-averaging (dual_averaging) schemes. The new adapter applies the Adam optimizer of Kingma & Ba (2014) to the log of the proposal scale, using the acceptance-rate residualtarget_accept_prob accept_probas the (stochastic) gradient — the same gradient signal already used by the two existing scale adapters, plugged into the Adam update rule instead of a power-law schedule.The design follows the C++ implementation in the
walnutslibrary, including itslearning_rate / t^learn_rate_decayschedule needed to match the stability of dual averaging (per Bob Carpenter's note on NUTS improvements).Public API
Two-level exposure as requested in #130:
High-level wrapper —
scale_adapter(algorithm = "adam", ...)accepts the commoninitial_scale/target_accept_probarguments plus the single Adam tuning knob users actually reach for:Low-level function —
adam_scale_adapter()exposes all Adam hyperparameters for advanced users:Defaults and rationale
learning_rate0.051e-3is too slow to hit the shared 2000-iteration /tol = 1e-2convergence test used by all scale adapters.0.05gives fast, stable coercion in typical MCMC warm-up lengths. The docstring notes that1e-3matches the original paper.beta_10.9beta_20.999epsilon1e-8learn_rate_decay0.50) oscillates.Algorithm
On each
update, witht = sample_index:β₁ᵗandβ₂ᵗare maintained incrementally asbeta_1_pow,beta_2_powrather than recomputed each step, matching walnuts and avoiding.Machine$double.xminunderflow for long chains.Sign convention
grad = target − observedcombined withlog_scale −= η · m̂ / (√v̂ + ε)reproduces the "accept_prob > target ⇒ scale increases" behaviour of the other two scale adapters. This is verified by both the directional and convergence tests in the sweep below. An inline comment inadam_scale_adapterflags the sign for future maintainers.Changes
R/adaptation.Rscale_adapter()roxygen:"adam"bullet under@param algorithm.@param ...to document the two-level exposure design.[adam_scale_adapter()]to@seealso.@references.adam = adam_scale_adapterto theswitch()inscale_adapter().adam_scale_adapter()function (roxygen block + implementation) placed immediately afterdual_averaging_scale_adapter.state():log_scale,m,v(bothbeta_*_powkept internal — they're implementation detail and would only add noise towarm_up_statisticswhentrace_warm_up = TRUE).initialize()resets all Adam state so the same adapter object can be re-initialised mid-run (used in the tests).finalize = NULL, matching walnuts and the SA adapter — no smoothed-scale tail.tests/testthat/test-adaptation.Rcheck_adam_scale_adapter_state()helper (mirrors the SA / dual averaging state checkers).target_accept_prob ∈ {0.2, 0.4, 0.6}×initial_scale ∈ {0.5, 1, 2}×learning_rate ∈ {0.05, 0.1}— 18 test cases — each of which:t = 1for bothaccept_prob > targetand
accept_prob < target,check_scale_adapter_coerces_to_target_accept_prob()helper (2000 iterations,
tol = 1e-2).{1L, 2L, 5L}) using the sharedcheck_scale_adapter_with_default_args_works()helper to verify the default-construction path.All unit tests passed when I ran
devtools::test()