33# ' @param algorithm String specifying algorithm to use. One of:
44# ' * "stochastic_approximation" to use a Robbins-Monro (1951) based scheme,
55# ' * "dual_averaging" to use dual-averaging scheme of Nesterov (2009).
6+ # ' * "adam" to use the Adam optimizer of Kingma and Ba (2014) applied to the
7+ # ' acceptance-rate residual, following the implementation in the `walnuts`
8+ # ' library.
69# ' @param initial_scale Initial value to use for scale parameter. If not set
710# ' explicitly a proposal and dimension dependent default will be used.
811# ' @param target_accept_prob Target value for average accept probability for
912# ' chain. If not set a proposal dependent default will be used.
10- # ' @param ... Any additional algorithmic parameters to pass through to
11- # ' [dual_averaging_scale_adapter()] or [stochastic_approximation_scale_adapter()].
13+ # ' @param ... Any additional algorithmic parameters to pass through to the
14+ # ' selected adapter constructor: see [dual_averaging_scale_adapter()],
15+ # ' [stochastic_approximation_scale_adapter()] or [adam_scale_adapter()] for
16+ # ' the full list of parameters accepted by each. In practice, most users
17+ # ' tuning the Adam adapter only need to adjust `learning_rate`; the
18+ # ' moment-decay parameters `beta_1`, `beta_2`, `epsilon` and
19+ # ' `learn_rate_decay` have sensible defaults that rarely need adjustment.
1220# '
1321# ' @return List of functions with entries
1422# ' * `initialize`, a function for initializing adapter state and proposal
2129# ' * `state` a zero-argument function for accessing current values of adapter
2230# ' state variables.
2331# '
24- # ' @seealso [dual_averaging_scale_adapter()], [stochastic_approximation_scale_adapter()]
32+ # ' @seealso [dual_averaging_scale_adapter()],
33+ # ' [stochastic_approximation_scale_adapter()], [adam_scale_adapter()]
2534# '
2635# ' @references Nesterov, Y. (2009). Primal-dual subgradient methods for convex
2736# ' problems. _Mathematical Programming_, 120(1), 221-259.
2837# ' @references Robbins, H., & Monro, S. (1951). A stochastic approximation
2938# ' method. _The Annals of Mathematical Statistics_, 400-407.
39+ # ' @references Kingma, D. P., & Ba, J. (2014). Adam: A method for stochastic
40+ # ' optimization. _arXiv preprint_ arXiv:1412.6980.
3041# '
3142# ' @export
3243# '
@@ -43,6 +54,7 @@ scale_adapter <- function(
4354 adapter_function <- switch (algorithm ,
4455 dual_averaging = dual_averaging_scale_adapter ,
4556 stochastic_approximation = stochastic_approximation_scale_adapter ,
57+ adam = adam_scale_adapter ,
4658 stop(sprintf(" Unrecognized algorithm choice %s" , algorithm ))
4759 )
4860 adapter_function(initial_scale , target_accept_prob , ... )
@@ -187,6 +199,116 @@ dual_averaging_scale_adapter <- function(
187199 )
188200}
189201
202+ # ' Create object to adapt proposal scale to coerce average acceptance rate
203+ # ' using the Adam optimizer of Kingma and Ba (2014).
204+ # '
205+ # ' Applies the Adam stochastic-gradient optimizer to the log of the proposal
206+ # ' scale, using the acceptance-rate residual `target_accept_prob - accept_prob`
207+ # ' as the (stochastic) gradient. This corresponds to treating
208+ # ' `-0.5 * (accept_prob - target_accept_prob)^2` as the objective and is the
209+ # ' same gradient signal used by [dual_averaging_scale_adapter()] and
210+ # ' [stochastic_approximation_scale_adapter()], but plugged into the Adam
211+ # ' update rule instead of a dual-averaging or Robbins-Monro schedule. Follows
212+ # ' the reference implementation in the `walnuts` library (see references
213+ # ' below).
214+ # '
215+ # ' To match the stability provided by dual averaging, a learning-rate decay
216+ # ' `learning_rate / t^learn_rate_decay` is applied to the per-iteration Adam
217+ # ' step; a value of `learn_rate_decay = 0` recovers standard Adam, while
218+ # ' `learn_rate_decay = 0.5` (the default here) is recommended by the `walnuts`
219+ # ' authors for stable convergence.
220+ # '
221+ # ' @inherit scale_adapter params return
222+ # '
223+ # ' @param learning_rate Learning rate for Adam optimizer (the `alpha`
224+ # ' hyperparameter in the original Adam paper). Controls the magnitude of the
225+ # ' update applied to the log-scale on each iteration. Should be positive.
226+ # ' Defaults to `0.05`, which is a practical setting that gives fast
227+ # ' convergence of the acceptance-rate coercion within typical MCMC warm-up
228+ # ' lengths; the original Adam paper uses `1e-3`.
229+ # ' @param beta_1 Exponential decay rate for the first-moment estimate of the
230+ # ' gradient (the `beta_1` hyperparameter in the original Adam paper). Should
231+ # ' be in `[0, 1)`. Defaults to `0.9`.
232+ # ' @param beta_2 Exponential decay rate for the second-moment (squared
233+ # ' gradient) estimate (the `beta_2` hyperparameter in the original Adam
234+ # ' paper). Should be in `[0, 1)`. Defaults to `0.999`.
235+ # ' @param epsilon Small positive constant added to the square root of the
236+ # ' second-moment estimate in the denominator of the Adam update for
237+ # ' numerical stability. Defaults to `1e-8`.
238+ # ' @param learn_rate_decay Exponent controlling the decay of the effective
239+ # ' learning rate across iterations: on iteration `t` the effective learning
240+ # ' rate is `learning_rate / t^learn_rate_decay`. Should be in `[0, 1]`. A
241+ # ' value of `0` recovers standard Adam; the default of `0.5` matches the
242+ # ' value recommended in the `walnuts` implementation.
243+ # '
244+ # ' @references Kingma, D. P., & Ba, J. (2014). Adam: A method for stochastic
245+ # ' optimization. _arXiv preprint_ arXiv:1412.6980.
246+ # ' @references Reference implementation in `walnuts`:
247+ # ' <https://github.com/flatironinstitute/walnuts/blob/main/include/walnuts/adam.hpp>
248+ # '
249+ # ' @export
250+ # '
251+ # ' @examples
252+ # ' proposal <- barker_proposal()
253+ # ' adapter <- adam_scale_adapter(
254+ # ' initial_scale = 1., target_accept_prob = 0.4
255+ # ' )
256+ # ' adapter$initialize(proposal, chain_state(c(0, 0)))
257+ adam_scale_adapter <- function (
258+ initial_scale = NULL ,
259+ target_accept_prob = NULL ,
260+ learning_rate = 0.05 ,
261+ beta_1 = 0.9 ,
262+ beta_2 = 0.999 ,
263+ epsilon = 1e-8 ,
264+ learn_rate_decay = 0.5
265+ ) {
266+ log_scale <- NULL
267+ m <- 0
268+ v <- 0
269+ beta_1_pow <- 1
270+ beta_2_pow <- 1
271+ initialize <- function (proposal , initial_state ) {
272+ if (is.null(initial_scale )) {
273+ initial_scale <- proposal $ default_initial_scale(initial_state $ dimension())
274+ }
275+ log_scale <<- log(initial_scale )
276+ m <<- 0
277+ v <<- 0
278+ beta_1_pow <<- 1
279+ beta_2_pow <<- 1
280+ proposal $ update(scale = initial_scale )
281+ }
282+ update <- function (proposal , sample_index , state_and_statistics ) {
283+ if (is.null(target_accept_prob )) {
284+ target_accept_prob <- proposal $ default_target_accept_prob()
285+ }
286+ accept_prob <- state_and_statistics $ statistics $ accept_prob
287+ # Gradient of the squared-error objective in log-scale space. Note the
288+ # sign: `grad = target - observed` combined with the `log_scale -= ...`
289+ # update below reproduces the "observed > target => scale increases"
290+ # behaviour of the other scale adapters.
291+ grad <- target_accept_prob - accept_prob
292+ beta_1_pow <<- beta_1_pow * beta_1
293+ beta_2_pow <<- beta_2_pow * beta_2
294+ m <<- beta_1 * m + (1 - beta_1 ) * grad
295+ v <<- beta_2 * v + (1 - beta_2 ) * grad ^ 2
296+ m_hat <- m / (1 - beta_1_pow )
297+ v_hat <- v / (1 - beta_2_pow )
298+ effective_learning_rate <- learning_rate / sample_index ^ learn_rate_decay
299+ log_scale <<- log_scale - (
300+ effective_learning_rate * m_hat / (sqrt(v_hat ) + epsilon )
301+ )
302+ proposal $ update(scale = exp(log_scale ))
303+ }
304+ list (
305+ initialize = initialize ,
306+ update = update ,
307+ finalize = NULL ,
308+ state = function () list (log_scale = log_scale , m = m , v = v )
309+ )
310+ }
311+
190312# ' Create object to adapt proposal shape.
191313# '
192314# ' @param type Type of shape adapter to use. One of:
0 commit comments