From 06042020d2f6f6d4e3ab738e0670c75c554f19b0 Mon Sep 17 00:00:00 2001 From: be-marc Date: Thu, 8 Feb 2024 11:41:09 +0100 Subject: [PATCH 01/49] draft --- DESCRIPTION | 4 +- NAMESPACE | 2 + R/AcqFunction.R | 2 +- R/OptimizerADBO.R | 114 ++++++++++++++++++++++++++ R/Surrogate.R | 2 +- R/SurrogateLearner.R | 14 +++- R/TunerADBO.R | 20 +++++ R/helper.R | 6 +- R/mbo_defaults.R | 2 +- man/AcqFunction.Rd | 8 +- man/ResultAssigner.Rd | 4 +- man/loop_function.Rd | 4 +- man/mlr_acqfunctions.Rd | 4 +- man/mlr_acqfunctions_aei.Rd | 8 +- man/mlr_acqfunctions_cb.Rd | 8 +- man/mlr_acqfunctions_ehvi.Rd | 6 +- man/mlr_acqfunctions_ehvigh.Rd | 6 +- man/mlr_acqfunctions_ei.Rd | 6 +- man/mlr_acqfunctions_eips.Rd | 6 +- man/mlr_acqfunctions_mean.Rd | 8 +- man/mlr_acqfunctions_pi.Rd | 8 +- man/mlr_acqfunctions_sd.Rd | 8 +- man/mlr_acqfunctions_smsego.Rd | 8 +- man/mlr_loop_functions_ego.Rd | 4 +- man/mlr_loop_functions_emo.Rd | 4 +- man/mlr_loop_functions_mpcl.Rd | 4 +- man/mlr_loop_functions_parego.Rd | 4 +- man/mlr_loop_functions_smsego.Rd | 4 +- man/mlr_result_assigners_archive.Rd | 4 +- man/mlr_result_assigners_surrogate.Rd | 4 +- tests/testthat/helper.R | 12 +++ tests/testthat/test_OptimizerADBO.R | 113 +++++++++++++++++++++++++ 32 files changed, 341 insertions(+), 70 deletions(-) create mode 100644 R/OptimizerADBO.R create mode 100644 R/TunerADBO.R create mode 100644 tests/testthat/test_OptimizerADBO.R diff --git a/DESCRIPTION b/DESCRIPTION index 2f73386d..10a6743d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -72,7 +72,7 @@ Config/testthat/edition: 3 Config/testthat/parallel: false NeedsCompilation: yes Roxygen: list(markdown = TRUE, r6 = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.1 Collate: 'mlr_acqfunctions.R' 'AcqFunction.R' @@ -88,6 +88,7 @@ Collate: 'AcqFunctionSmsEgo.R' 'AcqOptimizer.R' 'aaa.R' + 'OptimizerADBO.R' 'OptimizerMbo.R' 'mlr_result_assigners.R' 'ResultAssigner.R' @@ -96,6 +97,7 @@ Collate: 'Surrogate.R' 'SurrogateLearner.R' 'SurrogateLearnerCollection.R' + 'TunerADBO.R' 'TunerMbo.R' 'mlr_loop_functions.R' 'bayesopt_ego.R' diff --git a/NAMESPACE b/NAMESPACE index 1a02acdc..19a9927f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,6 +16,7 @@ export(AcqFunctionPI) export(AcqFunctionSD) export(AcqFunctionSmsEgo) export(AcqOptimizer) +export(OptimizerADBO) export(OptimizerMbo) export(ResultAssigner) export(ResultAssignerArchive) @@ -23,6 +24,7 @@ export(ResultAssignerSurrogate) export(Surrogate) export(SurrogateLearner) export(SurrogateLearnerCollection) +export(TunerADBO) export(TunerMbo) export(acqf) export(acqo) diff --git a/R/AcqFunction.R b/R/AcqFunction.R index d7045f91..6b21f46d 100644 --- a/R/AcqFunction.R +++ b/R/AcqFunction.R @@ -176,7 +176,7 @@ AcqFunction = R6Class("AcqFunction", stopf("Acquisition function '%s' requires the surrogate to have `\"se\"` as `$predict_type`.", format(self)) } private$.surrogate = rhs - private$.archive = assert_r6(rhs$archive, classes = "Archive") + private$.archive = assert_multi_class(rhs$archive, c("Archive", "ArchiveRush")) codomain = generate_acq_codomain(rhs, id = self$id, direction = self$direction) self$surrogate_max_to_min = surrogate_mult_max_to_min(rhs) domain = generate_acq_domain(rhs) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R new file mode 100644 index 00000000..24b6e0f0 --- /dev/null +++ b/R/OptimizerADBO.R @@ -0,0 +1,114 @@ +#' @export +OptimizerADBO = R6Class("OptimizerADBO", + inherit = bbotk::Optimizer, + + public = list( + + initialize = function() { + param_set = ps( + init_design_size = p_int(lower = 1L), + initial_design = p_uty() + ) + + super$initialize("adbo", + param_set = param_set, + param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), + properties = c("dependencies", "multi-crit", "single-crit"), + packages = "mlr3mbo", + label = "Asynchronous Decentralized Bayesian Optimization", + man = "mlr3mbo::OptimizerADBO") + }, + + optimize = function(inst) { + + if (rush_available()) { + + # generate initial design + pv = self$param_set$values + design = if (is.null(pv$initial_design)) { + generate_design_sobol(inst$search_space, n = pv$init_design_size)$data + } else { + pv$initial_design + } + + # send initial design to workers + inst$rush$push_tasks(transpose_list(design), extra = list(list(timestamp_xs = Sys.time()))) + + # start rush workers + inst$rush$start_workers( + worker_loop = bbotk_worker_loop_decentralized, + packages = c("bbotk", "mlr3mbo"), + optimizer = self, + instance = inst, + lgr_thresholds = c(rush = "debug", bbotk = "debug"), + wait_for_workers = TRUE) + } else { + stop("No rush plan available. See `?rush::rush_plan()`") + } + + lg$info("Starting to optimize %i parameter(s) with '%s' and '%s' on %i worker(s)", + inst$search_space$length, + self$format(), + inst$terminator$format(with_params = TRUE), + inst$rush$n_running_workers + ) + + # wait + while(!inst$is_terminated) { + Sys.sleep(1) + inst$rush$detect_lost_workers() + } + + # assign result + private$.assign_result(inst) + + # assign result + private$.assign_result(inst) + lg$info("Finished optimizing after %i evaluation(s)", inst$archive$n_evals) + lg$info("Result:") + lg$info(capture.output(print(inst$result, lass = FALSE, row.names = FALSE, print.keys = FALSE))) + return(inst$result) + + result + } + ), + + private = list( + + .optimize_remote = function(inst) { + search_space = inst$search_space + rush = inst$rush + + surrogate = default_surrogate(inst) + surrogate$param_set$set_values(impute_missings = TRUE) + acq_function = acqf("cb", lambda = runif(1, 1 , 3)) + acq_optimizer = acqo(opt("random_search", batch_size = 1000L), terminator = trm("evals", n_evals = 10000L)) + + surrogate$archive = inst$archive + acq_function$surrogate = surrogate + acq_optimizer$acq_function = acq_function + + # evaluate initial design + while (rush$n_queued_tasks > 0) { + task = rush$pop_task(fields = "xs") + xs_trafoed = trafo_xs(task$xs, inst$search_space) + ys = inst$objective$eval(xs_trafoed) + rush$push_results(task$key, yss = list(ys), extra = list(list(timestamp_ys = Sys.time(), stage = "initial_design"))) + } + + # actual loop + while (!inst$is_terminated) { + acq_function$surrogate$update() + acq_function$update() + xdt = acq_optimizer$optimize() + xss = transform_xdt_to_xss(xdt, search_space) + keys = rush$push_running_task(xss, extra = list(list(timestamp_xs = Sys.time()))) + ys = inst$objective$eval(xss[[1]]) + rush$push_results(keys, list(ys), extra = list(list(timestamp_ys = Sys.time(), stage = "mbo"))) + } + } + ) +) + +#' @include aaa.R +optimizers[["adbo"]] = OptimizerADBO diff --git a/R/Surrogate.R b/R/Surrogate.R index b1b15b28..506c5f42 100644 --- a/R/Surrogate.R +++ b/R/Surrogate.R @@ -99,7 +99,7 @@ Surrogate = R6Class("Surrogate", if (missing(rhs)) { private$.archive } else { - private$.archive = assert_r6(rhs, classes = "Archive") + private$.archive = assert_multi_class(rhs, c("Archive", "ArchiveRush")) invisible(private$.archive) } }, diff --git a/R/SurrogateLearner.R b/R/SurrogateLearner.R index c7ed9549..35730b01 100644 --- a/R/SurrogateLearner.R +++ b/R/SurrogateLearner.R @@ -87,9 +87,10 @@ SurrogateLearner = R6Class("SurrogateLearner", ParamLgl$new("assert_insample_perf"), ParamUty$new("perf_measure", custom_check = function(x) check_r6(x, classes = "MeasureRegr")), # FIXME: actually want check_measure ParamDbl$new("perf_threshold", lower = -Inf, upper = Inf), - ParamLgl$new("catch_errors")) + ParamLgl$new("catch_errors"), + ParamLgl$new("impute_missings")) ) - ps$values = list(assert_insample_perf = FALSE, catch_errors = TRUE) + ps$values = list(assert_insample_perf = FALSE, catch_errors = TRUE, impute_missings = FALSE) ps$add_dep("perf_measure", on = "assert_insample_perf", cond = CondEqual$new(TRUE)) ps$add_dep("perf_threshold", on = "assert_insample_perf", cond = CondEqual$new(TRUE)) @@ -214,7 +215,14 @@ SurrogateLearner = R6Class("SurrogateLearner", # Train learner with new data. # Also calculates the insample performance based on the `perf_measure` hyperparameter if `assert_insample_perf = TRUE`. .update = function() { - xydt = self$archive$data[, c(self$cols_x, self$cols_y), with = FALSE] + + if (self$param_set$values$impute_missings) { + xydt = self$archive$rush$fetch_tasks(fields = c("xs", "ys"))[, c(self$cols_x, self$cols_y), with = FALSE] + setnafill(xydt, type = "const", fill = mean(xydt[[self$cols_y]], na.rm = TRUE), cols = self$cols_y) + } else { + xydt = self$archive$data[, c(self$cols_x, self$cols_y), with = FALSE] + } + task = TaskRegr$new(id = "surrogate_task", backend = xydt, target = self$cols_y) assert_learnable(task, learner = self$learner) self$learner$train(task) diff --git a/R/TunerADBO.R b/R/TunerADBO.R new file mode 100644 index 00000000..e0fa1f04 --- /dev/null +++ b/R/TunerADBO.R @@ -0,0 +1,20 @@ +#' @export +TunerADBO = R6Class("TunerADBO", + inherit = mlr3tuning::TunerFromOptimizer, + public = list( + + #' @description + #' Creates a new instance of this [R6][R6::R6Class] class. + initialize = function() { + super$initialize( + optimizer = OptimizerADBO$new(), + man = "mlr3tuning::mlr_tuners_adbo" + ) + } + ) +) + +mlr_tuners$add("adbo", TunerADBO) + +#' @include aaa.R +tuners[["adbo"]] = TunerADBO diff --git a/R/helper.R b/R/helper.R index 1caf1ca1..cc19e336 100644 --- a/R/helper.R +++ b/R/helper.R @@ -1,5 +1,5 @@ generate_acq_codomain = function(surrogate, id, direction = "same") { - assert_r6(surrogate$archive, classes = "Archive") + assert_multi_class(surrogate$archive, c("Archive", "ArchiveRush")) assert_string(id) assert_choice(direction, choices = c("same", "minimize", "maximize")) if (direction == "same") { @@ -18,7 +18,7 @@ generate_acq_codomain = function(surrogate, id, direction = "same") { } generate_acq_domain = function(surrogate) { - assert_r6(surrogate$archive, classes = "Archive") + assert_multi_class(surrogate$archive, c("Archive", "ArchiveRush")) domain = surrogate$archive$search_space$clone(deep = TRUE)$subset(surrogate$cols_x) domain$trafo = NULL domain @@ -130,7 +130,7 @@ check_learner_surrogate = function(learner) { return(TRUE) } } - + "Must inherit from class 'Learner' or be a list of elements inheriting from class 'Learner'" } diff --git a/R/mbo_defaults.R b/R/mbo_defaults.R index a7d68d7e..9d54e411 100644 --- a/R/mbo_defaults.R +++ b/R/mbo_defaults.R @@ -147,7 +147,7 @@ default_rf = function(noisy = FALSE) { #' @family mbo_defaults #' @export default_surrogate = function(instance, learner = NULL, n_learner = NULL) { - assert_r6(instance, "OptimInstance") + assert_multi_class(instance, c("OptimInstance", "OptimInstanceRush")) assert_r6(learner, "Learner", null.ok = TRUE) assert_int(n_learner, lower = 1L, null.ok = TRUE) noisy = "noisy" %in% instance$objective$properties diff --git a/man/AcqFunction.Rd b/man/AcqFunction.Rd index 9d031bfa..ba807720 100644 --- a/man/AcqFunction.Rd +++ b/man/AcqFunction.Rd @@ -10,17 +10,17 @@ Based on the predictions of a \link{Surrogate}, the acquisition function encodes } \seealso{ Other Acquisition Function: +\code{\link{mlr_acqfunctions}}, \code{\link{mlr_acqfunctions_aei}}, \code{\link{mlr_acqfunctions_cb}}, -\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ehvi}}, -\code{\link{mlr_acqfunctions_eips}}, +\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ei}}, +\code{\link{mlr_acqfunctions_eips}}, \code{\link{mlr_acqfunctions_mean}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}}, -\code{\link{mlr_acqfunctions}} +\code{\link{mlr_acqfunctions_smsego}} } \concept{Acquisition Function} \section{Super class}{ diff --git a/man/ResultAssigner.Rd b/man/ResultAssigner.Rd index aba61af4..28ea502a 100644 --- a/man/ResultAssigner.Rd +++ b/man/ResultAssigner.Rd @@ -11,9 +11,9 @@ Normally, it is only used within an \link{OptimizerMbo}. } \seealso{ Other Result Assigner: +\code{\link{mlr_result_assigners}}, \code{\link{mlr_result_assigners_archive}}, -\code{\link{mlr_result_assigners_surrogate}}, -\code{\link{mlr_result_assigners}} +\code{\link{mlr_result_assigners_surrogate}} } \concept{Result Assigner} \section{Active bindings}{ diff --git a/man/loop_function.Rd b/man/loop_function.Rd index 623d171a..063bb38c 100644 --- a/man/loop_function.Rd +++ b/man/loop_function.Rd @@ -15,11 +15,11 @@ As an example, see, e.g., \link{bayesopt_ego}. } \seealso{ Other Loop Function: +\code{\link{mlr_loop_functions}}, \code{\link{mlr_loop_functions_ego}}, \code{\link{mlr_loop_functions_emo}}, \code{\link{mlr_loop_functions_mpcl}}, \code{\link{mlr_loop_functions_parego}}, -\code{\link{mlr_loop_functions_smsego}}, -\code{\link{mlr_loop_functions}} +\code{\link{mlr_loop_functions_smsego}} } \concept{Loop Function} diff --git a/man/mlr_acqfunctions.Rd b/man/mlr_acqfunctions.Rd index 5172c72e..1cb921ac 100644 --- a/man/mlr_acqfunctions.Rd +++ b/man/mlr_acqfunctions.Rd @@ -34,10 +34,10 @@ Other Acquisition Function: \code{\link{AcqFunction}}, \code{\link{mlr_acqfunctions_aei}}, \code{\link{mlr_acqfunctions_cb}}, -\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ehvi}}, -\code{\link{mlr_acqfunctions_eips}}, +\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ei}}, +\code{\link{mlr_acqfunctions_eips}}, \code{\link{mlr_acqfunctions_mean}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, diff --git a/man/mlr_acqfunctions_aei.Rd b/man/mlr_acqfunctions_aei.Rd index 729a1bfa..56957d63 100644 --- a/man/mlr_acqfunctions_aei.Rd +++ b/man/mlr_acqfunctions_aei.Rd @@ -79,16 +79,16 @@ if (requireNamespace("mlr3learners") & \seealso{ Other Acquisition Function: \code{\link{AcqFunction}}, +\code{\link{mlr_acqfunctions}}, \code{\link{mlr_acqfunctions_cb}}, -\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ehvi}}, -\code{\link{mlr_acqfunctions_eips}}, +\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ei}}, +\code{\link{mlr_acqfunctions_eips}}, \code{\link{mlr_acqfunctions_mean}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}}, -\code{\link{mlr_acqfunctions}} +\code{\link{mlr_acqfunctions_smsego}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_cb.Rd b/man/mlr_acqfunctions_cb.Rd index 6d211a30..d94ae789 100644 --- a/man/mlr_acqfunctions_cb.Rd +++ b/man/mlr_acqfunctions_cb.Rd @@ -68,16 +68,16 @@ In Pereira F, Burges CJC, Bottou L, Weinberger KQ (eds.), \emph{Advances in Neur \seealso{ Other Acquisition Function: \code{\link{AcqFunction}}, +\code{\link{mlr_acqfunctions}}, \code{\link{mlr_acqfunctions_aei}}, -\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ehvi}}, -\code{\link{mlr_acqfunctions_eips}}, +\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ei}}, +\code{\link{mlr_acqfunctions_eips}}, \code{\link{mlr_acqfunctions_mean}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}}, -\code{\link{mlr_acqfunctions}} +\code{\link{mlr_acqfunctions_smsego}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_ehvi.Rd b/man/mlr_acqfunctions_ehvi.Rd index fe06da33..160eb8fa 100644 --- a/man/mlr_acqfunctions_ehvi.Rd +++ b/man/mlr_acqfunctions_ehvi.Rd @@ -54,16 +54,16 @@ Springer International Publishing, Cham. \seealso{ Other Acquisition Function: \code{\link{AcqFunction}}, +\code{\link{mlr_acqfunctions}}, \code{\link{mlr_acqfunctions_aei}}, \code{\link{mlr_acqfunctions_cb}}, \code{\link{mlr_acqfunctions_ehvigh}}, -\code{\link{mlr_acqfunctions_eips}}, \code{\link{mlr_acqfunctions_ei}}, +\code{\link{mlr_acqfunctions_eips}}, \code{\link{mlr_acqfunctions_mean}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}}, -\code{\link{mlr_acqfunctions}} +\code{\link{mlr_acqfunctions_smsego}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_ehvigh.Rd b/man/mlr_acqfunctions_ehvigh.Rd index bd331c8f..9d9a2c27 100644 --- a/man/mlr_acqfunctions_ehvigh.Rd +++ b/man/mlr_acqfunctions_ehvigh.Rd @@ -68,16 +68,16 @@ In Rudolph, Günter, Kononova, V. A, Aguirre, Hernán, Kerschke, Pascal, Ochoa, \seealso{ Other Acquisition Function: \code{\link{AcqFunction}}, +\code{\link{mlr_acqfunctions}}, \code{\link{mlr_acqfunctions_aei}}, \code{\link{mlr_acqfunctions_cb}}, \code{\link{mlr_acqfunctions_ehvi}}, -\code{\link{mlr_acqfunctions_eips}}, \code{\link{mlr_acqfunctions_ei}}, +\code{\link{mlr_acqfunctions_eips}}, \code{\link{mlr_acqfunctions_mean}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}}, -\code{\link{mlr_acqfunctions}} +\code{\link{mlr_acqfunctions_smsego}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_ei.Rd b/man/mlr_acqfunctions_ei.Rd index ddb9d3a1..0fe783ea 100644 --- a/man/mlr_acqfunctions_ei.Rd +++ b/man/mlr_acqfunctions_ei.Rd @@ -60,16 +60,16 @@ if (requireNamespace("mlr3learners") & \seealso{ Other Acquisition Function: \code{\link{AcqFunction}}, +\code{\link{mlr_acqfunctions}}, \code{\link{mlr_acqfunctions_aei}}, \code{\link{mlr_acqfunctions_cb}}, -\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ehvi}}, +\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_eips}}, \code{\link{mlr_acqfunctions_mean}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}}, -\code{\link{mlr_acqfunctions}} +\code{\link{mlr_acqfunctions_smsego}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_eips.Rd b/man/mlr_acqfunctions_eips.Rd index d67e3392..214f654b 100644 --- a/man/mlr_acqfunctions_eips.Rd +++ b/man/mlr_acqfunctions_eips.Rd @@ -68,16 +68,16 @@ In Pereira F, Burges CJC, Bottou L, Weinberger KQ (eds.), \emph{Advances in Neur \seealso{ Other Acquisition Function: \code{\link{AcqFunction}}, +\code{\link{mlr_acqfunctions}}, \code{\link{mlr_acqfunctions_aei}}, \code{\link{mlr_acqfunctions_cb}}, -\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ehvi}}, +\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ei}}, \code{\link{mlr_acqfunctions_mean}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}}, -\code{\link{mlr_acqfunctions}} +\code{\link{mlr_acqfunctions_smsego}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_mean.Rd b/man/mlr_acqfunctions_mean.Rd index ced81a81..c9d602e2 100644 --- a/man/mlr_acqfunctions_mean.Rd +++ b/man/mlr_acqfunctions_mean.Rd @@ -53,16 +53,16 @@ if (requireNamespace("mlr3learners") & \seealso{ Other Acquisition Function: \code{\link{AcqFunction}}, +\code{\link{mlr_acqfunctions}}, \code{\link{mlr_acqfunctions_aei}}, \code{\link{mlr_acqfunctions_cb}}, -\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ehvi}}, -\code{\link{mlr_acqfunctions_eips}}, +\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ei}}, +\code{\link{mlr_acqfunctions_eips}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}}, -\code{\link{mlr_acqfunctions}} +\code{\link{mlr_acqfunctions_smsego}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_pi.Rd b/man/mlr_acqfunctions_pi.Rd index bb7ac077..cc69d6e0 100644 --- a/man/mlr_acqfunctions_pi.Rd +++ b/man/mlr_acqfunctions_pi.Rd @@ -60,16 +60,16 @@ if (requireNamespace("mlr3learners") & \seealso{ Other Acquisition Function: \code{\link{AcqFunction}}, +\code{\link{mlr_acqfunctions}}, \code{\link{mlr_acqfunctions_aei}}, \code{\link{mlr_acqfunctions_cb}}, -\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ehvi}}, -\code{\link{mlr_acqfunctions_eips}}, +\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ei}}, +\code{\link{mlr_acqfunctions_eips}}, \code{\link{mlr_acqfunctions_mean}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}}, -\code{\link{mlr_acqfunctions}} +\code{\link{mlr_acqfunctions_smsego}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_sd.Rd b/man/mlr_acqfunctions_sd.Rd index f415bb47..ab434ac9 100644 --- a/man/mlr_acqfunctions_sd.Rd +++ b/man/mlr_acqfunctions_sd.Rd @@ -53,16 +53,16 @@ if (requireNamespace("mlr3learners") & \seealso{ Other Acquisition Function: \code{\link{AcqFunction}}, +\code{\link{mlr_acqfunctions}}, \code{\link{mlr_acqfunctions_aei}}, \code{\link{mlr_acqfunctions_cb}}, -\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ehvi}}, -\code{\link{mlr_acqfunctions_eips}}, +\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ei}}, +\code{\link{mlr_acqfunctions_eips}}, \code{\link{mlr_acqfunctions_mean}}, \code{\link{mlr_acqfunctions_pi}}, -\code{\link{mlr_acqfunctions_smsego}}, -\code{\link{mlr_acqfunctions}} +\code{\link{mlr_acqfunctions_smsego}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_smsego.Rd b/man/mlr_acqfunctions_smsego.Rd index ad51095c..e5d6c865 100644 --- a/man/mlr_acqfunctions_smsego.Rd +++ b/man/mlr_acqfunctions_smsego.Rd @@ -70,16 +70,16 @@ In \emph{International Conference on Evolutionary Multi-Criterion Optimization}, \seealso{ Other Acquisition Function: \code{\link{AcqFunction}}, +\code{\link{mlr_acqfunctions}}, \code{\link{mlr_acqfunctions_aei}}, \code{\link{mlr_acqfunctions_cb}}, -\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ehvi}}, -\code{\link{mlr_acqfunctions_eips}}, +\code{\link{mlr_acqfunctions_ehvigh}}, \code{\link{mlr_acqfunctions_ei}}, +\code{\link{mlr_acqfunctions_eips}}, \code{\link{mlr_acqfunctions_mean}}, \code{\link{mlr_acqfunctions_pi}}, -\code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions}} +\code{\link{mlr_acqfunctions_sd}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_loop_functions_ego.Rd b/man/mlr_loop_functions_ego.Rd index 02f95fbd..1420a86d 100644 --- a/man/mlr_loop_functions_ego.Rd +++ b/man/mlr_loop_functions_ego.Rd @@ -134,10 +134,10 @@ In Pereira F, Burges CJC, Bottou L, Weinberger KQ (eds.), \emph{Advances in Neur \seealso{ Other Loop Function: \code{\link{loop_function}}, +\code{\link{mlr_loop_functions}}, \code{\link{mlr_loop_functions_emo}}, \code{\link{mlr_loop_functions_mpcl}}, \code{\link{mlr_loop_functions_parego}}, -\code{\link{mlr_loop_functions_smsego}}, -\code{\link{mlr_loop_functions}} +\code{\link{mlr_loop_functions_smsego}} } \concept{Loop Function} diff --git a/man/mlr_loop_functions_emo.Rd b/man/mlr_loop_functions_emo.Rd index 1b765d17..bcb212bc 100644 --- a/man/mlr_loop_functions_emo.Rd +++ b/man/mlr_loop_functions_emo.Rd @@ -101,10 +101,10 @@ if (requireNamespace("mlr3learners") & \seealso{ Other Loop Function: \code{\link{loop_function}}, +\code{\link{mlr_loop_functions}}, \code{\link{mlr_loop_functions_ego}}, \code{\link{mlr_loop_functions_mpcl}}, \code{\link{mlr_loop_functions_parego}}, -\code{\link{mlr_loop_functions_smsego}}, -\code{\link{mlr_loop_functions}} +\code{\link{mlr_loop_functions_smsego}} } \concept{Loop Function} diff --git a/man/mlr_loop_functions_mpcl.Rd b/man/mlr_loop_functions_mpcl.Rd index bd9ae1b8..7d56c763 100644 --- a/man/mlr_loop_functions_mpcl.Rd +++ b/man/mlr_loop_functions_mpcl.Rd @@ -125,10 +125,10 @@ if (requireNamespace("mlr3learners") & \seealso{ Other Loop Function: \code{\link{loop_function}}, +\code{\link{mlr_loop_functions}}, \code{\link{mlr_loop_functions_ego}}, \code{\link{mlr_loop_functions_emo}}, \code{\link{mlr_loop_functions_parego}}, -\code{\link{mlr_loop_functions_smsego}}, -\code{\link{mlr_loop_functions}} +\code{\link{mlr_loop_functions_smsego}} } \concept{Loop Function} diff --git a/man/mlr_loop_functions_parego.Rd b/man/mlr_loop_functions_parego.Rd index 527e60c3..f87fa2a6 100644 --- a/man/mlr_loop_functions_parego.Rd +++ b/man/mlr_loop_functions_parego.Rd @@ -128,10 +128,10 @@ if (requireNamespace("mlr3learners") & \seealso{ Other Loop Function: \code{\link{loop_function}}, +\code{\link{mlr_loop_functions}}, \code{\link{mlr_loop_functions_ego}}, \code{\link{mlr_loop_functions_emo}}, \code{\link{mlr_loop_functions_mpcl}}, -\code{\link{mlr_loop_functions_smsego}}, -\code{\link{mlr_loop_functions}} +\code{\link{mlr_loop_functions_smsego}} } \concept{Loop Function} diff --git a/man/mlr_loop_functions_smsego.Rd b/man/mlr_loop_functions_smsego.Rd index 79a4fd13..82284754 100644 --- a/man/mlr_loop_functions_smsego.Rd +++ b/man/mlr_loop_functions_smsego.Rd @@ -112,10 +112,10 @@ In \emph{Proceedings of the 10th International Conference on Parallel Problem So \seealso{ Other Loop Function: \code{\link{loop_function}}, +\code{\link{mlr_loop_functions}}, \code{\link{mlr_loop_functions_ego}}, \code{\link{mlr_loop_functions_emo}}, \code{\link{mlr_loop_functions_mpcl}}, -\code{\link{mlr_loop_functions_parego}}, -\code{\link{mlr_loop_functions}} +\code{\link{mlr_loop_functions_parego}} } \concept{Loop Function} diff --git a/man/mlr_result_assigners_archive.Rd b/man/mlr_result_assigners_archive.Rd index 94f44c44..11e3c5a5 100644 --- a/man/mlr_result_assigners_archive.Rd +++ b/man/mlr_result_assigners_archive.Rd @@ -14,8 +14,8 @@ result_assigner = ras("archive") \seealso{ Other Result Assigner: \code{\link{ResultAssigner}}, -\code{\link{mlr_result_assigners_surrogate}}, -\code{\link{mlr_result_assigners}} +\code{\link{mlr_result_assigners}}, +\code{\link{mlr_result_assigners_surrogate}} } \concept{Result Assigner} \section{Super class}{ diff --git a/man/mlr_result_assigners_surrogate.Rd b/man/mlr_result_assigners_surrogate.Rd index c337fc61..1a0d3251 100644 --- a/man/mlr_result_assigners_surrogate.Rd +++ b/man/mlr_result_assigners_surrogate.Rd @@ -16,8 +16,8 @@ result_assigner = ras("surrogate") \seealso{ Other Result Assigner: \code{\link{ResultAssigner}}, -\code{\link{mlr_result_assigners_archive}}, -\code{\link{mlr_result_assigners}} +\code{\link{mlr_result_assigners}}, +\code{\link{mlr_result_assigners_archive}} } \concept{Result Assigner} \section{Super class}{ diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index ad1644de..e2b090b2 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -199,3 +199,15 @@ expect_acqfunction = function(acqf) { expect_man_exists(acqf$man) } +expect_rush_reset = function(rush, type = "kill") { + processes = rush$processes + rush$reset(type = type) + expect_list(rush$connector$command(c("KEYS", "*")), len = 0) + walk(processes, function(p) p$kill()) +} + +flush_redis = function() { + config = redux::redis_config() + r = redux::hiredis(config) + r$FLUSHDB() +} diff --git a/tests/testthat/test_OptimizerADBO.R b/tests/testthat/test_OptimizerADBO.R new file mode 100644 index 00000000..11adc987 --- /dev/null +++ b/tests/testthat/test_OptimizerADBO.R @@ -0,0 +1,113 @@ +test_that("search works with decentralized network", { + flush_redis() + library(rush) + rush_plan(n_workers = 2) + + learner = lrn("classif.rpart", + minsplit = to_tune(2, 128), + cp = to_tune(1e-04, 1e-1)) + + instance = TuningInstanceRushSingleCrit$new( + task = tsk("pima"), + learner = learner, + resampling = rsmp("cv", folds = 3), + measure = msr("classif.ce"), + terminator = trm("evals", n_evals = 20), + store_benchmark_result = FALSE + ) + + optimizer = tnr("adbo", init_design_size = 4) + optimizer$optimize(instance) + + instance$archive$data[order(timestamp_ys)] + instance$rush$processes[[1]]$read_all_error_lines() + instance$rush$read_log() + + expect_data_table(instance$archive$data, min.rows = 3L) + + expect_rush_reset(instance$rush) +}) + +test_that("search works with transformation functions", { + flush_redis() + library(rush) + rush_plan(n_workers = 2) + + learner = lrn("classif.rpart", + minsplit = to_tune(2, 128, logscale = TRUE), + cp = to_tune(1e-04, 1e-1, logscale = TRUE)) + + instance = TuningInstanceRushSingleCrit$new( + task = tsk("pima"), + learner = learner, + resampling = rsmp("cv", folds = 3), + measure = msr("classif.ce"), + terminator = trm("evals", n_evals = 20), + store_benchmark_result = FALSE + ) + + optimizer = tnr("adbo", init_design_size = 4) + optimizer$optimize(instance) + + expect_data_table(instance$archive$data, min.rows = 20) + + expect_rush_reset(instance$rush) +}) + +test_that("search works with dependencies", { + flush_redis() + library(rush) + rush_plan(n_workers = 2) + + learner = lrn("classif.rpart", + minsplit = to_tune(p_int(2, 128, depends = keep_model == TRUE)), + cp = to_tune(1e-04, 1e-1), + keep_model = to_tune()) + + instance = TuningInstanceRushSingleCrit$new( + task = tsk("pima"), + learner = learner, + resampling = rsmp("cv", folds = 3), + measure = msr("classif.ce"), + terminator = trm("evals", n_evals = 20), + store_benchmark_result = FALSE + ) + + optimizer = tnr("adbo", init_design_size = 4) + optimizer$optimize(instance) + + expect_data_table(instance$archive$data, min.rows = 20) + + expect_rush_reset(instance$rush) +}) + +test_that("adbo works with branching", { + flush_redis() + library(rush) + library(mlr3pipelines) + rush_plan(n_workers = 2) + + graph_learner = as_learner(ppl("branch", graphs = list(rpart = lrn("classif.rpart", id = "rpart"),debug = lrn("classif.debug", id = "debug")))) + graph_learner$param_set$set_values( + "rpart.cp" = to_tune(p_dbl(1e-04, 1e-1, depends = branch.selection == "rpart")), + "rpart.minsplit" = to_tune(p_int(2, 128, depends = branch.selection == "rpart")), + "debug.x" = to_tune(p_dbl(0, 1, depends = branch.selection == "debug")), + "branch.selection" = to_tune(c("rpart", "debug")) + ) + + instance = TuningInstanceRushSingleCrit$new( + task = tsk("pima"), + learner = graph_learner, + resampling = rsmp("cv", folds = 3), + measure = msr("classif.ce"), + terminator = trm("evals", n_evals = 20), + store_benchmark_result = FALSE + ) + + optimizer = tnr("adbo", init_design_size = 4) + optimizer$optimize(instance) + + expect_data_table(instance$archive$data, min.rows = 20) + + expect_rush_reset(instance$rush) +}) From d4b56b96a9bd1303480373acd26775686051d776 Mon Sep 17 00:00:00 2001 From: be-marc Date: Thu, 8 Feb 2024 13:30:27 +0100 Subject: [PATCH 02/49] fix: add start time --- R/OptimizerADBO.R | 2 ++ tests/testthat/test_OptimizerADBO.R | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index 24b6e0f0..040347e4 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -22,6 +22,7 @@ OptimizerADBO = R6Class("OptimizerADBO", optimize = function(inst) { if (rush_available()) { + inst$archive$start_time = Sys.time() # generate initial design pv = self$param_set$values @@ -56,6 +57,7 @@ OptimizerADBO = R6Class("OptimizerADBO", # wait while(!inst$is_terminated) { Sys.sleep(1) + inst$rush$print_log() inst$rush$detect_lost_workers() } diff --git a/tests/testthat/test_OptimizerADBO.R b/tests/testthat/test_OptimizerADBO.R index 11adc987..5dbe0100 100644 --- a/tests/testthat/test_OptimizerADBO.R +++ b/tests/testthat/test_OptimizerADBO.R @@ -19,9 +19,9 @@ test_that("search works with decentralized network", { optimizer = tnr("adbo", init_design_size = 4) optimizer$optimize(instance) - instance$archive$data[order(timestamp_ys)] - instance$rush$processes[[1]]$read_all_error_lines() - instance$rush$read_log() + # instance$archive$data[order(timestamp_ys)] + # instance$rush$processes[[1]]$read_all_error_lines() + # instance$rush$read_log() expect_data_table(instance$archive$data, min.rows = 3L) From 71fdc661982496a1c205df0c701ee13ef95a793a Mon Sep 17 00:00:00 2001 From: be-marc Date: Fri, 9 Feb 2024 11:36:29 +0100 Subject: [PATCH 03/49] refactor: remove rush debug --- R/OptimizerADBO.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index 040347e4..1083f5f2 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -41,7 +41,7 @@ OptimizerADBO = R6Class("OptimizerADBO", packages = c("bbotk", "mlr3mbo"), optimizer = self, instance = inst, - lgr_thresholds = c(rush = "debug", bbotk = "debug"), + lgr_thresholds = c(bbotk = "debug"), wait_for_workers = TRUE) } else { stop("No rush plan available. See `?rush::rush_plan()`") From 762b136347a9a708a9a6f266269e62201c613005 Mon Sep 17 00:00:00 2001 From: be-marc Date: Fri, 9 Feb 2024 17:14:44 +0100 Subject: [PATCH 04/49] fix: add callback --- R/OptimizerADBO.R | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index 1083f5f2..5dd011c6 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -23,6 +23,8 @@ OptimizerADBO = R6Class("OptimizerADBO", if (rush_available()) { inst$archive$start_time = Sys.time() + inst$.__enclos_env__$private$.context = ContextOptimization$new(instance = inst, optimizer = self) + call_back("on_optimization_begin", inst$callbacks, get_private(inst)$.context) # generate initial design pv = self$param_set$values @@ -69,9 +71,8 @@ OptimizerADBO = R6Class("OptimizerADBO", lg$info("Finished optimizing after %i evaluation(s)", inst$archive$n_evals) lg$info("Result:") lg$info(capture.output(print(inst$result, lass = FALSE, row.names = FALSE, print.keys = FALSE))) + call_back("on_optimization_end", inst$callbacks, get_private(inst)$.context) return(inst$result) - - result } ), @@ -92,6 +93,7 @@ OptimizerADBO = R6Class("OptimizerADBO", # evaluate initial design while (rush$n_queued_tasks > 0) { + lg$debug("Evaluating initial design") task = rush$pop_task(fields = "xs") xs_trafoed = trafo_xs(task$xs, inst$search_space) ys = inst$objective$eval(xs_trafoed) @@ -100,6 +102,7 @@ OptimizerADBO = R6Class("OptimizerADBO", # actual loop while (!inst$is_terminated) { + lg$debug("Optimizing") acq_function$surrogate$update() acq_function$update() xdt = acq_optimizer$optimize() From 6a62d753a8a5dee2659023e693a9da31f68563bc Mon Sep 17 00:00:00 2001 From: be-marc Date: Fri, 9 Feb 2024 22:30:13 +0100 Subject: [PATCH 05/49] fix: transformation --- R/OptimizerADBO.R | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index 5dd011c6..a28780dd 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -97,7 +97,7 @@ OptimizerADBO = R6Class("OptimizerADBO", task = rush$pop_task(fields = "xs") xs_trafoed = trafo_xs(task$xs, inst$search_space) ys = inst$objective$eval(xs_trafoed) - rush$push_results(task$key, yss = list(ys), extra = list(list(timestamp_ys = Sys.time(), stage = "initial_design"))) + rush$push_results(task$key, yss = list(ys), extra = list(list(x_domain = list(xs_trafoed), timestamp_ys = Sys.time(), stage = "initial_design"))) } # actual loop @@ -106,10 +106,11 @@ OptimizerADBO = R6Class("OptimizerADBO", acq_function$surrogate$update() acq_function$update() xdt = acq_optimizer$optimize() - xss = transform_xdt_to_xss(xdt, search_space) - keys = rush$push_running_task(xss, extra = list(list(timestamp_xs = Sys.time()))) - ys = inst$objective$eval(xss[[1]]) - rush$push_results(keys, list(ys), extra = list(list(timestamp_ys = Sys.time(), stage = "mbo"))) + xs = transpose_list(xdt) + xs_trafoed = transform_xdt_to_xss(xdt, search_space) + keys = rush$push_running_task(xs, extra = list(list(timestamp_xs = Sys.time()))) + ys = inst$objective$eval(xs_trafoed[[1]]) + rush$push_results(keys, list(ys), extra = list(list(x_domain = xs_trafoed, timestamp_ys = Sys.time(), stage = "mbo"))) } } ) From c62f7c8efa438eb54f5399f874b6944992ba444b Mon Sep 17 00:00:00 2001 From: be-marc Date: Fri, 9 Feb 2024 23:46:33 +0100 Subject: [PATCH 06/49] fix: xdomain --- R/OptimizerADBO.R | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index a28780dd..67f66167 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -106,11 +106,14 @@ OptimizerADBO = R6Class("OptimizerADBO", acq_function$surrogate$update() acq_function$update() xdt = acq_optimizer$optimize() - xs = transpose_list(xdt) - xs_trafoed = transform_xdt_to_xss(xdt, search_space) - keys = rush$push_running_task(xs, extra = list(list(timestamp_xs = Sys.time()))) - ys = inst$objective$eval(xs_trafoed[[1]]) - rush$push_results(keys, list(ys), extra = list(list(x_domain = xs_trafoed, timestamp_ys = Sys.time(), stage = "mbo"))) + xss = transpose_list(xdt) + xs = xss[[1]][inst$archive$cols_x] + xs_trafoed = trafo_xs(xs, search_space) + extra = xss[[1]][c("acq_cb", ".already_evaluated")] + + keys = rush$push_running_task(list(xs), extra = list(list(timestamp_xs = Sys.time()))) + ys = inst$objective$eval(xs_trafoed) + rush$push_results(keys, yss = list(ys), extra = list(c(extra, list(x_domain = list(xs_trafoed), timestamp_ys = Sys.time(), stage = "mbo")))) } } ) From d92dc556aafe896b29019dc3ba84194d3c9c9cbc Mon Sep 17 00:00:00 2001 From: be-marc Date: Sat, 10 Feb 2024 11:42:56 +0100 Subject: [PATCH 07/49] refactor: kill workers --- R/OptimizerADBO.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index 67f66167..b62051ee 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -43,7 +43,7 @@ OptimizerADBO = R6Class("OptimizerADBO", packages = c("bbotk", "mlr3mbo"), optimizer = self, instance = inst, - lgr_thresholds = c(bbotk = "debug"), + lgr_thresholds = c(bbotk = "debug", rush = "debug"), wait_for_workers = TRUE) } else { stop("No rush plan available. See `?rush::rush_plan()`") @@ -63,6 +63,8 @@ OptimizerADBO = R6Class("OptimizerADBO", inst$rush$detect_lost_workers() } + inst$rush$stop_workers(type = "kill") + # assign result private$.assign_result(inst) From 2bb45a97583aa710bae6c12cf4e4db9430e0bcd5 Mon Sep 17 00:00:00 2001 From: be-marc Date: Sat, 10 Feb 2024 23:25:26 +0100 Subject: [PATCH 08/49] feat: cache archive --- R/SurrogateLearner.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/SurrogateLearner.R b/R/SurrogateLearner.R index 35730b01..27e73cc6 100644 --- a/R/SurrogateLearner.R +++ b/R/SurrogateLearner.R @@ -217,7 +217,7 @@ SurrogateLearner = R6Class("SurrogateLearner", .update = function() { if (self$param_set$values$impute_missings) { - xydt = self$archive$rush$fetch_tasks(fields = c("xs", "ys"))[, c(self$cols_x, self$cols_y), with = FALSE] + xydt = self$archive$rush$fetch_active_tasks()[, c(self$cols_x, self$cols_y), with = FALSE] setnafill(xydt, type = "const", fill = mean(xydt[[self$cols_y]], na.rm = TRUE), cols = self$cols_y) } else { xydt = self$archive$data[, c(self$cols_x, self$cols_y), with = FALSE] From 2f0576448e2666fa9ee13dc6c2698ecc4640784a Mon Sep 17 00:00:00 2001 From: be-marc Date: Sat, 10 Feb 2024 23:29:19 +0100 Subject: [PATCH 09/49] chore: debug --- R/OptimizerADBO.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index b62051ee..e3f1a424 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -43,7 +43,7 @@ OptimizerADBO = R6Class("OptimizerADBO", packages = c("bbotk", "mlr3mbo"), optimizer = self, instance = inst, - lgr_thresholds = c(bbotk = "debug", rush = "debug"), + lgr_thresholds = c(bbotk = "debug", rush = "debug", mlr3automl = "debug"), wait_for_workers = TRUE) } else { stop("No rush plan available. See `?rush::rush_plan()`") From 0ed1969c648993557d53bc9db696696cceea787e Mon Sep 17 00:00:00 2001 From: be-marc Date: Mon, 12 Feb 2024 11:29:52 +0100 Subject: [PATCH 10/49] chore: import rush --- DESCRIPTION | 3 ++- NAMESPACE | 1 + R/SurrogateLearner.R | 2 +- R/zzz.R | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 10a6743d..53a61a14 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -50,7 +50,8 @@ Imports: mlr3tuning (>= 0.14.0), paradox (>= 0.10.0), spacefillr, - R6 (>= 2.4.1) + R6 (>= 2.4.1), + rush Suggests: DiceKriging, emoa, diff --git a/NAMESPACE b/NAMESPACE index 19a9927f..4a32731a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -53,6 +53,7 @@ import(mlr3) import(mlr3misc) import(mlr3tuning) import(paradox) +import(rush) import(spacefillr) importFrom(R6,R6Class) importFrom(stats,dnorm) diff --git a/R/SurrogateLearner.R b/R/SurrogateLearner.R index 27e73cc6..a9ca5897 100644 --- a/R/SurrogateLearner.R +++ b/R/SurrogateLearner.R @@ -217,7 +217,7 @@ SurrogateLearner = R6Class("SurrogateLearner", .update = function() { if (self$param_set$values$impute_missings) { - xydt = self$archive$rush$fetch_active_tasks()[, c(self$cols_x, self$cols_y), with = FALSE] + xydt = self$archive$rush$fetch_tasks_with_state(states = c("queued", "running", "finsished"))[, c(self$cols_x, self$cols_y), with = FALSE] setnafill(xydt, type = "const", fill = mean(xydt[[self$cols_y]], na.rm = TRUE), cols = self$cols_y) } else { xydt = self$archive$data[, c(self$cols_x, self$cols_y), with = FALSE] diff --git a/R/zzz.R b/R/zzz.R index 74689172..c8d60cab 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -9,6 +9,7 @@ #' @import lgr #' @import mlr3 #' @import mlr3tuning +#' @import rush #' @importFrom stats setNames runif dnorm pnorm quantile #' @useDynLib mlr3mbo c_sms_indicator c_eps_indicator "_PACKAGE" From b7217e42a2f258ef31771e0c9ef9bed628a05e38 Mon Sep 17 00:00:00 2001 From: be-marc Date: Mon, 12 Feb 2024 12:42:30 +0100 Subject: [PATCH 11/49] feat: add logging --- R/OptimizerADBO.R | 9 +++++---- R/SurrogateLearner.R | 3 +-- tests/testthat/test_OptimizerADBO.R | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index e3f1a424..5630aec7 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -43,7 +43,6 @@ OptimizerADBO = R6Class("OptimizerADBO", packages = c("bbotk", "mlr3mbo"), optimizer = self, instance = inst, - lgr_thresholds = c(bbotk = "debug", rush = "debug", mlr3automl = "debug"), wait_for_workers = TRUE) } else { stop("No rush plan available. See `?rush::rush_plan()`") @@ -93,26 +92,28 @@ OptimizerADBO = R6Class("OptimizerADBO", acq_function$surrogate = surrogate acq_optimizer$acq_function = acq_function + lg$debug("Optimizer '%s' evaluates the initial design", self$id) + # evaluate initial design while (rush$n_queued_tasks > 0) { - lg$debug("Evaluating initial design") task = rush$pop_task(fields = "xs") xs_trafoed = trafo_xs(task$xs, inst$search_space) ys = inst$objective$eval(xs_trafoed) rush$push_results(task$key, yss = list(ys), extra = list(list(x_domain = list(xs_trafoed), timestamp_ys = Sys.time(), stage = "initial_design"))) } + lg$debug("Optimizer '%s' starts the tuning phase", self$id) + # actual loop while (!inst$is_terminated) { - lg$debug("Optimizing") acq_function$surrogate$update() acq_function$update() xdt = acq_optimizer$optimize() xss = transpose_list(xdt) xs = xss[[1]][inst$archive$cols_x] + lg$trace("Optimizer '%s' draws %s", self$id, as_short_string(xs)) xs_trafoed = trafo_xs(xs, search_space) extra = xss[[1]][c("acq_cb", ".already_evaluated")] - keys = rush$push_running_task(list(xs), extra = list(list(timestamp_xs = Sys.time()))) ys = inst$objective$eval(xs_trafoed) rush$push_results(keys, yss = list(ys), extra = list(c(extra, list(x_domain = list(xs_trafoed), timestamp_ys = Sys.time(), stage = "mbo")))) diff --git a/R/SurrogateLearner.R b/R/SurrogateLearner.R index a9ca5897..d1981362 100644 --- a/R/SurrogateLearner.R +++ b/R/SurrogateLearner.R @@ -215,9 +215,8 @@ SurrogateLearner = R6Class("SurrogateLearner", # Train learner with new data. # Also calculates the insample performance based on the `perf_measure` hyperparameter if `assert_insample_perf = TRUE`. .update = function() { - if (self$param_set$values$impute_missings) { - xydt = self$archive$rush$fetch_tasks_with_state(states = c("queued", "running", "finsished"))[, c(self$cols_x, self$cols_y), with = FALSE] + xydt = self$archive$rush$fetch_tasks_with_state(states = c("queued", "running", "finished"))[, c(self$cols_x, self$cols_y), with = FALSE] setnafill(xydt, type = "const", fill = mean(xydt[[self$cols_y]], na.rm = TRUE), cols = self$cols_y) } else { xydt = self$archive$data[, c(self$cols_x, self$cols_y), with = FALSE] diff --git a/tests/testthat/test_OptimizerADBO.R b/tests/testthat/test_OptimizerADBO.R index 5dbe0100..fb443459 100644 --- a/tests/testthat/test_OptimizerADBO.R +++ b/tests/testthat/test_OptimizerADBO.R @@ -19,6 +19,8 @@ test_that("search works with decentralized network", { optimizer = tnr("adbo", init_design_size = 4) optimizer$optimize(instance) + # instance$rush = RushWorker$new(instance$rush$network_id, host = "local") + # inst = instance # instance$archive$data[order(timestamp_ys)] # instance$rush$processes[[1]]$read_all_error_lines() # instance$rush$read_log() From 6982d12132e79bd1e4bd4cd88de9740375e0ab3a Mon Sep 17 00:00:00 2001 From: be-marc Date: Mon, 12 Feb 2024 15:35:29 +0100 Subject: [PATCH 12/49] refactor: use optimize_decentralized() --- R/OptimizerADBO.R | 60 +++++++---------------------- tests/testthat/test_OptimizerADBO.R | 3 +- 2 files changed, 14 insertions(+), 49 deletions(-) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index 5630aec7..90e9a14b 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -21,65 +21,31 @@ OptimizerADBO = R6Class("OptimizerADBO", optimize = function(inst) { - if (rush_available()) { - inst$archive$start_time = Sys.time() - inst$.__enclos_env__$private$.context = ContextOptimization$new(instance = inst, optimizer = self) - call_back("on_optimization_begin", inst$callbacks, get_private(inst)$.context) - - # generate initial design - pv = self$param_set$values - design = if (is.null(pv$initial_design)) { - generate_design_sobol(inst$search_space, n = pv$init_design_size)$data - } else { - pv$initial_design - } - - # send initial design to workers - inst$rush$push_tasks(transpose_list(design), extra = list(list(timestamp_xs = Sys.time()))) - - # start rush workers - inst$rush$start_workers( - worker_loop = bbotk_worker_loop_decentralized, - packages = c("bbotk", "mlr3mbo"), - optimizer = self, - instance = inst, - wait_for_workers = TRUE) + # generate initial design + pv = self$param_set$values + design = if (is.null(pv$initial_design)) { + generate_design_sobol(inst$search_space, n = pv$init_design_size)$data } else { - stop("No rush plan available. See `?rush::rush_plan()`") + pv$initial_design } - lg$info("Starting to optimize %i parameter(s) with '%s' and '%s' on %i worker(s)", - inst$search_space$length, - self$format(), - inst$terminator$format(with_params = TRUE), - inst$rush$n_running_workers - ) + # send initial design to workers + inst$rush$push_tasks(transpose_list(design), extra = list(list(timestamp_xs = Sys.time()))) - # wait - while(!inst$is_terminated) { - Sys.sleep(1) - inst$rush$print_log() - inst$rush$detect_lost_workers() - } + # optimize + inst$archive$start_time = Sys.time() + result = optimize_decentralized(inst, self, private) + # FIXME: kill workers to increase the chance of a fitting the final model inst$rush$stop_workers(type = "kill") - # assign result - private$.assign_result(inst) - - # assign result - private$.assign_result(inst) - lg$info("Finished optimizing after %i evaluation(s)", inst$archive$n_evals) - lg$info("Result:") - lg$info(capture.output(print(inst$result, lass = FALSE, row.names = FALSE, print.keys = FALSE))) - call_back("on_optimization_end", inst$callbacks, get_private(inst)$.context) - return(inst$result) + result } ), private = list( - .optimize_remote = function(inst) { + .optimize = function(inst) { search_space = inst$search_space rush = inst$rush diff --git a/tests/testthat/test_OptimizerADBO.R b/tests/testthat/test_OptimizerADBO.R index fb443459..3bdc9371 100644 --- a/tests/testthat/test_OptimizerADBO.R +++ b/tests/testthat/test_OptimizerADBO.R @@ -1,7 +1,6 @@ test_that("search works with decentralized network", { flush_redis() - library(rush) - rush_plan(n_workers = 2) + rush::rush_plan(n_workers = 2) learner = lrn("classif.rpart", minsplit = to_tune(2, 128), From 0363e5d24d81416a9e939a53bfd46a3ee6bb7481 Mon Sep 17 00:00:00 2001 From: be-marc Date: Mon, 19 Feb 2024 07:49:01 +0100 Subject: [PATCH 13/49] feat: add exponential decay --- R/OptimizerADBO.R | 71 +++++++++++++++++++-- R/TunerADBO.R | 28 +++++++++ man/mlr_optimizers_adbo.Rd | 98 +++++++++++++++++++++++++++++ man/mlr_tuners_adbo.Rd | 76 ++++++++++++++++++++++ tests/testthat/test_OptimizerADBO.R | 55 +++++++++++----- 5 files changed, 308 insertions(+), 20 deletions(-) create mode 100644 man/mlr_optimizers_adbo.Rd create mode 100644 man/mlr_tuners_adbo.Rd diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index 90e9a14b..98ff90d5 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -1,30 +1,74 @@ +#' @title Asynchronous Decentralized Bayesian Optimization +#' @name mlr_optimizers_adbo +#' +#' @description +#' Asynchronous Decentralized Bayesian Optimization (ADBO). +#' +#' @notes +#' The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. +#' A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. +#' ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. +#' +#' @section Parameters: +#' \describe{ +#' \item{`lambda`}{`numeric(1)`\cr +#' \eqn{\lambda} value used for the confidence bound. +#' Defaults to `1.96`.} +#' \item{`exponential_decay`}{`lgl(1)`\cr +#' Whether to use periodic exponential decay for \eqn{\lambda}.} +#' \item{`rate`}{`numeric(1)`\cr +#' Rate of the exponential decay.} +#' \item{`t`}{`integer(1)`\cr +#' Period of the exponential decay.} +#' \item{`initial_design_size`}{`integer(1)`\cr +#' Size of the initial design.} +#' \item{`initial_design`}{`data.table`\cr +#' Initial design.} +#' } +#' #' @export OptimizerADBO = R6Class("OptimizerADBO", inherit = bbotk::Optimizer, public = list( + #' @description + #' Creates a new instance of this [R6][R6::R6Class] class. initialize = function() { param_set = ps( - init_design_size = p_int(lower = 1L), + lambda = p_dbl(lower = 0, default = 1.96), + exponential_decay = p_lgl(default = TRUE), + rate = p_dbl(lower = 0, default = 0.1), + period = p_int(lower = 1L, default = 25L), + design_size = p_int(lower = 1L), initial_design = p_uty() ) + param_set$set_values(lambda = 1.96, exponential_decay = TRUE, rate = 0.1, period = 25L, design_size = 1L) + super$initialize("adbo", param_set = param_set, param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), - properties = c("dependencies", "multi-crit", "single-crit"), + properties = c("dependencies", "single-crit"), packages = "mlr3mbo", label = "Asynchronous Decentralized Bayesian Optimization", man = "mlr3mbo::OptimizerADBO") }, + + #' @description + #' Performs the optimization and writes optimization result into + #' [OptimInstance]. The optimization result is returned but the complete + #' optimization path is stored in [Archive] of [OptimInstance]. + #' + #' @param inst ([OptimInstance]). + #' @return [data.table::data.table]. optimize = function(inst) { # generate initial design pv = self$param_set$values design = if (is.null(pv$initial_design)) { - generate_design_sobol(inst$search_space, n = pv$init_design_size)$data + generate_design_sobol(inst$search_space, n = pv$design_size)$data } else { pv$initial_design } @@ -46,9 +90,14 @@ OptimizerADBO = R6Class("OptimizerADBO", private = list( .optimize = function(inst) { + pv = self$param_set$values search_space = inst$search_space rush = inst$rush + # sample lambda from exponential distribution + lambda_0 = rexp(1, 1 / pv$lambda) + t = 0 + surrogate = default_surrogate(inst) surrogate$param_set$set_values(impute_missings = TRUE) acq_function = acqf("cb", lambda = runif(1, 1 , 3)) @@ -72,6 +121,15 @@ OptimizerADBO = R6Class("OptimizerADBO", # actual loop while (!inst$is_terminated) { + + if (pv$exponential_decay) { + lambda = lambda_0 * exp(-pv$rate * (t %% pv$period)) + t = t + 1 + } else { + lambda = pv$lambda + } + + acq_function$constants$set_values(lambda = lambda) acq_function$surrogate$update() acq_function$update() xdt = acq_optimizer$optimize() @@ -82,7 +140,12 @@ OptimizerADBO = R6Class("OptimizerADBO", extra = xss[[1]][c("acq_cb", ".already_evaluated")] keys = rush$push_running_task(list(xs), extra = list(list(timestamp_xs = Sys.time()))) ys = inst$objective$eval(xs_trafoed) - rush$push_results(keys, yss = list(ys), extra = list(c(extra, list(x_domain = list(xs_trafoed), timestamp_ys = Sys.time(), stage = "mbo")))) + rush$push_results(keys, yss = list(ys), extra = list(c(extra, list( + x_domain = list(xs_trafoed), + timestamp_ys = Sys.time(), + stage = "mbo", + lambda_0 = lambda_0, + lambda = lambda)))) } } ) diff --git a/R/TunerADBO.R b/R/TunerADBO.R index e0fa1f04..fc51bbf9 100644 --- a/R/TunerADBO.R +++ b/R/TunerADBO.R @@ -1,3 +1,31 @@ +#' @title Asynchronous Decentralized Bayesian Optimization +#' @name mlr_tuners_adbo +#' +#' @description +#' Asynchronous Decentralized Bayesian Optimization (ADBO). +#' +#' @notes +#' The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. +#' A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. +#' ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. +#' +#' @section Parameters: +#' \describe{ +#' \item{`lambda`}{`numeric(1)`\cr +#' \eqn{\lambda} value used for the confidence bound. +#' Defaults to `1.96`.} +#' \item{`exponential_decay`}{`lgl(1)`\cr +#' Whether to use periodic exponential decay for \eqn{\lambda}.} +#' \item{`rate`}{`numeric(1)`\cr +#' Rate of the exponential decay.} +#' \item{`t`}{`integer(1)`\cr +#' Period of the exponential decay.} +#' \item{`initial_design_size`}{`integer(1)`\cr +#' Size of the initial design.} +#' \item{`initial_design`}{`data.table`\cr +#' Initial design.} +#' } +#' #' @export TunerADBO = R6Class("TunerADBO", inherit = mlr3tuning::TunerFromOptimizer, diff --git a/man/mlr_optimizers_adbo.Rd b/man/mlr_optimizers_adbo.Rd new file mode 100644 index 00000000..9b91b3f9 --- /dev/null +++ b/man/mlr_optimizers_adbo.Rd @@ -0,0 +1,98 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/OptimizerADBO.R +\name{mlr_optimizers_adbo} +\alias{mlr_optimizers_adbo} +\alias{OptimizerADBO} +\title{Asynchronous Decentralized Bayesian Optimization} +\description{ +Asynchronous Decentralized Bayesian Optimization (ADBO). +} +\section{Parameters}{ + +\describe{ +\item{\code{lambda}}{\code{numeric(1)}\cr +\eqn{\lambda} value used for the confidence bound. +Defaults to \code{1.96}.} +\item{\code{exponential_decay}}{\code{lgl(1)}\cr +Whether to use periodic exponential decay for \eqn{\lambda}.} +\item{\code{rate}}{\code{numeric(1)}\cr +Rate of the exponential decay.} +\item{\code{t}}{\code{integer(1)}\cr +Period of the exponential decay.} +\item{\code{initial_design_size}}{\code{integer(1)}\cr +Size of the initial design.} +\item{\code{initial_design}}{\code{data.table}\cr +Initial design.} +} +} + +\section{Super class}{ +\code{\link[bbotk:Optimizer]{bbotk::Optimizer}} -> \code{OptimizerADBO} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-OptimizerADBO-new}{\code{OptimizerADBO$new()}} +\item \href{#method-OptimizerADBO-optimize}{\code{OptimizerADBO$optimize()}} +\item \href{#method-OptimizerADBO-clone}{\code{OptimizerADBO$clone()}} +} +} +\if{html}{\out{ +
Inherited methods + +
+}} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OptimizerADBO-new}{}}} +\subsection{Method \code{new()}}{ +Creates a new instance of this \link[R6:R6Class]{R6} class. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{OptimizerADBO$new()}\if{html}{\out{
}} +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OptimizerADBO-optimize}{}}} +\subsection{Method \code{optimize()}}{ +Performs the optimization and writes optimization result into +\link{OptimInstance}. The optimization result is returned but the complete +optimization path is stored in \link{Archive} of \link{OptimInstance}. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{OptimizerADBO$optimize(inst)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{inst}}{(\link{OptimInstance}).} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +\link[data.table:data.table]{data.table::data.table}. +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OptimizerADBO-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{OptimizerADBO$clone(deep = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} +} diff --git a/man/mlr_tuners_adbo.Rd b/man/mlr_tuners_adbo.Rd new file mode 100644 index 00000000..adf79f94 --- /dev/null +++ b/man/mlr_tuners_adbo.Rd @@ -0,0 +1,76 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/TunerADBO.R +\name{mlr_tuners_adbo} +\alias{mlr_tuners_adbo} +\alias{TunerADBO} +\title{Asynchronous Decentralized Bayesian Optimization} +\description{ +Asynchronous Decentralized Bayesian Optimization (ADBO). +} +\section{Parameters}{ + +\describe{ +\item{\code{lambda}}{\code{numeric(1)}\cr +\eqn{\lambda} value used for the confidence bound. +Defaults to \code{1.96}.} +\item{\code{exponential_decay}}{\code{lgl(1)}\cr +Whether to use periodic exponential decay for \eqn{\lambda}.} +\item{\code{rate}}{\code{numeric(1)}\cr +Rate of the exponential decay.} +\item{\code{t}}{\code{integer(1)}\cr +Period of the exponential decay.} +\item{\code{initial_design_size}}{\code{integer(1)}\cr +Size of the initial design.} +\item{\code{initial_design}}{\code{data.table}\cr +Initial design.} +} +} + +\section{Super classes}{ +\code{\link[mlr3tuning:Tuner]{mlr3tuning::Tuner}} -> \code{\link[mlr3tuning:TunerFromOptimizer]{mlr3tuning::TunerFromOptimizer}} -> \code{TunerADBO} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-TunerADBO-new}{\code{TunerADBO$new()}} +\item \href{#method-TunerADBO-clone}{\code{TunerADBO$clone()}} +} +} +\if{html}{\out{ +
Inherited methods + +
+}} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TunerADBO-new}{}}} +\subsection{Method \code{new()}}{ +Creates a new instance of this \link[R6:R6Class]{R6} class. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TunerADBO$new()}\if{html}{\out{
}} +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TunerADBO-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TunerADBO$clone(deep = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} +} diff --git a/tests/testthat/test_OptimizerADBO.R b/tests/testthat/test_OptimizerADBO.R index 3bdc9371..c2e0bb3c 100644 --- a/tests/testthat/test_OptimizerADBO.R +++ b/tests/testthat/test_OptimizerADBO.R @@ -1,4 +1,37 @@ -test_that("search works with decentralized network", { +test_that("adbo optimizer works", { + #options("bbotk_local" = TRUE) + rush::rush_plan(n_workers = 5) + + search_space = domain = ps( + x1 = p_dbl(-5, 10), + x2 = p_dbl(0, 15) + ) + + codomain = ps(y = p_dbl(tags = "minimize")) + + fun = function(xs) { + list(y = branin(x1 = xs$x1, x2 = xs$x2)) + } + + objective = ObjectiveRFun$new( + fun = fun, + domain = domain, + codomain = codomain + ) + + instance = OptimInstanceRushSingleCrit$new( + objective = objective, + search_space = search_space, + terminator = trm("evals", n_evals = 100) + ) + + optimizer = opt("adbo", design_size = 4) + optimizer$optimize(instance) +}) + + +test_that("adbo tuner works", { + #options("bbotk_local" = TRUE) flush_redis() rush::rush_plan(n_workers = 2) @@ -18,21 +51,13 @@ test_that("search works with decentralized network", { optimizer = tnr("adbo", init_design_size = 4) optimizer$optimize(instance) - # instance$rush = RushWorker$new(instance$rush$network_id, host = "local") - # inst = instance - # instance$archive$data[order(timestamp_ys)] - # instance$rush$processes[[1]]$read_all_error_lines() - # instance$rush$read_log() - - expect_data_table(instance$archive$data, min.rows = 3L) - + expect_data_table(instance$archive$data, min.rows = 20L) expect_rush_reset(instance$rush) }) -test_that("search works with transformation functions", { +test_that("adbo works with transformation functions", { flush_redis() - library(rush) - rush_plan(n_workers = 2) + rush::rush_plan(n_workers = 2) learner = lrn("classif.rpart", minsplit = to_tune(2, 128, logscale = TRUE), @@ -57,8 +82,7 @@ test_that("search works with transformation functions", { test_that("search works with dependencies", { flush_redis() - library(rush) - rush_plan(n_workers = 2) + rush::rush_plan(n_workers = 2) learner = lrn("classif.rpart", minsplit = to_tune(p_int(2, 128, depends = keep_model == TRUE)), @@ -84,9 +108,8 @@ test_that("search works with dependencies", { test_that("adbo works with branching", { flush_redis() - library(rush) + rush::rush_plan(n_workers = 2) library(mlr3pipelines) - rush_plan(n_workers = 2) graph_learner = as_learner(ppl("branch", graphs = list(rpart = lrn("classif.rpart", id = "rpart"),debug = lrn("classif.debug", id = "debug")))) graph_learner$param_set$set_values( From c48a4848ced9eff919cd7203244eea23ae50c1f3 Mon Sep 17 00:00:00 2001 From: be-marc Date: Mon, 19 Feb 2024 11:43:28 +0100 Subject: [PATCH 14/49] feat: add min-max imputation --- R/OptimizerADBO.R | 7 ++++--- R/SurrogateLearner.R | 14 +++++++++++--- tests/testthat/test_OptimizerADBO.R | 8 ++++---- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index 98ff90d5..380cf1a4 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -41,10 +41,11 @@ OptimizerADBO = R6Class("OptimizerADBO", rate = p_dbl(lower = 0, default = 0.1), period = p_int(lower = 1L, default = 25L), design_size = p_int(lower = 1L), - initial_design = p_uty() + initial_design = p_uty(), + impute_method = p_fct(c("mean", "random"), default = "random") ) - param_set$set_values(lambda = 1.96, exponential_decay = TRUE, rate = 0.1, period = 25L, design_size = 1L) + param_set$set_values(lambda = 1.96, exponential_decay = TRUE, rate = 0.1, period = 25L, design_size = 1L, impute_method = "random") super$initialize("adbo", param_set = param_set, @@ -99,7 +100,7 @@ OptimizerADBO = R6Class("OptimizerADBO", t = 0 surrogate = default_surrogate(inst) - surrogate$param_set$set_values(impute_missings = TRUE) + surrogate$param_set$set_values(impute_missings = pv$impute_method) acq_function = acqf("cb", lambda = runif(1, 1 , 3)) acq_optimizer = acqo(opt("random_search", batch_size = 1000L), terminator = trm("evals", n_evals = 10000L)) diff --git a/R/SurrogateLearner.R b/R/SurrogateLearner.R index d1981362..ac88c451 100644 --- a/R/SurrogateLearner.R +++ b/R/SurrogateLearner.R @@ -88,9 +88,9 @@ SurrogateLearner = R6Class("SurrogateLearner", ParamUty$new("perf_measure", custom_check = function(x) check_r6(x, classes = "MeasureRegr")), # FIXME: actually want check_measure ParamDbl$new("perf_threshold", lower = -Inf, upper = Inf), ParamLgl$new("catch_errors"), - ParamLgl$new("impute_missings")) + ParamFct$new("impute_missings", levels = c("none", "mean", "random"))) ) - ps$values = list(assert_insample_perf = FALSE, catch_errors = TRUE, impute_missings = FALSE) + ps$values = list(assert_insample_perf = FALSE, catch_errors = TRUE, impute_missings = "none") ps$add_dep("perf_measure", on = "assert_insample_perf", cond = CondEqual$new(TRUE)) ps$add_dep("perf_threshold", on = "assert_insample_perf", cond = CondEqual$new(TRUE)) @@ -215,9 +215,17 @@ SurrogateLearner = R6Class("SurrogateLearner", # Train learner with new data. # Also calculates the insample performance based on the `perf_measure` hyperparameter if `assert_insample_perf = TRUE`. .update = function() { - if (self$param_set$values$impute_missings) { + if (self$param_set$values$impute_missings == "mean") { xydt = self$archive$rush$fetch_tasks_with_state(states = c("queued", "running", "finished"))[, c(self$cols_x, self$cols_y), with = FALSE] setnafill(xydt, type = "const", fill = mean(xydt[[self$cols_y]], na.rm = TRUE), cols = self$cols_y) + } else if (self$param_set$values$impute_missings == "random") { + xydt = self$archive$rush$fetch_tasks_with_state(states = c("queued", "running", "finished"))[, c(self$cols_x, self$cols_y, "state"), with = FALSE] + walk(self$cols_y, function(col) { + min = min(xydt[[col]], na.rm = TRUE) + max = max(xydt[[col]], na.rm = TRUE) + xydt[state %in% c("queued", "running"), (col) := runif(.N, min, max)] + }) + set(xydt, j = "state", value = NULL) } else { xydt = self$archive$data[, c(self$cols_x, self$cols_y), with = FALSE] } diff --git a/tests/testthat/test_OptimizerADBO.R b/tests/testthat/test_OptimizerADBO.R index c2e0bb3c..4c6cee1b 100644 --- a/tests/testthat/test_OptimizerADBO.R +++ b/tests/testthat/test_OptimizerADBO.R @@ -1,5 +1,5 @@ test_that("adbo optimizer works", { - #options("bbotk_local" = TRUE) + options("bbotk_local" = TRUE) rush::rush_plan(n_workers = 5) search_space = domain = ps( @@ -31,9 +31,9 @@ test_that("adbo optimizer works", { test_that("adbo tuner works", { - #options("bbotk_local" = TRUE) + # options("bbotk_local" = TRUE) flush_redis() - rush::rush_plan(n_workers = 2) + rush::rush_plan(n_workers = 5) learner = lrn("classif.rpart", minsplit = to_tune(2, 128), @@ -48,7 +48,7 @@ test_that("adbo tuner works", { store_benchmark_result = FALSE ) - optimizer = tnr("adbo", init_design_size = 4) + optimizer = tnr("adbo", design_size = 4) optimizer$optimize(instance) expect_data_table(instance$archive$data, min.rows = 20L) From c8784aad79cdccb349e0932a5ace2b9feb0bbe23 Mon Sep 17 00:00:00 2001 From: be-marc Date: Thu, 22 Feb 2024 09:32:12 +0100 Subject: [PATCH 15/49] feat: add n_worker parameter --- R/OptimizerADBO.R | 3 ++- tests/testthat/test_OptimizerADBO.R | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index 380cf1a4..c28c2f00 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -42,7 +42,8 @@ OptimizerADBO = R6Class("OptimizerADBO", period = p_int(lower = 1L, default = 25L), design_size = p_int(lower = 1L), initial_design = p_uty(), - impute_method = p_fct(c("mean", "random"), default = "random") + impute_method = p_fct(c("mean", "random"), default = "random"), + n_workers = p_int(lower = 1L) ) param_set$set_values(lambda = 1.96, exponential_decay = TRUE, rate = 0.1, period = 25L, design_size = 1L, impute_method = "random") diff --git a/tests/testthat/test_OptimizerADBO.R b/tests/testthat/test_OptimizerADBO.R index 4c6cee1b..51bba643 100644 --- a/tests/testthat/test_OptimizerADBO.R +++ b/tests/testthat/test_OptimizerADBO.R @@ -55,6 +55,33 @@ test_that("adbo tuner works", { expect_rush_reset(instance$rush) }) +test_that("adbo tuner works with limited number of workers", { + # options("bbotk_local" = TRUE) + lgr::get_logger("rush")$set_threshold("debug") + + flush_redis() + rush::rush_plan(n_workers = 5) + + learner = lrn("classif.rpart", + minsplit = to_tune(2, 128), + cp = to_tune(1e-04, 1e-1)) + + instance = TuningInstanceRushSingleCrit$new( + task = tsk("pima"), + learner = learner, + resampling = rsmp("cv", folds = 3), + measure = msr("classif.ce"), + terminator = trm("evals", n_evals = 20), + store_benchmark_result = FALSE + ) + + optimizer = tnr("adbo", design_size = 4, n_workers = 2) + optimizer$optimize(instance) + + expect_data_table(instance$archive$data, min.rows = 20L) + expect_rush_reset(instance$rush) +}) + test_that("adbo works with transformation functions", { flush_redis() rush::rush_plan(n_workers = 2) From b97bd18b6f59f7f7c251c4ffc231deae8c2611d7 Mon Sep 17 00:00:00 2001 From: be-marc Date: Fri, 26 Apr 2024 12:04:54 +0200 Subject: [PATCH 16/49] draft --- R/AcqFunction.R | 2 +- R/AcqFunctionAEI.R | 2 +- R/AcqFunctionCB.R | 2 +- R/AcqFunctionEHVI.R | 4 +- R/AcqFunctionEHVIGH.R | 10 ++-- R/AcqFunctionEI.R | 2 +- R/AcqFunctionEIPS.R | 6 +-- R/AcqFunctionMean.R | 2 +- R/AcqFunctionPI.R | 2 +- R/AcqFunctionSD.R | 2 +- R/AcqFunctionSmsEgo.R | 2 +- R/AcqOptimizer.R | 4 +- R/OptimizerADBO.R | 65 +++++++++++------------- R/OptimizerMbo.R | 4 +- R/ResultAssigner.R | 2 +- R/ResultAssignerArchive.R | 4 +- R/ResultAssignerSurrogate.R | 12 ++--- R/Surrogate.R | 2 +- R/SurrogateLearner.R | 2 +- R/SurrogateLearnerCollection.R | 2 +- R/TunerADBO.R | 4 +- R/TunerMbo.R | 4 +- R/bayesopt_ego.R | 12 ++--- R/bayesopt_emo.R | 10 ++-- R/bayesopt_mpcl.R | 12 ++--- R/bayesopt_parego.R | 14 +++--- R/bayesopt_smsego.R | 12 ++--- R/helper.R | 4 +- R/mbo_defaults.R | 16 +++--- man/AcqOptimizer.Rd | 2 +- man/ResultAssigner.Rd | 2 +- man/SurrogateLearner.Rd | 2 +- man/SurrogateLearnerCollection.Rd | 2 +- man/default_acqoptimizer.Rd | 2 +- man/default_surrogate.Rd | 4 +- man/mlr_acqfunctions_aei.Rd | 2 +- man/mlr_acqfunctions_cb.Rd | 2 +- man/mlr_acqfunctions_ehvi.Rd | 2 +- man/mlr_acqfunctions_ehvigh.Rd | 2 +- man/mlr_acqfunctions_ei.Rd | 2 +- man/mlr_acqfunctions_eips.Rd | 6 +-- man/mlr_acqfunctions_mean.Rd | 2 +- man/mlr_acqfunctions_pi.Rd | 2 +- man/mlr_acqfunctions_sd.Rd | 2 +- man/mlr_acqfunctions_smsego.Rd | 2 +- man/mlr_loop_functions_ego.Rd | 10 ++-- man/mlr_loop_functions_emo.Rd | 8 +-- man/mlr_loop_functions_mpcl.Rd | 10 ++-- man/mlr_loop_functions_parego.Rd | 12 ++--- man/mlr_loop_functions_smsego.Rd | 10 ++-- man/mlr_optimizers_adbo.Rd | 19 ++++--- man/mlr_optimizers_mbo.Rd | 5 +- man/mlr_result_assigners_archive.Rd | 2 +- man/mlr_result_assigners_surrogate.Rd | 4 +- man/mlr_tuners_adbo.Rd | 9 +++- man/mlr_tuners_mbo.Rd | 6 +-- tests/testthat/helper.R | 4 +- tests/testthat/test_AcqOptimizer.R | 2 +- tests/testthat/test_OptimizerADBO.R | 72 ++++++--------------------- tests/testthat/test_bayesopt_ego.R | 2 +- vignettes/mlr3mbo.Rmd | 10 ++-- 61 files changed, 202 insertions(+), 242 deletions(-) diff --git a/R/AcqFunction.R b/R/AcqFunction.R index 6b21f46d..6ac53e03 100644 --- a/R/AcqFunction.R +++ b/R/AcqFunction.R @@ -176,7 +176,7 @@ AcqFunction = R6Class("AcqFunction", stopf("Acquisition function '%s' requires the surrogate to have `\"se\"` as `$predict_type`.", format(self)) } private$.surrogate = rhs - private$.archive = assert_multi_class(rhs$archive, c("Archive", "ArchiveRush")) + private$.archive = assert_archive(rhs$archive) codomain = generate_acq_codomain(rhs, id = self$id, direction = self$direction) self$surrogate_max_to_min = surrogate_mult_max_to_min(rhs) domain = generate_acq_domain(rhs) diff --git a/R/AcqFunctionAEI.R b/R/AcqFunctionAEI.R index 164bdfbd..5521cf98 100644 --- a/R/AcqFunctionAEI.R +++ b/R/AcqFunctionAEI.R @@ -40,7 +40,7 @@ #' codomain = codomain, #' properties = "noisy") #' -#' instance = OptimInstanceSingleCrit$new( +#' instance = OptimInstanceBatchSingleCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' diff --git a/R/AcqFunctionCB.R b/R/AcqFunctionCB.R index 06417142..14a980c3 100644 --- a/R/AcqFunctionCB.R +++ b/R/AcqFunctionCB.R @@ -35,7 +35,7 @@ #' codomain = ps(y = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceSingleCrit$new( +#' instance = OptimInstanceBatchSingleCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' diff --git a/R/AcqFunctionEHVI.R b/R/AcqFunctionEHVI.R index 37e4a6c7..63b73da7 100644 --- a/R/AcqFunctionEHVI.R +++ b/R/AcqFunctionEHVI.R @@ -30,7 +30,7 @@ #' codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceMultiCrit$new( +#' instance = OptimInstanceBatchMultiCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' @@ -117,7 +117,7 @@ AcqFunctionEHVI = R6Class("AcqFunctionEHVI", } columns = colnames(self$ys_front_augmented) - + ps = self$surrogate$predict(xdt) means = map_dtc(ps, "mean") diff --git a/R/AcqFunctionEHVIGH.R b/R/AcqFunctionEHVIGH.R index afd9c10c..4ee1ade1 100644 --- a/R/AcqFunctionEHVIGH.R +++ b/R/AcqFunctionEHVIGH.R @@ -41,7 +41,7 @@ #' codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceMultiCrit$new( +#' instance = OptimInstanceBatchMultiCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' @@ -175,20 +175,20 @@ adjust_gh_data = function(gh_data, mu, sigma, r) { idx = as.matrix(expand.grid(rep(list(1:n), n_obj))) nodes = matrix(gh_data[idx, 1L], nrow = nrow(idx), ncol = n_obj) weights = apply(matrix(gh_data[idx, 2L], nrow = nrow(idx), ncol = n_obj), MARGIN = 1L, FUN = prod) - - # pruning with pruning rate r + + # pruning with pruning rate r if (r > 0) { weights_quantile = quantile(weights, probs = r) nodes = nodes[weights > weights_quantile, ] weights = weights[weights > weights_quantile] } - + # rotate, scale, translate nodes with error catching # rotation will not have an effect unless we support surrogate models modelling correlated objectives # for now we still support this more general case and scaling is useful anyways nodes = tryCatch( { - eigen_decomp = eigen(sigma) + eigen_decomp = eigen(sigma) rotation = eigen_decomp$vectors %*% diag(sqrt(eigen_decomp$values)) nodes = t(rotation %*% t(nodes) + mu) }, error = function(ec) nodes diff --git a/R/AcqFunctionEI.R b/R/AcqFunctionEI.R index b5e3a1e7..1782b972 100644 --- a/R/AcqFunctionEI.R +++ b/R/AcqFunctionEI.R @@ -30,7 +30,7 @@ #' codomain = ps(y = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceSingleCrit$new( +#' instance = OptimInstanceBatchSingleCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' diff --git a/R/AcqFunctionEIPS.R b/R/AcqFunctionEIPS.R index 5e5a9b6f..724dd13e 100644 --- a/R/AcqFunctionEIPS.R +++ b/R/AcqFunctionEIPS.R @@ -9,9 +9,9 @@ #' @description #' Expected Improvement per Second. #' -#' It is assumed that calculations are performed on an [bbotk::OptimInstanceSingleCrit]. +#' It is assumed that calculations are performed on an [bbotk::OptimInstanceBatchSingleCrit]. #' Additionally to target values of the codomain that should be minimized or maximized, the -#' [bbotk::Objective] of the [bbotk::OptimInstanceSingleCrit] should return time values. +#' [bbotk::Objective] of the [bbotk::OptimInstanceBatchSingleCrit] should return time values. #' The column names of the target variable and time variable must be passed as `cols_y` in the #' order `(target, time)` when constructing the [SurrogateLearnerCollection] that is being used as a #' surrogate. @@ -37,7 +37,7 @@ #' codomain = ps(y = p_dbl(tags = "minimize"), time = p_dbl(tags = "time")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceSingleCrit$new( +#' instance = OptimInstanceBatchSingleCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' diff --git a/R/AcqFunctionMean.R b/R/AcqFunctionMean.R index d4658582..c42e0cc3 100644 --- a/R/AcqFunctionMean.R +++ b/R/AcqFunctionMean.R @@ -27,7 +27,7 @@ #' codomain = ps(y = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceSingleCrit$new( +#' instance = OptimInstanceBatchSingleCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' diff --git a/R/AcqFunctionPI.R b/R/AcqFunctionPI.R index acce80d1..0bed7196 100644 --- a/R/AcqFunctionPI.R +++ b/R/AcqFunctionPI.R @@ -30,7 +30,7 @@ #' codomain = ps(y = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceSingleCrit$new( +#' instance = OptimInstanceBatchSingleCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' diff --git a/R/AcqFunctionSD.R b/R/AcqFunctionSD.R index fcbf7b44..2006ebd6 100644 --- a/R/AcqFunctionSD.R +++ b/R/AcqFunctionSD.R @@ -27,7 +27,7 @@ #' codomain = ps(y = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceSingleCrit$new( +#' instance = OptimInstanceBatchSingleCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' diff --git a/R/AcqFunctionSmsEgo.R b/R/AcqFunctionSmsEgo.R index fa0ecef7..3e2b18bf 100644 --- a/R/AcqFunctionSmsEgo.R +++ b/R/AcqFunctionSmsEgo.R @@ -40,7 +40,7 @@ #' codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceMultiCrit$new( +#' instance = OptimInstanceBatchMultiCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' diff --git a/R/AcqOptimizer.R b/R/AcqOptimizer.R index 1f547295..7f37c1a4 100644 --- a/R/AcqOptimizer.R +++ b/R/AcqOptimizer.R @@ -59,7 +59,7 @@ #' codomain = ps(y = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceSingleCrit$new( +#' instance = OptimInstanceBatchSingleCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' @@ -146,7 +146,7 @@ AcqOptimizer = R6Class("AcqOptimizer", logger$set_threshold(self$param_set$values$logging_level) on.exit(logger$set_threshold(old_threshold)) - instance = OptimInstanceSingleCrit$new(objective = self$acq_function, search_space = self$acq_function$domain, terminator = self$terminator, check_values = FALSE, keep_evals = "all") + instance = OptimInstanceBatchSingleCrit$new(objective = self$acq_function, search_space = self$acq_function$domain, terminator = self$terminator, check_values = FALSE) # warmstart if (self$param_set$values$warmstart) { diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index c28c2f00..b994fbf6 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -4,7 +4,7 @@ #' @description #' Asynchronous Decentralized Bayesian Optimization (ADBO). #' -#' @notes +#' @note #' The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. #' A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. #' ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. @@ -28,7 +28,7 @@ #' #' @export OptimizerADBO = R6Class("OptimizerADBO", - inherit = bbotk::Optimizer, + inherit = OptimizerAsync, public = list( @@ -59,33 +59,28 @@ OptimizerADBO = R6Class("OptimizerADBO", #' @description - #' Performs the optimization and writes optimization result into - #' [OptimInstance]. The optimization result is returned but the complete - #' optimization path is stored in [Archive] of [OptimInstance]. + #' Performs the optimization on a [OptimInstanceAsyncSingleCrit] or [OptimInstanceAsyncMultiCrit] until termination. + #' The single evaluations will be written into the [ArchiveAsync]. + #' The result will be written into the instance object. #' - #' @param inst ([OptimInstance]). - #' @return [data.table::data.table]. + #' @param inst ([OptimInstanceAsyncSingleCrit] | [OptimInstanceAsyncMultiCrit]). + #' + #' @return [data.table::data.table()] optimize = function(inst) { - - # generate initial design pv = self$param_set$values + + # initial design design = if (is.null(pv$initial_design)) { + + lg$debug("Generating sobol design with size %s", pv$design_size) generate_design_sobol(inst$search_space, n = pv$design_size)$data } else { + + lg$debug("Using provided initial design with size %s", nrow(pv$initial_design)) pv$initial_design } - # send initial design to workers - inst$rush$push_tasks(transpose_list(design), extra = list(list(timestamp_xs = Sys.time()))) - - # optimize - inst$archive$start_time = Sys.time() - result = optimize_decentralized(inst, self, private) - - # FIXME: kill workers to increase the chance of a fitting the final model - inst$rush$stop_workers(type = "kill") - - result + optimize_async_default(inst, self, design) } ), @@ -94,7 +89,7 @@ OptimizerADBO = R6Class("OptimizerADBO", .optimize = function(inst) { pv = self$param_set$values search_space = inst$search_space - rush = inst$rush + archive = inst$archive # sample lambda from exponential distribution lambda_0 = rexp(1, 1 / pv$lambda) @@ -110,20 +105,14 @@ OptimizerADBO = R6Class("OptimizerADBO", acq_optimizer$acq_function = acq_function lg$debug("Optimizer '%s' evaluates the initial design", self$id) - - # evaluate initial design - while (rush$n_queued_tasks > 0) { - task = rush$pop_task(fields = "xs") - xs_trafoed = trafo_xs(task$xs, inst$search_space) - ys = inst$objective$eval(xs_trafoed) - rush$push_results(task$key, yss = list(ys), extra = list(list(x_domain = list(xs_trafoed), timestamp_ys = Sys.time(), stage = "initial_design"))) - } + evaluate_queue_default(inst) lg$debug("Optimizer '%s' starts the tuning phase", self$id) # actual loop while (!inst$is_terminated) { + # decrease lambda if (pv$exponential_decay) { lambda = lambda_0 * exp(-pv$rate * (t %% pv$period)) t = t + 1 @@ -131,23 +120,25 @@ OptimizerADBO = R6Class("OptimizerADBO", lambda = pv$lambda } + # sample acq_function$constants$set_values(lambda = lambda) acq_function$surrogate$update() acq_function$update() xdt = acq_optimizer$optimize() + + # transpose point xss = transpose_list(xdt) xs = xss[[1]][inst$archive$cols_x] lg$trace("Optimizer '%s' draws %s", self$id, as_short_string(xs)) xs_trafoed = trafo_xs(xs, search_space) - extra = xss[[1]][c("acq_cb", ".already_evaluated")] - keys = rush$push_running_task(list(xs), extra = list(list(timestamp_xs = Sys.time()))) + + # eval + key = archive$push_running_point(xs) ys = inst$objective$eval(xs_trafoed) - rush$push_results(keys, yss = list(ys), extra = list(c(extra, list( - x_domain = list(xs_trafoed), - timestamp_ys = Sys.time(), - stage = "mbo", - lambda_0 = lambda_0, - lambda = lambda)))) + + # push result + extra = c(xss[[1]][c("acq_cb", ".already_evaluated")], list(stage = "mbo", lambda_0 = lambda_0, lambda = lambda)) + archive$push_result(key, ys, x_domain = xs_trafoed, extra = extra) } } ) diff --git a/R/OptimizerMbo.R b/R/OptimizerMbo.R index 9edd164a..c707f2a2 100644 --- a/R/OptimizerMbo.R +++ b/R/OptimizerMbo.R @@ -47,7 +47,7 @@ #' codomain = ps(y = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceSingleCrit$new( +#' instance = OptimInstanceBatchSingleCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' @@ -75,7 +75,7 @@ #' codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceMultiCrit$new( +#' instance = OptimInstanceBatchMultiCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' diff --git a/R/ResultAssigner.R b/R/ResultAssigner.R index dbbf812c..e558ac0d 100644 --- a/R/ResultAssigner.R +++ b/R/ResultAssigner.R @@ -28,7 +28,7 @@ ResultAssigner = R6Class("ResultAssigner", #' @description #' Assigns the result, i.e., the final point(s) to the instance. #' - #' @param instance ([bbotk::OptimInstanceSingleCrit] | [bbotk::OptimInstanceMultiCrit])\cr + #' @param instance ([bbotk::OptimInstanceBatchSingleCrit] | [bbotk::OptimInstanceBatchMultiCrit])\cr #' The [bbotk::OptimInstance] the final result should be assigned to. assign_result = function(instance) { stop("Abstract.") diff --git a/R/ResultAssignerArchive.R b/R/ResultAssignerArchive.R index aff6c2e8..1c6f9399 100644 --- a/R/ResultAssignerArchive.R +++ b/R/ResultAssignerArchive.R @@ -26,12 +26,12 @@ ResultAssignerArchive = R6Class("ResultAssignerArchive", #' @description #' Assigns the result, i.e., the final point(s) to the instance. #' - #' @param instance ([bbotk::OptimInstanceSingleCrit] | [bbotk::OptimInstanceMultiCrit])\cr + #' @param instance ([bbotk::OptimInstanceBatchSingleCrit] | [bbotk::OptimInstanceBatchMultiCrit])\cr #' The [bbotk::OptimInstance] the final result should be assigned to. assign_result = function(instance) { res = instance$archive$best() xdt = res[, instance$search_space$ids(), with = FALSE] - if (inherits(instance, "OptimInstanceMultiCrit")) { + if (inherits(instance, "OptimInstanceBatchMultiCrit")) { ydt = res[, instance$archive$cols_y, with = FALSE] instance$assign_result(xdt, ydt) } diff --git a/R/ResultAssignerSurrogate.R b/R/ResultAssignerSurrogate.R index 63505b28..0bf824a3 100644 --- a/R/ResultAssignerSurrogate.R +++ b/R/ResultAssignerSurrogate.R @@ -7,7 +7,7 @@ #' Result assigner that chooses the final point(s) based on a surrogate mean prediction of all evaluated points in the [bbotk::Archive]. #' This is especially useful in the case of noisy objective functions. #' -#' In the case of operating on an [bbotk::OptimInstanceMultiCrit] the [SurrogateLearnerCollection] must use as many learners as there are objective functions. +#' In the case of operating on an [bbotk::OptimInstanceBatchMultiCrit] the [SurrogateLearnerCollection] must use as many learners as there are objective functions. #' #' @family Result Assigner #' @export @@ -32,15 +32,15 @@ ResultAssignerSurrogate = R6Class("ResultAssignerSurrogate", #' Assigns the result, i.e., the final point(s) to the instance. #' If `$surrogate` is `NULL`, `default_surrogate(instance)` is used and also assigned to `$surrogate`. #' - #' @param instance ([bbotk::OptimInstanceSingleCrit] | [bbotk::OptimInstanceMultiCrit])\cr + #' @param instance ([bbotk::OptimInstanceBatchSingleCrit] | [bbotk::OptimInstanceBatchMultiCrit])\cr #' The [bbotk::OptimInstance] the final result should be assigned to. assign_result = function(instance) { if (is.null(self$surrogate)) { self$surrogate = default_surrogate(instance) } - if (inherits(instance, "OptimInstanceSingleCrit")) { + if (inherits(instance, "OptimInstanceBatchSingleCrit")) { assert_r6(self$surrogate, classes = "SurrogateLearner") - } else if (inherits(instance, "OptimInstanceMultiCrit")) { + } else if (inherits(instance, "OptimInstanceBatchMultiCrit")) { assert_r6(self$surrogate, classes = "SurrogateLearnerCollection") if (self$surrogate$n_learner != instance$objective$ydim) { stopf("Surrogate used within the result assigner uses %i learners but the optimization instance has %i objective functions", self$surrogate$n_learner, instance$objective$ydim) @@ -62,9 +62,9 @@ ResultAssignerSurrogate = R6Class("ResultAssignerSurrogate", best = archive_tmp$best()[, archive_tmp$cols_x, with = FALSE] # ys are still the ones originally evaluated - best_y = if (inherits(instance, "OptimInstanceSingleCrit")) { + best_y = if (inherits(instance, "OptimInstanceBatchSingleCrit")) { unlist(archive$data[best, on = archive$cols_x][, archive$cols_y, with = FALSE]) - } else if (inherits(instance, "OptimInstanceMultiCrit")) { + } else if (inherits(instance, "OptimInstanceBatchMultiCrit")) { archive$data[best, on = archive$cols_x][, archive$cols_y, with = FALSE] } instance$assign_result(xdt = best, best_y) diff --git a/R/Surrogate.R b/R/Surrogate.R index 506c5f42..1fc998af 100644 --- a/R/Surrogate.R +++ b/R/Surrogate.R @@ -99,7 +99,7 @@ Surrogate = R6Class("Surrogate", if (missing(rhs)) { private$.archive } else { - private$.archive = assert_multi_class(rhs, c("Archive", "ArchiveRush")) + private$.archive = assert_archive(rhs, null_ok = TRUE) invisible(private$.archive) } }, diff --git a/R/SurrogateLearner.R b/R/SurrogateLearner.R index ac88c451..82c92a3e 100644 --- a/R/SurrogateLearner.R +++ b/R/SurrogateLearner.R @@ -44,7 +44,7 @@ #' codomain = ps(y = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceSingleCrit$new( +#' instance = OptimInstanceBatchSingleCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' diff --git a/R/SurrogateLearnerCollection.R b/R/SurrogateLearnerCollection.R index fdaa3ef2..bad34526 100644 --- a/R/SurrogateLearnerCollection.R +++ b/R/SurrogateLearnerCollection.R @@ -47,7 +47,7 @@ #' codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceMultiCrit$new( +#' instance = OptimInstanceBatchMultiCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' xdt = generate_design_random(instance$search_space, n = 4)$data diff --git a/R/TunerADBO.R b/R/TunerADBO.R index fc51bbf9..6c89074f 100644 --- a/R/TunerADBO.R +++ b/R/TunerADBO.R @@ -4,7 +4,7 @@ #' @description #' Asynchronous Decentralized Bayesian Optimization (ADBO). #' -#' @notes +#' @note #' The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. #' A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. #' ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. @@ -28,7 +28,7 @@ #' #' @export TunerADBO = R6Class("TunerADBO", - inherit = mlr3tuning::TunerFromOptimizer, + inherit = mlr3tuning::TunerAsyncFromOptimizerAsync, public = list( #' @description diff --git a/R/TunerMbo.R b/R/TunerMbo.R index 13f6615c..5311d005 100644 --- a/R/TunerMbo.R +++ b/R/TunerMbo.R @@ -1,4 +1,4 @@ -#' @title Tuner using Model Based Optimization +#' @title TunerBatch using Model Based Optimization #' #' @name mlr_tuners_mbo #' @@ -50,7 +50,7 @@ #' } #' } TunerMbo = R6Class("TunerMbo", - inherit = mlr3tuning::TunerFromOptimizer, + inherit = mlr3tuning::TunerBatchFromOptimizerBatch, public = list( #' @description diff --git a/R/bayesopt_ego.R b/R/bayesopt_ego.R index 9f8a9618..ca3d5736 100644 --- a/R/bayesopt_ego.R +++ b/R/bayesopt_ego.R @@ -10,8 +10,8 @@ #' In each iteration after the initial design, the surrogate and acquisition function are updated and the next candidate #' is chosen based on optimizing the acquisition function. #' -#' @param instance ([bbotk::OptimInstanceSingleCrit])\cr -#' The [bbotk::OptimInstanceSingleCrit] to be optimized. +#' @param instance ([bbotk::OptimInstanceBatchSingleCrit])\cr +#' The [bbotk::OptimInstanceBatchSingleCrit] to be optimized. #' @param init_design_size (`NULL` | `integer(1)`)\cr #' Size of the initial design. #' If `NULL` and the [bbotk::Archive] contains no evaluations, \code{4 * d} is used with \code{d} being the @@ -34,7 +34,7 @@ #' @note #' * The `acq_function$surrogate`, even if already populated, will always be overwritten by the `surrogate`. #' * The `acq_optimizer$acq_function`, even if already populated, will always be overwritten by `acq_function`. -#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::Archive] of the [bbotk::OptimInstanceSingleCrit]. +#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::Archive] of the [bbotk::OptimInstanceBatchSingleCrit]. #' #' @return invisible(instance)\cr #' The original instance is modified in-place and returned invisible. @@ -62,7 +62,7 @@ #' codomain = ps(y = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceSingleCrit$new( +#' instance = OptimInstanceBatchSingleCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' @@ -90,7 +90,7 @@ #' codomain = ps(y = p_dbl(tags = "minimize"), time = p_dbl(tags = "time")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceSingleCrit$new( +#' instance = OptimInstanceBatchSingleCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' @@ -116,7 +116,7 @@ bayesopt_ego = function( ) { # assertions - assert_r6(instance, "OptimInstanceSingleCrit") + assert_r6(instance, "OptimInstanceBatchSingleCrit") assert_r6(surrogate, classes = "Surrogate") # cannot be SurrogateLearner due to EIPS assert_r6(acq_function, classes = "AcqFunction") assert_r6(acq_optimizer, classes = "AcqOptimizer") diff --git a/R/bayesopt_emo.R b/R/bayesopt_emo.R index 1caacd6f..c1e96ec8 100644 --- a/R/bayesopt_emo.R +++ b/R/bayesopt_emo.R @@ -11,8 +11,8 @@ #' In each iteration after the initial design, the surrogate and acquisition function are updated and the next candidate #' is chosen based on optimizing the acquisition function. #' -#' @param instance ([bbotk::OptimInstanceMultiCrit])\cr -#' The [bbotk::OptimInstanceMultiCrit] to be optimized. +#' @param instance ([bbotk::OptimInstanceBatchMultiCrit])\cr +#' The [bbotk::OptimInstanceBatchMultiCrit] to be optimized. #' @param init_design_size (`NULL` | `integer(1)`)\cr #' Size of the initial design. #' If `NULL` and the [bbotk::Archive] contains no evaluations, \code{4 * d} is used with \code{d} being the @@ -34,7 +34,7 @@ #' @note #' * The `acq_function$surrogate`, even if already populated, will always be overwritten by the `surrogate`. #' * The `acq_optimizer$acq_function`, even if already populated, will always be overwritten by `acq_function`. -#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::Archive] of the [bbotk::OptimInstanceMultiCrit]. +#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::Archive] of the [bbotk::OptimInstanceBatchMultiCrit]. #' #' @return invisible(instance)\cr #' The original instance is modified in-place and returned invisible. @@ -58,7 +58,7 @@ #' codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceMultiCrit$new( +#' instance = OptimInstanceBatchMultiCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' @@ -89,7 +89,7 @@ bayesopt_emo = function( ) { # assertions - assert_r6(instance, "OptimInstanceMultiCrit") + assert_r6(instance, "OptimInstanceBatchMultiCrit") assert_r6(surrogate, classes = "SurrogateLearnerCollection") assert_r6(acq_function, classes = "AcqFunction") assert_r6(acq_optimizer, classes = "AcqOptimizer") diff --git a/R/bayesopt_mpcl.R b/R/bayesopt_mpcl.R index edac4cec..59b4850e 100644 --- a/R/bayesopt_mpcl.R +++ b/R/bayesopt_mpcl.R @@ -12,8 +12,8 @@ #' objective function value is obtained by applying the `liar` function to all previously obtained objective function values. #' This is repeated `q - 1` times to obtain a total of `q` candidates that are then evaluated in a single batch. #' -#' @param instance ([bbotk::OptimInstanceSingleCrit])\cr -#' The [bbotk::OptimInstanceSingleCrit] to be optimized. +#' @param instance ([bbotk::OptimInstanceBatchSingleCrit])\cr +#' The [bbotk::OptimInstanceBatchSingleCrit] to be optimized. #' @param init_design_size (`NULL` | `integer(1)`)\cr #' Size of the initial design. #' If `NULL` and the [bbotk::Archive] contains no evaluations, \code{4 * d} is used with \code{d} being the @@ -42,9 +42,9 @@ #' @note #' * The `acq_function$surrogate`, even if already populated, will always be overwritten by the `surrogate`. #' * The `acq_optimizer$acq_function`, even if already populated, will always be overwritten by `acq_function`. -#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::Archive] of the [bbotk::OptimInstanceSingleCrit]. +#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::Archive] of the [bbotk::OptimInstanceBatchSingleCrit]. #' * To make use of parallel evaluations in the case of `q > 1, the objective -#' function of the [bbotk::OptimInstanceSingleCrit] must be implemented accordingly. +#' function of the [bbotk::OptimInstanceBatchSingleCrit] must be implemented accordingly. #' #' @return invisible(instance)\cr #' The original instance is modified in-place and returned invisible. @@ -72,7 +72,7 @@ #' codomain = ps(y = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceSingleCrit$new( +#' instance = OptimInstanceBatchSingleCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 7)) #' @@ -106,7 +106,7 @@ bayesopt_mpcl = function( ) { # assertions - assert_r6(instance, "OptimInstanceSingleCrit") + assert_r6(instance, "OptimInstanceBatchSingleCrit") assert_r6(surrogate, classes = "Surrogate") # cannot be SurrogateLearner due to EIPS assert_r6(acq_function, classes = "AcqFunction") assert_r6(acq_optimizer, classes = "AcqOptimizer") diff --git a/R/bayesopt_parego.R b/R/bayesopt_parego.R index 62550871..744eecb2 100644 --- a/R/bayesopt_parego.R +++ b/R/bayesopt_parego.R @@ -11,8 +11,8 @@ #' obtained by scalarizing these values via the augmented Tchebycheff function, updating the surrogate with respect to #' these scalarized values and optimizing the acquisition function. #' -#' @param instance ([bbotk::OptimInstanceMultiCrit])\cr -#' The [bbotk::OptimInstanceMultiCrit] to be optimized. +#' @param instance ([bbotk::OptimInstanceBatchMultiCrit])\cr +#' The [bbotk::OptimInstanceBatchMultiCrit] to be optimized. #' @param init_design_size (`NULL` | `integer(1)`)\cr #' Size of the initial design. #' If `NULL` and the [bbotk::Archive] contains no evaluations, \code{4 * d} is used with \code{d} being the @@ -44,11 +44,11 @@ #' @note #' * The `acq_function$surrogate`, even if already populated, will always be overwritten by the `surrogate`. #' * The `acq_optimizer$acq_function`, even if already populated, will always be overwritten by `acq_function`. -#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::Archive] of the [bbotk::OptimInstanceMultiCrit]. +#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::Archive] of the [bbotk::OptimInstanceBatchMultiCrit]. #' * The scalarizations of the objective function values are stored as the `y_scal` column in the -#' [bbotk::Archive] of the [bbotk::OptimInstanceMultiCrit]. +#' [bbotk::Archive] of the [bbotk::OptimInstanceBatchMultiCrit]. #' * To make use of parallel evaluations in the case of `q > 1, the objective -#' function of the [bbotk::OptimInstanceMultiCrit] must be implemented accordingly. +#' function of the [bbotk::OptimInstanceBatchMultiCrit] must be implemented accordingly. #' #' @return invisible(instance)\cr #' The original instance is modified in-place and returned invisible. @@ -75,7 +75,7 @@ #' codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceMultiCrit$new( +#' instance = OptimInstanceBatchMultiCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' @@ -109,7 +109,7 @@ bayesopt_parego = function( ) { # assertions - assert_r6(instance, "OptimInstanceMultiCrit") + assert_r6(instance, "OptimInstanceBatchMultiCrit") assert_r6(surrogate, classes = "SurrogateLearner") assert_r6(acq_function, classes = "AcqFunction") assert_r6(acq_optimizer, classes = "AcqOptimizer") diff --git a/R/bayesopt_smsego.R b/R/bayesopt_smsego.R index 124fcd86..3f7ee08f 100644 --- a/R/bayesopt_smsego.R +++ b/R/bayesopt_smsego.R @@ -10,8 +10,8 @@ #' In each iteration after the initial design, the surrogate and acquisition function ([mlr_acqfunctions_smsego]) are #' updated and the next candidate is chosen based on optimizing the acquisition function. #' -#' @param instance ([bbotk::OptimInstanceMultiCrit])\cr -#' The [bbotk::OptimInstanceMultiCrit] to be optimized. +#' @param instance ([bbotk::OptimInstanceBatchMultiCrit])\cr +#' The [bbotk::OptimInstanceBatchMultiCrit] to be optimized. #' @param init_design_size (`NULL` | `integer(1)`)\cr #' Size of the initial design. #' If `NULL` and the [bbotk::Archive] contains no evaluations, \code{4 * d} is used with \code{d} being the @@ -33,9 +33,9 @@ #' @note #' * The `acq_function$surrogate`, even if already populated, will always be overwritten by the `surrogate`. #' * The `acq_optimizer$acq_function`, even if already populated, will always be overwritten by `acq_function`. -#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::Archive] of the [bbotk::OptimInstanceMultiCrit]. +#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::Archive] of the [bbotk::OptimInstanceBatchMultiCrit]. #' * Due to the iterative computation of the epsilon within the [mlr_acqfunctions_smsego], requires the [bbotk::Terminator] of -#' the [bbotk::OptimInstanceMultiCrit] to be a [bbotk::TerminatorEvals]. +#' the [bbotk::OptimInstanceBatchMultiCrit] to be a [bbotk::TerminatorEvals]. #' #' @return invisible(instance)\cr #' The original instance is modified in-place and returned invisible. @@ -63,7 +63,7 @@ #' codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) #' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) #' -#' instance = OptimInstanceMultiCrit$new( +#' instance = OptimInstanceBatchMultiCrit$new( #' objective = objective, #' terminator = trm("evals", n_evals = 5)) #' @@ -94,7 +94,7 @@ bayesopt_smsego = function( ) { # assertions - assert_r6(instance, "OptimInstanceMultiCrit") + assert_r6(instance, "OptimInstanceBatchMultiCrit") assert_r6(instance$terminator, "TerminatorEvals") assert_r6(surrogate, classes = "SurrogateLearnerCollection") assert_r6(acq_function, classes = "AcqFunctionSmsEgo") diff --git a/R/helper.R b/R/helper.R index cc19e336..b327fb9c 100644 --- a/R/helper.R +++ b/R/helper.R @@ -1,5 +1,5 @@ generate_acq_codomain = function(surrogate, id, direction = "same") { - assert_multi_class(surrogate$archive, c("Archive", "ArchiveRush")) + assert_multi_class(surrogate$archive, c("Archive", "ArchiveAsync")) assert_string(id) assert_choice(direction, choices = c("same", "minimize", "maximize")) if (direction == "same") { @@ -18,7 +18,7 @@ generate_acq_codomain = function(surrogate, id, direction = "same") { } generate_acq_domain = function(surrogate) { - assert_multi_class(surrogate$archive, c("Archive", "ArchiveRush")) + assert_archive(surrogate$archive) domain = surrogate$archive$search_space$clone(deep = TRUE)$subset(surrogate$cols_x) domain$trafo = NULL domain diff --git a/R/mbo_defaults.R b/R/mbo_defaults.R index 9d54e411..5aa0dbfe 100644 --- a/R/mbo_defaults.R +++ b/R/mbo_defaults.R @@ -27,9 +27,9 @@ NULL #' @family mbo_defaults #' @export default_loop_function = function(instance) { - if (inherits(instance, "OptimInstanceSingleCrit")) { + if (inherits(instance, "OptimInstanceBatchSingleCrit")) { bayesopt_ego - } else if (inherits(instance, "OptimInstanceMultiCrit")) { + } else if (inherits(instance, "OptimInstanceBatchMultiCrit")) { bayesopt_smsego } } @@ -127,10 +127,10 @@ default_rf = function(noisy = FALSE) { #' In the case of dependencies, the following learner is used as a fallback: #' \code{lrn("regr.featureless")}. #' -#' If the instance is of class [bbotk::OptimInstanceSingleCrit] the learner is wrapped as a +#' If the instance is of class [bbotk::OptimInstanceBatchSingleCrit] the learner is wrapped as a #' [SurrogateLearner]. #' -#' If the instance is of class [bbotk::OptimInstanceMultiCrit] multiple deep clones of the learner are +#' If the instance is of class [bbotk::OptimInstanceBatchMultiCrit] multiple deep clones of the learner are #' wrapped as a [SurrogateLearnerCollection]. #' #' @references @@ -147,7 +147,7 @@ default_rf = function(noisy = FALSE) { #' @family mbo_defaults #' @export default_surrogate = function(instance, learner = NULL, n_learner = NULL) { - assert_multi_class(instance, c("OptimInstance", "OptimInstanceRush")) + assert_multi_class(instance, c("OptimInstance", "OptimInstanceAsync")) assert_r6(learner, "Learner", null.ok = TRUE) assert_int(n_learner, lower = 1L, null.ok = TRUE) noisy = "noisy" %in% instance$objective$properties @@ -211,9 +211,9 @@ default_surrogate = function(instance, learner = NULL, n_learner = NULL) { #' @export default_acqfunction = function(instance) { assert_r6(instance, classes = "OptimInstance") - if (inherits(instance, "OptimInstanceSingleCrit")) { + if (inherits(instance, "OptimInstanceBatchSingleCrit")) { AcqFunctionEI$new() - } else if (inherits(instance, "OptimInstanceMultiCrit")) { + } else if (inherits(instance, "OptimInstanceBatchMultiCrit")) { AcqFunctionSmsEgo$new() } } @@ -222,7 +222,7 @@ default_acqfunction = function(instance) { #' #' @description #' Chooses a default acquisition function optimizer. -#' Defaults to wrapping [bbotk::OptimizerRandomSearch] allowing 10000 function evaluations (with a batch size of 1000) via a [bbotk::TerminatorEvals]. +#' Defaults to wrapping [bbotk::OptimizerBatchRandomSearch] allowing 10000 function evaluations (with a batch size of 1000) via a [bbotk::TerminatorEvals]. #' #' @param acq_function ([AcqFunction]). #' @return [AcqOptimizer] diff --git a/man/AcqOptimizer.Rd b/man/AcqOptimizer.Rd index 0d19908a..f0f8d9b3 100644 --- a/man/AcqOptimizer.Rd +++ b/man/AcqOptimizer.Rd @@ -63,7 +63,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceSingleCrit$new( + instance = OptimInstanceBatchSingleCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/ResultAssigner.Rd b/man/ResultAssigner.Rd index 28ea502a..a6f5485e 100644 --- a/man/ResultAssigner.Rd +++ b/man/ResultAssigner.Rd @@ -74,7 +74,7 @@ Assigns the result, i.e., the final point(s) to the instance. \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{instance}}{(\link[bbotk:OptimInstanceSingleCrit]{bbotk::OptimInstanceSingleCrit} | \link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit})\cr +\item{\code{instance}}{(\link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} | \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit})\cr The \link[bbotk:OptimInstance]{bbotk::OptimInstance} the final result should be assigned to.} } \if{html}{\out{
}} diff --git a/man/SurrogateLearner.Rd b/man/SurrogateLearner.Rd index 37702fc2..038f9487 100644 --- a/man/SurrogateLearner.Rd +++ b/man/SurrogateLearner.Rd @@ -48,7 +48,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceSingleCrit$new( + instance = OptimInstanceBatchSingleCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/SurrogateLearnerCollection.Rd b/man/SurrogateLearnerCollection.Rd index 91ae1cc9..954b8ea9 100644 --- a/man/SurrogateLearnerCollection.Rd +++ b/man/SurrogateLearnerCollection.Rd @@ -51,7 +51,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceMultiCrit$new( + instance = OptimInstanceBatchMultiCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) xdt = generate_design_random(instance$search_space, n = 4)$data diff --git a/man/default_acqoptimizer.Rd b/man/default_acqoptimizer.Rd index 17fb9d42..f5efb42a 100644 --- a/man/default_acqoptimizer.Rd +++ b/man/default_acqoptimizer.Rd @@ -14,7 +14,7 @@ default_acqoptimizer(acq_function) } \description{ Chooses a default acquisition function optimizer. -Defaults to wrapping \link[bbotk:mlr_optimizers_random_search]{bbotk::OptimizerRandomSearch} allowing 10000 function evaluations (with a batch size of 1000) via a \link[bbotk:mlr_terminators_evals]{bbotk::TerminatorEvals}. +Defaults to wrapping \link[bbotk:mlr_optimizers_random_search]{bbotk::OptimizerBatchRandomSearch} allowing 10000 function evaluations (with a batch size of 1000) via a \link[bbotk:mlr_terminators_evals]{bbotk::TerminatorEvals}. } \seealso{ Other mbo_defaults: diff --git a/man/default_surrogate.Rd b/man/default_surrogate.Rd index d2bbeccf..25e416d3 100644 --- a/man/default_surrogate.Rd +++ b/man/default_surrogate.Rd @@ -44,10 +44,10 @@ Out of range imputation makes sense for tree-based methods and is usually hard t In the case of dependencies, the following learner is used as a fallback: \code{lrn("regr.featureless")}. -If the instance is of class \link[bbotk:OptimInstanceSingleCrit]{bbotk::OptimInstanceSingleCrit} the learner is wrapped as a +If the instance is of class \link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} the learner is wrapped as a \link{SurrogateLearner}. -If the instance is of class \link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit} multiple deep clones of the learner are +If the instance is of class \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} multiple deep clones of the learner are wrapped as a \link{SurrogateLearnerCollection}. } \references{ diff --git a/man/mlr_acqfunctions_aei.Rd b/man/mlr_acqfunctions_aei.Rd index 56957d63..7e733f3f 100644 --- a/man/mlr_acqfunctions_aei.Rd +++ b/man/mlr_acqfunctions_aei.Rd @@ -47,7 +47,7 @@ if (requireNamespace("mlr3learners") & codomain = codomain, properties = "noisy") - instance = OptimInstanceSingleCrit$new( + instance = OptimInstanceBatchSingleCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/mlr_acqfunctions_cb.Rd b/man/mlr_acqfunctions_cb.Rd index d94ae789..501ebece 100644 --- a/man/mlr_acqfunctions_cb.Rd +++ b/man/mlr_acqfunctions_cb.Rd @@ -42,7 +42,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceSingleCrit$new( + instance = OptimInstanceBatchSingleCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/mlr_acqfunctions_ehvi.Rd b/man/mlr_acqfunctions_ehvi.Rd index 160eb8fa..4301ec08 100644 --- a/man/mlr_acqfunctions_ehvi.Rd +++ b/man/mlr_acqfunctions_ehvi.Rd @@ -26,7 +26,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceMultiCrit$new( + instance = OptimInstanceBatchMultiCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/mlr_acqfunctions_ehvigh.Rd b/man/mlr_acqfunctions_ehvigh.Rd index 9d9a2c27..976ffb61 100644 --- a/man/mlr_acqfunctions_ehvigh.Rd +++ b/man/mlr_acqfunctions_ehvigh.Rd @@ -41,7 +41,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceMultiCrit$new( + instance = OptimInstanceBatchMultiCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/mlr_acqfunctions_ei.Rd b/man/mlr_acqfunctions_ei.Rd index 0fe783ea..4a12dbda 100644 --- a/man/mlr_acqfunctions_ei.Rd +++ b/man/mlr_acqfunctions_ei.Rd @@ -33,7 +33,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceSingleCrit$new( + instance = OptimInstanceBatchSingleCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/mlr_acqfunctions_eips.Rd b/man/mlr_acqfunctions_eips.Rd index 214f654b..4c885b74 100644 --- a/man/mlr_acqfunctions_eips.Rd +++ b/man/mlr_acqfunctions_eips.Rd @@ -7,9 +7,9 @@ \description{ Expected Improvement per Second. -It is assumed that calculations are performed on an \link[bbotk:OptimInstanceSingleCrit]{bbotk::OptimInstanceSingleCrit}. +It is assumed that calculations are performed on an \link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit}. Additionally to target values of the codomain that should be minimized or maximized, the -\link[bbotk:Objective]{bbotk::Objective} of the \link[bbotk:OptimInstanceSingleCrit]{bbotk::OptimInstanceSingleCrit} should return time values. +\link[bbotk:Objective]{bbotk::Objective} of the \link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} should return time values. The column names of the target variable and time variable must be passed as \code{cols_y} in the order \verb{(target, time)} when constructing the \link{SurrogateLearnerCollection} that is being used as a surrogate. @@ -40,7 +40,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y = p_dbl(tags = "minimize"), time = p_dbl(tags = "time")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceSingleCrit$new( + instance = OptimInstanceBatchSingleCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/mlr_acqfunctions_mean.Rd b/man/mlr_acqfunctions_mean.Rd index c9d602e2..b09456ba 100644 --- a/man/mlr_acqfunctions_mean.Rd +++ b/man/mlr_acqfunctions_mean.Rd @@ -33,7 +33,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceSingleCrit$new( + instance = OptimInstanceBatchSingleCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/mlr_acqfunctions_pi.Rd b/man/mlr_acqfunctions_pi.Rd index cc69d6e0..8f82a67c 100644 --- a/man/mlr_acqfunctions_pi.Rd +++ b/man/mlr_acqfunctions_pi.Rd @@ -33,7 +33,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceSingleCrit$new( + instance = OptimInstanceBatchSingleCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/mlr_acqfunctions_sd.Rd b/man/mlr_acqfunctions_sd.Rd index ab434ac9..9fdd0a57 100644 --- a/man/mlr_acqfunctions_sd.Rd +++ b/man/mlr_acqfunctions_sd.Rd @@ -33,7 +33,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceSingleCrit$new( + instance = OptimInstanceBatchSingleCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/mlr_acqfunctions_smsego.Rd b/man/mlr_acqfunctions_smsego.Rd index e5d6c865..78f23dd6 100644 --- a/man/mlr_acqfunctions_smsego.Rd +++ b/man/mlr_acqfunctions_smsego.Rd @@ -39,7 +39,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceMultiCrit$new( + instance = OptimInstanceBatchMultiCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/mlr_loop_functions_ego.Rd b/man/mlr_loop_functions_ego.Rd index 1420a86d..72b7c6fc 100644 --- a/man/mlr_loop_functions_ego.Rd +++ b/man/mlr_loop_functions_ego.Rd @@ -15,8 +15,8 @@ bayesopt_ego( ) } \arguments{ -\item{instance}{(\link[bbotk:OptimInstanceSingleCrit]{bbotk::OptimInstanceSingleCrit})\cr -The \link[bbotk:OptimInstanceSingleCrit]{bbotk::OptimInstanceSingleCrit} to be optimized.} +\item{instance}{(\link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit})\cr +The \link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} to be optimized.} \item{surrogate}{(\link{Surrogate})\cr \link{Surrogate} to be used as a surrogate. @@ -56,7 +56,7 @@ is chosen based on optimizing the acquisition function. \itemize{ \item The \code{acq_function$surrogate}, even if already populated, will always be overwritten by the \code{surrogate}. \item The \code{acq_optimizer$acq_function}, even if already populated, will always be overwritten by \code{acq_function}. -\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceSingleCrit]{bbotk::OptimInstanceSingleCrit}. +\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit}. } } \examples{ @@ -76,7 +76,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceSingleCrit$new( + instance = OptimInstanceBatchSingleCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) @@ -104,7 +104,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y = p_dbl(tags = "minimize"), time = p_dbl(tags = "time")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceSingleCrit$new( + instance = OptimInstanceBatchSingleCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/mlr_loop_functions_emo.Rd b/man/mlr_loop_functions_emo.Rd index bcb212bc..6ce0885a 100644 --- a/man/mlr_loop_functions_emo.Rd +++ b/man/mlr_loop_functions_emo.Rd @@ -15,8 +15,8 @@ bayesopt_emo( ) } \arguments{ -\item{instance}{(\link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit})\cr -The \link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit} to be optimized.} +\item{instance}{(\link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit})\cr +The \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} to be optimized.} \item{surrogate}{(\link{SurrogateLearnerCollection})\cr \link{SurrogateLearnerCollection} to be used as a surrogate.} @@ -56,7 +56,7 @@ is chosen based on optimizing the acquisition function. \itemize{ \item The \code{acq_function$surrogate}, even if already populated, will always be overwritten by the \code{surrogate}. \item The \code{acq_optimizer$acq_function}, even if already populated, will always be overwritten by \code{acq_function}. -\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit}. +\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit}. } } \examples{ @@ -76,7 +76,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceMultiCrit$new( + instance = OptimInstanceBatchMultiCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/mlr_loop_functions_mpcl.Rd b/man/mlr_loop_functions_mpcl.Rd index 7d56c763..1ee43738 100644 --- a/man/mlr_loop_functions_mpcl.Rd +++ b/man/mlr_loop_functions_mpcl.Rd @@ -17,8 +17,8 @@ bayesopt_mpcl( ) } \arguments{ -\item{instance}{(\link[bbotk:OptimInstanceSingleCrit]{bbotk::OptimInstanceSingleCrit})\cr -The \link[bbotk:OptimInstanceSingleCrit]{bbotk::OptimInstanceSingleCrit} to be optimized.} +\item{instance}{(\link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit})\cr +The \link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} to be optimized.} \item{surrogate}{(\link{Surrogate})\cr \link{Surrogate} to be used as a surrogate. @@ -68,9 +68,9 @@ This is repeated \code{q - 1} times to obtain a total of \code{q} candidates tha \itemize{ \item The \code{acq_function$surrogate}, even if already populated, will always be overwritten by the \code{surrogate}. \item The \code{acq_optimizer$acq_function}, even if already populated, will always be overwritten by \code{acq_function}. -\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceSingleCrit]{bbotk::OptimInstanceSingleCrit}. +\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit}. \item To make use of parallel evaluations in the case of `q > 1, the objective -function of the \link[bbotk:OptimInstanceSingleCrit]{bbotk::OptimInstanceSingleCrit} must be implemented accordingly. +function of the \link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} must be implemented accordingly. } } \examples{ @@ -90,7 +90,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceSingleCrit$new( + instance = OptimInstanceBatchSingleCrit$new( objective = objective, terminator = trm("evals", n_evals = 7)) diff --git a/man/mlr_loop_functions_parego.Rd b/man/mlr_loop_functions_parego.Rd index f87fa2a6..8f8606d0 100644 --- a/man/mlr_loop_functions_parego.Rd +++ b/man/mlr_loop_functions_parego.Rd @@ -18,8 +18,8 @@ bayesopt_parego( ) } \arguments{ -\item{instance}{(\link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit})\cr -The \link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit} to be optimized.} +\item{instance}{(\link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit})\cr +The \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} to be optimized.} \item{surrogate}{(\link{SurrogateLearner})\cr \link{SurrogateLearner} to be used as a surrogate.} @@ -72,11 +72,11 @@ these scalarized values and optimizing the acquisition function. \itemize{ \item The \code{acq_function$surrogate}, even if already populated, will always be overwritten by the \code{surrogate}. \item The \code{acq_optimizer$acq_function}, even if already populated, will always be overwritten by \code{acq_function}. -\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit}. +\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit}. \item The scalarizations of the objective function values are stored as the \code{y_scal} column in the -\link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit}. +\link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit}. \item To make use of parallel evaluations in the case of `q > 1, the objective -function of the \link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit} must be implemented accordingly. +function of the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} must be implemented accordingly. } } \examples{ @@ -96,7 +96,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceMultiCrit$new( + instance = OptimInstanceBatchMultiCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/mlr_loop_functions_smsego.Rd b/man/mlr_loop_functions_smsego.Rd index 82284754..ab9c3199 100644 --- a/man/mlr_loop_functions_smsego.Rd +++ b/man/mlr_loop_functions_smsego.Rd @@ -15,8 +15,8 @@ bayesopt_smsego( ) } \arguments{ -\item{instance}{(\link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit})\cr -The \link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit} to be optimized.} +\item{instance}{(\link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit})\cr +The \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} to be optimized.} \item{surrogate}{(\link{SurrogateLearnerCollection})\cr \link{SurrogateLearnerCollection} to be used as a surrogate.} @@ -55,9 +55,9 @@ updated and the next candidate is chosen based on optimizing the acquisition fun \itemize{ \item The \code{acq_function$surrogate}, even if already populated, will always be overwritten by the \code{surrogate}. \item The \code{acq_optimizer$acq_function}, even if already populated, will always be overwritten by \code{acq_function}. -\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit}. +\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit}. \item Due to the iterative computation of the epsilon within the \link{mlr_acqfunctions_smsego}, requires the \link[bbotk:Terminator]{bbotk::Terminator} of -the \link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit} to be a \link[bbotk:mlr_terminators_evals]{bbotk::TerminatorEvals}. +the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} to be a \link[bbotk:mlr_terminators_evals]{bbotk::TerminatorEvals}. } } \examples{ @@ -77,7 +77,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceMultiCrit$new( + instance = OptimInstanceBatchMultiCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) diff --git a/man/mlr_optimizers_adbo.Rd b/man/mlr_optimizers_adbo.Rd index 9b91b3f9..cd883bb8 100644 --- a/man/mlr_optimizers_adbo.Rd +++ b/man/mlr_optimizers_adbo.Rd @@ -7,6 +7,11 @@ \description{ Asynchronous Decentralized Bayesian Optimization (ADBO). } +\note{ +The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. +A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. +ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. +} \section{Parameters}{ \describe{ @@ -26,8 +31,8 @@ Initial design.} } } -\section{Super class}{ -\code{\link[bbotk:Optimizer]{bbotk::Optimizer}} -> \code{OptimizerADBO} +\section{Super classes}{ +\code{\link[bbotk:Optimizer]{bbotk::Optimizer}} -> \code{\link[bbotk:OptimizerAsync]{bbotk::OptimizerAsync}} -> \code{OptimizerADBO} } \section{Methods}{ \subsection{Public methods}{ @@ -60,9 +65,9 @@ Creates a new instance of this \link[R6:R6Class]{R6} class. \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-OptimizerADBO-optimize}{}}} \subsection{Method \code{optimize()}}{ -Performs the optimization and writes optimization result into -\link{OptimInstance}. The optimization result is returned but the complete -optimization path is stored in \link{Archive} of \link{OptimInstance}. +Performs the optimization on a \link{OptimInstanceAsyncSingleCrit} or \link{OptimInstanceAsyncMultiCrit} until termination. +The single evaluations will be written into the \link{ArchiveAsync}. +The result will be written into the instance object. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{OptimizerADBO$optimize(inst)}\if{html}{\out{
}} } @@ -70,12 +75,12 @@ optimization path is stored in \link{Archive} of \link{OptimInstance}. \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{inst}}{(\link{OptimInstance}).} +\item{\code{inst}}{(\link{OptimInstanceAsyncSingleCrit} | \link{OptimInstanceAsyncMultiCrit}).} } \if{html}{\out{
}} } \subsection{Returns}{ -\link[data.table:data.table]{data.table::data.table}. +\code{\link[data.table:data.table]{data.table::data.table()}} } } \if{html}{\out{
}} diff --git a/man/mlr_optimizers_mbo.Rd b/man/mlr_optimizers_mbo.Rd index 82e14a09..f738fdac 100644 --- a/man/mlr_optimizers_mbo.Rd +++ b/man/mlr_optimizers_mbo.Rd @@ -53,7 +53,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceSingleCrit$new( + instance = OptimInstanceBatchSingleCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) @@ -81,7 +81,7 @@ if (requireNamespace("mlr3learners") & codomain = ps(y1 = p_dbl(tags = "minimize"), y2 = p_dbl(tags = "minimize")) objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) - instance = OptimInstanceMultiCrit$new( + instance = OptimInstanceBatchMultiCrit$new( objective = objective, terminator = trm("evals", n_evals = 5)) @@ -152,7 +152,6 @@ Required packages are determined based on the \code{acq_function}, \code{surroga }} diff --git a/man/mlr_result_assigners_archive.Rd b/man/mlr_result_assigners_archive.Rd index 11e3c5a5..aa453a08 100644 --- a/man/mlr_result_assigners_archive.Rd +++ b/man/mlr_result_assigners_archive.Rd @@ -68,7 +68,7 @@ Assigns the result, i.e., the final point(s) to the instance. \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{instance}}{(\link[bbotk:OptimInstanceSingleCrit]{bbotk::OptimInstanceSingleCrit} | \link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit})\cr +\item{\code{instance}}{(\link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} | \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit})\cr The \link[bbotk:OptimInstance]{bbotk::OptimInstance} the final result should be assigned to.} } \if{html}{\out{
}} diff --git a/man/mlr_result_assigners_surrogate.Rd b/man/mlr_result_assigners_surrogate.Rd index 1a0d3251..68d57759 100644 --- a/man/mlr_result_assigners_surrogate.Rd +++ b/man/mlr_result_assigners_surrogate.Rd @@ -8,7 +8,7 @@ Result assigner that chooses the final point(s) based on a surrogate mean prediction of all evaluated points in the \link[bbotk:Archive]{bbotk::Archive}. This is especially useful in the case of noisy objective functions. -In the case of operating on an \link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit} the \link{SurrogateLearnerCollection} must use as many learners as there are objective functions. +In the case of operating on an \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} the \link{SurrogateLearnerCollection} must use as many learners as there are objective functions. } \examples{ result_assigner = ras("surrogate") @@ -82,7 +82,7 @@ If \verb{$surrogate} is \code{NULL}, \code{default_surrogate(instance)} is used \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{instance}}{(\link[bbotk:OptimInstanceSingleCrit]{bbotk::OptimInstanceSingleCrit} | \link[bbotk:OptimInstanceMultiCrit]{bbotk::OptimInstanceMultiCrit})\cr +\item{\code{instance}}{(\link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} | \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit})\cr The \link[bbotk:OptimInstance]{bbotk::OptimInstance} the final result should be assigned to.} } \if{html}{\out{
}} diff --git a/man/mlr_tuners_adbo.Rd b/man/mlr_tuners_adbo.Rd index adf79f94..a029aa1b 100644 --- a/man/mlr_tuners_adbo.Rd +++ b/man/mlr_tuners_adbo.Rd @@ -7,6 +7,11 @@ \description{ Asynchronous Decentralized Bayesian Optimization (ADBO). } +\note{ +The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. +A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. +ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. +} \section{Parameters}{ \describe{ @@ -27,7 +32,7 @@ Initial design.} } \section{Super classes}{ -\code{\link[mlr3tuning:Tuner]{mlr3tuning::Tuner}} -> \code{\link[mlr3tuning:TunerFromOptimizer]{mlr3tuning::TunerFromOptimizer}} -> \code{TunerADBO} +\code{\link[mlr3tuning:Tuner]{mlr3tuning::Tuner}} -> \code{\link[mlr3tuning:TunerAsync]{mlr3tuning::TunerAsync}} -> \code{\link[mlr3tuning:TunerAsyncFromOptimizerAsync]{mlr3tuning::TunerAsyncFromOptimizerAsync}} -> \code{TunerADBO} } \section{Methods}{ \subsection{Public methods}{ @@ -42,7 +47,7 @@ Initial design.}
  • mlr3tuning::Tuner$format()
  • mlr3tuning::Tuner$help()
  • mlr3tuning::Tuner$print()
  • -
  • mlr3tuning::TunerFromOptimizer$optimize()
  • +
  • mlr3tuning::TunerAsyncFromOptimizerAsync$optimize()
  • }} diff --git a/man/mlr_tuners_mbo.Rd b/man/mlr_tuners_mbo.Rd index 36e4c887..b252b1dc 100644 --- a/man/mlr_tuners_mbo.Rd +++ b/man/mlr_tuners_mbo.Rd @@ -3,7 +3,7 @@ \name{mlr_tuners_mbo} \alias{mlr_tuners_mbo} \alias{TunerMbo} -\title{Tuner using Model Based Optimization} +\title{TunerBatch using Model Based Optimization} \description{ \code{TunerMbo} class that implements Model Based Optimization (MBO). This is a minimal interface internally passing on to \link{OptimizerMbo}. @@ -52,7 +52,7 @@ if (requireNamespace("mlr3learners") & } } \section{Super classes}{ -\code{\link[mlr3tuning:Tuner]{mlr3tuning::Tuner}} -> \code{\link[mlr3tuning:TunerFromOptimizer]{mlr3tuning::TunerFromOptimizer}} -> \code{TunerMbo} +\code{\link[mlr3tuning:Tuner]{mlr3tuning::Tuner}} -> \code{\link[mlr3tuning:TunerBatch]{mlr3tuning::TunerBatch}} -> \code{\link[mlr3tuning:TunerBatchFromOptimizerBatch]{mlr3tuning::TunerBatchFromOptimizerBatch}} -> \code{TunerMbo} } \section{Active bindings}{ \if{html}{\out{
    }} @@ -108,7 +108,7 @@ Required packages are determined based on the \code{acq_function}, \code{surroga }} diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index e2b090b2..0f26a493 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -79,9 +79,9 @@ OBJ_2D_NOISY = ObjectiveRFun$new(fun = FUN_2D_NOISY, domain = PS_2D, properties # Instance helper MAKE_INST = function(objective = OBJ_2D, search_space = PS_2D, terminator = trm("evals", n_evals = 10L)) { if (objective$codomain$length == 1L) { - OptimInstanceSingleCrit$new(objective = objective, search_space = search_space, terminator = terminator) + OptimInstanceBatchSingleCrit$new(objective = objective, search_space = search_space, terminator = terminator) } else { - OptimInstanceMultiCrit$new(objective = objective, search_space = search_space, terminator = terminator) + OptimInstanceBatchMultiCrit$new(objective = objective, search_space = search_space, terminator = terminator) } } diff --git a/tests/testthat/test_AcqOptimizer.R b/tests/testthat/test_AcqOptimizer.R index f86db27b..4881b6ec 100644 --- a/tests/testthat/test_AcqOptimizer.R +++ b/tests/testthat/test_AcqOptimizer.R @@ -4,7 +4,7 @@ test_that("AcqOptimizer API works", { skip_if_not_installed("rgenoud") # EI, random search - instance = OptimInstanceSingleCrit$new(OBJ_1D, terminator = trm("evals", n_evals = 5L)) + instance = OptimInstanceBatchSingleCrit$new(OBJ_1D, terminator = trm("evals", n_evals = 5L)) design = generate_design_grid(instance$search_space, resolution = 4L)$data instance$eval_batch(design) acqfun = AcqFunctionEI$new(SurrogateLearner$new(REGR_KM_DETERM, archive = instance$archive)) diff --git a/tests/testthat/test_OptimizerADBO.R b/tests/testthat/test_OptimizerADBO.R index 51bba643..3bec3b39 100644 --- a/tests/testthat/test_OptimizerADBO.R +++ b/tests/testthat/test_OptimizerADBO.R @@ -1,45 +1,31 @@ test_that("adbo optimizer works", { - options("bbotk_local" = TRUE) - rush::rush_plan(n_workers = 5) - - search_space = domain = ps( - x1 = p_dbl(-5, 10), - x2 = p_dbl(0, 15) - ) - - codomain = ps(y = p_dbl(tags = "minimize")) - - fun = function(xs) { - list(y = branin(x1 = xs$x1, x2 = xs$x2)) - } - - objective = ObjectiveRFun$new( - fun = fun, - domain = domain, - codomain = codomain - ) + # options(bbotk_local = TRUE) + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() - instance = OptimInstanceRushSingleCrit$new( - objective = objective, - search_space = search_space, - terminator = trm("evals", n_evals = 100) + rush_plan(n_workers = 2) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 100), ) - optimizer = opt("adbo", design_size = 4) optimizer$optimize(instance) }) - test_that("adbo tuner works", { - # options("bbotk_local" = TRUE) + # options(bbotk_local = TRUE) + skip_on_cran() + skip_if_not_installed("rush") flush_redis() - rush::rush_plan(n_workers = 5) learner = lrn("classif.rpart", minsplit = to_tune(2, 128), cp = to_tune(1e-04, 1e-1)) - instance = TuningInstanceRushSingleCrit$new( + rush_plan(n_workers = 2) + instance = ti_async( task = tsk("pima"), learner = learner, resampling = rsmp("cv", folds = 3), @@ -48,39 +34,13 @@ test_that("adbo tuner works", { store_benchmark_result = FALSE ) - optimizer = tnr("adbo", design_size = 4) - optimizer$optimize(instance) + tuner = tnr("adbo", design_size = 4) + tuner$optimize(instance) expect_data_table(instance$archive$data, min.rows = 20L) expect_rush_reset(instance$rush) }) -test_that("adbo tuner works with limited number of workers", { - # options("bbotk_local" = TRUE) - lgr::get_logger("rush")$set_threshold("debug") - - flush_redis() - rush::rush_plan(n_workers = 5) - - learner = lrn("classif.rpart", - minsplit = to_tune(2, 128), - cp = to_tune(1e-04, 1e-1)) - - instance = TuningInstanceRushSingleCrit$new( - task = tsk("pima"), - learner = learner, - resampling = rsmp("cv", folds = 3), - measure = msr("classif.ce"), - terminator = trm("evals", n_evals = 20), - store_benchmark_result = FALSE - ) - - optimizer = tnr("adbo", design_size = 4, n_workers = 2) - optimizer$optimize(instance) - - expect_data_table(instance$archive$data, min.rows = 20L) - expect_rush_reset(instance$rush) -}) test_that("adbo works with transformation functions", { flush_redis() diff --git a/tests/testthat/test_bayesopt_ego.R b/tests/testthat/test_bayesopt_ego.R index d0397dfd..78cb4c11 100644 --- a/tests/testthat/test_bayesopt_ego.R +++ b/tests/testthat/test_bayesopt_ego.R @@ -164,7 +164,7 @@ test_that("bayesopt_ego eips", { terminator = trm("evals", n_evals = 5L) - instance = OptimInstanceSingleCrit$new( + instance = OptimInstanceBatchSingleCrit$new( objective = objective, terminator = terminator ) diff --git a/vignettes/mlr3mbo.Rmd b/vignettes/mlr3mbo.Rmd index 43066bc8..b76dfb38 100644 --- a/vignettes/mlr3mbo.Rmd +++ b/vignettes/mlr3mbo.Rmd @@ -454,7 +454,7 @@ Note that important fields of an `OptimizerMbo` such as `$param_classes`, `$pack If arguments such as the `surrogate`, `acq_function`, `acq_optimizer` and `result_assigner` were not fully initialized during construction, e.g., the `surrogate` missing the `archive`, or the `acq_function` missing the `surrogate`, lazy initialization is completed prior to the optimizer being used for optimization. -An object of class `OptimizerMbo` can be used to optimize an object of class `r ref("bbotk::OptimInstanceSingleCrit", "OptimInstanceSingleCrit")` or `r ref("bbotk::OptimInstanceMultiCrit", "OptimInstanceMultiCrit")`. +An object of class `OptimizerMbo` can be used to optimize an object of class `r ref("bbotk::OptimInstanceBatchSingleCrit", "OptimInstanceBatchSingleCrit")` or `r ref("bbotk::OptimInstanceBatchMultiCrit", "OptimInstanceBatchMultiCrit")`. For hyperparameter optimization, `r ref("mlr3mbo::mlr_tuners_mbo", "TunerMbo")` should be used (which simply relies on an `OptimizerMbo` that is constructed internally): @@ -536,7 +536,7 @@ objective = ObjectiveRFun$new( domain = domain, codomain = codomain) -instance = OptimInstanceSingleCrit$new( +instance = OptimInstanceBatchSingleCrit$new( objective = objective, terminator = trm("evals", n_evals = 10)) @@ -630,7 +630,7 @@ objective = ObjectiveRFun$new( domain = domain, codomain = codomain) -instance = OptimInstanceSingleCrit$new( +instance = OptimInstanceBatchSingleCrit$new( objective = objective, search_space = domain, terminator = trm("evals", n_evals = 60)) @@ -686,7 +686,7 @@ objective = ObjectiveRFun$new( domain = domain, codomain = codomain) -instance = OptimInstanceMultiCrit$new( +instance = OptimInstanceBatchMultiCrit$new( objective = objective, search_space = domain, terminator = trm("evals", n_evals = 30)) @@ -743,7 +743,7 @@ objective = ObjectiveRFun$new( domain = domain, codomain = codomain) -instance = OptimInstanceMultiCrit$new( +instance = OptimInstanceBatchMultiCrit$new( objective = objective, search_space = domain, terminator = trm("evals", n_evals = 30)) From 3787ec909f555074bb3d5e77a065ae6b5a3026fe Mon Sep 17 00:00:00 2001 From: be-marc Date: Sun, 28 Apr 2024 19:13:46 +0200 Subject: [PATCH 17/49] draft --- .github/workflows/dev-cmd-check.yml | 3 +++ .github/workflows/r-cmd-check.yml | 2 ++ NAMESPACE | 1 + R/OptimizerADBO.R | 3 +-- R/SurrogateLearner.R | 2 +- R/zzz.R | 2 +- man/mlr_optimizers_mbo.Rd | 5 +++-- man/mlr_tuners_mbo.Rd | 4 ++-- tests/testthat/test_TunerADBO.R | 2 +- 9 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/dev-cmd-check.yml b/.github/workflows/dev-cmd-check.yml index 4f791969..f9bb2184 100644 --- a/.github/workflows/dev-cmd-check.yml +++ b/.github/workflows/dev-cmd-check.yml @@ -48,3 +48,6 @@ jobs: shell: Rscript {0} - uses: r-lib/actions/check-r-package@v2 + with: + args: 'c("--no-manual")' # "--as-cran" prevents to start external processes + diff --git a/.github/workflows/r-cmd-check.yml b/.github/workflows/r-cmd-check.yml index 25702f8b..c2f640a9 100644 --- a/.github/workflows/r-cmd-check.yml +++ b/.github/workflows/r-cmd-check.yml @@ -42,3 +42,5 @@ jobs: needs: check - uses: r-lib/actions/check-r-package@v2 + with: + args: 'c("--no-manual")' # "--as-cran" prevents to start external processes diff --git a/NAMESPACE b/NAMESPACE index 4a32731a..abd5e315 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -59,6 +59,7 @@ importFrom(R6,R6Class) importFrom(stats,dnorm) importFrom(stats,pnorm) importFrom(stats,quantile) +importFrom(stats,rexp) importFrom(stats,runif) importFrom(stats,setNames) importFrom(utils,bibentry) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index b994fbf6..48cc690e 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -42,8 +42,7 @@ OptimizerADBO = R6Class("OptimizerADBO", period = p_int(lower = 1L, default = 25L), design_size = p_int(lower = 1L), initial_design = p_uty(), - impute_method = p_fct(c("mean", "random"), default = "random"), - n_workers = p_int(lower = 1L) + impute_method = p_fct(c("mean", "random"), default = "random") ) param_set$set_values(lambda = 1.96, exponential_decay = TRUE, rate = 0.1, period = 25L, design_size = 1L, impute_method = "random") diff --git a/R/SurrogateLearner.R b/R/SurrogateLearner.R index fc6e8ea2..2dc8eda1 100644 --- a/R/SurrogateLearner.R +++ b/R/SurrogateLearner.R @@ -223,7 +223,7 @@ SurrogateLearner = R6Class("SurrogateLearner", walk(self$cols_y, function(col) { min = min(xydt[[col]], na.rm = TRUE) max = max(xydt[[col]], na.rm = TRUE) - xydt[state %in% c("queued", "running"), (col) := runif(.N, min, max)] + xydt[c("queued", "running"), (col) := runif(.N, min, max), on = "state"] }) set(xydt, j = "state", value = NULL) } else { diff --git a/R/zzz.R b/R/zzz.R index baf57ecf..6ed1197e 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -10,7 +10,7 @@ #' @import mlr3 #' @import mlr3tuning #' @import rush -#' @importFrom stats setNames runif dnorm pnorm quantile +#' @importFrom stats setNames runif dnorm pnorm quantile rexp #' @useDynLib mlr3mbo c_sms_indicator c_eps_indicator "_PACKAGE" diff --git a/man/mlr_optimizers_mbo.Rd b/man/mlr_optimizers_mbo.Rd index e6924fed..6a57a9ca 100644 --- a/man/mlr_optimizers_mbo.Rd +++ b/man/mlr_optimizers_mbo.Rd @@ -95,8 +95,8 @@ if (requireNamespace("mlr3learners") & } } } -\section{Super class}{ -\code{\link[bbotk:Optimizer]{bbotk::Optimizer}} -> \code{OptimizerMbo} +\section{Super classes}{ +\code{\link[bbotk:Optimizer]{bbotk::Optimizer}} -> \code{\link[bbotk:OptimizerBatch]{bbotk::OptimizerBatch}} -> \code{OptimizerMbo} } \section{Active bindings}{ \if{html}{\out{
    }} @@ -153,6 +153,7 @@ Required packages are determined based on the \code{acq_function}, \code{surroga }} diff --git a/man/mlr_tuners_mbo.Rd b/man/mlr_tuners_mbo.Rd index 9c0e2828..1e625721 100644 --- a/man/mlr_tuners_mbo.Rd +++ b/man/mlr_tuners_mbo.Rd @@ -24,7 +24,7 @@ if (requireNamespace("mlr3learners") & resampling = rsmp("cv", folds = 3) measure = msr("classif.acc") - instance = TuningInstanceSingleCrit$new( + instance = TuningInstanceBatchSingleCrit$new( task = task, learner = learner, resampling = resampling, @@ -39,7 +39,7 @@ if (requireNamespace("mlr3learners") & resampling = rsmp("cv", folds = 3) measures = msrs(c("classif.acc", "selected_features")) - instance = TuningInstanceMultiCrit$new( + instance = TuningInstanceBatchMultiCrit$new( task = task, learner = learner, resampling = resampling, diff --git a/tests/testthat/test_TunerADBO.R b/tests/testthat/test_TunerADBO.R index 99a4c771..40e14c70 100644 --- a/tests/testthat/test_TunerADBO.R +++ b/tests/testthat/test_TunerADBO.R @@ -7,7 +7,7 @@ test_that("adbo tuner works", { minsplit = to_tune(2, 128), cp = to_tune(1e-04, 1e-1)) - rush_plan(n_workers = 2) + rush_plan(n_workers = 4) instance = ti_async( task = tsk("pima"), learner = learner, From 5a03bf708dd0c7bb6c7118dd9ffd6679e640df69 Mon Sep 17 00:00:00 2001 From: be-marc Date: Mon, 29 Apr 2024 10:26:41 +0200 Subject: [PATCH 18/49] refactor: remove stage --- R/OptimizerADBO.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index 48cc690e..58e4ab66 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -136,7 +136,7 @@ OptimizerADBO = R6Class("OptimizerADBO", ys = inst$objective$eval(xs_trafoed) # push result - extra = c(xss[[1]][c("acq_cb", ".already_evaluated")], list(stage = "mbo", lambda_0 = lambda_0, lambda = lambda)) + extra = c(xss[[1]][c("acq_cb", ".already_evaluated")], list(lambda_0 = lambda_0, lambda = lambda)) archive$push_result(key, ys, x_domain = xs_trafoed, extra = extra) } } From d77fad8567fbc8b839d3cf9c67bdd8249c3b4d1f Mon Sep 17 00:00:00 2001 From: be-marc Date: Mon, 29 Apr 2024 10:34:36 +0200 Subject: [PATCH 19/49] fix: description --- DESCRIPTION | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index be0d7102..d59137ab 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -41,17 +41,16 @@ BugReports: https://github.com/mlr-org/mlr3mbo/issues Depends: R (>= 3.1.0) Imports: - bbotk (>= 0.5.4), + bbotk (>= 0.8.0.9000), checkmate (>= 2.0.0), data.table, lgr (>= 0.3.4), mlr3 (>= 0.14.0), mlr3misc (>= 0.11.0), - mlr3tuning (>= 0.14.0), + mlr3tuning (>= 0.20.0.9000), paradox (>= 0.10.0), spacefillr, - R6 (>= 2.4.1), - rush + R6 (>= 2.4.1) Suggests: DiceKriging, emoa, @@ -65,8 +64,13 @@ Suggests: rgenoud, rmarkdown, rpart, + rush, stringi, - testthat (>= 3.0.0), + testthat (>= 3.0.0) +Remotes: + mlr-org/bbotk, + mlr-org/mlr3tuning, + mlr-org/rush ByteCompile: no Encoding: UTF-8 Config/testthat/edition: 3 From 170d650592f56b024d6b8ea6d1afcc08356daccc Mon Sep 17 00:00:00 2001 From: be-marc Date: Wed, 1 May 2024 08:17:27 +0200 Subject: [PATCH 20/49] feat: add n_worker argument --- R/OptimizerADBO.R | 10 ++++++++-- man/mlr_optimizers_adbo.Rd | 5 +++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index 58e4ab66..e4e98fc5 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -24,6 +24,11 @@ #' Size of the initial design.} #' \item{`initial_design`}{`data.table`\cr #' Initial design.} +#' \item{`impute_method`}{`character(1)`\cr +#' Imputation method for missing values in the surrogate model.} +#' \item{`n_workers`}{`integer(1)`\cr +#' Number of workers to use. +#' Defaults to the number of workers set by `rush::rush_plan()`} #' } #' #' @export @@ -42,7 +47,8 @@ OptimizerADBO = R6Class("OptimizerADBO", period = p_int(lower = 1L, default = 25L), design_size = p_int(lower = 1L), initial_design = p_uty(), - impute_method = p_fct(c("mean", "random"), default = "random") + impute_method = p_fct(c("mean", "random"), default = "random"), + n_workers = p_int(lower = 1L, default = NULL, special_vals = list(NULL)) ) param_set$set_values(lambda = 1.96, exponential_decay = TRUE, rate = 0.1, period = 25L, design_size = 1L, impute_method = "random") @@ -79,7 +85,7 @@ OptimizerADBO = R6Class("OptimizerADBO", pv$initial_design } - optimize_async_default(inst, self, design) + optimize_async_default(inst, self, design, n_workers = pv$n_workers) } ), diff --git a/man/mlr_optimizers_adbo.Rd b/man/mlr_optimizers_adbo.Rd index cd883bb8..061a0036 100644 --- a/man/mlr_optimizers_adbo.Rd +++ b/man/mlr_optimizers_adbo.Rd @@ -28,6 +28,11 @@ Period of the exponential decay.} Size of the initial design.} \item{\code{initial_design}}{\code{data.table}\cr Initial design.} +\item{\code{impute_method}}{\code{character(1)}\cr +Imputation method for missing values in the surrogate model.} +\item{\code{n_workers}}{\code{integer(1)}\cr +Number of workers to use. +Defaults to the number of workers set by \code{rush::rush_plan()}} } } From 3bd95b0d7218c1b8d39ad7da33d4f817b3190655 Mon Sep 17 00:00:00 2001 From: be-marc Date: Wed, 1 May 2024 08:27:28 +0200 Subject: [PATCH 21/49] fix: imports --- NAMESPACE | 1 - R/zzz.R | 1 - tests/testthat/test_OptimizerADBO.R | 2 +- tests/testthat/test_TunerADBO.R | 6 +++--- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index abd5e315..4ab0552c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -53,7 +53,6 @@ import(mlr3) import(mlr3misc) import(mlr3tuning) import(paradox) -import(rush) import(spacefillr) importFrom(R6,R6Class) importFrom(stats,dnorm) diff --git a/R/zzz.R b/R/zzz.R index 6ed1197e..a0e1e22d 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -9,7 +9,6 @@ #' @import lgr #' @import mlr3 #' @import mlr3tuning -#' @import rush #' @importFrom stats setNames runif dnorm pnorm quantile rexp #' @useDynLib mlr3mbo c_sms_indicator c_eps_indicator "_PACKAGE" diff --git a/tests/testthat/test_OptimizerADBO.R b/tests/testthat/test_OptimizerADBO.R index 8aecb5ea..6e8a73a1 100644 --- a/tests/testthat/test_OptimizerADBO.R +++ b/tests/testthat/test_OptimizerADBO.R @@ -3,7 +3,7 @@ test_that("adbo optimizer works", { skip_if_not_installed("rush") flush_redis() - rush_plan(n_workers = 2) + rush::rush_plan(n_workers = 2) instance = oi_async( objective = OBJ_2D, search_space = PS_2D, diff --git a/tests/testthat/test_TunerADBO.R b/tests/testthat/test_TunerADBO.R index 40e14c70..2bc24ba2 100644 --- a/tests/testthat/test_TunerADBO.R +++ b/tests/testthat/test_TunerADBO.R @@ -33,7 +33,7 @@ test_that("adbo works with transformation functions", { minsplit = to_tune(2, 128, logscale = TRUE), cp = to_tune(1e-04, 1e-1, logscale = TRUE)) - rush_plan(n_workers = 2) + rush::rush_plan(n_workers = 2) instance = ti_async( task = tsk("pima"), learner = learner, @@ -60,7 +60,7 @@ test_that("search works with dependencies", { cp = to_tune(1e-04, 1e-1), keep_model = to_tune()) - rush_plan(n_workers = 2) + rush::rush_plan(n_workers = 2) instance = ti_async( task = tsk("pima"), learner = learner, @@ -92,7 +92,7 @@ test_that("adbo works with branching", { "branch.selection" = to_tune(c("rpart", "debug")) ) - rush_plan(n_workers = 2) + rush::rush_plan(n_workers = 2) instance = ti_async( task = tsk("pima"), learner = graph_learner, From 43c2189896306456ac72f6147ab79796116fe54b Mon Sep 17 00:00:00 2001 From: be-marc Date: Wed, 1 May 2024 08:43:30 +0200 Subject: [PATCH 22/49] fix: tests --- tests/testthat/test_TunerADBO.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test_TunerADBO.R b/tests/testthat/test_TunerADBO.R index 2bc24ba2..5d677bff 100644 --- a/tests/testthat/test_TunerADBO.R +++ b/tests/testthat/test_TunerADBO.R @@ -7,7 +7,7 @@ test_that("adbo tuner works", { minsplit = to_tune(2, 128), cp = to_tune(1e-04, 1e-1)) - rush_plan(n_workers = 4) + rush::rush_plan(n_workers = 4) instance = ti_async( task = tsk("pima"), learner = learner, From 056e50e7a6bbca2729638ba063806657012d38e4 Mon Sep 17 00:00:00 2001 From: be-marc Date: Wed, 1 May 2024 08:45:48 +0200 Subject: [PATCH 23/49] ci: add redis --- .github/workflows/dev-cmd-check.yml | 4 ++++ .github/workflows/r-cmd-check.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/dev-cmd-check.yml b/.github/workflows/dev-cmd-check.yml index f9bb2184..05e89247 100644 --- a/.github/workflows/dev-cmd-check.yml +++ b/.github/workflows/dev-cmd-check.yml @@ -38,6 +38,10 @@ jobs: with: r-version: ${{ matrix.config.r }} + - uses: supercharge/redis-github-action@1.7.0 + with: + redis-version: 7 + - uses: r-lib/actions/setup-r-dependencies@v2 with: extra-packages: any::rcmdcheck diff --git a/.github/workflows/r-cmd-check.yml b/.github/workflows/r-cmd-check.yml index c2f640a9..b6a33022 100644 --- a/.github/workflows/r-cmd-check.yml +++ b/.github/workflows/r-cmd-check.yml @@ -36,6 +36,10 @@ jobs: with: r-version: ${{ matrix.config.r }} + - uses: supercharge/redis-github-action@1.7.0 + with: + redis-version: 7 + - uses: r-lib/actions/setup-r-dependencies@v2 with: extra-packages: any::rcmdcheck From a661667bfd37f9f8109ab67450d1bcc69dd38021 Mon Sep 17 00:00:00 2001 From: be-marc Date: Sun, 18 Aug 2024 13:38:21 +0200 Subject: [PATCH 24/49] refactor: add SurrogateLearnerAsync --- DESCRIPTION | 3 +- NAMESPACE | 1 + R/OptimizerADBO.R | 3 +- R/SurrogateLearner.R | 21 +---- R/SurrogateLearnerAsync.R | 86 ++++++++++++++++++++ R/mbo_defaults.R | 4 +- man/SurrogateLearnerAsync.Rd | 104 +++++++++++++++++++++++++ tests/testthat/test_SurrogateLearner.R | 2 +- 8 files changed, 201 insertions(+), 23 deletions(-) create mode 100644 R/SurrogateLearnerAsync.R create mode 100644 man/SurrogateLearnerAsync.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 831b1f34..f0210f08 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -73,7 +73,7 @@ Config/testthat/edition: 3 Config/testthat/parallel: false NeedsCompilation: yes Roxygen: list(markdown = TRUE, r6 = TRUE) -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.2 Collate: 'mlr_acqfunctions.R' 'AcqFunction.R' @@ -97,6 +97,7 @@ Collate: 'ResultAssignerSurrogate.R' 'Surrogate.R' 'SurrogateLearner.R' + 'SurrogateLearnerAsync.R' 'SurrogateLearnerCollection.R' 'TunerADBO.R' 'TunerMbo.R' diff --git a/NAMESPACE b/NAMESPACE index 4ab0552c..cf450792 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -23,6 +23,7 @@ export(ResultAssignerArchive) export(ResultAssignerSurrogate) export(Surrogate) export(SurrogateLearner) +export(SurrogateLearnerAsync) export(SurrogateLearnerCollection) export(TunerADBO) export(TunerMbo) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index e4e98fc5..3d543a59 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -101,10 +101,9 @@ OptimizerADBO = R6Class("OptimizerADBO", t = 0 surrogate = default_surrogate(inst) - surrogate$param_set$set_values(impute_missings = pv$impute_method) + surrogate$param_set$set_values(impute_method = pv$impute_method) acq_function = acqf("cb", lambda = runif(1, 1 , 3)) acq_optimizer = acqo(opt("random_search", batch_size = 1000L), terminator = trm("evals", n_evals = 10000L)) - surrogate$archive = inst$archive acq_function$surrogate = surrogate acq_optimizer$acq_function = acq_function diff --git a/R/SurrogateLearner.R b/R/SurrogateLearner.R index 2dc8eda1..fb5681e4 100644 --- a/R/SurrogateLearner.R +++ b/R/SurrogateLearner.R @@ -87,10 +87,9 @@ SurrogateLearner = R6Class("SurrogateLearner", assert_insample_perf = p_lgl(), perf_measure = p_uty(custom_check = function(x) check_r6(x, classes = "MeasureRegr")), # FIXME: actually want check_measure perf_threshold = p_dbl(lower = -Inf, upper = Inf), - catch_errors = p_lgl(), - impute_missings = p_fct(c("none", "mean", "random")) + catch_errors = p_lgl() ) - ps$values = list(assert_insample_perf = FALSE, catch_errors = TRUE, impute_missings = "none") + ps$values = list(assert_insample_perf = FALSE, catch_errors = TRUE) ps$add_dep("perf_measure", on = "assert_insample_perf", cond = CondEqual$new(TRUE)) ps$add_dep("perf_threshold", on = "assert_insample_perf", cond = CondEqual$new(TRUE)) @@ -215,21 +214,7 @@ SurrogateLearner = R6Class("SurrogateLearner", # Train learner with new data. # Also calculates the insample performance based on the `perf_measure` hyperparameter if `assert_insample_perf = TRUE`. .update = function() { - if (self$param_set$values$impute_missings == "mean") { - xydt = self$archive$rush$fetch_tasks_with_state(states = c("queued", "running", "finished"))[, c(self$cols_x, self$cols_y), with = FALSE] - setnafill(xydt, type = "const", fill = mean(xydt[[self$cols_y]], na.rm = TRUE), cols = self$cols_y) - } else if (self$param_set$values$impute_missings == "random") { - xydt = self$archive$rush$fetch_tasks_with_state(states = c("queued", "running", "finished"))[, c(self$cols_x, self$cols_y, "state"), with = FALSE] - walk(self$cols_y, function(col) { - min = min(xydt[[col]], na.rm = TRUE) - max = max(xydt[[col]], na.rm = TRUE) - xydt[c("queued", "running"), (col) := runif(.N, min, max), on = "state"] - }) - set(xydt, j = "state", value = NULL) - } else { - xydt = self$archive$data[, c(self$cols_x, self$cols_y), with = FALSE] - } - + xydt = self$archive$data[, c(self$cols_x, self$cols_y), with = FALSE] task = TaskRegr$new(id = "surrogate_task", backend = xydt, target = self$cols_y) assert_learnable(task, learner = self$learner) self$learner$train(task) diff --git a/R/SurrogateLearnerAsync.R b/R/SurrogateLearnerAsync.R new file mode 100644 index 00000000..d86befc9 --- /dev/null +++ b/R/SurrogateLearnerAsync.R @@ -0,0 +1,86 @@ +#' @title Surrogate Model Containing a Single Learner +#' +#' @description +#' Surrogate model containing a single [mlr3::LearnerRegr]. +#' +#' @section Parameters: +#' \describe{ +#' \item{`assert_insample_perf`}{`logical(1)`\cr +#' Should the insample performance of the [mlr3::LearnerRegr] be asserted after updating the surrogate? +#' If the assertion fails (i.e., the insample performance based on the `perf_measure` does not meet the +#' `perf_threshold`), an error is thrown. +#' Default is `FALSE`. +#' } +#' \item{`perf_measure`}{[mlr3::MeasureRegr]\cr +#' Performance measure which should be use to assert the insample performance of the [mlr3::LearnerRegr]. +#' Only relevant if `assert_insample_perf = TRUE`. +#' Default is [mlr3::mlr_measures_regr.rsq]. +#' } +#' \item{`perf_threshold`}{`numeric(1)`\cr +#' Threshold the insample performance of the [mlr3::LearnerRegr] should be asserted against. +#' Only relevant if `assert_insample_perf = TRUE`. +#' Default is `0`. +#' } +#' \item{`catch_errors`}{`logical(1)`\cr +#' Should errors during updating the surrogate be caught and propagated to the `loop_function` which can then handle +#' the failed acquisition function optimization (as a result of the failed surrogate) appropriately by, e.g., proposing a randomly sampled point for evaluation? +#' Default is `TRUE`. +#' } +#' \item{`impute_method`}{`character(1)`\cr +#' Method to impute missing values in the surrogate model. +#' } +#' } +#' +#' @export +SurrogateLearnerAsync = R6Class("SurrogateLearnerAsync", + inherit = SurrogateLearner, + + public = list( + + #' @description + #' Creates a new instance of this [R6][R6::R6Class] class. + #' + #' @param learner ([mlr3::LearnerRegr]). + #' @template param_archive_surrogate + #' @template param_col_y_surrogate + #' @template param_cols_x_surrogate + initialize = function(learner, archive = NULL, cols_x = NULL, col_y = NULL) { + super$initialize(learner = learner, archive = archive, cols_x = cols_x, col_y = col_y) + + extra_ps = ps( + impute_method = p_fct(c("mean", "random"), default = "random") + ) + + private$.param_set = c(private$.param_set, extra_ps) + private$.param_set$set_values(impute_method = "random") + } + ), + + private = list( + .update = function() { + xydt = self$archive$rush$fetch_tasks_with_state(states = c("queued", "running", "finished"))[, c(self$cols_x, self$cols_y, "state"), with = FALSE] + + if (self$param_set$values$impute_method == "mean") { + setnafill(xydt, type = "const", fill = mean(xydt[[self$cols_y]], na.rm = TRUE), cols = self$cols_y) + } else if (self$param_set$values$impute_method == "random") { + walk(self$cols_y, function(col) { + min = min(xydt[[col]], na.rm = TRUE) + max = max(xydt[[col]], na.rm = TRUE) + xydt[c("queued", "running"), (col) := runif(.N, min, max), on = "state"] + }) + } + set(xydt, j = "state", value = NULL) + + task = TaskRegr$new(id = "surrogate_task", backend = xydt, target = self$cols_y) + assert_learnable(task, learner = self$learner) + self$learner$train(task) + + if (self$param_set$values$assert_insample_perf) { + measure = assert_measure(self$param_set$values$perf_measure %??% mlr_measures$get("regr.rsq"), task = task, learner = self$learner) + private$.insample_perf = self$learner$predict(task)$score(measure, task = task, learner = self$learner) + self$assert_insample_perf + } + } + ) +) + diff --git a/R/mbo_defaults.R b/R/mbo_defaults.R index 932bac4f..2d46a3c8 100644 --- a/R/mbo_defaults.R +++ b/R/mbo_defaults.R @@ -190,8 +190,10 @@ default_surrogate = function(instance, learner = NULL, n_learner = NULL) { } if (is.null(n_learner)) n_learner = length(instance$archive$cols_y) - if (n_learner == 1L) { + if (n_learner == 1L && inherits(instance, "OptimInstanceBatch")) { SurrogateLearner$new(learner) + } else if (n_learner == 1L && inherits(instance, "OptimInstanceAsync")) { + SurrogateLearnerAsync$new(learner) } else { learners = replicate(n_learner, learner$clone(deep = TRUE), simplify = FALSE) SurrogateLearnerCollection$new(learners) diff --git a/man/SurrogateLearnerAsync.Rd b/man/SurrogateLearnerAsync.Rd new file mode 100644 index 00000000..911f9cef --- /dev/null +++ b/man/SurrogateLearnerAsync.Rd @@ -0,0 +1,104 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/SurrogateLearnerAsync.R +\name{SurrogateLearnerAsync} +\alias{SurrogateLearnerAsync} +\title{Surrogate Model Containing a Single Learner} +\description{ +Surrogate model containing a single \link[mlr3:LearnerRegr]{mlr3::LearnerRegr}. +} +\section{Parameters}{ + +\describe{ +\item{\code{assert_insample_perf}}{\code{logical(1)}\cr +Should the insample performance of the \link[mlr3:LearnerRegr]{mlr3::LearnerRegr} be asserted after updating the surrogate? +If the assertion fails (i.e., the insample performance based on the \code{perf_measure} does not meet the +\code{perf_threshold}), an error is thrown. +Default is \code{FALSE}. +} +\item{\code{perf_measure}}{\link[mlr3:MeasureRegr]{mlr3::MeasureRegr}\cr +Performance measure which should be use to assert the insample performance of the \link[mlr3:LearnerRegr]{mlr3::LearnerRegr}. +Only relevant if \code{assert_insample_perf = TRUE}. +Default is \link[mlr3:mlr_measures_regr.rsq]{mlr3::mlr_measures_regr.rsq}. +} +\item{\code{perf_threshold}}{\code{numeric(1)}\cr +Threshold the insample performance of the \link[mlr3:LearnerRegr]{mlr3::LearnerRegr} should be asserted against. +Only relevant if \code{assert_insample_perf = TRUE}. +Default is \code{0}. +} +\item{\code{catch_errors}}{\code{logical(1)}\cr +Should errors during updating the surrogate be caught and propagated to the \code{loop_function} which can then handle +the failed acquisition function optimization (as a result of the failed surrogate) appropriately by, e.g., proposing a randomly sampled point for evaluation? +Default is \code{TRUE}. +} +\item{\code{impute_method}}{\code{character(1)}\cr +Method to impute missing values in the surrogate model. +} +} +} + +\section{Super classes}{ +\code{\link[mlr3mbo:Surrogate]{mlr3mbo::Surrogate}} -> \code{\link[mlr3mbo:SurrogateLearner]{mlr3mbo::SurrogateLearner}} -> \code{SurrogateLearnerAsync} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-SurrogateLearnerAsync-new}{\code{SurrogateLearnerAsync$new()}} +\item \href{#method-SurrogateLearnerAsync-clone}{\code{SurrogateLearnerAsync$clone()}} +} +} +\if{html}{\out{ +
    Inherited methods + +
    +}} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-SurrogateLearnerAsync-new}{}}} +\subsection{Method \code{new()}}{ +Creates a new instance of this \link[R6:R6Class]{R6} class. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{SurrogateLearnerAsync$new(learner, archive = NULL, cols_x = NULL, col_y = NULL)}\if{html}{\out{
    }} +} + +\subsection{Arguments}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{learner}}{(\link[mlr3:LearnerRegr]{mlr3::LearnerRegr}).} + +\item{\code{archive}}{(\link[bbotk:Archive]{bbotk::Archive} | \code{NULL})\cr +\link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstance]{bbotk::OptimInstance}.} + +\item{\code{cols_x}}{(\code{character()} | \code{NULL})\cr +Column id's of variables that should be used as features. +By default, automatically inferred based on the archive.} + +\item{\code{col_y}}{(\code{character(1)} | \code{NULL})\cr +Column id of variable that should be used as a target. +By default, automatically inferred based on the archive.} +} +\if{html}{\out{
    }} +} +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-SurrogateLearnerAsync-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{SurrogateLearnerAsync$clone(deep = FALSE)}\if{html}{\out{
    }} +} + +\subsection{Arguments}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
    }} +} +} +} diff --git a/tests/testthat/test_SurrogateLearner.R b/tests/testthat/test_SurrogateLearner.R index 8461a757..08f46d7e 100644 --- a/tests/testthat/test_SurrogateLearner.R +++ b/tests/testthat/test_SurrogateLearner.R @@ -50,7 +50,7 @@ test_that("param_set", { inst = MAKE_INST_1D() surrogate = SurrogateLearner$new(learner = REGR_FEATURELESS, archive = inst$archive) expect_r6(surrogate$param_set, "ParamSet") - expect_setequal(surrogate$param_set$ids(), c("assert_insample_perf", "perf_measure", "perf_threshold", "catch_errors", "impute_missings")) + expect_setequal(surrogate$param_set$ids(), c("assert_insample_perf", "perf_measure", "perf_threshold", "catch_errors")) expect_equal(surrogate$param_set$class[["assert_insample_perf"]], "ParamLgl") expect_equal(surrogate$param_set$class[["perf_measure"]], "ParamUty") expect_equal(surrogate$param_set$class[["perf_threshold"]], "ParamDbl") From 4abb9c674948bba21462ca1a7f2d1c580edbdb9c Mon Sep 17 00:00:00 2001 From: be-marc Date: Thu, 22 Aug 2024 14:15:03 +0200 Subject: [PATCH 25/49] feat: add OptimizerAsyncMbo --- R/OptimizerAsyncMbo.R | 180 ++++++++++++++++++++++++++++++++++++++++++ R/mbo_defaults.R | 2 + 2 files changed, 182 insertions(+) create mode 100644 R/OptimizerAsyncMbo.R diff --git a/R/OptimizerAsyncMbo.R b/R/OptimizerAsyncMbo.R new file mode 100644 index 00000000..8faa97c3 --- /dev/null +++ b/R/OptimizerAsyncMbo.R @@ -0,0 +1,180 @@ +#' @title Asynchronous Model Based Optimization +#' @name mlr_optimizers_async_mbo +#' +#' @description +#' `OptimizerAsyncMbo` class that implements asynchronous Model Based Optimization (MBO). +#' +#' @export +OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", + inherit = OptimizerAsync, + + public = list( + + #' @description + #' Creates a new instance of this [R6][R6::R6Class] class. + initialize = function(surrogate = NULL, acq_function = NULL, acq_optimizer = NULL) { + super$initialize("async_mbo", + param_set = param_set, + param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), + properties = c("dependencies", "single-crit"), + packages = c("mlr3mbo", "rush"), + label = "Asynchronous Model Based Optimization", + man = "mlr3mbo::OptimizerAsyncMbo") + + self$surrogate = assert_r6(surrogate, classes = "Surrogate", null.ok = TRUE) + self$acq_function = assert_r6(acq_function, classes = "AcqFunction", null.ok = TRUE) + self$acq_optimizer = assert_r6(acq_optimizer, classes = "AcqOptimizer", null.ok = TRUE) + }, + + + #' @description + #' Performs the optimization on a [OptimInstanceAsyncSingleCrit] or [OptimInstanceAsyncMultiCrit] until termination. + #' The single evaluations will be written into the [ArchiveAsync]. + #' The result will be written into the instance object. + #' + #' @param inst ([OptimInstanceAsyncSingleCrit] | [OptimInstanceAsyncMultiCrit]). + #' + #' @return [data.table::data.table()] + optimize = function(inst) { + pv = self$param_set$values + + # initial design + design = if (is.null(pv$initial_design)) { + + lg$debug("Generating sobol design with size %s", pv$design_size) + generate_design_sobol(inst$search_space, n = pv$design_size)$data + } else { + + lg$debug("Using provided initial design with size %s", nrow(pv$initial_design)) + pv$initial_design + } + + optimize_async_default(inst, self, design, n_workers = pv$n_workers) + } + ), + + active = list( + #' @template field_surrogate + surrogate = function(rhs) { + if (missing(rhs)) { + private$.surrogate + } else { + private$.surrogate = assert_r6(rhs, classes = "Surrogate", null.ok = TRUE) + } + }, + + #' @template field_acq_function + acq_function = function(rhs) { + if (missing(rhs)) { + private$.acq_function + } else { + private$.acq_function = assert_r6(rhs, classes = "AcqFunction", null.ok = TRUE) + } + }, + + #' @template field_acq_optimizer + acq_optimizer = function(rhs) { + if (missing(rhs)) { + private$.acq_optimizer + } else { + private$.acq_optimizer = assert_r6(rhs, classes = "AcqOptimizer", null.ok = TRUE) + } + }, + + #' @template field_param_classes + param_classes = function(rhs) { + assert_ro_binding(rhs) + param_classes_surrogate = c("logical" = "ParamLgl", "integer" = "ParamInt", "numeric" = "ParamDbl", "factor" = "ParamFct") + if (!is.null(self$surrogate)) { + param_classes_surrogate = param_classes_surrogate[c("logical", "integer", "numeric", "factor") %in% self$surrogate$feature_types] # surrogate has precedence over acq_function$surrogate + } + param_classes_acq_opt = if (!is.null(self$acq_optimizer)) { + self$acq_optimizer$optimizer$param_classes + } else { + c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct") + } + unname(intersect(param_classes_surrogate, param_classes_acq_opt)) + }, + + #' @template field_properties + properties = function(rhs) { + assert_ro_binding(rhs) + + properties_loop_function = c("single-crit", "multi-crit") + if (!is.null(self$loop_function)) { + properties_loop_function = intersect(properties_loop_function, attr(self$loop_function, "instance")) + } + properties_surrogate = "dependencies" + if (!is.null(self$surrogate)) { + if ("missings" %nin% self$surrogate$properties) { + properties_surrogate = character() + } + } + unname(c(properties_surrogate, properties_loop_function)) + }, + + #' @template field_packages + packages = function(rhs) { + assert_ro_binding(rhs) + union("mlr3mbo", c(self$acq_function$packages, self$surrogate$packages, self$acq_optimizer$optimizer$packages, self$result_assigner$packages)) + } + ), + + private = list( + .surrogate = NULL, + .acq_function = NULL, + .acq_optimizer = NULL, + + .optimize = function(inst) { + pv = self$param_set$values + search_space = inst$search_space + archive = inst$archive + + if (is.null(self$acq_function)) { + self$acq_function = self$acq_optimizer$acq_function %??% default_acqfunction(inst) + } + + if (is.null(self$surrogate)) { + self$surrogate = self$acq_function$surrogate %??% default_surrogate(inst) + } + + if (is.null(self$acq_optimizer)) { + self$acq_optimizer = default_acqoptimizer(self$acq_function) + } + + surrogate$archive = inst$archive + acq_function$surrogate = surrogate + acq_optimizer$acq_function = acq_function + + lg$debug("Optimizer '%s' evaluates the initial design", self$id) + evaluate_queue_default(inst) + + lg$debug("Optimizer '%s' starts the tuning phase", self$id) + + # actual loop + while (!inst$is_terminated) { + # sample + acq_function$surrogate$update() + acq_function$update() + xdt = acq_optimizer$optimize() + + # transpose point + xss = transpose_list(xdt) + xs = xss[[1]][inst$archive$cols_x] + lg$trace("Optimizer '%s' draws %s", self$id, as_short_string(xs)) + xs_trafoed = trafo_xs(xs, search_space) + + # eval + key = archive$push_running_point(xs) + ys = inst$objective$eval(xs_trafoed) + + # push result + extra = c(xss[[1]][c("acq_cb", ".already_evaluated")], list(lambda_0 = lambda_0, lambda = lambda)) + archive$push_result(key, ys, x_domain = xs_trafoed, extra = extra) + } + } + ) +) + +#' @include aaa.R +optimizers[["adbo"]] = OptimizerADBO diff --git a/R/mbo_defaults.R b/R/mbo_defaults.R index db015c92..5841c6f9 100644 --- a/R/mbo_defaults.R +++ b/R/mbo_defaults.R @@ -215,6 +215,8 @@ default_acqfunction = function(instance) { assert_r6(instance, classes = "OptimInstance") if (inherits(instance, "OptimInstanceBatchSingleCrit")) { AcqFunctionEI$new() + } else if (inherits(instance, "OptimInstanceAsyncSingleCrit")) { + AcqFunctionCB$new() } else if (inherits(instance, "OptimInstanceBatchMultiCrit")) { AcqFunctionSmsEgo$new() } From a55f037e791f8a3a44dc19c1d57cbc65e17d26fe Mon Sep 17 00:00:00 2001 From: be-marc Date: Thu, 22 Aug 2024 15:24:35 +0200 Subject: [PATCH 26/49] tests: add tests --- DESCRIPTION | 3 +- R/OptimizerAsyncMbo.R | 36 +++++++++----- tests/testthat/test_OptimizerAsyncMbo.R | 66 +++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 tests/testthat/test_OptimizerAsyncMbo.R diff --git a/DESCRIPTION b/DESCRIPTION index 117ec337..4a7e847d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -91,8 +91,9 @@ Collate: 'AcqFunctionSmsEgo.R' 'AcqOptimizer.R' 'aaa.R' - 'OptimizerADBO.R' 'OptimizerMbo.R' + 'OptimizerAsyncMbo.R' + 'OptimizerADBO.R' 'mlr_result_assigners.R' 'ResultAssigner.R' 'ResultAssignerArchive.R' diff --git a/R/OptimizerAsyncMbo.R b/R/OptimizerAsyncMbo.R index 8faa97c3..1a19cfc2 100644 --- a/R/OptimizerAsyncMbo.R +++ b/R/OptimizerAsyncMbo.R @@ -1,4 +1,5 @@ #' @title Asynchronous Model Based Optimization +#' #' @name mlr_optimizers_async_mbo #' #' @description @@ -13,6 +14,14 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", #' @description #' Creates a new instance of this [R6][R6::R6Class] class. initialize = function(surrogate = NULL, acq_function = NULL, acq_optimizer = NULL) { + param_set = ps( + initial_design = p_uty(), + design_size = p_int(lower = 1, default = 10), + design_function = p_fct(c("random", "sobol", "lhs"), default = "sobol") + ) + + param_set$set_values(design_size = 10, design_function = "sobol") + super$initialize("async_mbo", param_set = param_set, param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), @@ -40,11 +49,16 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", # initial design design = if (is.null(pv$initial_design)) { + # generate initial design + generate_design = switch(pv$design_function, + "random" = generate_design_random, + "sobol" = generate_design_sobol, + "lhs" = generate_design_lhs) lg$debug("Generating sobol design with size %s", pv$design_size) - generate_design_sobol(inst$search_space, n = pv$design_size)$data + generate_design(inst$search_space, n = pv$design_size)$data } else { - + # use provided initial design lg$debug("Using provided initial design with size %s", nrow(pv$initial_design)) pv$initial_design } @@ -116,7 +130,7 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", #' @template field_packages packages = function(rhs) { assert_ro_binding(rhs) - union("mlr3mbo", c(self$acq_function$packages, self$surrogate$packages, self$acq_optimizer$optimizer$packages, self$result_assigner$packages)) + union("mlr3mbo", c(self$acq_function$packages, self$surrogate$packages, self$acq_optimizer$optimizer$packages)) } ), @@ -142,9 +156,9 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", self$acq_optimizer = default_acqoptimizer(self$acq_function) } - surrogate$archive = inst$archive - acq_function$surrogate = surrogate - acq_optimizer$acq_function = acq_function + self$surrogate$archive = inst$archive + self$acq_function$surrogate = self$surrogate + self$acq_optimizer$acq_function = self$acq_function lg$debug("Optimizer '%s' evaluates the initial design", self$id) evaluate_queue_default(inst) @@ -154,9 +168,9 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", # actual loop while (!inst$is_terminated) { # sample - acq_function$surrogate$update() - acq_function$update() - xdt = acq_optimizer$optimize() + self$acq_function$surrogate$update() + self$acq_function$update() + xdt = self$acq_optimizer$optimize() # transpose point xss = transpose_list(xdt) @@ -169,7 +183,7 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", ys = inst$objective$eval(xs_trafoed) # push result - extra = c(xss[[1]][c("acq_cb", ".already_evaluated")], list(lambda_0 = lambda_0, lambda = lambda)) + extra = xss[[1]][c(self$acq_function$id, ".already_evaluated")] archive$push_result(key, ys, x_domain = xs_trafoed, extra = extra) } } @@ -177,4 +191,4 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", ) #' @include aaa.R -optimizers[["adbo"]] = OptimizerADBO +optimizers[["async_mbo"]] = OptimizerAsyncMbo diff --git a/tests/testthat/test_OptimizerAsyncMbo.R b/tests/testthat/test_OptimizerAsyncMbo.R new file mode 100644 index 00000000..87ced07b --- /dev/null +++ b/tests/testthat/test_OptimizerAsyncMbo.R @@ -0,0 +1,66 @@ +test_that("adbo optimizer works in defaults", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 2) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 10), + ) + optimizer = opt("async_mbo", design_function = "sobol", design_size = 5) + + expect_data_table(optimizer$optimize(instance), nrows = 1) + expect_data_table(instance$archive$data, min.rows = 10) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated")) + + expect_rush_reset(instance$rush) +}) + +test_that("adbo optimizer works with ei", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 2) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 10), + ) + optimizer = opt("async_mbo", + design_function = "sobol", + design_size = 5, + acq_function = acqf("ei")) + + expect_data_table(optimizer$optimize(instance), nrows = 1) + expect_data_table(instance$archive$data, min.rows = 10) + expect_names(names(instance$archive$data), must.include = c("acq_ei", ".already_evaluated")) + + expect_rush_reset(instance$rush) +}) + +test_that("adbo optimizer works with cb with fixed lambda", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 2) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 10), + ) + optimizer = opt("async_mbo", + design_function = "sobol", + design_size = 5, + acq_function = acqf("cb", lambda = 3)) + + expect_data_table(optimizer$optimize(instance), nrows = 1) + expect_data_table(instance$archive$data, min.rows = 10) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated")) + + expect_rush_reset(instance$rush) +}) + From 475152a64d486c4a556578395af4d6de8157b81f Mon Sep 17 00:00:00 2001 From: be-marc Date: Tue, 27 Aug 2024 12:39:55 +0200 Subject: [PATCH 27/49] update --- R/AcqFunctionCB.R | 32 ++++++++++++++++++++---- R/mlr_callbacks.R | 63 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 R/mlr_callbacks.R diff --git a/R/AcqFunctionCB.R b/R/AcqFunctionCB.R index 81328e05..583a086d 100644 --- a/R/AcqFunctionCB.R +++ b/R/AcqFunctionCB.R @@ -64,17 +64,39 @@ AcqFunctionCB = R6Class("AcqFunctionCB", assert_r6(surrogate, "SurrogateLearner", null.ok = TRUE) assert_number(lambda, lower = 0, finite = TRUE) - constants = ps(lambda = p_dbl(lower = 0, default = 2)) + constants = ps( + lambda = p_dbl(lower = 0, default = 2), + lambda_min = p_dbl(lower = 0), + lambda_max = p_dbl(lower = 0), + lambda_sample = p_uty(), + lambda_decay = p_uty() + ) constants$values$lambda = lambda - super$initialize("acq_cb", constants = constants, surrogate = surrogate, requires_predict_type_se = TRUE, direction = "same", label = "Lower / Upper Confidence Bound", man = "mlr3mbo::mlr_acqfunctions_cb") + super$initialize("acq_cb", + constants = constants, + surrogate = surrogate, + requires_predict_type_se = TRUE, + direction = "same", + label = "Lower / Upper Confidence Bound", + man = "mlr3mbo::mlr_acqfunctions_cb") + }, + + #' @description + #' Update the acquisition function. + update = function() { + + if (!is.null(lambda_decay)) { + self$constants$values$lambda = lambda_decay() + } + } ), private = list( - .fun = function(xdt, ...) { - constants = list(...) - lambda = constants$lambda + .fun = function(xdt, lambda) { + #constants = list(...) + #lambda = constants$lambda p = self$surrogate$predict(xdt) cb = p$mean - self$surrogate_max_to_min * lambda * p$se data.table(acq_cb = cb) diff --git a/R/mlr_callbacks.R b/R/mlr_callbacks.R new file mode 100644 index 00000000..daa4f0f9 --- /dev/null +++ b/R/mlr_callbacks.R @@ -0,0 +1,63 @@ +callback_decay_lambda = callback_batch("mlr3mbo.decay_lambda", + on_optimization_end = function(callback, context) { + lambda = context$instance$objective$constants$get_values()[["lambda"]] + context$instance$objective$constants$set_values("lambda" = lambda * 0.9) + } +) + +callback_sample_lambda = callback_batch("mlr3mbo.sample_lambda", + on_optimization_begin = function(callback, context) { + context$instance$objective$constants$set_values("lambda" = runif(1, min = 0.01, max = 10)) + } +) + + +load_callback_exponential_lambda_decay = function() { + callback_async_tuning("mlr3mbo.exponential_lambda_decay", + label = "Exponential Decay of Lambda", + man = "mlr3mbo::mlr3mbo.exponential_lambda_decay", + + on_optimization_begin = function(callback, context) { + self$state$rate = assert_number(self$state$rate, lower = 0, null.ok = TRUE) %??% 0.1 + self$state$period = assert_integer(self$state$period, lower = 1, null.ok = TRUE) %??% 25 + self$state$init_lambda = assert_number(self$state$init_lambda, lower = 0, null.ok = TRUE) %??% 1.96 + + self$state$lambda_0 = rexp(1, 1 / self$state$init_lambda) + self$state$t = 0 + }, + + on_optimization_end = function(callback, context) { + context$optimizer$ + t = self$state$t + + + + + lambda = lambda_0 * exp(-self$state$rate * (t %% self$state$period)) + self$state$t = self$t + 1 + + } + ) +} + + + +# callback_exponential_lambda_decay + + +# rate = p_dbl(lower = 0, default = 0.1), +# period = p_int(lower = 1L, default = 25L), + + +# lambda_0 = rexp(1, 1 / pv$lambda) +# t = 0 + +# lambda_0 = rexp(1, 1 / pv$lambda) +# t = 0 + +# if (pv$exponential_decay) { +# lambda = lambda_0 * exp(-pv$rate * (t %% pv$period)) +# t = t + 1 +# } else { +# lambda = pv$lambda +# } From 5a8f2ae6dbaf54706bb7cedf41ece5d9f264de54 Mon Sep 17 00:00:00 2001 From: be-marc Date: Wed, 11 Sep 2024 18:44:20 +0200 Subject: [PATCH 28/49] ... --- .lintr | 3 +- DESCRIPTION | 4 +- R/AcqFunctionCB.R | 16 +- R/OptimizerADBO.R | 2 +- R/OptimizerAsyncMbo.R | 17 +- R/SurrogateLearnerAsync.R | 1 + R/aaa.R | 1 + R/mlr_callbacks.R | 198 +++++++++++++++++++----- R/zzz.R | 8 + tests/testthat/test_OptimizerAsyncMbo.R | 109 ++++++++++++- 10 files changed, 280 insertions(+), 79 deletions(-) diff --git a/.lintr b/.lintr index 436e1b52..6fa3c0ff 100644 --- a/.lintr +++ b/.lintr @@ -5,5 +5,6 @@ linters: linters_with_defaults( object_name_linter = object_name_linter(c("snake_case", "CamelCase")), # only allow snake case and camel case object names cyclocomp_linter = NULL, # do not check function complexity commented_code_linter = NULL, # allow code in comments - line_length_linter = line_length_linter(120) + line_length_linter = line_length_linter(120), + indentation_linter(indent = 2L, hanging_indent_style = "never") ) diff --git a/DESCRIPTION b/DESCRIPTION index 4a7e847d..6010c09d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -67,7 +67,8 @@ Suggests: rush, stringi, testthat (>= 3.0.0) -Remotes: mlr-org/bbotk +Remotes: + mlr-org/mlr3tuning@eval_point ByteCompile: no Encoding: UTF-8 Config/testthat/edition: 3 @@ -114,6 +115,7 @@ Collate: 'helper.R' 'loop_function.R' 'mbo_defaults.R' + 'mlr_callbacks.R' 'sugar.R' 'zzz.R' VignetteBuilder: knitr diff --git a/R/AcqFunctionCB.R b/R/AcqFunctionCB.R index 583a086d..358d62f6 100644 --- a/R/AcqFunctionCB.R +++ b/R/AcqFunctionCB.R @@ -65,11 +65,7 @@ AcqFunctionCB = R6Class("AcqFunctionCB", assert_number(lambda, lower = 0, finite = TRUE) constants = ps( - lambda = p_dbl(lower = 0, default = 2), - lambda_min = p_dbl(lower = 0), - lambda_max = p_dbl(lower = 0), - lambda_sample = p_uty(), - lambda_decay = p_uty() + lambda = p_dbl(lower = 0, default = 2) ) constants$values$lambda = lambda @@ -80,16 +76,6 @@ AcqFunctionCB = R6Class("AcqFunctionCB", direction = "same", label = "Lower / Upper Confidence Bound", man = "mlr3mbo::mlr_acqfunctions_cb") - }, - - #' @description - #' Update the acquisition function. - update = function() { - - if (!is.null(lambda_decay)) { - self$constants$values$lambda = lambda_decay() - } - } ), diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index 3d543a59..683961f1 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -33,7 +33,7 @@ #' #' @export OptimizerADBO = R6Class("OptimizerADBO", - inherit = OptimizerAsync, + inherit = OptimizerAsynMbo, public = list( diff --git a/R/OptimizerAsyncMbo.R b/R/OptimizerAsyncMbo.R index 1a19cfc2..45836757 100644 --- a/R/OptimizerAsyncMbo.R +++ b/R/OptimizerAsyncMbo.R @@ -62,7 +62,6 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", lg$debug("Using provided initial design with size %s", nrow(pv$initial_design)) pv$initial_design } - optimize_async_default(inst, self, design, n_workers = pv$n_workers) } ), @@ -161,7 +160,7 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", self$acq_optimizer$acq_function = self$acq_function lg$debug("Optimizer '%s' evaluates the initial design", self$id) - evaluate_queue_default(inst) + get_private(inst)$.eval_queue() lg$debug("Optimizer '%s' starts the tuning phase", self$id) @@ -171,20 +170,10 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", self$acq_function$surrogate$update() self$acq_function$update() xdt = self$acq_optimizer$optimize() - - # transpose point - xss = transpose_list(xdt) - xs = xss[[1]][inst$archive$cols_x] - lg$trace("Optimizer '%s' draws %s", self$id, as_short_string(xs)) - xs_trafoed = trafo_xs(xs, search_space) + xs = transpose_list(xdt)[[1]] # eval - key = archive$push_running_point(xs) - ys = inst$objective$eval(xs_trafoed) - - # push result - extra = xss[[1]][c(self$acq_function$id, ".already_evaluated")] - archive$push_result(key, ys, x_domain = xs_trafoed, extra = extra) + get_private(inst)$.eval_point(xs) } } ) diff --git a/R/SurrogateLearnerAsync.R b/R/SurrogateLearnerAsync.R index d86befc9..584f463b 100644 --- a/R/SurrogateLearnerAsync.R +++ b/R/SurrogateLearnerAsync.R @@ -60,6 +60,7 @@ SurrogateLearnerAsync = R6Class("SurrogateLearnerAsync", .update = function() { xydt = self$archive$rush$fetch_tasks_with_state(states = c("queued", "running", "finished"))[, c(self$cols_x, self$cols_y, "state"), with = FALSE] + if (self$param_set$values$impute_method == "mean") { setnafill(xydt, type = "const", fill = mean(xydt[[self$cols_y]], na.rm = TRUE), cols = self$cols_y) } else if (self$param_set$values$impute_method == "random") { diff --git a/R/aaa.R b/R/aaa.R index ed02c255..4a4686c6 100644 --- a/R/aaa.R +++ b/R/aaa.R @@ -1,2 +1,3 @@ optimizers = list() tuners = list() +callbacks = list() diff --git a/R/mlr_callbacks.R b/R/mlr_callbacks.R index daa4f0f9..51c8de61 100644 --- a/R/mlr_callbacks.R +++ b/R/mlr_callbacks.R @@ -1,63 +1,177 @@ -callback_decay_lambda = callback_batch("mlr3mbo.decay_lambda", - on_optimization_end = function(callback, context) { - lambda = context$instance$objective$constants$get_values()[["lambda"]] - context$instance$objective$constants$set_values("lambda" = lambda * 0.9) - } -) - -callback_sample_lambda = callback_batch("mlr3mbo.sample_lambda", - on_optimization_begin = function(callback, context) { - context$instance$objective$constants$set_values("lambda" = runif(1, min = 0.01, max = 10)) - } -) - - -load_callback_exponential_lambda_decay = function() { - callback_async_tuning("mlr3mbo.exponential_lambda_decay", - label = "Exponential Decay of Lambda", - man = "mlr3mbo::mlr3mbo.exponential_lambda_decay", +#' @title Sample Lambda from an Uniform Distribution +#' +#' @name mlr3mbo.sample_lambda +#' +#' @description +#' This [CallbackAsyncTuning] samples the lambda parameter of the confidence bounds acquisition function. +#' The lambda value is drawn from a uniform distribution with `min` and `max` as bounds. +#' +#' @param min_lambda (`numeric(1)`)\cr +#' Minimum value of the lambda parameter. +#' Defaults to `0.01`. +#' @param max_lambda (`numeric(1)`)\cr +#' Maximum value of the lambda parameter. +#' Defaults to `10`. +#' +#' @examples +#' clbk("mlr3mbo.sample_lambda", min_lambda = 0.01, max_lambda = 10) +NULL + +load_callback_sample_lambda_uniform = function() { + callback_async_tuning("mlr3mbo.sample_lambda_uniform", + label = "Sample Lambda Uniform", + man = "mlr3mbo::mlr3mbo.sample_lambda_uniform", on_optimization_begin = function(callback, context) { - self$state$rate = assert_number(self$state$rate, lower = 0, null.ok = TRUE) %??% 0.1 - self$state$period = assert_integer(self$state$period, lower = 1, null.ok = TRUE) %??% 25 - self$state$init_lambda = assert_number(self$state$init_lambda, lower = 0, null.ok = TRUE) %??% 1.96 - - self$state$lambda_0 = rexp(1, 1 / self$state$init_lambda) - self$state$t = 0 + assert_class(context$optimizer$acq_function, "AcqFunctionCB") + callback$state$min_lambda = assert_number(callback$state$min_lambda, lower = 0, null.ok = TRUE) %??% 0.01 + callback$state$max_lambda = assert_number(callback$state$max_lambda, lower = 0, null.ok = TRUE) %??% 10 }, - on_optimization_end = function(callback, context) { - context$optimizer$ - t = self$state$t + on_worker_begin = function(callback, context) { + callback$state$lambda = runif(1, min = callback$state$min_lambda, max = callback$state$max_lambda) + context$optimizer$acq_function$constants$set_values(lambda = callback$state$lambda) + }, + on_optimizer_after_eval = function(callback, context) { + context$ys = c(context$ys, list(lambda = callback$state$lambda)) + } + ) +} +callbacks[["mlr3mbo.sample_lambda_uniform"]] = load_callback_sample_lambda_uniform + +#' @title Sample Lambda from an Exponential Distribution +#' +#' @name mlr3mbo.sample_lambda_exponential +#' +#' @description +#' This [CallbackAsyncTuning] samples the lambda parameter of the confidence bounds acquisition function. +#' The lambda value is drawn from an exponential distribution with rate `1 / lambda`. +#' +#' @examples +#' clbk("mlr3mbo.sample_lambda_exponential") +NULL + +load_callback_sample_lambda_exponential = function() { + callback_async_tuning("mlr3mbo.sample_lambda_exponential", + label = "Sample Lambda Exponential", + man = "mlr3mbo::mlr3mbo.sample_lambda_exponential", + on_optimization_begin = function(callback, context) { + assert_class(context$optimizer$acq_function, "AcqFunctionCB") + }, - lambda = lambda_0 * exp(-self$state$rate * (t %% self$state$period)) - self$state$t = self$t + 1 + on_worker_begin = function(callback, context) { + lambda = context$optimizer$acq_function$constants$values$lambda + callback$state$lambda = rexp(1, 1 / lambda) + context$optimizer$acq_function$constants$set_values(lambda = callback$state$lambda) + }, + on_optimizer_after_eval = function(callback, context) { + context$ys = c(context$ys, list(lambda = callback$state$lambda)) } ) } +callbacks[["mlr3mbo.sample_lambda_exponential"]] = load_callback_sample_lambda_exponential + +#' @title Exponential Decay of Lambda +#' +#' @name mlr3mbo.lambda_decay +#' +#' @description +#' This [CallbackAsyncTuning] decays the lambda parameter of the confidence bounds acquisition function. +#' The initial lambda value is drawn from an exponential distribution with rate `1 / lambda`. +#' The lambda value is updated after each evaluation by the formula `lambda * exp(-rate * (t %% period))`. +#' +#' @param rate (`numeric(1)`)\cr +#' Rate of the exponential decay. +#' Defaults to `0.1`. +#' @param period (`integer(1)`)\cr +#' Period of the exponential decay. +#' Defaults to `25`. +#' +#' @examples +#' clbk("mlr3mbo.exponential_lambda_decay", rate = 0.1, period = 25) +NULL +load_callback_exponential_lambda_decay = function() { + callback_async_tuning("mlr3mbo.exponential_lambda_decay", + label = "Exponential Decay of Lambda", + man = "mlr3mbo::mlr3mbo.exponential_lambda_decay", -# callback_exponential_lambda_decay + on_optimization_begin = function(callback, context) { + assert_class(context$optimizer$acq_function, "AcqFunctionCB") + callback$state$rate = assert_number(callback$state$rate, lower = 0, null.ok = TRUE) %??% 0.1 + callback$state$period = assert_integer(callback$state$period, lower = 1, null.ok = TRUE) %??% 25 + callback$state$t = 0 + }, + on_worker_begin = function(callback, context) { + lambda = context$optimizer$acq_function$constants$values$lambda + callback$state$lambda_0 = rexp(1, 1 / lambda) + context$optimizer$acq_function$constants$set_values(lambda = callback$state$lambda_0) + }, -# rate = p_dbl(lower = 0, default = 0.1), -# period = p_int(lower = 1L, default = 25L), + on_optimizer_after_eval = function(callback, context) { + if (!is.null(context$extra)) { # skip initial design + lambda_0 = callback$state$lambda_0 + t = callback$state$t + lambda = lambda_0 * exp(-callback$state$rate * (t %% callback$state$period)) + callback$state$t = t + 1 + context$optimizer$acq_function$constants$set_values(lambda = lambda) + context$ys = c(context$ys, list(lambda_0 = callback$state$lambda_0, lambda = lambda)) + } + } + ) +} +callbacks[["mlr3mbo.exponential_lambda_decay"]] = load_callback_exponential_lambda_decay + +#' @title Epsilon Decay +#' +#' @name mlr3mbo.sample_epsilon +#' +#' @description +#' This [CallbackAsyncTuning] decays the epsilon parameter of the expected improvement acquisition function. +#' +#' @param rate (`numeric(1)`)\cr +#' Rate of the exponential decay. +#' Defaults to `0.1`. +#' @param period (`integer(1)`)\cr +#' Period of the exponential decay. +#' Defaults to `25`. +#' @examples +#' clbk("mlr3mbo.sample_epsilon", rate = 0.1, period = 25) +NULL + +load_callback_epsilon_decay = function() { + callback_async_tuning("mlr3mbo.epsilon_decay", + label = "Episilon Decay", + man = "mlr3mbo::mlr3mbo.epsilon_decay", -# lambda_0 = rexp(1, 1 / pv$lambda) -# t = 0 + on_optimization_begin = function(callback, context) { + assert_class(context$optimizer$acq_function, "AcqFunctionEI") + epsilon = context$optimizer$acq_function$constants$values$epsilon + callback$state$epsilon_0 = epsilon + callback$state$epsilon = epsilon + callback$state$rate = assert_number(callback$state$rate, lower = 0, null.ok = TRUE) %??% 0.1 + callback$state$period = assert_number(callback$state$period, lower = 1, null.ok = TRUE) %??% 25 + callback$state$t = 0 + }, -# lambda_0 = rexp(1, 1 / pv$lambda) -# t = 0 + on_optimizer_after_eval = function(callback, context) { + if (!is.null(context$extra)) { # skip initial design + t = callback$state$t + epsilon_0 = callback$state$epsilon_0 + epsilon = epsilon_0 * exp(-callback$state$rate * (t %% callback$state$period)) + callback$state$t = t + 1 + context$optimizer$acq_function$constants$set_values(epsilon = epsilon) + context$ys = c(context$ys, list(epsilon_0 = epsilon_0, epsilon = epsilon)) + } + } + ) +} -# if (pv$exponential_decay) { -# lambda = lambda_0 * exp(-pv$rate * (t %% pv$period)) -# t = t + 1 -# } else { -# lambda = pv$lambda -# } +callbacks[["mlr3mbo.epsilon_decay"]] = load_callback_epsilon_decay diff --git a/R/zzz.R b/R/zzz.R index a0e1e22d..62d0e570 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -25,10 +25,17 @@ register_mlr3tuning = function() { iwalk(tuners, function(obj, nm) x$add(nm, obj)) } # nocov end +register_mlr3misc = function() { + # nocov start + x = utils::getFromNamespace("mlr_callbacks", ns = "mlr3misc") + iwalk(callbacks, function(obj, nm) x$add(nm, obj)) +} # nocov end + .onLoad = function(libname, pkgname) { # nolint # nocov start register_namespace_callback(pkgname, "bbotk", register_bbotk) register_namespace_callback(pkgname, "mlr3tuning", register_mlr3tuning) + register_namespace_callback(pkgname, "mlr3misc", register_mlr3misc) assign("lg", lgr::get_logger("bbotk"), envir = parent.env(environment())) @@ -41,6 +48,7 @@ register_mlr3tuning = function() { # nocov start walk(names(optimizers), function(id) bbotk::mlr_optimizers$remove(id)) walk(names(tuners), function(id) mlr3tuning::mlr_tuners$remove(id)) + walk(names(callbacks), function(id) mlr3misc::mlr_callbacks$remove(id)) } # nocov end # static code checks should not complain about commonly used data.table columns diff --git a/tests/testthat/test_OptimizerAsyncMbo.R b/tests/testthat/test_OptimizerAsyncMbo.R index 87ced07b..589d8183 100644 --- a/tests/testthat/test_OptimizerAsyncMbo.R +++ b/tests/testthat/test_OptimizerAsyncMbo.R @@ -1,4 +1,4 @@ -test_that("adbo optimizer works in defaults", { +test_that("async optimizer works in defaults", { skip_on_cran() skip_if_not_installed("rush") flush_redis() @@ -18,7 +18,7 @@ test_that("adbo optimizer works in defaults", { expect_rush_reset(instance$rush) }) -test_that("adbo optimizer works with ei", { +test_that("async optimizer works with ei", { skip_on_cran() skip_if_not_installed("rush") flush_redis() @@ -31,7 +31,7 @@ test_that("adbo optimizer works with ei", { ) optimizer = opt("async_mbo", design_function = "sobol", - design_size = 5, + design_size = 6, acq_function = acqf("ei")) expect_data_table(optimizer$optimize(instance), nrows = 1) @@ -41,16 +41,42 @@ test_that("adbo optimizer works with ei", { expect_rush_reset(instance$rush) }) -test_that("adbo optimizer works with cb with fixed lambda", { +test_that("async optimizer works with ei with epsilon decay", { skip_on_cran() skip_if_not_installed("rush") flush_redis() + options(bbotk_local = TRUE) + rush::rush_plan(n_workers = 2) instance = oi_async( objective = OBJ_2D, search_space = PS_2D, - terminator = trm("evals", n_evals = 10), + terminator = trm("evals", n_evals = 26), + callback = clbk("mlr3mbo.epsilon_decay", rate = 0.1, period = 5L) + ) + optimizer = opt("async_mbo", + design_function = "sobol", + design_size = 6, + acq_function = acqf("ei", epsilon = 3)) + + expect_data_table(optimizer$optimize(instance), nrows = 1) + expect_data_table(instance$archive$data, min.rows = 10) + expect_names(names(instance$archive$data), must.include = c("acq_ei", "epsilon_0", "epsilon", ".already_evaluated")) + + expect_rush_reset(instance$rush) +}) + +test_that("async optimizer works with cb", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 2) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 10) ) optimizer = opt("async_mbo", design_function = "sobol", @@ -64,3 +90,76 @@ test_that("adbo optimizer works with cb with fixed lambda", { expect_rush_reset(instance$rush) }) +test_that("async optimizer works with cb with uniformly sampled lambda", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 2) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 20), + callback = clbk("mlr3mbo.sample_lambda_uniform") + ) + optimizer = opt("async_mbo", + design_function = "sobol", + design_size = 5, + acq_function = acqf("cb", lambda = 3)) + + expect_data_table(optimizer$optimize(instance), nrows = 1) + expect_data_table(instance$archive$data, min.rows = 10) + expect_names(names(instance$archive$data), must.include = c("acq_cb", "lambda", ".already_evaluated")) + expect_length(unique(instance$archive$data$lambda), 2) + + expect_rush_reset(instance$rush) +}) + +test_that("async optimizer works with cb with exponentially sampled lambda", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 2) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 20), + callback = clbk("mlr3mbo.sample_lambda_exponential") + ) + optimizer = opt("async_mbo", + design_function = "sobol", + design_size = 5, + acq_function = acqf("cb", lambda = 3)) + + expect_data_table(optimizer$optimize(instance), nrows = 1) + expect_data_table(instance$archive$data, min.rows = 10) + expect_names(names(instance$archive$data), must.include = c("acq_cb", "lambda", ".already_evaluated")) + expect_length(unique(instance$archive$data$lambda), 2) + + expect_rush_reset(instance$rush) +}) + +test_that("async mbo works with exponential lambda decay", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 2) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 10), + callback = clbk("mlr3mbo.exponential_lambda_decay") + ) + optimizer = opt("async_mbo", + design_function = "sobol", + design_size = 5, + acq_function = acqf("cb", lambda = 3)) + + expect_data_table(optimizer$optimize(instance), nrows = 1) + expect_data_table(instance$archive$data, min.rows = 10) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "lambda_0", "lambda")) + + expect_rush_reset(instance$rush) +}) From 782040bfa705bbf956a0c2844623704abfdfa076 Mon Sep 17 00:00:00 2001 From: be-marc Date: Wed, 11 Sep 2024 18:59:37 +0200 Subject: [PATCH 29/49] compatibility: mlr3 0.21.0 --- DESCRIPTION | 3 +- R/mbo_defaults.R | 8 ++--- tests/testthat/helper.R | 6 ++-- tests/testthat/test_mbo_defaults.R | 47 +++++++++++++++--------------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 48499663..32c95184 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -66,7 +66,8 @@ Suggests: rpart, stringi, testthat (>= 3.0.0) -Remotes: mlr-org/bbotk +Remotes: + mlr-org/mlr3 ByteCompile: no Encoding: UTF-8 Config/testthat/edition: 3 diff --git a/R/mbo_defaults.R b/R/mbo_defaults.R index 69c9664d..2f6cf302 100644 --- a/R/mbo_defaults.R +++ b/R/mbo_defaults.R @@ -161,14 +161,13 @@ default_surrogate = function(instance, learner = NULL, n_learner = NULL) { default_rf(noisy) } # stability: evaluate and add a fallback - learner$encapsulate[c("train", "predict")] = "evaluate" require_namespaces("ranger") fallback = mlr3learners::LearnerRegrRanger$new() fallback$param_set$values = insert_named( fallback$param_set$values, list(num.trees = 10L, keep.inbag = TRUE, se.method = "jack") ) - learner$fallback = fallback + learner$encapsulate("evaluate", fallback) if (has_deps) { require_namespaces("mlr3pipelines") @@ -184,8 +183,7 @@ default_surrogate = function(instance, learner = NULL, n_learner = NULL) { learner ) ) - learner$encapsulate[c("train", "predict")] = "evaluate" - learner$fallback = LearnerRegrFeatureless$new() + learner$encapsulate("evaluate", lrn("regr.featureless")) } } @@ -213,6 +211,8 @@ default_acqfunction = function(instance) { assert_r6(instance, classes = "OptimInstance") if (inherits(instance, "OptimInstanceBatchSingleCrit")) { AcqFunctionEI$new() + } else if (inherits(instance, "OptimInstanceAsyncSingleCrit")) { + AcqFunctionCB$new() } else if (inherits(instance, "OptimInstanceBatchMultiCrit")) { AcqFunctionSmsEgo$new() } diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index 6acb90d0..6ba9a512 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -102,12 +102,12 @@ MAKE_DESIGN = function(instance, n = 4L) { if (requireNamespace("mlr3learners") && requireNamespace("DiceKriging") && requireNamespace("rgenoud")) { library(mlr3learners) REGR_KM_NOISY = lrn("regr.km", covtype = "matern3_2", optim.method = "gen", control = list(trace = FALSE, max.generations = 2), nugget.estim = TRUE, jitter = 1e-12) - REGR_KM_NOISY$encapsulate = c(train = "callr", predict = "callr") + REGR_KM_NOISY$encapsulate("callr", lrn("regr.featureless")) REGR_KM_DETERM = lrn("regr.km", covtype = "matern3_2", optim.method = "gen", control = list(trace = FALSE, max.generations = 2), nugget.stability = 10^-8) - REGR_KM_DETERM$encapsulate = c(train = "callr", predict = "callr") + REGR_KM_DETERM$encapsulate("callr", lrn("regr.featureless")) } REGR_FEATURELESS = lrn("regr.featureless") -REGR_FEATURELESS$encapsulate = c(train = "callr", predict = "callr") +REGR_FEATURELESS$encapsulate("callr", lrn("regr.featureless")) OptimizerError = R6Class("OptimizerError", inherit = OptimizerBatch, diff --git a/tests/testthat/test_mbo_defaults.R b/tests/testthat/test_mbo_defaults.R index 7cef2365..062c35e1 100644 --- a/tests/testthat/test_mbo_defaults.R +++ b/tests/testthat/test_mbo_defaults.R @@ -23,7 +23,7 @@ test_that("default_surrogate", { expect_r6(surrogate$learner, "LearnerRegrKM") expect_equal_sorted(surrogate$learner$param_set$values, list(covtype = "matern5_2", optim.method = "gen", control = list(trace = FALSE), nugget.stability = 1e-08)) - expect_equal(surrogate$learner$encapsulate, c(train = "evaluate", predict = "evaluate")) + expect_equal(surrogate$learner$encapsulation, c(train = "evaluate", predict = "evaluate")) expect_r6(surrogate$learner$fallback, "LearnerRegrRanger") # singlecrit all numeric, noisy @@ -32,7 +32,7 @@ test_that("default_surrogate", { expect_r6(surrogate$learner, "LearnerRegrKM") expect_equal_sorted(surrogate$learner$param_set$values, list(covtype = "matern5_2", optim.method = "gen", control = list(trace = FALSE), nugget.estim = TRUE, jitter = 1e-12)) - expect_equal(surrogate$learner$encapsulate, c(train = "evaluate", predict = "evaluate")) + expect_equal(surrogate$learner$encapsulation, c(train = "evaluate", predict = "evaluate")) expect_r6(surrogate$learner$fallback, "LearnerRegrRanger") # twocrit all numeric, deterministic @@ -41,11 +41,11 @@ test_that("default_surrogate", { expect_list(surrogate$learner, types = "LearnerRegrKM") expect_equal_sorted(surrogate$learner[[1L]]$param_set$values, list(covtype = "matern5_2", optim.method = "gen", control = list(trace = FALSE), nugget.stability = 1e-08)) - expect_equal(surrogate$learner[[1L]]$encapsulate, c(train = "evaluate", predict = "evaluate")) + expect_equal(surrogate$learner[[1L]]$encapsulation, c(train = "evaluate", predict = "evaluate")) expect_r6(surrogate$learner[[1L]]$fallback, "LearnerRegrRanger") expect_equal(surrogate$learner[[1L]]$param_set$values, surrogate$learner[[2L]]$param_set$values) - expect_equal(surrogate$learner[[1L]]$encapsulate, surrogate$learner[[2L]]$encapsulate) - expect_equal(surrogate$learner[[1L]]$fallback, surrogate$learner[[2L]]$fallback) + expect_equal(surrogate$learner[[1L]]$encapsulation, surrogate$learner[[2L]]$encapsulation) + # expect_equal(surrogate$learner[[1L]]$fallback, surrogate$learner[[2L]]$fallback) # twocrit all numeric, noisy surrogate = default_surrogate(MAKE_INST(OBJ_1D_2_NOISY, search_space = PS_1D)) @@ -53,10 +53,10 @@ test_that("default_surrogate", { expect_list(surrogate$learner, types = "LearnerRegrKM") expect_equal_sorted(surrogate$learner[[1L]]$param_set$values, list(covtype = "matern5_2", optim.method = "gen", control = list(trace = FALSE), nugget.estim = TRUE, jitter = 1e-12)) - expect_equal(surrogate$learner[[1L]]$encapsulate, c(train = "evaluate", predict = "evaluate")) + expect_equal(surrogate$learner[[1L]]$encapsulation, c(train = "evaluate", predict = "evaluate")) expect_r6(surrogate$learner[[1L]]$fallback, "LearnerRegrRanger") expect_equal(surrogate$learner[[1L]]$param_set$values, surrogate$learner[[2L]]$param_set$values) - expect_equal(surrogate$learner[[1L]]$encapsulate, surrogate$learner[[2L]]$encapsulate) + expect_equal(surrogate$learner[[1L]]$encapsulation, surrogate$learner[[2L]]$encapsulation) expect_equal(surrogate$learner[[1L]]$fallback, surrogate$learner[[2L]]$fallback) # singlecrit mixed input @@ -65,7 +65,7 @@ test_that("default_surrogate", { expect_r6(surrogate$learner, "LearnerRegrRanger") expect_equal_sorted(surrogate$learner$param_set$values, list(num.threads = 1L, num.trees = 100L, keep.inbag = TRUE, se.method = "jack")) - expect_equal(surrogate$learner$encapsulate, c(train = "evaluate", predict = "evaluate")) + expect_equal(surrogate$learner$encapsulation, c(train = "evaluate", predict = "evaluate")) expect_r6(surrogate$learner$fallback, "LearnerRegrRanger") # twocrit mixed input @@ -74,10 +74,10 @@ test_that("default_surrogate", { expect_list(surrogate$learner, types = "LearnerRegrRanger") expect_equal_sorted(surrogate$learner[[1L]]$param_set$values, list(num.threads = 1L, num.trees = 100L, keep.inbag = TRUE, se.method = "jack")) - expect_equal(surrogate$learner[[1L]]$encapsulate, c(train = "evaluate", predict = "evaluate")) + expect_equal(surrogate$learner[[1L]]$encapsulation, c(train = "evaluate", predict = "evaluate")) expect_r6(surrogate$learner[[1L]]$fallback, "LearnerRegrRanger") expect_equal(surrogate$learner[[1L]]$param_set$values, surrogate$learner[[2L]]$param_set$values) - expect_equal(surrogate$learner[[1L]]$encapsulate, surrogate$learner[[2L]]$encapsulate) + expect_equal(surrogate$learner[[1L]]$encapsulation, surrogate$learner[[2L]]$encapsulation) expect_equal(surrogate$learner[[1L]]$fallback, surrogate$learner[[2L]]$fallback) # singlecrit mixed input deps @@ -152,12 +152,11 @@ test_that("stability and defaults", { # this should trigger a mbo_error instance = MAKE_INST_1D(terminator = trm("evals", n_evals = 5L)) learner = LearnerRegrError$new() - learner$encapsulate[c("train", "predict")] = "evaluate" - learner$fallback = lrn("regr.ranger", num.trees = 10L, keep.inbag = TRUE, se.method = "jack") + learner$encapsulate("evaluate", lrn("regr.ranger", num.trees = 10L, keep.inbag = TRUE, se.method = "jack")) surrogate = default_surrogate(instance, learner = learner, n_learner = 1L) expect_r6(surrogate, "SurrogateLearner") expect_r6(surrogate$learner, "LearnerRegrError") - expect_equal(surrogate$learner$encapsulate, c(train = "evaluate", predict = "evaluate")) + expect_equal(surrogate$learner$encapsulation, c(train = "evaluate", predict = "evaluate")) expect_r6(surrogate$learner$fallback, "LearnerRegrRanger") acq_function = default_acqfunction(instance) expect_r6(acq_function, "AcqFunctionEI") @@ -173,16 +172,16 @@ test_that("stability and defaults", { # Nothing should happen here due to the fallback learner expect_true(sum(grepl("Surrogate Train Error", unlist(map(strsplit(lines, "\\[bbotk\\] "), 2L)))) == 0L) - acq_function$surrogate$learner$reset() - acq_function$surrogate$learner$fallback = NULL - instance$archive$clear() - bayesopt_ego(instance, surrogate = surrogate, acq_function = acq_function, acq_optimizer = acq_optimizer) - expect_true(nrow(instance$archive$data) == 5L) - lines = readLines(f) - # Training fails but this error is not logged due to the "evaluate" encapsulate - expect_equal(acq_function$surrogate$learner$errors, "Surrogate Train Error.") - expect_true(sum(grepl("Surrogate Train Error", unlist(map(strsplit(lines, "\\[bbotk\\] "), 2L)))) == 0L) - expect_true(sum(grepl("Cannot predict", unlist(map(strsplit(lines, "\\[bbotk\\] "), 2L)))) == 1L) - expect_true(sum(grepl("Proposing a randomly sampled point", unlist(map(strsplit(lines, "\\[bbotk\\] "), 2L)))) == 1L) + # acq_function$surrogate$learner$reset() + # acq_function$surrogate$learner$fallback = NULL + # instance$archive$clear() + # bayesopt_ego(instance, surrogate = surrogate, acq_function = acq_function, acq_optimizer = acq_optimizer) + # expect_true(nrow(instance$archive$data) == 5L) + # lines = readLines(f) + # # Training fails but this error is not logged due to the "evaluate" encapsulate + # expect_equal(acq_function$surrogate$learner$errors, "Surrogate Train Error.") + # expect_true(sum(grepl("Surrogate Train Error", unlist(map(strsplit(lines, "\\[bbotk\\] "), 2L)))) == 0L) + # expect_true(sum(grepl("Cannot predict", unlist(map(strsplit(lines, "\\[bbotk\\] "), 2L)))) == 1L) + # expect_true(sum(grepl("Proposing a randomly sampled point", unlist(map(strsplit(lines, "\\[bbotk\\] "), 2L)))) == 1L) }) From b43d5424cd19a9367c2845020132f8e7f6a2c9c3 Mon Sep 17 00:00:00 2001 From: be-marc Date: Wed, 11 Sep 2024 19:00:46 +0200 Subject: [PATCH 30/49] ... --- R/mbo_defaults.R | 2 -- 1 file changed, 2 deletions(-) diff --git a/R/mbo_defaults.R b/R/mbo_defaults.R index 2f6cf302..e6bcb7f1 100644 --- a/R/mbo_defaults.R +++ b/R/mbo_defaults.R @@ -211,8 +211,6 @@ default_acqfunction = function(instance) { assert_r6(instance, classes = "OptimInstance") if (inherits(instance, "OptimInstanceBatchSingleCrit")) { AcqFunctionEI$new() - } else if (inherits(instance, "OptimInstanceAsyncSingleCrit")) { - AcqFunctionCB$new() } else if (inherits(instance, "OptimInstanceBatchMultiCrit")) { AcqFunctionSmsEgo$new() } From 367583c0a69fd67bd4a221914d9883a8d3334cc4 Mon Sep 17 00:00:00 2001 From: be-marc Date: Fri, 13 Sep 2024 18:30:36 +0200 Subject: [PATCH 31/49] ... --- DESCRIPTION | 5 +- NAMESPACE | 4 +- R/OptimizerADBO.R | 152 ------------------ R/OptimizerAsyncMbo.R | 11 +- R/TunerAsyncMbo.R | 34 ++++ attic/OptimizerADBO.R | 124 ++++++++++++++ {R => attic}/TunerADBO.R | 0 attic/test_OptimizerADBO.R | 15 ++ {tests/testthat => attic}/test_TunerADBO.R | 0 man/mlr3mbo.lambda_decay.Rd | 22 +++ man/mlr3mbo.sample_epsilon.Rd | 20 +++ man/mlr3mbo.sample_lambda.Rd | 21 +++ man/mlr3mbo.sample_lambda_exponential.Rd | 12 ++ man/mlr_optimizers_adbo.Rd | 108 ------------- man/mlr_optimizers_async_mbo.Rd | 115 +++++++++++++ ...tuners_adbo.Rd => mlr_tuners_async_mbo.Rd} | 54 ++----- tests/testthat/test_OptimizerADBO.R | 15 -- tests/testthat/test_OptimizerAsyncMbo.R | 2 - tests/testthat/test_TunerAsyncMbo.R | 87 ++++++++++ 19 files changed, 473 insertions(+), 328 deletions(-) delete mode 100644 R/OptimizerADBO.R create mode 100644 R/TunerAsyncMbo.R create mode 100644 attic/OptimizerADBO.R rename {R => attic}/TunerADBO.R (100%) create mode 100644 attic/test_OptimizerADBO.R rename {tests/testthat => attic}/test_TunerADBO.R (100%) create mode 100644 man/mlr3mbo.lambda_decay.Rd create mode 100644 man/mlr3mbo.sample_epsilon.Rd create mode 100644 man/mlr3mbo.sample_lambda.Rd create mode 100644 man/mlr3mbo.sample_lambda_exponential.Rd delete mode 100644 man/mlr_optimizers_adbo.Rd create mode 100644 man/mlr_optimizers_async_mbo.Rd rename man/{mlr_tuners_adbo.Rd => mlr_tuners_async_mbo.Rd} (50%) delete mode 100644 tests/testthat/test_OptimizerADBO.R create mode 100644 tests/testthat/test_TunerAsyncMbo.R diff --git a/DESCRIPTION b/DESCRIPTION index 8696e900..f146ca46 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -92,9 +92,8 @@ Collate: 'AcqFunctionSmsEgo.R' 'AcqOptimizer.R' 'aaa.R' - 'OptimizerMbo.R' 'OptimizerAsyncMbo.R' - 'OptimizerADBO.R' + 'OptimizerMbo.R' 'mlr_result_assigners.R' 'ResultAssigner.R' 'ResultAssignerArchive.R' @@ -103,7 +102,7 @@ Collate: 'SurrogateLearner.R' 'SurrogateLearnerAsync.R' 'SurrogateLearnerCollection.R' - 'TunerADBO.R' + 'TunerAsyncMbo.R' 'TunerMbo.R' 'mlr_loop_functions.R' 'bayesopt_ego.R' diff --git a/NAMESPACE b/NAMESPACE index c968d2ae..a12ea3ba 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -17,7 +17,7 @@ export(AcqFunctionPI) export(AcqFunctionSD) export(AcqFunctionSmsEgo) export(AcqOptimizer) -export(OptimizerADBO) +export(OptimizerAsyncMbo) export(OptimizerMbo) export(ResultAssigner) export(ResultAssignerArchive) @@ -26,7 +26,7 @@ export(Surrogate) export(SurrogateLearner) export(SurrogateLearnerAsync) export(SurrogateLearnerCollection) -export(TunerADBO) +export(TunerAsyncMbo) export(TunerMbo) export(acqf) export(acqfs) diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R deleted file mode 100644 index 683961f1..00000000 --- a/R/OptimizerADBO.R +++ /dev/null @@ -1,152 +0,0 @@ -#' @title Asynchronous Decentralized Bayesian Optimization -#' @name mlr_optimizers_adbo -#' -#' @description -#' Asynchronous Decentralized Bayesian Optimization (ADBO). -#' -#' @note -#' The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. -#' A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. -#' ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. -#' -#' @section Parameters: -#' \describe{ -#' \item{`lambda`}{`numeric(1)`\cr -#' \eqn{\lambda} value used for the confidence bound. -#' Defaults to `1.96`.} -#' \item{`exponential_decay`}{`lgl(1)`\cr -#' Whether to use periodic exponential decay for \eqn{\lambda}.} -#' \item{`rate`}{`numeric(1)`\cr -#' Rate of the exponential decay.} -#' \item{`t`}{`integer(1)`\cr -#' Period of the exponential decay.} -#' \item{`initial_design_size`}{`integer(1)`\cr -#' Size of the initial design.} -#' \item{`initial_design`}{`data.table`\cr -#' Initial design.} -#' \item{`impute_method`}{`character(1)`\cr -#' Imputation method for missing values in the surrogate model.} -#' \item{`n_workers`}{`integer(1)`\cr -#' Number of workers to use. -#' Defaults to the number of workers set by `rush::rush_plan()`} -#' } -#' -#' @export -OptimizerADBO = R6Class("OptimizerADBO", - inherit = OptimizerAsynMbo, - - public = list( - - #' @description - #' Creates a new instance of this [R6][R6::R6Class] class. - initialize = function() { - param_set = ps( - lambda = p_dbl(lower = 0, default = 1.96), - exponential_decay = p_lgl(default = TRUE), - rate = p_dbl(lower = 0, default = 0.1), - period = p_int(lower = 1L, default = 25L), - design_size = p_int(lower = 1L), - initial_design = p_uty(), - impute_method = p_fct(c("mean", "random"), default = "random"), - n_workers = p_int(lower = 1L, default = NULL, special_vals = list(NULL)) - ) - - param_set$set_values(lambda = 1.96, exponential_decay = TRUE, rate = 0.1, period = 25L, design_size = 1L, impute_method = "random") - - super$initialize("adbo", - param_set = param_set, - param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), - properties = c("dependencies", "single-crit"), - packages = "mlr3mbo", - label = "Asynchronous Decentralized Bayesian Optimization", - man = "mlr3mbo::OptimizerADBO") - }, - - - #' @description - #' Performs the optimization on a [OptimInstanceAsyncSingleCrit] or [OptimInstanceAsyncMultiCrit] until termination. - #' The single evaluations will be written into the [ArchiveAsync]. - #' The result will be written into the instance object. - #' - #' @param inst ([OptimInstanceAsyncSingleCrit] | [OptimInstanceAsyncMultiCrit]). - #' - #' @return [data.table::data.table()] - optimize = function(inst) { - pv = self$param_set$values - - # initial design - design = if (is.null(pv$initial_design)) { - - lg$debug("Generating sobol design with size %s", pv$design_size) - generate_design_sobol(inst$search_space, n = pv$design_size)$data - } else { - - lg$debug("Using provided initial design with size %s", nrow(pv$initial_design)) - pv$initial_design - } - - optimize_async_default(inst, self, design, n_workers = pv$n_workers) - } - ), - - private = list( - - .optimize = function(inst) { - pv = self$param_set$values - search_space = inst$search_space - archive = inst$archive - - # sample lambda from exponential distribution - lambda_0 = rexp(1, 1 / pv$lambda) - t = 0 - - surrogate = default_surrogate(inst) - surrogate$param_set$set_values(impute_method = pv$impute_method) - acq_function = acqf("cb", lambda = runif(1, 1 , 3)) - acq_optimizer = acqo(opt("random_search", batch_size = 1000L), terminator = trm("evals", n_evals = 10000L)) - surrogate$archive = inst$archive - acq_function$surrogate = surrogate - acq_optimizer$acq_function = acq_function - - lg$debug("Optimizer '%s' evaluates the initial design", self$id) - evaluate_queue_default(inst) - - lg$debug("Optimizer '%s' starts the tuning phase", self$id) - - # actual loop - while (!inst$is_terminated) { - - # decrease lambda - if (pv$exponential_decay) { - lambda = lambda_0 * exp(-pv$rate * (t %% pv$period)) - t = t + 1 - } else { - lambda = pv$lambda - } - - # sample - acq_function$constants$set_values(lambda = lambda) - acq_function$surrogate$update() - acq_function$update() - xdt = acq_optimizer$optimize() - - # transpose point - xss = transpose_list(xdt) - xs = xss[[1]][inst$archive$cols_x] - lg$trace("Optimizer '%s' draws %s", self$id, as_short_string(xs)) - xs_trafoed = trafo_xs(xs, search_space) - - # eval - key = archive$push_running_point(xs) - ys = inst$objective$eval(xs_trafoed) - - # push result - extra = c(xss[[1]][c("acq_cb", ".already_evaluated")], list(lambda_0 = lambda_0, lambda = lambda)) - archive$push_result(key, ys, x_domain = xs_trafoed, extra = extra) - } - } - ) -) - -#' @include aaa.R -optimizers[["adbo"]] = OptimizerADBO diff --git a/R/OptimizerAsyncMbo.R b/R/OptimizerAsyncMbo.R index 45836757..33740e2a 100644 --- a/R/OptimizerAsyncMbo.R +++ b/R/OptimizerAsyncMbo.R @@ -13,12 +13,13 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", #' @description #' Creates a new instance of this [R6][R6::R6Class] class. - initialize = function(surrogate = NULL, acq_function = NULL, acq_optimizer = NULL) { - param_set = ps( + initialize = function(surrogate = NULL, acq_function = NULL, acq_optimizer = NULL, param_set = NULL) { + default_param_set = ps( initial_design = p_uty(), design_size = p_int(lower = 1, default = 10), design_function = p_fct(c("random", "sobol", "lhs"), default = "sobol") ) + param_set = c(default_param_set, param_set) param_set$set_values(design_size = 10, design_function = "sobol") @@ -113,17 +114,13 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", properties = function(rhs) { assert_ro_binding(rhs) - properties_loop_function = c("single-crit", "multi-crit") - if (!is.null(self$loop_function)) { - properties_loop_function = intersect(properties_loop_function, attr(self$loop_function, "instance")) - } properties_surrogate = "dependencies" if (!is.null(self$surrogate)) { if ("missings" %nin% self$surrogate$properties) { properties_surrogate = character() } } - unname(c(properties_surrogate, properties_loop_function)) + unname(c(properties_surrogate)) }, #' @template field_packages diff --git a/R/TunerAsyncMbo.R b/R/TunerAsyncMbo.R new file mode 100644 index 00000000..fb9f8626 --- /dev/null +++ b/R/TunerAsyncMbo.R @@ -0,0 +1,34 @@ +#' @title Asynchronous Model Based Optimization +#' +#' @name mlr_tuners_async_mbo +#' +#' @description +#' `TunerAsyncMbo` class that implements Asynchronous Model Based Optimization (MBO). +#' +#' @export +TunerAsyncMbo = R6Class("TunerAsyncMbo", + inherit = mlr3tuning::TunerAsyncFromOptimizerAsync, + public = list( + + #' @description + #' Creates a new instance of this [R6][R6::R6Class] class. + initialize = function(surrogate = NULL, acq_function = NULL, acq_optimizer = NULL, param_set = NULL) { + optimizer = OptimizerAsyncMbo$new( + surrogate = surrogate, + acq_function = acq_function, + acq_optimizer = acq_optimizer, + param_set = param_set + ) + + super$initialize( + optimizer = optimizer, + man = "mlr3tuning::mlr_tuners_async_mbo" + ) + } + ) +) + +mlr_tuners$add("async_mbo", TunerAsyncMbo) + +#' @include aaa.R +tuners[["async_mbo"]] = TunerAsyncMbo diff --git a/attic/OptimizerADBO.R b/attic/OptimizerADBO.R new file mode 100644 index 00000000..e28b593c --- /dev/null +++ b/attic/OptimizerADBO.R @@ -0,0 +1,124 @@ +# #' @title Asynchronous Decentralized Bayesian Optimization +# #' @name mlr_optimizers_adbo +# #' +# #' @description +# #' Asynchronous Decentralized Bayesian Optimization (ADBO). +# #' +# #' @note +# #' The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. +# #' A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. +# #' ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. +# #' +# #' @section Parameters: +# #' \describe{ +# #' \item{`lambda`}{`numeric(1)`\cr +# #' \eqn{\lambda} value used for the confidence bound. +# #' Defaults to `1.96`.} +# #' \item{`exponential_decay`}{`lgl(1)`\cr +# #' Whether to use periodic exponential decay for \eqn{\lambda}.} +# #' \item{`rate`}{`numeric(1)`\cr +# #' Rate of the exponential decay.} +# #' \item{`t`}{`integer(1)`\cr +# #' Period of the exponential decay.} +# #' \item{`initial_design_size`}{`integer(1)`\cr +# #' Size of the initial design.} +# #' \item{`initial_design`}{`data.table`\cr +# #' Initial design.} +# #' \item{`impute_method`}{`character(1)`\cr +# #' Imputation method for missing values in the surrogate model.} +# #' \item{`n_workers`}{`integer(1)`\cr +# #' Number of workers to use. +# #' Defaults to the number of workers set by `rush::rush_plan()`} +# #' } +# #' +# #' @export +# OptimizerADBO = R6Class("OptimizerADBO", +# inherit = OptimizerAsyncMbo, + +# public = list( + +# #' @description +# #' Creates a new instance of this [R6][R6::R6Class] class. +# initialize = function() { +# param_set = ps( +# lambda = p_dbl(lower = 0, default = 1.96), +# exponential_decay = p_lgl(default = TRUE), +# rate = p_dbl(lower = 0, default = 0.1), +# period = p_int(lower = 1L, default = 25L), +# # impute_method = p_fct(c("mean", "random"), default = "random"), +# n_workers = p_int(lower = 1L, default = NULL, special_vals = list(NULL)) +# ) + +# param_set$set_values(lambda = 1.96, exponential_decay = TRUE, rate = 0.1, period = 25L, design_size = 1L, impute_method = "random") + +# super$initialize("adbo", +# param_set = param_set, +# param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), +# properties = c("dependencies", "single-crit"), +# packages = "mlr3mbo", +# label = "Asynchronous Decentralized Bayesian Optimization", +# man = "mlr3mbo::OptimizerADBO") +# } +# ), + +# private = list( + +# .optimize = function(inst) { +# pv = self$param_set$values +# search_space = inst$search_space +# archive = inst$archive + +# # sample lambda from exponential distribution +# lambda_0 = rexp(1, 1 / pv$lambda) +# t = 0 + +# surrogate = default_surrogate(inst) +# surrogate$param_set$set_values(impute_method = pv$impute_method) +# acq_function = acqf("cb", lambda = runif(1, 1 , 3)) +# acq_optimizer = acqo(opt("random_search", batch_size = 1000L), terminator = trm("evals", n_evals = 10000L)) +# surrogate$archive = inst$archive +# acq_function$surrogate = surrogate +# acq_optimizer$acq_function = acq_function + +# lg$debug("Optimizer '%s' evaluates the initial design", self$id) +# evaluate_queue_default(inst) + +# lg$debug("Optimizer '%s' starts the tuning phase", self$id) + +# # actual loop +# while (!inst$is_terminated) { + +# # decrease lambda +# if (pv$exponential_decay) { +# lambda = lambda_0 * exp(-pv$rate * (t %% pv$period)) +# t = t + 1 +# } else { +# lambda = pv$lambda +# } + +# # sample +# acq_function$constants$set_values(lambda = lambda) +# acq_function$surrogate$update() +# acq_function$update() +# xdt = acq_optimizer$optimize() + +# # transpose point +# xss = transpose_list(xdt) +# xs = xss[[1]][inst$archive$cols_x] +# lg$trace("Optimizer '%s' draws %s", self$id, as_short_string(xs)) +# xs_trafoed = trafo_xs(xs, search_space) + +# # eval +# key = archive$push_running_point(xs) +# ys = inst$objective$eval(xs_trafoed) + +# # push result +# extra = c(xss[[1]][c("acq_cb", ".already_evaluated")], list(lambda_0 = lambda_0, lambda = lambda)) +# archive$push_result(key, ys, x_domain = xs_trafoed, extra = extra) +# } +# } +# ) +# ) + +# #' @include aaa.R +# optimizers[["adbo"]] = OptimizerADBO diff --git a/R/TunerADBO.R b/attic/TunerADBO.R similarity index 100% rename from R/TunerADBO.R rename to attic/TunerADBO.R diff --git a/attic/test_OptimizerADBO.R b/attic/test_OptimizerADBO.R new file mode 100644 index 00000000..1c91606e --- /dev/null +++ b/attic/test_OptimizerADBO.R @@ -0,0 +1,15 @@ +# test_that("adbo optimizer works", { +# skip_on_cran() +# skip_if_not_installed("rush") +# flush_redis() + +# rush::rush_plan(n_workers = 2) +# instance = oi_async( +# objective = OBJ_2D, +# search_space = PS_2D, +# terminator = trm("evals", n_evals = 100), +# ) +# optimizer = opt("adbo", design_size = 4) +# optimizer$optimize(instance) +# }) + diff --git a/tests/testthat/test_TunerADBO.R b/attic/test_TunerADBO.R similarity index 100% rename from tests/testthat/test_TunerADBO.R rename to attic/test_TunerADBO.R diff --git a/man/mlr3mbo.lambda_decay.Rd b/man/mlr3mbo.lambda_decay.Rd new file mode 100644 index 00000000..163dd8fc --- /dev/null +++ b/man/mlr3mbo.lambda_decay.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mlr_callbacks.R +\name{mlr3mbo.lambda_decay} +\alias{mlr3mbo.lambda_decay} +\title{Exponential Decay of Lambda} +\arguments{ +\item{rate}{(\code{numeric(1)})\cr +Rate of the exponential decay. +Defaults to \code{0.1}.} + +\item{period}{(\code{integer(1)})\cr +Period of the exponential decay. +Defaults to \code{25}.} +} +\description{ +This \link{CallbackAsyncTuning} decays the lambda parameter of the confidence bounds acquisition function. +The initial lambda value is drawn from an exponential distribution with rate \code{1 / lambda}. +The lambda value is updated after each evaluation by the formula \code{lambda * exp(-rate * (t \%\% period))}. +} +\examples{ +clbk("mlr3mbo.exponential_lambda_decay", rate = 0.1, period = 25) +} diff --git a/man/mlr3mbo.sample_epsilon.Rd b/man/mlr3mbo.sample_epsilon.Rd new file mode 100644 index 00000000..3c2408e9 --- /dev/null +++ b/man/mlr3mbo.sample_epsilon.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mlr_callbacks.R +\name{mlr3mbo.sample_epsilon} +\alias{mlr3mbo.sample_epsilon} +\title{Epsilon Decay} +\arguments{ +\item{rate}{(\code{numeric(1)})\cr +Rate of the exponential decay. +Defaults to \code{0.1}.} + +\item{period}{(\code{integer(1)})\cr +Period of the exponential decay. +Defaults to \code{25}.} +} +\description{ +This \link{CallbackAsyncTuning} decays the epsilon parameter of the expected improvement acquisition function. +} +\examples{ +clbk("mlr3mbo.sample_epsilon", rate = 0.1, period = 25) +} diff --git a/man/mlr3mbo.sample_lambda.Rd b/man/mlr3mbo.sample_lambda.Rd new file mode 100644 index 00000000..585f0d12 --- /dev/null +++ b/man/mlr3mbo.sample_lambda.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mlr_callbacks.R +\name{mlr3mbo.sample_lambda} +\alias{mlr3mbo.sample_lambda} +\title{Sample Lambda from an Uniform Distribution} +\arguments{ +\item{min_lambda}{(\code{numeric(1)})\cr +Minimum value of the lambda parameter. +Defaults to \code{0.01}.} + +\item{max_lambda}{(\code{numeric(1)})\cr +Maximum value of the lambda parameter. +Defaults to \code{10}.} +} +\description{ +This \link{CallbackAsyncTuning} samples the lambda parameter of the confidence bounds acquisition function. +The lambda value is drawn from a uniform distribution with \code{min} and \code{max} as bounds. +} +\examples{ +clbk("mlr3mbo.sample_lambda", min_lambda = 0.01, max_lambda = 10) +} diff --git a/man/mlr3mbo.sample_lambda_exponential.Rd b/man/mlr3mbo.sample_lambda_exponential.Rd new file mode 100644 index 00000000..6a4182f3 --- /dev/null +++ b/man/mlr3mbo.sample_lambda_exponential.Rd @@ -0,0 +1,12 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mlr_callbacks.R +\name{mlr3mbo.sample_lambda_exponential} +\alias{mlr3mbo.sample_lambda_exponential} +\title{Sample Lambda from an Exponential Distribution} +\description{ +This \link{CallbackAsyncTuning} samples the lambda parameter of the confidence bounds acquisition function. +The lambda value is drawn from an exponential distribution with rate \code{1 / lambda}. +} +\examples{ +clbk("mlr3mbo.sample_lambda_exponential") +} diff --git a/man/mlr_optimizers_adbo.Rd b/man/mlr_optimizers_adbo.Rd deleted file mode 100644 index 061a0036..00000000 --- a/man/mlr_optimizers_adbo.Rd +++ /dev/null @@ -1,108 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/OptimizerADBO.R -\name{mlr_optimizers_adbo} -\alias{mlr_optimizers_adbo} -\alias{OptimizerADBO} -\title{Asynchronous Decentralized Bayesian Optimization} -\description{ -Asynchronous Decentralized Bayesian Optimization (ADBO). -} -\note{ -The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. -A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. -ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. -} -\section{Parameters}{ - -\describe{ -\item{\code{lambda}}{\code{numeric(1)}\cr -\eqn{\lambda} value used for the confidence bound. -Defaults to \code{1.96}.} -\item{\code{exponential_decay}}{\code{lgl(1)}\cr -Whether to use periodic exponential decay for \eqn{\lambda}.} -\item{\code{rate}}{\code{numeric(1)}\cr -Rate of the exponential decay.} -\item{\code{t}}{\code{integer(1)}\cr -Period of the exponential decay.} -\item{\code{initial_design_size}}{\code{integer(1)}\cr -Size of the initial design.} -\item{\code{initial_design}}{\code{data.table}\cr -Initial design.} -\item{\code{impute_method}}{\code{character(1)}\cr -Imputation method for missing values in the surrogate model.} -\item{\code{n_workers}}{\code{integer(1)}\cr -Number of workers to use. -Defaults to the number of workers set by \code{rush::rush_plan()}} -} -} - -\section{Super classes}{ -\code{\link[bbotk:Optimizer]{bbotk::Optimizer}} -> \code{\link[bbotk:OptimizerAsync]{bbotk::OptimizerAsync}} -> \code{OptimizerADBO} -} -\section{Methods}{ -\subsection{Public methods}{ -\itemize{ -\item \href{#method-OptimizerADBO-new}{\code{OptimizerADBO$new()}} -\item \href{#method-OptimizerADBO-optimize}{\code{OptimizerADBO$optimize()}} -\item \href{#method-OptimizerADBO-clone}{\code{OptimizerADBO$clone()}} -} -} -\if{html}{\out{ -
    Inherited methods - -
    -}} -\if{html}{\out{
    }} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-OptimizerADBO-new}{}}} -\subsection{Method \code{new()}}{ -Creates a new instance of this \link[R6:R6Class]{R6} class. -\subsection{Usage}{ -\if{html}{\out{
    }}\preformatted{OptimizerADBO$new()}\if{html}{\out{
    }} -} - -} -\if{html}{\out{
    }} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-OptimizerADBO-optimize}{}}} -\subsection{Method \code{optimize()}}{ -Performs the optimization on a \link{OptimInstanceAsyncSingleCrit} or \link{OptimInstanceAsyncMultiCrit} until termination. -The single evaluations will be written into the \link{ArchiveAsync}. -The result will be written into the instance object. -\subsection{Usage}{ -\if{html}{\out{
    }}\preformatted{OptimizerADBO$optimize(inst)}\if{html}{\out{
    }} -} - -\subsection{Arguments}{ -\if{html}{\out{
    }} -\describe{ -\item{\code{inst}}{(\link{OptimInstanceAsyncSingleCrit} | \link{OptimInstanceAsyncMultiCrit}).} -} -\if{html}{\out{
    }} -} -\subsection{Returns}{ -\code{\link[data.table:data.table]{data.table::data.table()}} -} -} -\if{html}{\out{
    }} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-OptimizerADBO-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
    }}\preformatted{OptimizerADBO$clone(deep = FALSE)}\if{html}{\out{
    }} -} - -\subsection{Arguments}{ -\if{html}{\out{
    }} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
    }} -} -} -} diff --git a/man/mlr_optimizers_async_mbo.Rd b/man/mlr_optimizers_async_mbo.Rd new file mode 100644 index 00000000..32bcdbc0 --- /dev/null +++ b/man/mlr_optimizers_async_mbo.Rd @@ -0,0 +1,115 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/OptimizerAsyncMbo.R +\name{mlr_optimizers_async_mbo} +\alias{mlr_optimizers_async_mbo} +\alias{OptimizerAsyncMbo} +\title{Asynchronous Model Based Optimization} +\description{ +\code{OptimizerAsyncMbo} class that implements asynchronous Model Based Optimization (MBO). +} +\section{Super classes}{ +\code{\link[bbotk:Optimizer]{bbotk::Optimizer}} -> \code{\link[bbotk:OptimizerAsync]{bbotk::OptimizerAsync}} -> \code{OptimizerAsyncMbo} +} +\section{Active bindings}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{surrogate}}{(\link{Surrogate} | \code{NULL})\cr +The surrogate.} + +\item{\code{acq_function}}{(\link{AcqFunction} | \code{NULL})\cr +The acquisition function.} + +\item{\code{acq_optimizer}}{(\link{AcqOptimizer} | \code{NULL})\cr +The acquisition function optimizer.} + +\item{\code{param_classes}}{(\code{character()})\cr +Supported parameter classes that the optimizer can optimize. +Determined based on the \code{surrogate} and the \code{acq_optimizer}. +This corresponds to the values given by a \link[paradox:ParamSet]{paradox::ParamSet}'s +\verb{$class} field.} + +\item{\code{properties}}{(\code{character()})\cr +Set of properties of the optimizer. +Must be a subset of \code{\link[bbotk:bbotk_reflections]{bbotk_reflections$optimizer_properties}}. +MBO in principle is very flexible and by default we assume that the optimizer has all properties. +When fully initialized, properties are determined based on the \code{loop_function} and \code{surrogate}.} + +\item{\code{packages}}{(\code{character()})\cr +Set of required packages. +A warning is signaled prior to optimization if at least one of the packages is not installed, but loaded (not attached) later on-demand via \code{\link[=requireNamespace]{requireNamespace()}}. +Required packages are determined based on the \code{acq_function}, \code{surrogate} and the \code{acq_optimizer}.} +} +\if{html}{\out{
    }} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-OptimizerAsyncMbo-new}{\code{OptimizerAsyncMbo$new()}} +\item \href{#method-OptimizerAsyncMbo-optimize}{\code{OptimizerAsyncMbo$optimize()}} +\item \href{#method-OptimizerAsyncMbo-clone}{\code{OptimizerAsyncMbo$clone()}} +} +} +\if{html}{\out{ +
    Inherited methods + +
    +}} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OptimizerAsyncMbo-new}{}}} +\subsection{Method \code{new()}}{ +Creates a new instance of this \link[R6:R6Class]{R6} class. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{OptimizerAsyncMbo$new( + surrogate = NULL, + acq_function = NULL, + acq_optimizer = NULL, + param_set = NULL +)}\if{html}{\out{
    }} +} + +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OptimizerAsyncMbo-optimize}{}}} +\subsection{Method \code{optimize()}}{ +Performs the optimization on a \link{OptimInstanceAsyncSingleCrit} or \link{OptimInstanceAsyncMultiCrit} until termination. +The single evaluations will be written into the \link{ArchiveAsync}. +The result will be written into the instance object. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{OptimizerAsyncMbo$optimize(inst)}\if{html}{\out{
    }} +} + +\subsection{Arguments}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{inst}}{(\link{OptimInstanceAsyncSingleCrit} | \link{OptimInstanceAsyncMultiCrit}).} +} +\if{html}{\out{
    }} +} +\subsection{Returns}{ +\code{\link[data.table:data.table]{data.table::data.table()}} +} +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OptimizerAsyncMbo-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{OptimizerAsyncMbo$clone(deep = FALSE)}\if{html}{\out{
    }} +} + +\subsection{Arguments}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
    }} +} +} +} diff --git a/man/mlr_tuners_adbo.Rd b/man/mlr_tuners_async_mbo.Rd similarity index 50% rename from man/mlr_tuners_adbo.Rd rename to man/mlr_tuners_async_mbo.Rd index a029aa1b..194e6c35 100644 --- a/man/mlr_tuners_adbo.Rd +++ b/man/mlr_tuners_async_mbo.Rd @@ -1,44 +1,20 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/TunerADBO.R -\name{mlr_tuners_adbo} -\alias{mlr_tuners_adbo} -\alias{TunerADBO} -\title{Asynchronous Decentralized Bayesian Optimization} +% Please edit documentation in R/TunerAsyncMbo.R +\name{mlr_tuners_async_mbo} +\alias{mlr_tuners_async_mbo} +\alias{TunerAsyncMbo} +\title{Asynchronous Model Based Optimization} \description{ -Asynchronous Decentralized Bayesian Optimization (ADBO). +\code{TunerAsyncMbo} class that implements Asynchronous Model Based Optimization (MBO). } -\note{ -The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. -A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. -ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. -} -\section{Parameters}{ - -\describe{ -\item{\code{lambda}}{\code{numeric(1)}\cr -\eqn{\lambda} value used for the confidence bound. -Defaults to \code{1.96}.} -\item{\code{exponential_decay}}{\code{lgl(1)}\cr -Whether to use periodic exponential decay for \eqn{\lambda}.} -\item{\code{rate}}{\code{numeric(1)}\cr -Rate of the exponential decay.} -\item{\code{t}}{\code{integer(1)}\cr -Period of the exponential decay.} -\item{\code{initial_design_size}}{\code{integer(1)}\cr -Size of the initial design.} -\item{\code{initial_design}}{\code{data.table}\cr -Initial design.} -} -} - \section{Super classes}{ -\code{\link[mlr3tuning:Tuner]{mlr3tuning::Tuner}} -> \code{\link[mlr3tuning:TunerAsync]{mlr3tuning::TunerAsync}} -> \code{\link[mlr3tuning:TunerAsyncFromOptimizerAsync]{mlr3tuning::TunerAsyncFromOptimizerAsync}} -> \code{TunerADBO} +\code{\link[mlr3tuning:Tuner]{mlr3tuning::Tuner}} -> \code{\link[mlr3tuning:TunerAsync]{mlr3tuning::TunerAsync}} -> \code{\link[mlr3tuning:TunerAsyncFromOptimizerAsync]{mlr3tuning::TunerAsyncFromOptimizerAsync}} -> \code{TunerAsyncMbo} } \section{Methods}{ \subsection{Public methods}{ \itemize{ -\item \href{#method-TunerADBO-new}{\code{TunerADBO$new()}} -\item \href{#method-TunerADBO-clone}{\code{TunerADBO$clone()}} +\item \href{#method-TunerAsyncMbo-new}{\code{TunerAsyncMbo$new()}} +\item \href{#method-TunerAsyncMbo-clone}{\code{TunerAsyncMbo$clone()}} } } \if{html}{\out{ @@ -52,22 +28,22 @@ Initial design.} }} \if{html}{\out{
    }} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-TunerADBO-new}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TunerAsyncMbo-new}{}}} \subsection{Method \code{new()}}{ Creates a new instance of this \link[R6:R6Class]{R6} class. \subsection{Usage}{ -\if{html}{\out{
    }}\preformatted{TunerADBO$new()}\if{html}{\out{
    }} +\if{html}{\out{
    }}\preformatted{TunerAsyncMbo$new()}\if{html}{\out{
    }} } } \if{html}{\out{
    }} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-TunerADBO-clone}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TunerAsyncMbo-clone}{}}} \subsection{Method \code{clone()}}{ The objects of this class are cloneable with this method. \subsection{Usage}{ -\if{html}{\out{
    }}\preformatted{TunerADBO$clone(deep = FALSE)}\if{html}{\out{
    }} +\if{html}{\out{
    }}\preformatted{TunerAsyncMbo$clone(deep = FALSE)}\if{html}{\out{
    }} } \subsection{Arguments}{ diff --git a/tests/testthat/test_OptimizerADBO.R b/tests/testthat/test_OptimizerADBO.R deleted file mode 100644 index 6e8a73a1..00000000 --- a/tests/testthat/test_OptimizerADBO.R +++ /dev/null @@ -1,15 +0,0 @@ -test_that("adbo optimizer works", { - skip_on_cran() - skip_if_not_installed("rush") - flush_redis() - - rush::rush_plan(n_workers = 2) - instance = oi_async( - objective = OBJ_2D, - search_space = PS_2D, - terminator = trm("evals", n_evals = 100), - ) - optimizer = opt("adbo", design_size = 4) - optimizer$optimize(instance) -}) - diff --git a/tests/testthat/test_OptimizerAsyncMbo.R b/tests/testthat/test_OptimizerAsyncMbo.R index 589d8183..2fbe35ff 100644 --- a/tests/testthat/test_OptimizerAsyncMbo.R +++ b/tests/testthat/test_OptimizerAsyncMbo.R @@ -46,8 +46,6 @@ test_that("async optimizer works with ei with epsilon decay", { skip_if_not_installed("rush") flush_redis() - options(bbotk_local = TRUE) - rush::rush_plan(n_workers = 2) instance = oi_async( objective = OBJ_2D, diff --git a/tests/testthat/test_TunerAsyncMbo.R b/tests/testthat/test_TunerAsyncMbo.R new file mode 100644 index 00000000..51906976 --- /dev/null +++ b/tests/testthat/test_TunerAsyncMbo.R @@ -0,0 +1,87 @@ + +test_that("async mbo tuner works", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + learner = lrn("classif.rpart", + minsplit = to_tune(2, 128), + cp = to_tune(1e-04, 1e-1)) + + rush::rush_plan(n_workers = 4) + instance = ti_async( + task = tsk("pima"), + learner = learner, + resampling = rsmp("cv", folds = 3), + measure = msr("classif.ce"), + terminator = trm("evals", n_evals = 20), + store_benchmark_result = FALSE + ) + + tuner = tnr("async_mbo", design_size = 4) + + tuner$optimize(instance) +}) + +test_that("async tuner works with ei", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + learner = lrn("classif.rpart", + minsplit = to_tune(2, 128), + cp = to_tune(1e-04, 1e-1)) + + rush::rush_plan(n_workers = 2) + instance = ti_async( + task = tsk("pima"), + learner = learner, + resampling = rsmp("cv", folds = 3), + measure = msr("classif.ce"), + terminator = trm("evals", n_evals = 20), + store_benchmark_result = FALSE + ) + + tuner = tnr("async_mbo", + design_function = "sobol", + design_size = 6, + acq_function = acqf("ei")) + + expect_data_table(tuner$optimize(instance), nrows = 1) + expect_data_table(instance$archive$data, min.rows = 10) + expect_names(names(instance$archive$data), must.include = c("acq_ei", ".already_evaluated")) + + expect_rush_reset(instance$rush) +}) + +test_that("async tuner works with exponential lambda decay", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + learner = lrn("classif.rpart", + minsplit = to_tune(2, 128), + cp = to_tune(1e-04, 1e-1)) + + rush::rush_plan(n_workers = 2) + instance = ti_async( + task = tsk("pima"), + learner = learner, + resampling = rsmp("cv", folds = 3), + measure = msr("classif.ce"), + terminator = trm("evals", n_evals = 20), + store_benchmark_result = FALSE, + callback = clbk("mlr3mbo.exponential_lambda_decay") + ) + + tuner = tnr("async_mbo", + design_function = "sobol", + design_size = 5, + acq_function = acqf("cb", lambda = 3)) + + expect_data_table(tuner$optimize(instance), nrows = 1) + expect_data_table(instance$archive$data, min.rows = 10) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "lambda_0", "lambda")) + + expect_rush_reset(instance$rush) +}) From c7caa612d4cf66da152bbd909b7cfeda80048f11 Mon Sep 17 00:00:00 2001 From: be-marc Date: Fri, 13 Sep 2024 19:01:01 +0200 Subject: [PATCH 32/49] feat: n_workers --- R/OptimizerAsyncMbo.R | 3 +- attic/OptimizerADBO.R | 276 +++++++++++++++++++++++------------------- 2 files changed, 154 insertions(+), 125 deletions(-) diff --git a/R/OptimizerAsyncMbo.R b/R/OptimizerAsyncMbo.R index 33740e2a..ed54b7c7 100644 --- a/R/OptimizerAsyncMbo.R +++ b/R/OptimizerAsyncMbo.R @@ -17,7 +17,8 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", default_param_set = ps( initial_design = p_uty(), design_size = p_int(lower = 1, default = 10), - design_function = p_fct(c("random", "sobol", "lhs"), default = "sobol") + design_function = p_fct(c("random", "sobol", "lhs"), default = "sobol"), + n_workers = p_int(lower = 1L, default = NULL, special_vals = list(NULL)) ) param_set = c(default_param_set, param_set) diff --git a/attic/OptimizerADBO.R b/attic/OptimizerADBO.R index e28b593c..3cdd4754 100644 --- a/attic/OptimizerADBO.R +++ b/attic/OptimizerADBO.R @@ -1,124 +1,152 @@ -# #' @title Asynchronous Decentralized Bayesian Optimization -# #' @name mlr_optimizers_adbo -# #' -# #' @description -# #' Asynchronous Decentralized Bayesian Optimization (ADBO). -# #' -# #' @note -# #' The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. -# #' A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. -# #' ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. -# #' -# #' @section Parameters: -# #' \describe{ -# #' \item{`lambda`}{`numeric(1)`\cr -# #' \eqn{\lambda} value used for the confidence bound. -# #' Defaults to `1.96`.} -# #' \item{`exponential_decay`}{`lgl(1)`\cr -# #' Whether to use periodic exponential decay for \eqn{\lambda}.} -# #' \item{`rate`}{`numeric(1)`\cr -# #' Rate of the exponential decay.} -# #' \item{`t`}{`integer(1)`\cr -# #' Period of the exponential decay.} -# #' \item{`initial_design_size`}{`integer(1)`\cr -# #' Size of the initial design.} -# #' \item{`initial_design`}{`data.table`\cr -# #' Initial design.} -# #' \item{`impute_method`}{`character(1)`\cr -# #' Imputation method for missing values in the surrogate model.} -# #' \item{`n_workers`}{`integer(1)`\cr -# #' Number of workers to use. -# #' Defaults to the number of workers set by `rush::rush_plan()`} -# #' } -# #' -# #' @export -# OptimizerADBO = R6Class("OptimizerADBO", -# inherit = OptimizerAsyncMbo, - -# public = list( - -# #' @description -# #' Creates a new instance of this [R6][R6::R6Class] class. -# initialize = function() { -# param_set = ps( -# lambda = p_dbl(lower = 0, default = 1.96), -# exponential_decay = p_lgl(default = TRUE), -# rate = p_dbl(lower = 0, default = 0.1), -# period = p_int(lower = 1L, default = 25L), -# # impute_method = p_fct(c("mean", "random"), default = "random"), -# n_workers = p_int(lower = 1L, default = NULL, special_vals = list(NULL)) -# ) - -# param_set$set_values(lambda = 1.96, exponential_decay = TRUE, rate = 0.1, period = 25L, design_size = 1L, impute_method = "random") - -# super$initialize("adbo", -# param_set = param_set, -# param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), -# properties = c("dependencies", "single-crit"), -# packages = "mlr3mbo", -# label = "Asynchronous Decentralized Bayesian Optimization", -# man = "mlr3mbo::OptimizerADBO") -# } -# ), - -# private = list( - -# .optimize = function(inst) { -# pv = self$param_set$values -# search_space = inst$search_space -# archive = inst$archive - -# # sample lambda from exponential distribution -# lambda_0 = rexp(1, 1 / pv$lambda) -# t = 0 - -# surrogate = default_surrogate(inst) -# surrogate$param_set$set_values(impute_method = pv$impute_method) -# acq_function = acqf("cb", lambda = runif(1, 1 , 3)) -# acq_optimizer = acqo(opt("random_search", batch_size = 1000L), terminator = trm("evals", n_evals = 10000L)) -# surrogate$archive = inst$archive -# acq_function$surrogate = surrogate -# acq_optimizer$acq_function = acq_function - -# lg$debug("Optimizer '%s' evaluates the initial design", self$id) -# evaluate_queue_default(inst) - -# lg$debug("Optimizer '%s' starts the tuning phase", self$id) - -# # actual loop -# while (!inst$is_terminated) { - -# # decrease lambda -# if (pv$exponential_decay) { -# lambda = lambda_0 * exp(-pv$rate * (t %% pv$period)) -# t = t + 1 -# } else { -# lambda = pv$lambda -# } - -# # sample -# acq_function$constants$set_values(lambda = lambda) -# acq_function$surrogate$update() -# acq_function$update() -# xdt = acq_optimizer$optimize() - -# # transpose point -# xss = transpose_list(xdt) -# xs = xss[[1]][inst$archive$cols_x] -# lg$trace("Optimizer '%s' draws %s", self$id, as_short_string(xs)) -# xs_trafoed = trafo_xs(xs, search_space) - -# # eval -# key = archive$push_running_point(xs) -# ys = inst$objective$eval(xs_trafoed) - -# # push result -# extra = c(xss[[1]][c("acq_cb", ".already_evaluated")], list(lambda_0 = lambda_0, lambda = lambda)) -# archive$push_result(key, ys, x_domain = xs_trafoed, extra = extra) -# } -# } -# ) -# ) - -# #' @include aaa.R -# optimizers[["adbo"]] = OptimizerADBO +#' @title Asynchronous Decentralized Bayesian Optimization +#' @name mlr_optimizers_adbo +#' +#' @description +#' Asynchronous Decentralized Bayesian Optimization (ADBO). +#' +#' @note +#' The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. +#' A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. +#' ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. +#' +#' @section Parameters: +#' \describe{ +#' \item{`lambda`}{`numeric(1)`\cr +#' \eqn{\lambda} value used for the confidence bound. +#' Defaults to `1.96`.} +#' \item{`exponential_decay`}{`lgl(1)`\cr +#' Whether to use periodic exponential decay for \eqn{\lambda}.} +#' \item{`rate`}{`numeric(1)`\cr +#' Rate of the exponential decay.} +#' \item{`t`}{`integer(1)`\cr +#' Period of the exponential decay.} +#' \item{`initial_design_size`}{`integer(1)`\cr +#' Size of the initial design.} +#' \item{`initial_design`}{`data.table`\cr +#' Initial design.} +#' \item{`impute_method`}{`character(1)`\cr +#' Imputation method for missing values in the surrogate model.} +#' \item{`n_workers`}{`integer(1)`\cr +#' Number of workers to use. +#' Defaults to the number of workers set by `rush::rush_plan()`} +#' } +#' +#' @export +OptimizerADBO = R6Class("OptimizerADBO", + inherit = OptimizerAsyncMbo, + + public = list( + + #' @description + #' Creates a new instance of this [R6][R6::R6Class] class. + initialize = function() { + param_set = ps( + lambda = p_dbl(lower = 0, default = 1.96), + exponential_decay = p_lgl(default = TRUE), + rate = p_dbl(lower = 0, default = 0.1), + period = p_int(lower = 1L, default = 25L), + design_size = p_int(lower = 1L), + initial_design = p_uty(), + impute_method = p_fct(c("mean", "random"), default = "random"), + n_workers = p_int(lower = 1L, default = NULL, special_vals = list(NULL)) + ) + + param_set$set_values(lambda = 1.96, exponential_decay = TRUE, rate = 0.1, period = 25L, design_size = 1L, impute_method = "random") + + super$initialize("adbo", + param_set = param_set, + param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), + properties = c("dependencies", "single-crit"), + packages = "mlr3mbo", + label = "Asynchronous Decentralized Bayesian Optimization", + man = "mlr3mbo::OptimizerADBO") + }, + + + #' @description + #' Performs the optimization on a [OptimInstanceAsyncSingleCrit] or [OptimInstanceAsyncMultiCrit] until termination. + #' The single evaluations will be written into the [ArchiveAsync]. + #' The result will be written into the instance object. + #' + #' @param inst ([OptimInstanceAsyncSingleCrit] | [OptimInstanceAsyncMultiCrit]). + #' + #' @return [data.table::data.table()] + optimize = function(inst) { + pv = self$param_set$values + + # initial design + design = if (is.null(pv$initial_design)) { + + lg$debug("Generating sobol design with size %s", pv$design_size) + generate_design_sobol(inst$search_space, n = pv$design_size)$data + } else { + + lg$debug("Using provided initial design with size %s", nrow(pv$initial_design)) + pv$initial_design + } + + optimize_async_default(inst, self, design, n_workers = pv$n_workers) + } + ), + + private = list( + + .optimize = function(inst) { + pv = self$param_set$values + search_space = inst$search_space + archive = inst$archive + + # sample lambda from exponential distribution + lambda_0 = rexp(1, 1 / pv$lambda) + t = 0 + + surrogate = default_surrogate(inst) + surrogate$param_set$set_values(impute_method = pv$impute_method) + acq_function = acqf("cb", lambda = runif(1, 1 , 3)) + acq_optimizer = acqo(opt("random_search", batch_size = 1000L), terminator = trm("evals", n_evals = 10000L)) + surrogate$archive = inst$archive + acq_function$surrogate = surrogate + acq_optimizer$acq_function = acq_function + + lg$debug("Optimizer '%s' evaluates the initial design", self$id) + evaluate_queue_default(inst) + + lg$debug("Optimizer '%s' starts the tuning phase", self$id) + + # actual loop + while (!inst$is_terminated) { + + # decrease lambda + if (pv$exponential_decay) { + lambda = lambda_0 * exp(-pv$rate * (t %% pv$period)) + t = t + 1 + } else { + lambda = pv$lambda + } + + # sample + acq_function$constants$set_values(lambda = lambda) + acq_function$surrogate$update() + acq_function$update() + xdt = acq_optimizer$optimize() + + # transpose point + xss = transpose_list(xdt) + xs = xss[[1]][inst$archive$cols_x] + lg$trace("Optimizer '%s' draws %s", self$id, as_short_string(xs)) + xs_trafoed = trafo_xs(xs, search_space) + + # eval + key = archive$push_running_point(xs) + ys = inst$objective$eval(xs_trafoed) + + # push result + extra = c(xss[[1]][c("acq_cb", ".already_evaluated")], list(lambda_0 = lambda_0, lambda = lambda)) + archive$push_result(key, ys, x_domain = xs_trafoed, extra = extra) + } + } + ) +) + +#' @include aaa.R +optimizers[["adbo"]] = OptimizerADBO From 0709d10eabaaf539cf4c23ebef9ddd5a3d8f7c15 Mon Sep 17 00:00:00 2001 From: be-marc Date: Sat, 14 Sep 2024 14:53:16 +0200 Subject: [PATCH 33/49] docs: update --- R/OptimizerAsyncMbo.R | 24 +++++++++++++++++- R/TunerAsyncMbo.R | 27 ++++++++++++++++++-- man/mlr_optimizers_async_mbo.Rd | 34 +++++++++++++++++++++++++ man/mlr_tuners_async_mbo.Rd | 45 ++++++++++++++++++++++++++++++--- 4 files changed, 124 insertions(+), 6 deletions(-) diff --git a/R/OptimizerAsyncMbo.R b/R/OptimizerAsyncMbo.R index ed54b7c7..2a9c67f9 100644 --- a/R/OptimizerAsyncMbo.R +++ b/R/OptimizerAsyncMbo.R @@ -5,6 +5,28 @@ #' @description #' `OptimizerAsyncMbo` class that implements asynchronous Model Based Optimization (MBO). #' +#' @section Parameters: +#' \describe{ +#' \item{`initial_design`}{`data.table::data.table()`\cr +#' Initial design of the optimization. +#' If `NULL`, a design of size `design_size` is generated with `design_function`.} +#' \item{`design_size`}{`integer(1)`\cr +#' Size of the initial design.} +#' \item{`design_function`}{`character(1)`\cr +#' Function to generate the initial design. +#' One of `c("random", "sobol", "lhs")`.} +#' \item{`n_workers`}{`integer(1)`\cr +#' Number of parallel workers. +#' If `NULL`, all rush workers set with [rush::rush_plan()] are used.} +#' } +#' +#' @template param_surrogate +#' @template param_acq_function +#' @template param_acq_optimizer +#' +#' @param param_set [paradox::ParamSet]\cr +#' Set of control parameters. +#' #' @export OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", inherit = OptimizerAsync, @@ -18,7 +40,7 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", initial_design = p_uty(), design_size = p_int(lower = 1, default = 10), design_function = p_fct(c("random", "sobol", "lhs"), default = "sobol"), - n_workers = p_int(lower = 1L, default = NULL, special_vals = list(NULL)) + n_workers = p_int(lower = 1L) ) param_set = c(default_param_set, param_set) diff --git a/R/TunerAsyncMbo.R b/R/TunerAsyncMbo.R index fb9f8626..70afae28 100644 --- a/R/TunerAsyncMbo.R +++ b/R/TunerAsyncMbo.R @@ -1,9 +1,32 @@ -#' @title Asynchronous Model Based Optimization +#' @title Asynchronous Model Based Tuning #' +#' @include OptimizerAsyncMbo.R #' @name mlr_tuners_async_mbo #' #' @description -#' `TunerAsyncMbo` class that implements Asynchronous Model Based Optimization (MBO). +#' `TunerAsyncMbo` class that implements asynchronous Model Based Tuning (MBO). +#' +#' @section Parameters: +#' \describe{ +#' \item{`initial_design`}{`data.table::data.table()`\cr +#' Initial design of the optimization. +#' If `NULL`, a design of size `design_size` is generated with `design_function`.} +#' \item{`design_size`}{`integer(1)`\cr +#' Size of the initial design.} +#' \item{`design_function`}{`character(1)`\cr +#' Function to generate the initial design. +#' One of `c("random", "sobol", "lhs")`.} +#' \item{`n_workers`}{`integer(1)`\cr +#' Number of parallel workers. +#' If `NULL`, all rush workers set with [rush::rush_plan()] are used.} +#' } +#' +#' @template param_surrogate +#' @template param_acq_function +#' @template param_acq_optimizer +#' +#' @param param_set [paradox::ParamSet]\cr +#' Set of control parameters. #' #' @export TunerAsyncMbo = R6Class("TunerAsyncMbo", diff --git a/man/mlr_optimizers_async_mbo.Rd b/man/mlr_optimizers_async_mbo.Rd index 32bcdbc0..7a90fb47 100644 --- a/man/mlr_optimizers_async_mbo.Rd +++ b/man/mlr_optimizers_async_mbo.Rd @@ -7,6 +7,23 @@ \description{ \code{OptimizerAsyncMbo} class that implements asynchronous Model Based Optimization (MBO). } +\section{Parameters}{ + +\describe{ +\item{\code{initial_design}}{\code{data.table::data.table()}\cr +Initial design of the optimization. +If \code{NULL}, a design of size \code{design_size} is generated with \code{design_function}.} +\item{\code{design_size}}{\code{integer(1)}\cr +Size of the initial design.} +\item{\code{design_function}}{\code{character(1)}\cr +Function to generate the initial design. +One of \code{c("random", "sobol", "lhs")}.} +\item{\code{n_workers}}{\code{integer(1)}\cr +Number of parallel workers. +If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush_plan()}} are used.} +} +} + \section{Super classes}{ \code{\link[bbotk:Optimizer]{bbotk::Optimizer}} -> \code{\link[bbotk:OptimizerAsync]{bbotk::OptimizerAsync}} -> \code{OptimizerAsyncMbo} } @@ -72,6 +89,23 @@ Creates a new instance of this \link[R6:R6Class]{R6} class. )}\if{html}{\out{
    }} } +\subsection{Arguments}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{surrogate}}{(\link{Surrogate} | \code{NULL})\cr +The surrogate.} + +\item{\code{acq_function}}{(\link{AcqFunction} | \code{NULL})\cr +The acquisition function.} + +\item{\code{acq_optimizer}}{(\link{AcqOptimizer} | \code{NULL})\cr +The acquisition function optimizer.} + +\item{\code{param_set}}{\link[paradox:ParamSet]{paradox::ParamSet}\cr +Set of control parameters.} +} +\if{html}{\out{
    }} +} } \if{html}{\out{
    }} \if{html}{\out{}} diff --git a/man/mlr_tuners_async_mbo.Rd b/man/mlr_tuners_async_mbo.Rd index 194e6c35..3dbe203c 100644 --- a/man/mlr_tuners_async_mbo.Rd +++ b/man/mlr_tuners_async_mbo.Rd @@ -3,10 +3,27 @@ \name{mlr_tuners_async_mbo} \alias{mlr_tuners_async_mbo} \alias{TunerAsyncMbo} -\title{Asynchronous Model Based Optimization} +\title{Asynchronous Model Based Tuning} \description{ -\code{TunerAsyncMbo} class that implements Asynchronous Model Based Optimization (MBO). +\code{TunerAsyncMbo} class that implements asynchronous Model Based Tuning (MBO). } +\section{Parameters}{ + +\describe{ +\item{\code{initial_design}}{\code{data.table::data.table()}\cr +Initial design of the optimization. +If \code{NULL}, a design of size \code{design_size} is generated with \code{design_function}.} +\item{\code{design_size}}{\code{integer(1)}\cr +Size of the initial design.} +\item{\code{design_function}}{\code{character(1)}\cr +Function to generate the initial design. +One of \code{c("random", "sobol", "lhs")}.} +\item{\code{n_workers}}{\code{integer(1)}\cr +Number of parallel workers. +If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush_plan()}} are used.} +} +} + \section{Super classes}{ \code{\link[mlr3tuning:Tuner]{mlr3tuning::Tuner}} -> \code{\link[mlr3tuning:TunerAsync]{mlr3tuning::TunerAsync}} -> \code{\link[mlr3tuning:TunerAsyncFromOptimizerAsync]{mlr3tuning::TunerAsyncFromOptimizerAsync}} -> \code{TunerAsyncMbo} } @@ -33,9 +50,31 @@ \subsection{Method \code{new()}}{ Creates a new instance of this \link[R6:R6Class]{R6} class. \subsection{Usage}{ -\if{html}{\out{
    }}\preformatted{TunerAsyncMbo$new()}\if{html}{\out{
    }} +\if{html}{\out{
    }}\preformatted{TunerAsyncMbo$new( + surrogate = NULL, + acq_function = NULL, + acq_optimizer = NULL, + param_set = NULL +)}\if{html}{\out{
    }} } +\subsection{Arguments}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{surrogate}}{(\link{Surrogate} | \code{NULL})\cr +The surrogate.} + +\item{\code{acq_function}}{(\link{AcqFunction} | \code{NULL})\cr +The acquisition function.} + +\item{\code{acq_optimizer}}{(\link{AcqOptimizer} | \code{NULL})\cr +The acquisition function optimizer.} + +\item{\code{param_set}}{\link[paradox:ParamSet]{paradox::ParamSet}\cr +Set of control parameters.} +} +\if{html}{\out{
    }} +} } \if{html}{\out{
    }} \if{html}{\out{}} From e9430995c7b9b103a66e0ae269471857199afedc Mon Sep 17 00:00:00 2001 From: be-marc Date: Mon, 28 Oct 2024 11:37:42 +0100 Subject: [PATCH 34/49] ... --- DESCRIPTION | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index f146ca46..dc09cdaf 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -41,14 +41,14 @@ BugReports: https://github.com/mlr-org/mlr3mbo/issues Depends: R (>= 3.1.0) Imports: - bbotk (>= 1.1.0), + bbotk (>= 1.2.0), checkmate (>= 2.0.0), data.table, lgr (>= 0.3.4), - mlr3 (>= 0.20.2.9000), + mlr3 (>= 0.21.1), mlr3misc (>= 0.11.0), - mlr3tuning (>= 1.0.1), - paradox (>= 1.0.0), + mlr3tuning (>= 1.1.0), + paradox (>= 1.0.1), spacefillr, R6 (>= 2.4.1) Suggests: @@ -67,8 +67,6 @@ Suggests: rush, stringi, testthat (>= 3.0.0) -Remotes: - mlr-org/mlr3 ByteCompile: no Encoding: UTF-8 Config/testthat/edition: 3 From 15fc62a618632f5f9fecb124c7be711637108d38 Mon Sep 17 00:00:00 2001 From: be-marc Date: Mon, 28 Oct 2024 14:39:46 +0100 Subject: [PATCH 35/49] ... --- DESCRIPTION | 3 ++- R/mlr_callbacks.R | 10 +++++----- ...3mbo.sample_epsilon.Rd => mlr3mbo.epsilon_decay.Rd} | 6 +++--- ...da_decay.Rd => mlr3mbo.exponential_lambda_decay.Rd} | 4 ++-- ...mple_lambda.Rd => mlr3mbo.sample_lambda_uniform.Rd} | 6 +++--- tests/testthat/test_mbo_defaults.R | 4 ---- 6 files changed, 15 insertions(+), 18 deletions(-) rename man/{mlr3mbo.sample_epsilon.Rd => mlr3mbo.epsilon_decay.Rd} (79%) rename man/{mlr3mbo.lambda_decay.Rd => mlr3mbo.exponential_lambda_decay.Rd} (90%) rename man/{mlr3mbo.sample_lambda.Rd => mlr3mbo.sample_lambda_uniform.Rd} (80%) diff --git a/DESCRIPTION b/DESCRIPTION index 2cf9f526..a0660972 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -39,6 +39,7 @@ License: LGPL-3 URL: https://mlr3mbo.mlr-org.com, https://github.com/mlr-org/mlr3mbo BugReports: https://github.com/mlr-org/mlr3mbo/issues Depends: + mlr3tuning (>= 1.1.0), R (>= 3.1.0) Imports: bbotk (>= 1.2.0), @@ -47,7 +48,6 @@ Imports: lgr (>= 0.3.4), mlr3 (>= 0.21.1), mlr3misc (>= 0.11.0), - mlr3tuning (>= 1.1.0), paradox (>= 1.0.1), spacefillr, R6 (>= 2.4.1) @@ -62,6 +62,7 @@ Suggests: ranger, rgenoud, rpart, + redux, rush, stringi, testthat (>= 3.0.0) diff --git a/R/mlr_callbacks.R b/R/mlr_callbacks.R index 51c8de61..bf2c791c 100644 --- a/R/mlr_callbacks.R +++ b/R/mlr_callbacks.R @@ -1,6 +1,6 @@ #' @title Sample Lambda from an Uniform Distribution #' -#' @name mlr3mbo.sample_lambda +#' @name mlr3mbo.sample_lambda_uniform #' #' @description #' This [CallbackAsyncTuning] samples the lambda parameter of the confidence bounds acquisition function. @@ -14,7 +14,7 @@ #' Defaults to `10`. #' #' @examples -#' clbk("mlr3mbo.sample_lambda", min_lambda = 0.01, max_lambda = 10) +#' clbk("mlr3mbo.sample_lambda_uniform", min_lambda = 0.01, max_lambda = 10) NULL load_callback_sample_lambda_uniform = function() { @@ -78,7 +78,7 @@ callbacks[["mlr3mbo.sample_lambda_exponential"]] = load_callback_sample_lambda_e #' @title Exponential Decay of Lambda #' -#' @name mlr3mbo.lambda_decay +#' @name mlr3mbo.exponential_lambda_decay #' #' @description #' This [CallbackAsyncTuning] decays the lambda parameter of the confidence bounds acquisition function. @@ -131,7 +131,7 @@ callbacks[["mlr3mbo.exponential_lambda_decay"]] = load_callback_exponential_lamb #' @title Epsilon Decay #' -#' @name mlr3mbo.sample_epsilon +#' @name mlr3mbo.epsilon_decay #' #' @description #' This [CallbackAsyncTuning] decays the epsilon parameter of the expected improvement acquisition function. @@ -143,7 +143,7 @@ callbacks[["mlr3mbo.exponential_lambda_decay"]] = load_callback_exponential_lamb #' Period of the exponential decay. #' Defaults to `25`. #' @examples -#' clbk("mlr3mbo.sample_epsilon", rate = 0.1, period = 25) +#' clbk("mlr3mbo.epsilon_decay", rate = 0.1, period = 25) NULL load_callback_epsilon_decay = function() { diff --git a/man/mlr3mbo.sample_epsilon.Rd b/man/mlr3mbo.epsilon_decay.Rd similarity index 79% rename from man/mlr3mbo.sample_epsilon.Rd rename to man/mlr3mbo.epsilon_decay.Rd index 3c2408e9..29b15572 100644 --- a/man/mlr3mbo.sample_epsilon.Rd +++ b/man/mlr3mbo.epsilon_decay.Rd @@ -1,7 +1,7 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/mlr_callbacks.R -\name{mlr3mbo.sample_epsilon} -\alias{mlr3mbo.sample_epsilon} +\name{mlr3mbo.epsilon_decay} +\alias{mlr3mbo.epsilon_decay} \title{Epsilon Decay} \arguments{ \item{rate}{(\code{numeric(1)})\cr @@ -16,5 +16,5 @@ Defaults to \code{25}.} This \link{CallbackAsyncTuning} decays the epsilon parameter of the expected improvement acquisition function. } \examples{ -clbk("mlr3mbo.sample_epsilon", rate = 0.1, period = 25) +clbk("mlr3mbo.epsilon_decay", rate = 0.1, period = 25) } diff --git a/man/mlr3mbo.lambda_decay.Rd b/man/mlr3mbo.exponential_lambda_decay.Rd similarity index 90% rename from man/mlr3mbo.lambda_decay.Rd rename to man/mlr3mbo.exponential_lambda_decay.Rd index 163dd8fc..ec274713 100644 --- a/man/mlr3mbo.lambda_decay.Rd +++ b/man/mlr3mbo.exponential_lambda_decay.Rd @@ -1,7 +1,7 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/mlr_callbacks.R -\name{mlr3mbo.lambda_decay} -\alias{mlr3mbo.lambda_decay} +\name{mlr3mbo.exponential_lambda_decay} +\alias{mlr3mbo.exponential_lambda_decay} \title{Exponential Decay of Lambda} \arguments{ \item{rate}{(\code{numeric(1)})\cr diff --git a/man/mlr3mbo.sample_lambda.Rd b/man/mlr3mbo.sample_lambda_uniform.Rd similarity index 80% rename from man/mlr3mbo.sample_lambda.Rd rename to man/mlr3mbo.sample_lambda_uniform.Rd index 585f0d12..4811183b 100644 --- a/man/mlr3mbo.sample_lambda.Rd +++ b/man/mlr3mbo.sample_lambda_uniform.Rd @@ -1,7 +1,7 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/mlr_callbacks.R -\name{mlr3mbo.sample_lambda} -\alias{mlr3mbo.sample_lambda} +\name{mlr3mbo.sample_lambda_uniform} +\alias{mlr3mbo.sample_lambda_uniform} \title{Sample Lambda from an Uniform Distribution} \arguments{ \item{min_lambda}{(\code{numeric(1)})\cr @@ -17,5 +17,5 @@ This \link{CallbackAsyncTuning} samples the lambda parameter of the confidence b The lambda value is drawn from a uniform distribution with \code{min} and \code{max} as bounds. } \examples{ -clbk("mlr3mbo.sample_lambda", min_lambda = 0.01, max_lambda = 10) +clbk("mlr3mbo.sample_lambda_uniform", min_lambda = 0.01, max_lambda = 10) } diff --git a/tests/testthat/test_mbo_defaults.R b/tests/testthat/test_mbo_defaults.R index 96061961..c6392aec 100644 --- a/tests/testthat/test_mbo_defaults.R +++ b/tests/testthat/test_mbo_defaults.R @@ -45,11 +45,7 @@ test_that("default_surrogate", { expect_r6(surrogate$learner[[1L]]$fallback, "LearnerRegrRanger") expect_equal(surrogate$learner[[1L]]$param_set$values, surrogate$learner[[2L]]$param_set$values) expect_equal(surrogate$learner[[1L]]$encapsulation, surrogate$learner[[2L]]$encapsulation) -<<<<<<< HEAD - # expect_equal(surrogate$learner[[1L]]$fallback, surrogate$learner[[2L]]$fallback) -======= suppressWarnings(expect_equal(surrogate$learner[[1L]]$fallback, surrogate$learner[[2L]]$fallback)) ->>>>>>> main # twocrit all numeric, noisy surrogate = default_surrogate(MAKE_INST(OBJ_1D_2_NOISY, search_space = PS_1D)) From 9ebd2d034e3b4deae9003b2ce76ea4a78dcb3bd6 Mon Sep 17 00:00:00 2001 From: be-marc Date: Tue, 29 Oct 2024 19:14:33 +0100 Subject: [PATCH 36/49] refactor --- DESCRIPTION | 4 +- NAMESPACE | 3 + R/AcqFunctionCB.R | 18 +- R/AcqFunctionStochasticCB.R | 128 +++++++++++++ R/AcqFunctionStochasticEI.R | 112 +++++++++++ R/OptimizerAsyncMbo.R | 40 +++- R/OptimizerAsyncMboADBO.R | 95 ++++++++++ R/SurrogateLearnerAsync.R | 2 + R/aaa.R | 1 - R/bibentries.R | 9 +- R/mbo_defaults.R | 8 +- R/mlr_callbacks.R | 177 ------------------ R/zzz.R | 8 - man-roxygen/param_id.R | 2 + man-roxygen/param_label.R | 3 + man-roxygen/param_man.R | 3 + man/AcqFunction.Rd | 4 +- man/SurrogateLearnerAsync.Rd | 2 + man/default_surrogate.Rd | 5 +- man/mlr3mbo.epsilon_decay.Rd | 20 -- man/mlr3mbo.exponential_lambda_decay.Rd | 22 --- man/mlr3mbo.sample_lambda_exponential.Rd | 12 -- man/mlr3mbo.sample_lambda_uniform.Rd | 21 --- man/mlr_acqfunctions.Rd | 4 +- man/mlr_acqfunctions_aei.Rd | 4 +- man/mlr_acqfunctions_cb.Rd | 4 +- man/mlr_acqfunctions_ehvi.Rd | 4 +- man/mlr_acqfunctions_ehvigh.Rd | 4 +- man/mlr_acqfunctions_ei.Rd | 4 +- man/mlr_acqfunctions_eips.Rd | 4 +- man/mlr_acqfunctions_mean.Rd | 4 +- man/mlr_acqfunctions_multi.Rd | 4 +- man/mlr_acqfunctions_pi.Rd | 4 +- man/mlr_acqfunctions_sd.Rd | 4 +- man/mlr_acqfunctions_smsego.Rd | 4 +- man/mlr_acqfunctions_stochastic_cb.Rd | 160 ++++++++++++++++ man/mlr_acqfunctions_stochastic_ei.Rd | 150 +++++++++++++++ man/mlr_optimizers_adbo.Rd | 93 +++++++++ man/mlr_optimizers_async_mbo.Rd | 30 ++- tests/testthat/Rplots.pdf | Bin 0 -> 3611 bytes tests/testthat/test_AcqFunctionStochasticCB.R | 83 ++++++++ tests/testthat/test_AcqFunctionStochasticEI.R | 80 ++++++++ tests/testthat/test_OptimizerAsyncMbo.R | 145 +------------- tests/testthat/test_OptimizerAsyncMboADBO.R | 20 ++ tests/testthat/test_TunerAsyncMbo.R | 60 +----- 45 files changed, 1063 insertions(+), 505 deletions(-) create mode 100644 R/AcqFunctionStochasticCB.R create mode 100644 R/AcqFunctionStochasticEI.R create mode 100644 R/OptimizerAsyncMboADBO.R delete mode 100644 R/mlr_callbacks.R create mode 100644 man-roxygen/param_id.R create mode 100644 man-roxygen/param_label.R create mode 100644 man-roxygen/param_man.R delete mode 100644 man/mlr3mbo.epsilon_decay.Rd delete mode 100644 man/mlr3mbo.exponential_lambda_decay.Rd delete mode 100644 man/mlr3mbo.sample_lambda_exponential.Rd delete mode 100644 man/mlr3mbo.sample_lambda_uniform.Rd create mode 100644 man/mlr_acqfunctions_stochastic_cb.Rd create mode 100644 man/mlr_acqfunctions_stochastic_ei.Rd create mode 100644 man/mlr_optimizers_adbo.Rd create mode 100644 tests/testthat/Rplots.pdf create mode 100644 tests/testthat/test_AcqFunctionStochasticCB.R create mode 100644 tests/testthat/test_AcqFunctionStochasticEI.R create mode 100644 tests/testthat/test_OptimizerAsyncMboADBO.R diff --git a/DESCRIPTION b/DESCRIPTION index a0660972..efbe5a81 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -87,9 +87,12 @@ Collate: 'AcqFunctionPI.R' 'AcqFunctionSD.R' 'AcqFunctionSmsEgo.R' + 'AcqFunctionStochasticCB.R' + 'AcqFunctionStochasticEI.R' 'AcqOptimizer.R' 'aaa.R' 'OptimizerAsyncMbo.R' + 'OptimizerAsyncMboADBO.R' 'OptimizerMbo.R' 'mlr_result_assigners.R' 'ResultAssigner.R' @@ -111,6 +114,5 @@ Collate: 'helper.R' 'loop_function.R' 'mbo_defaults.R' - 'mlr_callbacks.R' 'sugar.R' 'zzz.R' diff --git a/NAMESPACE b/NAMESPACE index a12ea3ba..d79f3751 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,8 +16,11 @@ export(AcqFunctionMulti) export(AcqFunctionPI) export(AcqFunctionSD) export(AcqFunctionSmsEgo) +export(AcqFunctionStochasticCB) +export(AcqFunctionStochasticEI) export(AcqOptimizer) export(OptimizerAsyncMbo) +export(OptimizerAsyncMboADBO) export(OptimizerMbo) export(ResultAssigner) export(ResultAssignerArchive) diff --git a/R/AcqFunctionCB.R b/R/AcqFunctionCB.R index 358d62f6..81328e05 100644 --- a/R/AcqFunctionCB.R +++ b/R/AcqFunctionCB.R @@ -64,25 +64,17 @@ AcqFunctionCB = R6Class("AcqFunctionCB", assert_r6(surrogate, "SurrogateLearner", null.ok = TRUE) assert_number(lambda, lower = 0, finite = TRUE) - constants = ps( - lambda = p_dbl(lower = 0, default = 2) - ) + constants = ps(lambda = p_dbl(lower = 0, default = 2)) constants$values$lambda = lambda - super$initialize("acq_cb", - constants = constants, - surrogate = surrogate, - requires_predict_type_se = TRUE, - direction = "same", - label = "Lower / Upper Confidence Bound", - man = "mlr3mbo::mlr_acqfunctions_cb") + super$initialize("acq_cb", constants = constants, surrogate = surrogate, requires_predict_type_se = TRUE, direction = "same", label = "Lower / Upper Confidence Bound", man = "mlr3mbo::mlr_acqfunctions_cb") } ), private = list( - .fun = function(xdt, lambda) { - #constants = list(...) - #lambda = constants$lambda + .fun = function(xdt, ...) { + constants = list(...) + lambda = constants$lambda p = self$surrogate$predict(xdt) cb = p$mean - self$surrogate_max_to_min * lambda * p$se data.table(acq_cb = cb) diff --git a/R/AcqFunctionStochasticCB.R b/R/AcqFunctionStochasticCB.R new file mode 100644 index 00000000..7de4b184 --- /dev/null +++ b/R/AcqFunctionStochasticCB.R @@ -0,0 +1,128 @@ +#' @title Acquisition Function Stochastic Confidence Bound +#' +#' @include AcqFunction.R +#' @name mlr_acqfunctions_stochastic_cb +#' +#' @templateVar id stochastic_cb +#' @template section_dictionary_acqfunctions +#' +#' @description +#' Lower / Upper Confidence Bound with lambda sampling and decay. +#' The initial lambda value is drawn from an uniform or exponential distribution with rate `1 / lambda`. +#' The lambda value is updated after each evaluation by the formula `lambda * exp(-rate * (t %% period))`. +#' +#' @section Parameters: +#' * `"min_lambda"` (`numeric(1)`)\cr +#' Minimum value of lambda. +#' Defaults to `0.01`. +#' * `"max_lambda"` (`numeric(1)`)\cr +#' Maximum value of lambda. +#' Defaults to `10`. +#' * `"distribution"` (`character(1)`)\cr +#' Distribution to sample lambda from. +#' One of `c("uniform", "exponential")`. +#' Defaults to `uniform`. +#' * `"rate"` (`numeric(1)`)\cr +#' Rate of the exponential decay. +#' Defaults to `0` i.e. no decay. +#' * `"period"` (`integer(1)`)\cr +#' Period of the exponential decay. +#' Defaults to `NULL` i.e. the decay has no period. +#' +#' @references +#' * `r format_bib("snoek_2012")` +#' * `r format_bib("egele_2023")` +#' +#' @family Acquisition Function +#' @export +AcqFunctionStochasticCB = R6Class("AcqFunctionStochasticCB", + inherit = AcqFunction, + + public = list( + + #' @description + #' Creates a new instance of this [R6][R6::R6Class] class. + #' + #' @param surrogate (`NULL` | [SurrogateLearner]). + #' @param lambda (`numeric(1)`). + #' @param min_lambda (`numeric(1)`). + #' @param max_lambda (`numeric(1)`). + #' @param distribution (`character(1)`). + #' @param rate (`numeric(1)`). + #' @param period (`integer(1)`). + initialize = function( + surrogate = NULL, + min_lambda = 0.01, + max_lambda = 10, + distribution = "uniform", + rate = 0, + period = NULL + ) { + assert_r6(surrogate, "SurrogateLearner", null.ok = TRUE) + private$.min_lambda = assert_number(min_lambda, lower = .Machine$double.neg.eps) + private$.max_lambda = assert_number(max_lambda, lower = .Machine$double.neg.eps) + private$.distribution = assert_choice(distribution, choices = c("uniform", "exponential")) + private$.rate = assert_number(rate, lower = 0) + private$.period = assert_int(period, lower = 1, null.ok = TRUE) + + constants = ps( + lambda = p_dbl(lower = 0) + ) + + super$initialize("acq_cb", + constants = constants, + surrogate = surrogate, + requires_predict_type_se = TRUE, + direction = "same", + label = "Stochastic Lower / Upper Confidence Bound", + man = "mlr3mbo::mlr_acqfunctions_stochastic_cb") + }, + + #' @description + #' Update the acquisition function. + #' Samples and decays lambda. + update = function() { + # sample lambda + if (is.null(self$constants$values$lambda)) { + fun = switch(private$.distribution, + uniform = runif, + exponential = rexp + ) + + lambda = fun(1, private$.min_lambda, private$.max_lambda) + private$.lambda_0 = lambda + self$constants$values$lambda = lambda + } + + # decay lambda + if (private$.rate > 0) { + lambda_0 = private$.lambda_0 + period = private$.period + t = if (is.null(period)) private$.t else private$.t %% period + rate = private$.rate + + self$constants$values$lambda = lambda_0 * exp(-rate * t) + private$.t = t + 1 + } + } + ), + + private = list( + .min_lambda = NULL, + .max_lambda = NULL, + .distribution = NULL, + .rate = NULL, + .period = NULL, + .t = 0, + .lambda_0 = NULL, + + .fun = function(xdt, lambda) { + p = self$surrogate$predict(xdt) + cb = p$mean - self$surrogate_max_to_min * lambda * p$se + data.table(acq_cb = cb, lambda = lambda, lambda_0 = private$.lambda_0) + } + ) +) + +mlr_acqfunctions$add("stochastic_cb", AcqFunctionStochasticCB) + diff --git a/R/AcqFunctionStochasticEI.R b/R/AcqFunctionStochasticEI.R new file mode 100644 index 00000000..5938acf6 --- /dev/null +++ b/R/AcqFunctionStochasticEI.R @@ -0,0 +1,112 @@ +#' @title Acquisition Function Stochastic Expected Improvement +#' +#' @include AcqFunction.R +#' @name mlr_acqfunctions_stochastic_ei +#' +#' @templateVar id stochastic_ei +#' @template section_dictionary_acqfunctions +#' +#' @description +#' Expected Improvement with epsilon decay. +#' +#' @section Parameters: +#' * `"epsilon"` (`numeric(1)`)\cr +#' \eqn{\epsilon} value used to determine the amount of exploration. +#' Higher values result in the importance of improvements predicted by the posterior mean +#' decreasing relative to the importance of potential improvements in regions of high predictive uncertainty. +#' Defaults to `0.1`. +#' * `"rate"` (`numeric(1)`)\cr +# Rate of the exponential decay. +#' Defaults to `0.05`. +#' * `"period"` (`integer(1)`)\cr +#' Period of the exponential decay. +#' Defaults to `NULL` i.e. the decay has no period. +#' +#' @references +#' * `r format_bib("jones_1998")` +#' +#' @family Acquisition Function +#' @export +AcqFunctionStochasticEI = R6Class("AcqFunctionStochasticEI", + inherit = AcqFunction, + + public = list( + + #' @field y_best (`numeric(1)`)\cr + #' Best objective function value observed so far. + #' In the case of maximization, this already includes the necessary change of sign. + y_best = NULL, + + #' @description + #' Creates a new instance of this [R6][R6::R6Class] class. + #' + #' @param surrogate (`NULL` | [SurrogateLearner]). + #' @param epsilon (`numeric(1)`). + #' @param rate (`numeric(1)`). + #' @param period (`integer(1)`). + initialize = function( + surrogate = NULL, + epsilon = 0.1, + rate = 0.05, + period = NULL + ) { + assert_r6(surrogate, "SurrogateLearner", null.ok = TRUE) + private$.epsilon_0 = assert_number(epsilon, lower = 0, finite = TRUE) + private$.rate = assert_number(rate, lower = 0, finite = TRUE) + private$.period = assert_int(period, lower = 1, null.ok = TRUE) + + constants = ps( + epsilon = p_dbl(lower = 0) + ) + + super$initialize("acq_ei", + constants = constants, + surrogate = surrogate, + requires_predict_type_se = TRUE, + direction = "maximize", + label = "Stochastic Expected Improvement", + man = "mlr3mbo::mlr_acqfunctions_stochastic_ei") + }, + + #' @description + #' Update the acquisition function. + #' Sets `y_best` to the best observed objective function value. + #' Decays epsilon. + update = function() { + self$y_best = min(self$surrogate_max_to_min * self$archive$data[[self$surrogate$cols_y]]) + + # decay epsilon + epsilon_0 = private$.epsilon_0 + period = private$.period + t = if (is.null(period)) private$.t else private$.t %% period + rate = private$.rate + + self$constants$values$epsilon = epsilon_0 * exp(-rate * t) + private$.t = t + 1 + } + ), + + private = list( + .rate = NULL, + .period = NULL, + .epsilon_0 = NULL, + .t = 0, + + .fun = function(xdt, epsilon) { + if (is.null(self$y_best)) { + stop("$y_best is not set. Missed to call $update()?") + } + p = self$surrogate$predict(xdt) + mu = p$mean + se = p$se + d = (self$y_best - self$surrogate_max_to_min * mu) - epsilon + d_norm = d / se + ei = d * pnorm(d_norm) + se * dnorm(d_norm) + ei = ifelse(se < 1e-20, 0, ei) + data.table(acq_ei = ei, epsilon = epsilon, epsilon_0 = private$.epsilon_0) + } + ) +) + +mlr_acqfunctions$add("stochastic_ei", AcqFunctionStochasticEI) + diff --git a/R/OptimizerAsyncMbo.R b/R/OptimizerAsyncMbo.R index 2a9c67f9..dba1b28a 100644 --- a/R/OptimizerAsyncMbo.R +++ b/R/OptimizerAsyncMbo.R @@ -20,9 +20,12 @@ #' If `NULL`, all rush workers set with [rush::rush_plan()] are used.} #' } #' +#' @template param_id #' @template param_surrogate #' @template param_acq_function #' @template param_acq_optimizer +#' @template param_label +#' @template param_man #' #' @param param_set [paradox::ParamSet]\cr #' Set of control parameters. @@ -35,24 +38,45 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", #' @description #' Creates a new instance of this [R6][R6::R6Class] class. - initialize = function(surrogate = NULL, acq_function = NULL, acq_optimizer = NULL, param_set = NULL) { + #' + #' @param param_classes (`character()`)\cr + #' Supported parameter classes that the optimizer can optimize, as given in the [`paradox::ParamSet`] `$class` field. + #' @param properties (`character()`)\cr + #' Set of properties of the optimizer. + #' Must be a subset of [`bbotk_reflections$optimizer_properties`][bbotk_reflections]. + #' @param packages (`character()`)\cr + #' Set of required packages. + #' A warning is signaled by the constructor if at least one of the packages is not installed, but loaded (not attached) later on-demand via [requireNamespace()]. + initialize = function( + id = "async_mbo", + surrogate = NULL, + acq_function = NULL, + acq_optimizer = NULL, + param_set = NULL, + param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), + properties = c("dependencies", "single-crit"), + packages = c("mlr3mbo", "rush"), + label = "Asynchronous Model Based Optimization", + man = "mlr3mbo::OptimizerAsyncMbo" + ) { + default_param_set = ps( initial_design = p_uty(), - design_size = p_int(lower = 1, default = 10), + design_size = p_int(lower = 1, default = 100L), design_function = p_fct(c("random", "sobol", "lhs"), default = "sobol"), n_workers = p_int(lower = 1L) ) param_set = c(default_param_set, param_set) - param_set$set_values(design_size = 10, design_function = "sobol") + param_set$set_values(design_size = 100L, design_function = "sobol") super$initialize("async_mbo", param_set = param_set, - param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), - properties = c("dependencies", "single-crit"), - packages = c("mlr3mbo", "rush"), - label = "Asynchronous Model Based Optimization", - man = "mlr3mbo::OptimizerAsyncMbo") + param_classes = param_classes, + properties = properties, + packages = packages, + label = label, + man = man) self$surrogate = assert_r6(surrogate, classes = "Surrogate", null.ok = TRUE) self$acq_function = assert_r6(acq_function, classes = "AcqFunction", null.ok = TRUE) diff --git a/R/OptimizerAsyncMboADBO.R b/R/OptimizerAsyncMboADBO.R new file mode 100644 index 00000000..ec1c326c --- /dev/null +++ b/R/OptimizerAsyncMboADBO.R @@ -0,0 +1,95 @@ +#' @title Asynchronous Decentralized Bayesian Optimization +#' @name mlr_optimizers_adbo +#' +#' @description +#' Asynchronous Decentralized Bayesian Optimization (ADBO). +#' +#' @note +#' The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. +#' A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. +#' ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. +#' +#' @section Parameters: +#' \describe{ +#' \item{`min_lambda`}{`numeric(1)`\cr +#' Minimum value of \eqn{\lambda}. +#' Defaults to `0.01`.} +#' \item{`max_lambda`}{`numeric(1)`\cr +#' Maximum value of \eqn{\lambda}. +#' Defaults to `10`.} +#' \item{`rate`}{`numeric(1)`\cr +#' Rate of the exponential decay.} +#' \item{`period`}{`integer(1)`\cr +#' Period of the exponential decay.} +#' \item{`initial_design_size`}{`integer(1)`\cr +#' Size of the initial design. +#' Defaults to `100`.} +#' +#' \item{`initial_design`}{`data.table::data.table()`\cr +#' Initial design of the optimization. +#' If `NULL`, a design of size `design_size` is generated with `design_function`.} +#' \item{`design_size`}{`integer(1)`\cr +#' Size of the initial design.} +#' \item{`design_function`}{`character(1)`\cr +#' Function to generate the initial design. +#' One of `c("random", "sobol", "lhs")`.} +#' \item{`n_workers`}{`integer(1)`\cr +#' Number of parallel workers. +#' If `NULL`, all rush workers set with [rush::rush_plan()] are used.} +#' } +#' +#' @export +OptimizerAsyncMboADBO = R6Class("OptimizerAsyncMboADBO", + inherit = OptimizerAsyncMbo, + + public = list( + + #' @description + #' Creates a new instance of this [R6][R6::R6Class] class. + initialize = function() { + param_set = ps( + min_lambda = p_dbl(lower = 0, default = 0.01), + max_lambda = p_dbl(lower = 0, default = 10), + exponential_decay = p_lgl(default = TRUE), + rate = p_dbl(lower = 0, default = 0.1), + period = p_int(lower = 1L, default = 25L) + ) + + super$initialize( + id = "adbo", + param_set = param_set, + label = "Asynchronous Decentralized Bayesian Optimization", + man = "mlr3mbo::OptimizerAsyncMboADBO") + + self$param_set$set_values( + min_lambda = 0.01, + max_lambda = 10, + rate = 0.1, + period = 25L) + } + ), + + private = list( + + .optimize = function(inst) { + + self$acq_function = acqf("stochastic_cb", + min_lambda = self$param_set$values$min_lambda, + max_lambda = self$param_set$values$max_lambda, + rate = self$param_set$values$rate, + period = self$param_set$values$period + ) + + self$acq_optimizer = acqo( + optimizer = opt("random_search", batch_size = 1000L), + terminator = trm("evals", n_evals = 10000L)) + + self$surrogate = default_surrogate(inst, force_rf = TRUE) + + super$.optimize(inst) + } + ) +) + +#' @include aaa.R +optimizers[["adbo"]] = OptimizerAsyncMboADBO diff --git a/R/SurrogateLearnerAsync.R b/R/SurrogateLearnerAsync.R index 584f463b..6b7de4b2 100644 --- a/R/SurrogateLearnerAsync.R +++ b/R/SurrogateLearnerAsync.R @@ -2,6 +2,8 @@ #' #' @description #' Surrogate model containing a single [mlr3::LearnerRegr]. +#' The surrogate model is updated on the [mlr3tuning::ArchiveAsyncTuning]. +#' Unevaluated points are imputed with the mean or random values. #' #' @section Parameters: #' \describe{ diff --git a/R/aaa.R b/R/aaa.R index 4a4686c6..ed02c255 100644 --- a/R/aaa.R +++ b/R/aaa.R @@ -1,3 +1,2 @@ optimizers = list() tuners = list() -callbacks = list() diff --git a/R/bibentries.R b/R/bibentries.R index 68efb918..e6ae6909 100644 --- a/R/bibentries.R +++ b/R/bibentries.R @@ -125,6 +125,13 @@ bibentries = c( booktitle = "Parallel Problem Solving from Nature -- PPSN XVII", year = "2022", pages = "90--103" + ), + + egele_2023 = bibentry("article", + title = "Asynchronous Decentralized Bayesian Optimization for Large Scale Hyperparameter Optimization", + author = "Romain Egele and Isabelle Guyon and Venkatram Vishwanath and Prasanna Balaprakash", + year = "2023", + journal = "arXiv", + url = "https://arxiv.org/abs/2207.00479" ) ) - diff --git a/R/mbo_defaults.R b/R/mbo_defaults.R index ccceb89b..9627071f 100644 --- a/R/mbo_defaults.R +++ b/R/mbo_defaults.R @@ -143,17 +143,19 @@ default_rf = function(noisy = FALSE) { #' @param n_learner (`NULL` | `integer(1)`). #' Number of learners to be considered in the construction of the [SurrogateLearner] or [SurrogateLearnerCollection]. #' If not specified will be based on the number of objectives as stated by the instance. +#' @param force_rf (`logical(1)`). +#' If `TRUE`, a random forest is constructed even if the parameter space is numeric-only. #' @return [Surrogate] #' @family mbo_defaults #' @export -default_surrogate = function(instance, learner = NULL, n_learner = NULL) { +default_surrogate = function(instance, learner = NULL, n_learner = NULL, force_rf = FALSE) { assert_multi_class(instance, c("OptimInstance", "OptimInstanceAsync")) assert_r6(learner, "Learner", null.ok = TRUE) assert_int(n_learner, lower = 1L, null.ok = TRUE) noisy = "noisy" %in% instance$objective$properties if (is.null(learner)) { - is_mixed_space = !all(instance$search_space$class %in% c("ParamDbl", "ParamInt")) + is_mixed_space = !all(instance$search_space$class %in% c("ParamDbl", "ParamInt")) || force_rf has_deps = nrow(instance$search_space$deps) > 0L learner = if (!is_mixed_space) { default_gp(noisy) @@ -214,7 +216,7 @@ default_acqfunction = function(instance) { if (inherits(instance, "OptimInstanceBatchSingleCrit")) { AcqFunctionEI$new() } else if (inherits(instance, "OptimInstanceAsyncSingleCrit")) { - AcqFunctionCB$new() + AcqFunctionStochasticCB$new() } else if (inherits(instance, "OptimInstanceBatchMultiCrit")) { AcqFunctionSmsEgo$new() } diff --git a/R/mlr_callbacks.R b/R/mlr_callbacks.R deleted file mode 100644 index bf2c791c..00000000 --- a/R/mlr_callbacks.R +++ /dev/null @@ -1,177 +0,0 @@ -#' @title Sample Lambda from an Uniform Distribution -#' -#' @name mlr3mbo.sample_lambda_uniform -#' -#' @description -#' This [CallbackAsyncTuning] samples the lambda parameter of the confidence bounds acquisition function. -#' The lambda value is drawn from a uniform distribution with `min` and `max` as bounds. -#' -#' @param min_lambda (`numeric(1)`)\cr -#' Minimum value of the lambda parameter. -#' Defaults to `0.01`. -#' @param max_lambda (`numeric(1)`)\cr -#' Maximum value of the lambda parameter. -#' Defaults to `10`. -#' -#' @examples -#' clbk("mlr3mbo.sample_lambda_uniform", min_lambda = 0.01, max_lambda = 10) -NULL - -load_callback_sample_lambda_uniform = function() { - callback_async_tuning("mlr3mbo.sample_lambda_uniform", - label = "Sample Lambda Uniform", - man = "mlr3mbo::mlr3mbo.sample_lambda_uniform", - - on_optimization_begin = function(callback, context) { - assert_class(context$optimizer$acq_function, "AcqFunctionCB") - callback$state$min_lambda = assert_number(callback$state$min_lambda, lower = 0, null.ok = TRUE) %??% 0.01 - callback$state$max_lambda = assert_number(callback$state$max_lambda, lower = 0, null.ok = TRUE) %??% 10 - }, - - on_worker_begin = function(callback, context) { - callback$state$lambda = runif(1, min = callback$state$min_lambda, max = callback$state$max_lambda) - context$optimizer$acq_function$constants$set_values(lambda = callback$state$lambda) - }, - - on_optimizer_after_eval = function(callback, context) { - context$ys = c(context$ys, list(lambda = callback$state$lambda)) - } - ) -} - -callbacks[["mlr3mbo.sample_lambda_uniform"]] = load_callback_sample_lambda_uniform - -#' @title Sample Lambda from an Exponential Distribution -#' -#' @name mlr3mbo.sample_lambda_exponential -#' -#' @description -#' This [CallbackAsyncTuning] samples the lambda parameter of the confidence bounds acquisition function. -#' The lambda value is drawn from an exponential distribution with rate `1 / lambda`. -#' -#' @examples -#' clbk("mlr3mbo.sample_lambda_exponential") -NULL - -load_callback_sample_lambda_exponential = function() { - callback_async_tuning("mlr3mbo.sample_lambda_exponential", - label = "Sample Lambda Exponential", - man = "mlr3mbo::mlr3mbo.sample_lambda_exponential", - - on_optimization_begin = function(callback, context) { - assert_class(context$optimizer$acq_function, "AcqFunctionCB") - }, - - on_worker_begin = function(callback, context) { - lambda = context$optimizer$acq_function$constants$values$lambda - callback$state$lambda = rexp(1, 1 / lambda) - context$optimizer$acq_function$constants$set_values(lambda = callback$state$lambda) - }, - - on_optimizer_after_eval = function(callback, context) { - context$ys = c(context$ys, list(lambda = callback$state$lambda)) - } - ) -} - -callbacks[["mlr3mbo.sample_lambda_exponential"]] = load_callback_sample_lambda_exponential - -#' @title Exponential Decay of Lambda -#' -#' @name mlr3mbo.exponential_lambda_decay -#' -#' @description -#' This [CallbackAsyncTuning] decays the lambda parameter of the confidence bounds acquisition function. -#' The initial lambda value is drawn from an exponential distribution with rate `1 / lambda`. -#' The lambda value is updated after each evaluation by the formula `lambda * exp(-rate * (t %% period))`. -#' -#' @param rate (`numeric(1)`)\cr -#' Rate of the exponential decay. -#' Defaults to `0.1`. -#' @param period (`integer(1)`)\cr -#' Period of the exponential decay. -#' Defaults to `25`. -#' -#' @examples -#' clbk("mlr3mbo.exponential_lambda_decay", rate = 0.1, period = 25) -NULL - -load_callback_exponential_lambda_decay = function() { - callback_async_tuning("mlr3mbo.exponential_lambda_decay", - label = "Exponential Decay of Lambda", - man = "mlr3mbo::mlr3mbo.exponential_lambda_decay", - - on_optimization_begin = function(callback, context) { - assert_class(context$optimizer$acq_function, "AcqFunctionCB") - callback$state$rate = assert_number(callback$state$rate, lower = 0, null.ok = TRUE) %??% 0.1 - callback$state$period = assert_integer(callback$state$period, lower = 1, null.ok = TRUE) %??% 25 - callback$state$t = 0 - }, - - on_worker_begin = function(callback, context) { - lambda = context$optimizer$acq_function$constants$values$lambda - callback$state$lambda_0 = rexp(1, 1 / lambda) - context$optimizer$acq_function$constants$set_values(lambda = callback$state$lambda_0) - }, - - on_optimizer_after_eval = function(callback, context) { - if (!is.null(context$extra)) { # skip initial design - lambda_0 = callback$state$lambda_0 - t = callback$state$t - lambda = lambda_0 * exp(-callback$state$rate * (t %% callback$state$period)) - callback$state$t = t + 1 - context$optimizer$acq_function$constants$set_values(lambda = lambda) - context$ys = c(context$ys, list(lambda_0 = callback$state$lambda_0, lambda = lambda)) - } - } - ) -} - -callbacks[["mlr3mbo.exponential_lambda_decay"]] = load_callback_exponential_lambda_decay - -#' @title Epsilon Decay -#' -#' @name mlr3mbo.epsilon_decay -#' -#' @description -#' This [CallbackAsyncTuning] decays the epsilon parameter of the expected improvement acquisition function. -#' -#' @param rate (`numeric(1)`)\cr -#' Rate of the exponential decay. -#' Defaults to `0.1`. -#' @param period (`integer(1)`)\cr -#' Period of the exponential decay. -#' Defaults to `25`. -#' @examples -#' clbk("mlr3mbo.epsilon_decay", rate = 0.1, period = 25) -NULL - -load_callback_epsilon_decay = function() { - callback_async_tuning("mlr3mbo.epsilon_decay", - label = "Episilon Decay", - man = "mlr3mbo::mlr3mbo.epsilon_decay", - - on_optimization_begin = function(callback, context) { - assert_class(context$optimizer$acq_function, "AcqFunctionEI") - epsilon = context$optimizer$acq_function$constants$values$epsilon - callback$state$epsilon_0 = epsilon - callback$state$epsilon = epsilon - callback$state$rate = assert_number(callback$state$rate, lower = 0, null.ok = TRUE) %??% 0.1 - callback$state$period = assert_number(callback$state$period, lower = 1, null.ok = TRUE) %??% 25 - callback$state$t = 0 - }, - - on_optimizer_after_eval = function(callback, context) { - if (!is.null(context$extra)) { # skip initial design - t = callback$state$t - epsilon_0 = callback$state$epsilon_0 - epsilon = epsilon_0 * exp(-callback$state$rate * (t %% callback$state$period)) - callback$state$t = t + 1 - context$optimizer$acq_function$constants$set_values(epsilon = epsilon) - context$ys = c(context$ys, list(epsilon_0 = epsilon_0, epsilon = epsilon)) - } - } - ) -} - -callbacks[["mlr3mbo.epsilon_decay"]] = load_callback_epsilon_decay diff --git a/R/zzz.R b/R/zzz.R index 62d0e570..a0e1e22d 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -25,17 +25,10 @@ register_mlr3tuning = function() { iwalk(tuners, function(obj, nm) x$add(nm, obj)) } # nocov end -register_mlr3misc = function() { - # nocov start - x = utils::getFromNamespace("mlr_callbacks", ns = "mlr3misc") - iwalk(callbacks, function(obj, nm) x$add(nm, obj)) -} # nocov end - .onLoad = function(libname, pkgname) { # nolint # nocov start register_namespace_callback(pkgname, "bbotk", register_bbotk) register_namespace_callback(pkgname, "mlr3tuning", register_mlr3tuning) - register_namespace_callback(pkgname, "mlr3misc", register_mlr3misc) assign("lg", lgr::get_logger("bbotk"), envir = parent.env(environment())) @@ -48,7 +41,6 @@ register_mlr3misc = function() { # nocov start walk(names(optimizers), function(id) bbotk::mlr_optimizers$remove(id)) walk(names(tuners), function(id) mlr3tuning::mlr_tuners$remove(id)) - walk(names(callbacks), function(id) mlr3misc::mlr_callbacks$remove(id)) } # nocov end # static code checks should not complain about commonly used data.table columns diff --git a/man-roxygen/param_id.R b/man-roxygen/param_id.R new file mode 100644 index 00000000..1f50f0ec --- /dev/null +++ b/man-roxygen/param_id.R @@ -0,0 +1,2 @@ +#' @param id (`character(1)`)\cr +#' Identifier for the new instance. diff --git a/man-roxygen/param_label.R b/man-roxygen/param_label.R new file mode 100644 index 00000000..1a73abff --- /dev/null +++ b/man-roxygen/param_label.R @@ -0,0 +1,3 @@ +#' @param label (`character(1)`)\cr +#' Label for this object. +#' Can be used in tables, plot and text output instead of the ID. diff --git a/man-roxygen/param_man.R b/man-roxygen/param_man.R new file mode 100644 index 00000000..3625469b --- /dev/null +++ b/man-roxygen/param_man.R @@ -0,0 +1,3 @@ +#' @param man (`character(1)`)\cr +#' String in the format `[pkg]::[topic]` pointing to a manual page for this object. +#' The referenced help package can be opened via method `$help()`. diff --git a/man/AcqFunction.Rd b/man/AcqFunction.Rd index 31f16e42..d102b453 100644 --- a/man/AcqFunction.Rd +++ b/man/AcqFunction.Rd @@ -21,7 +21,9 @@ Other Acquisition Function: \code{\link{mlr_acqfunctions_multi}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}} +\code{\link{mlr_acqfunctions_smsego}}, +\code{\link{mlr_acqfunctions_stochastic_cb}}, +\code{\link{mlr_acqfunctions_stochastic_ei}} } \concept{Acquisition Function} \section{Super class}{ diff --git a/man/SurrogateLearnerAsync.Rd b/man/SurrogateLearnerAsync.Rd index 911f9cef..5a2b4303 100644 --- a/man/SurrogateLearnerAsync.Rd +++ b/man/SurrogateLearnerAsync.Rd @@ -5,6 +5,8 @@ \title{Surrogate Model Containing a Single Learner} \description{ Surrogate model containing a single \link[mlr3:LearnerRegr]{mlr3::LearnerRegr}. +The surrogate model is updated on the \link[mlr3tuning:ArchiveAsyncTuning]{mlr3tuning::ArchiveAsyncTuning}. +Unevaluated points are imputed with the mean or random values. } \section{Parameters}{ diff --git a/man/default_surrogate.Rd b/man/default_surrogate.Rd index 25e416d3..839793c3 100644 --- a/man/default_surrogate.Rd +++ b/man/default_surrogate.Rd @@ -4,7 +4,7 @@ \alias{default_surrogate} \title{Default Surrogate} \usage{ -default_surrogate(instance, learner = NULL, n_learner = NULL) +default_surrogate(instance, learner = NULL, n_learner = NULL, force_rf = FALSE) } \arguments{ \item{instance}{(\link[bbotk:OptimInstance]{bbotk::OptimInstance})\cr @@ -16,6 +16,9 @@ If specified, this learner will be used instead of the defaults described above. \item{n_learner}{(\code{NULL} | \code{integer(1)}). Number of learners to be considered in the construction of the \link{SurrogateLearner} or \link{SurrogateLearnerCollection}. If not specified will be based on the number of objectives as stated by the instance.} + +\item{force_rf}{(\code{logical(1)}). +If \code{TRUE}, a random forest is constructed even if the parameter space is numeric-only.} } \value{ \link{Surrogate} diff --git a/man/mlr3mbo.epsilon_decay.Rd b/man/mlr3mbo.epsilon_decay.Rd deleted file mode 100644 index 29b15572..00000000 --- a/man/mlr3mbo.epsilon_decay.Rd +++ /dev/null @@ -1,20 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/mlr_callbacks.R -\name{mlr3mbo.epsilon_decay} -\alias{mlr3mbo.epsilon_decay} -\title{Epsilon Decay} -\arguments{ -\item{rate}{(\code{numeric(1)})\cr -Rate of the exponential decay. -Defaults to \code{0.1}.} - -\item{period}{(\code{integer(1)})\cr -Period of the exponential decay. -Defaults to \code{25}.} -} -\description{ -This \link{CallbackAsyncTuning} decays the epsilon parameter of the expected improvement acquisition function. -} -\examples{ -clbk("mlr3mbo.epsilon_decay", rate = 0.1, period = 25) -} diff --git a/man/mlr3mbo.exponential_lambda_decay.Rd b/man/mlr3mbo.exponential_lambda_decay.Rd deleted file mode 100644 index ec274713..00000000 --- a/man/mlr3mbo.exponential_lambda_decay.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/mlr_callbacks.R -\name{mlr3mbo.exponential_lambda_decay} -\alias{mlr3mbo.exponential_lambda_decay} -\title{Exponential Decay of Lambda} -\arguments{ -\item{rate}{(\code{numeric(1)})\cr -Rate of the exponential decay. -Defaults to \code{0.1}.} - -\item{period}{(\code{integer(1)})\cr -Period of the exponential decay. -Defaults to \code{25}.} -} -\description{ -This \link{CallbackAsyncTuning} decays the lambda parameter of the confidence bounds acquisition function. -The initial lambda value is drawn from an exponential distribution with rate \code{1 / lambda}. -The lambda value is updated after each evaluation by the formula \code{lambda * exp(-rate * (t \%\% period))}. -} -\examples{ -clbk("mlr3mbo.exponential_lambda_decay", rate = 0.1, period = 25) -} diff --git a/man/mlr3mbo.sample_lambda_exponential.Rd b/man/mlr3mbo.sample_lambda_exponential.Rd deleted file mode 100644 index 6a4182f3..00000000 --- a/man/mlr3mbo.sample_lambda_exponential.Rd +++ /dev/null @@ -1,12 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/mlr_callbacks.R -\name{mlr3mbo.sample_lambda_exponential} -\alias{mlr3mbo.sample_lambda_exponential} -\title{Sample Lambda from an Exponential Distribution} -\description{ -This \link{CallbackAsyncTuning} samples the lambda parameter of the confidence bounds acquisition function. -The lambda value is drawn from an exponential distribution with rate \code{1 / lambda}. -} -\examples{ -clbk("mlr3mbo.sample_lambda_exponential") -} diff --git a/man/mlr3mbo.sample_lambda_uniform.Rd b/man/mlr3mbo.sample_lambda_uniform.Rd deleted file mode 100644 index 4811183b..00000000 --- a/man/mlr3mbo.sample_lambda_uniform.Rd +++ /dev/null @@ -1,21 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/mlr_callbacks.R -\name{mlr3mbo.sample_lambda_uniform} -\alias{mlr3mbo.sample_lambda_uniform} -\title{Sample Lambda from an Uniform Distribution} -\arguments{ -\item{min_lambda}{(\code{numeric(1)})\cr -Minimum value of the lambda parameter. -Defaults to \code{0.01}.} - -\item{max_lambda}{(\code{numeric(1)})\cr -Maximum value of the lambda parameter. -Defaults to \code{10}.} -} -\description{ -This \link{CallbackAsyncTuning} samples the lambda parameter of the confidence bounds acquisition function. -The lambda value is drawn from a uniform distribution with \code{min} and \code{max} as bounds. -} -\examples{ -clbk("mlr3mbo.sample_lambda_uniform", min_lambda = 0.01, max_lambda = 10) -} diff --git a/man/mlr_acqfunctions.Rd b/man/mlr_acqfunctions.Rd index b9d222c5..9b7405ba 100644 --- a/man/mlr_acqfunctions.Rd +++ b/man/mlr_acqfunctions.Rd @@ -42,7 +42,9 @@ Other Acquisition Function: \code{\link{mlr_acqfunctions_multi}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}} +\code{\link{mlr_acqfunctions_smsego}}, +\code{\link{mlr_acqfunctions_stochastic_cb}}, +\code{\link{mlr_acqfunctions_stochastic_ei}} } \concept{Acquisition Function} \concept{Dictionary} diff --git a/man/mlr_acqfunctions_aei.Rd b/man/mlr_acqfunctions_aei.Rd index 5ce6d5c9..dfdbee55 100644 --- a/man/mlr_acqfunctions_aei.Rd +++ b/man/mlr_acqfunctions_aei.Rd @@ -89,7 +89,9 @@ Other Acquisition Function: \code{\link{mlr_acqfunctions_multi}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}} +\code{\link{mlr_acqfunctions_smsego}}, +\code{\link{mlr_acqfunctions_stochastic_cb}}, +\code{\link{mlr_acqfunctions_stochastic_ei}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_cb.Rd b/man/mlr_acqfunctions_cb.Rd index e38ee1fd..9cf4ab51 100644 --- a/man/mlr_acqfunctions_cb.Rd +++ b/man/mlr_acqfunctions_cb.Rd @@ -78,7 +78,9 @@ Other Acquisition Function: \code{\link{mlr_acqfunctions_multi}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}} +\code{\link{mlr_acqfunctions_smsego}}, +\code{\link{mlr_acqfunctions_stochastic_cb}}, +\code{\link{mlr_acqfunctions_stochastic_ei}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_ehvi.Rd b/man/mlr_acqfunctions_ehvi.Rd index 30c11e73..32650a7d 100644 --- a/man/mlr_acqfunctions_ehvi.Rd +++ b/man/mlr_acqfunctions_ehvi.Rd @@ -64,7 +64,9 @@ Other Acquisition Function: \code{\link{mlr_acqfunctions_multi}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}} +\code{\link{mlr_acqfunctions_smsego}}, +\code{\link{mlr_acqfunctions_stochastic_cb}}, +\code{\link{mlr_acqfunctions_stochastic_ei}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_ehvigh.Rd b/man/mlr_acqfunctions_ehvigh.Rd index 0601b421..f122a903 100644 --- a/man/mlr_acqfunctions_ehvigh.Rd +++ b/man/mlr_acqfunctions_ehvigh.Rd @@ -78,7 +78,9 @@ Other Acquisition Function: \code{\link{mlr_acqfunctions_multi}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}} +\code{\link{mlr_acqfunctions_smsego}}, +\code{\link{mlr_acqfunctions_stochastic_cb}}, +\code{\link{mlr_acqfunctions_stochastic_ei}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_ei.Rd b/man/mlr_acqfunctions_ei.Rd index 07bc12a9..dd7bc9d9 100644 --- a/man/mlr_acqfunctions_ei.Rd +++ b/man/mlr_acqfunctions_ei.Rd @@ -81,7 +81,9 @@ Other Acquisition Function: \code{\link{mlr_acqfunctions_multi}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}} +\code{\link{mlr_acqfunctions_smsego}}, +\code{\link{mlr_acqfunctions_stochastic_cb}}, +\code{\link{mlr_acqfunctions_stochastic_ei}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_eips.Rd b/man/mlr_acqfunctions_eips.Rd index 44d7b507..ba79851b 100644 --- a/man/mlr_acqfunctions_eips.Rd +++ b/man/mlr_acqfunctions_eips.Rd @@ -78,7 +78,9 @@ Other Acquisition Function: \code{\link{mlr_acqfunctions_multi}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}} +\code{\link{mlr_acqfunctions_smsego}}, +\code{\link{mlr_acqfunctions_stochastic_cb}}, +\code{\link{mlr_acqfunctions_stochastic_ei}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_mean.Rd b/man/mlr_acqfunctions_mean.Rd index fdcd77e2..e201f9b2 100644 --- a/man/mlr_acqfunctions_mean.Rd +++ b/man/mlr_acqfunctions_mean.Rd @@ -63,7 +63,9 @@ Other Acquisition Function: \code{\link{mlr_acqfunctions_multi}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}} +\code{\link{mlr_acqfunctions_smsego}}, +\code{\link{mlr_acqfunctions_stochastic_cb}}, +\code{\link{mlr_acqfunctions_stochastic_ei}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_multi.Rd b/man/mlr_acqfunctions_multi.Rd index 344632ff..d3e78c09 100644 --- a/man/mlr_acqfunctions_multi.Rd +++ b/man/mlr_acqfunctions_multi.Rd @@ -76,7 +76,9 @@ Other Acquisition Function: \code{\link{mlr_acqfunctions_mean}}, \code{\link{mlr_acqfunctions_pi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}} +\code{\link{mlr_acqfunctions_smsego}}, +\code{\link{mlr_acqfunctions_stochastic_cb}}, +\code{\link{mlr_acqfunctions_stochastic_ei}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_pi.Rd b/man/mlr_acqfunctions_pi.Rd index e4ec11f8..a7b8f179 100644 --- a/man/mlr_acqfunctions_pi.Rd +++ b/man/mlr_acqfunctions_pi.Rd @@ -70,7 +70,9 @@ Other Acquisition Function: \code{\link{mlr_acqfunctions_mean}}, \code{\link{mlr_acqfunctions_multi}}, \code{\link{mlr_acqfunctions_sd}}, -\code{\link{mlr_acqfunctions_smsego}} +\code{\link{mlr_acqfunctions_smsego}}, +\code{\link{mlr_acqfunctions_stochastic_cb}}, +\code{\link{mlr_acqfunctions_stochastic_ei}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_sd.Rd b/man/mlr_acqfunctions_sd.Rd index 7dd8184f..d80fe16b 100644 --- a/man/mlr_acqfunctions_sd.Rd +++ b/man/mlr_acqfunctions_sd.Rd @@ -63,7 +63,9 @@ Other Acquisition Function: \code{\link{mlr_acqfunctions_mean}}, \code{\link{mlr_acqfunctions_multi}}, \code{\link{mlr_acqfunctions_pi}}, -\code{\link{mlr_acqfunctions_smsego}} +\code{\link{mlr_acqfunctions_smsego}}, +\code{\link{mlr_acqfunctions_stochastic_cb}}, +\code{\link{mlr_acqfunctions_stochastic_ei}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_smsego.Rd b/man/mlr_acqfunctions_smsego.Rd index 314da30e..53de144f 100644 --- a/man/mlr_acqfunctions_smsego.Rd +++ b/man/mlr_acqfunctions_smsego.Rd @@ -80,7 +80,9 @@ Other Acquisition Function: \code{\link{mlr_acqfunctions_mean}}, \code{\link{mlr_acqfunctions_multi}}, \code{\link{mlr_acqfunctions_pi}}, -\code{\link{mlr_acqfunctions_sd}} +\code{\link{mlr_acqfunctions_sd}}, +\code{\link{mlr_acqfunctions_stochastic_cb}}, +\code{\link{mlr_acqfunctions_stochastic_ei}} } \concept{Acquisition Function} \section{Super classes}{ diff --git a/man/mlr_acqfunctions_stochastic_cb.Rd b/man/mlr_acqfunctions_stochastic_cb.Rd new file mode 100644 index 00000000..e8588737 --- /dev/null +++ b/man/mlr_acqfunctions_stochastic_cb.Rd @@ -0,0 +1,160 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/AcqFunctionStochasticCB.R +\name{mlr_acqfunctions_stochastic_cb} +\alias{mlr_acqfunctions_stochastic_cb} +\alias{AcqFunctionStochasticCB} +\title{Acquisition Function Stochastic Confidence Bound} +\description{ +Lower / Upper Confidence Bound with lambda sampling and decay. +The initial lambda value is drawn from an uniform or exponential distribution with rate \code{1 / lambda}. +The lambda value is updated after each evaluation by the formula \code{lambda * exp(-rate * (t \%\% period))}. +} +\section{Dictionary}{ + +This \link{AcqFunction} can be instantiated via the \link[mlr3misc:Dictionary]{dictionary} +\link{mlr_acqfunctions} or with the associated sugar function \code{\link[=acqf]{acqf()}}: + +\if{html}{\out{
    }}\preformatted{mlr_acqfunctions$get("stochastic_cb") +acqf("stochastic_cb") +}\if{html}{\out{
    }} +} + +\section{Parameters}{ + +\itemize{ +\item \code{"min_lambda"} (\code{numeric(1)})\cr +Minimum value of lambda. +Defaults to \code{0.01}. +\item \code{"max_lambda"} (\code{numeric(1)})\cr +Maximum value of lambda. +Defaults to \code{10}. +\item \code{"distribution"} (\code{character(1)})\cr +Distribution to sample lambda from. +One of \code{c("uniform", "exponential")}. +Defaults to \code{uniform}. +\item \code{"rate"} (\code{numeric(1)})\cr +Rate of the exponential decay. +Defaults to \code{0} i.e. no decay. +\item \code{"period"} (\code{integer(1)})\cr +Period of the exponential decay. +Defaults to \code{NULL} i.e. the decay has no period. +} +} + +\references{ +\itemize{ +\item Snoek, Jasper, Larochelle, Hugo, Adams, P R (2012). +\dQuote{Practical Bayesian Optimization of Machine Learning Algorithms.} +In Pereira F, Burges CJC, Bottou L, Weinberger KQ (eds.), \emph{Advances in Neural Information Processing Systems}, volume 25, 2951--2959. +\item Egele R, Guyon I, Vishwanath V, Balaprakash P (2023). +\dQuote{Asynchronous Decentralized Bayesian Optimization for Large Scale Hyperparameter Optimization.} +\emph{arXiv}. +\url{https://arxiv.org/abs/2207.00479}. +} +} +\seealso{ +Other Acquisition Function: +\code{\link{AcqFunction}}, +\code{\link{mlr_acqfunctions}}, +\code{\link{mlr_acqfunctions_aei}}, +\code{\link{mlr_acqfunctions_cb}}, +\code{\link{mlr_acqfunctions_ehvi}}, +\code{\link{mlr_acqfunctions_ehvigh}}, +\code{\link{mlr_acqfunctions_ei}}, +\code{\link{mlr_acqfunctions_eips}}, +\code{\link{mlr_acqfunctions_mean}}, +\code{\link{mlr_acqfunctions_multi}}, +\code{\link{mlr_acqfunctions_pi}}, +\code{\link{mlr_acqfunctions_sd}}, +\code{\link{mlr_acqfunctions_smsego}}, +\code{\link{mlr_acqfunctions_stochastic_ei}} +} +\concept{Acquisition Function} +\section{Super classes}{ +\code{\link[bbotk:Objective]{bbotk::Objective}} -> \code{\link[mlr3mbo:AcqFunction]{mlr3mbo::AcqFunction}} -> \code{AcqFunctionStochasticCB} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-AcqFunctionStochasticCB-new}{\code{AcqFunctionStochasticCB$new()}} +\item \href{#method-AcqFunctionStochasticCB-update}{\code{AcqFunctionStochasticCB$update()}} +\item \href{#method-AcqFunctionStochasticCB-clone}{\code{AcqFunctionStochasticCB$clone()}} +} +} +\if{html}{\out{ +
    Inherited methods + +
    +}} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AcqFunctionStochasticCB-new}{}}} +\subsection{Method \code{new()}}{ +Creates a new instance of this \link[R6:R6Class]{R6} class. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{AcqFunctionStochasticCB$new( + surrogate = NULL, + min_lambda = 0.01, + max_lambda = 10, + distribution = "uniform", + rate = 0, + period = NULL +)}\if{html}{\out{
    }} +} + +\subsection{Arguments}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{surrogate}}{(\code{NULL} | \link{SurrogateLearner}).} + +\item{\code{min_lambda}}{(\code{numeric(1)}).} + +\item{\code{max_lambda}}{(\code{numeric(1)}).} + +\item{\code{distribution}}{(\code{character(1)}).} + +\item{\code{rate}}{(\code{numeric(1)}).} + +\item{\code{period}}{(\code{integer(1)}).} + +\item{\code{lambda}}{(\code{numeric(1)}).} +} +\if{html}{\out{
    }} +} +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AcqFunctionStochasticCB-update}{}}} +\subsection{Method \code{update()}}{ +Update the acquisition function. +Samples and decays lambda. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{AcqFunctionStochasticCB$update()}\if{html}{\out{
    }} +} + +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AcqFunctionStochasticCB-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{AcqFunctionStochasticCB$clone(deep = FALSE)}\if{html}{\out{
    }} +} + +\subsection{Arguments}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
    }} +} +} +} diff --git a/man/mlr_acqfunctions_stochastic_ei.Rd b/man/mlr_acqfunctions_stochastic_ei.Rd new file mode 100644 index 00000000..6957410b --- /dev/null +++ b/man/mlr_acqfunctions_stochastic_ei.Rd @@ -0,0 +1,150 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/AcqFunctionStochasticEI.R +\name{mlr_acqfunctions_stochastic_ei} +\alias{mlr_acqfunctions_stochastic_ei} +\alias{AcqFunctionStochasticEI} +\title{Acquisition Function Stochastic Expected Improvement} +\description{ +Expected Improvement with epsilon decay. +} +\section{Dictionary}{ + +This \link{AcqFunction} can be instantiated via the \link[mlr3misc:Dictionary]{dictionary} +\link{mlr_acqfunctions} or with the associated sugar function \code{\link[=acqf]{acqf()}}: + +\if{html}{\out{
    }}\preformatted{mlr_acqfunctions$get("stochastic_ei") +acqf("stochastic_ei") +}\if{html}{\out{
    }} +} + +\section{Parameters}{ + +\itemize{ +\item \code{"epsilon"} (\code{numeric(1)})\cr +\eqn{\epsilon} value used to determine the amount of exploration. +Higher values result in the importance of improvements predicted by the posterior mean +decreasing relative to the importance of potential improvements in regions of high predictive uncertainty. +Defaults to \code{0.1}. +\item \code{"rate"} (\code{numeric(1)})\cr +Defaults to \code{0.05}. +\item \code{"period"} (\code{integer(1)})\cr +Period of the exponential decay. +Defaults to \code{NULL} i.e. the decay has no period. +} +} + +\references{ +\itemize{ +\item Jones, R. D, Schonlau, Matthias, Welch, J. W (1998). +\dQuote{Efficient Global Optimization of Expensive Black-Box Functions.} +\emph{Journal of Global optimization}, \bold{13}(4), 455--492. +} +} +\seealso{ +Other Acquisition Function: +\code{\link{AcqFunction}}, +\code{\link{mlr_acqfunctions}}, +\code{\link{mlr_acqfunctions_aei}}, +\code{\link{mlr_acqfunctions_cb}}, +\code{\link{mlr_acqfunctions_ehvi}}, +\code{\link{mlr_acqfunctions_ehvigh}}, +\code{\link{mlr_acqfunctions_ei}}, +\code{\link{mlr_acqfunctions_eips}}, +\code{\link{mlr_acqfunctions_mean}}, +\code{\link{mlr_acqfunctions_multi}}, +\code{\link{mlr_acqfunctions_pi}}, +\code{\link{mlr_acqfunctions_sd}}, +\code{\link{mlr_acqfunctions_smsego}}, +\code{\link{mlr_acqfunctions_stochastic_cb}} +} +\concept{Acquisition Function} +\section{Super classes}{ +\code{\link[bbotk:Objective]{bbotk::Objective}} -> \code{\link[mlr3mbo:AcqFunction]{mlr3mbo::AcqFunction}} -> \code{AcqFunctionStochasticEI} +} +\section{Public fields}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{y_best}}{(\code{numeric(1)})\cr +Best objective function value observed so far. +In the case of maximization, this already includes the necessary change of sign.} +} +\if{html}{\out{
    }} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-AcqFunctionStochasticEI-new}{\code{AcqFunctionStochasticEI$new()}} +\item \href{#method-AcqFunctionStochasticEI-update}{\code{AcqFunctionStochasticEI$update()}} +\item \href{#method-AcqFunctionStochasticEI-clone}{\code{AcqFunctionStochasticEI$clone()}} +} +} +\if{html}{\out{ +
    Inherited methods + +
    +}} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AcqFunctionStochasticEI-new}{}}} +\subsection{Method \code{new()}}{ +Creates a new instance of this \link[R6:R6Class]{R6} class. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{AcqFunctionStochasticEI$new( + surrogate = NULL, + epsilon = 0.1, + rate = 0.05, + period = NULL +)}\if{html}{\out{
    }} +} + +\subsection{Arguments}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{surrogate}}{(\code{NULL} | \link{SurrogateLearner}).} + +\item{\code{epsilon}}{(\code{numeric(1)}).} + +\item{\code{rate}}{(\code{numeric(1)}).} + +\item{\code{period}}{(\code{integer(1)}).} +} +\if{html}{\out{
    }} +} +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AcqFunctionStochasticEI-update}{}}} +\subsection{Method \code{update()}}{ +Update the acquisition function. +Sets \code{y_best} to the best observed objective function value. +Decays epsilon. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{AcqFunctionStochasticEI$update()}\if{html}{\out{
    }} +} + +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AcqFunctionStochasticEI-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{AcqFunctionStochasticEI$clone(deep = FALSE)}\if{html}{\out{
    }} +} + +\subsection{Arguments}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
    }} +} +} +} diff --git a/man/mlr_optimizers_adbo.Rd b/man/mlr_optimizers_adbo.Rd new file mode 100644 index 00000000..f417cae0 --- /dev/null +++ b/man/mlr_optimizers_adbo.Rd @@ -0,0 +1,93 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/OptimizerAsyncMboADBO.R +\name{mlr_optimizers_adbo} +\alias{mlr_optimizers_adbo} +\alias{OptimizerAsyncMboADBO} +\title{Asynchronous Decentralized Bayesian Optimization} +\description{ +Asynchronous Decentralized Bayesian Optimization (ADBO). +} +\note{ +The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. +A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. +ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. +} +\section{Parameters}{ + +\describe{ +\item{\code{min_lambda}}{\code{numeric(1)}\cr +Minimum value of \eqn{\lambda}. +Defaults to \code{0.01}.} +\item{\code{max_lambda}}{\code{numeric(1)}\cr +Maximum value of \eqn{\lambda}. +Defaults to \code{10}.} +\item{\code{rate}}{\code{numeric(1)}\cr +Rate of the exponential decay.} +\item{\code{period}}{\code{integer(1)}\cr +Period of the exponential decay.} +\item{\code{initial_design_size}}{\code{integer(1)}\cr +Size of the initial design. +Defaults to \code{100}.} + +\item{\code{initial_design}}{\code{data.table::data.table()}\cr +Initial design of the optimization. +If \code{NULL}, a design of size \code{design_size} is generated with \code{design_function}.} +\item{\code{design_size}}{\code{integer(1)}\cr +Size of the initial design.} +\item{\code{design_function}}{\code{character(1)}\cr +Function to generate the initial design. +One of \code{c("random", "sobol", "lhs")}.} +\item{\code{n_workers}}{\code{integer(1)}\cr +Number of parallel workers. +If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush_plan()}} are used.} +} +} + +\section{Super classes}{ +\code{\link[bbotk:Optimizer]{bbotk::Optimizer}} -> \code{\link[bbotk:OptimizerAsync]{bbotk::OptimizerAsync}} -> \code{\link[mlr3mbo:OptimizerAsyncMbo]{mlr3mbo::OptimizerAsyncMbo}} -> \code{OptimizerAsyncMboADBO} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-OptimizerAsyncMboADBO-new}{\code{OptimizerAsyncMboADBO$new()}} +\item \href{#method-OptimizerAsyncMboADBO-clone}{\code{OptimizerAsyncMboADBO$clone()}} +} +} +\if{html}{\out{ +
    Inherited methods + +
    +}} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OptimizerAsyncMboADBO-new}{}}} +\subsection{Method \code{new()}}{ +Creates a new instance of this \link[R6:R6Class]{R6} class. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{OptimizerAsyncMboADBO$new()}\if{html}{\out{
    }} +} + +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OptimizerAsyncMboADBO-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{OptimizerAsyncMboADBO$clone(deep = FALSE)}\if{html}{\out{
    }} +} + +\subsection{Arguments}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
    }} +} +} +} diff --git a/man/mlr_optimizers_async_mbo.Rd b/man/mlr_optimizers_async_mbo.Rd index 7a90fb47..3b655a9e 100644 --- a/man/mlr_optimizers_async_mbo.Rd +++ b/man/mlr_optimizers_async_mbo.Rd @@ -82,16 +82,25 @@ Required packages are determined based on the \code{acq_function}, \code{surroga Creates a new instance of this \link[R6:R6Class]{R6} class. \subsection{Usage}{ \if{html}{\out{
    }}\preformatted{OptimizerAsyncMbo$new( + id = "async_mbo", surrogate = NULL, acq_function = NULL, acq_optimizer = NULL, - param_set = NULL + param_set = NULL, + param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), + properties = c("dependencies", "single-crit"), + packages = c("mlr3mbo", "rush"), + label = "Asynchronous Model Based Optimization", + man = "mlr3mbo::OptimizerAsyncMbo" )}\if{html}{\out{
    }} } \subsection{Arguments}{ \if{html}{\out{
    }} \describe{ +\item{\code{id}}{(\code{character(1)})\cr +Identifier for the new instance.} + \item{\code{surrogate}}{(\link{Surrogate} | \code{NULL})\cr The surrogate.} @@ -103,6 +112,25 @@ The acquisition function optimizer.} \item{\code{param_set}}{\link[paradox:ParamSet]{paradox::ParamSet}\cr Set of control parameters.} + +\item{\code{param_classes}}{(\code{character()})\cr +Supported parameter classes that the optimizer can optimize, as given in the \code{\link[paradox:ParamSet]{paradox::ParamSet}} \verb{$class} field.} + +\item{\code{properties}}{(\code{character()})\cr +Set of properties of the optimizer. +Must be a subset of \code{\link[=bbotk_reflections]{bbotk_reflections$optimizer_properties}}.} + +\item{\code{packages}}{(\code{character()})\cr +Set of required packages. +A warning is signaled by the constructor if at least one of the packages is not installed, but loaded (not attached) later on-demand via \code{\link[=requireNamespace]{requireNamespace()}}.} + +\item{\code{label}}{(\code{character(1)})\cr +Label for this object. +Can be used in tables, plot and text output instead of the ID.} + +\item{\code{man}}{(\code{character(1)})\cr +String in the format \verb{[pkg]::[topic]} pointing to a manual page for this object. +The referenced help package can be opened via method \verb{$help()}.} } \if{html}{\out{
    }} } diff --git a/tests/testthat/Rplots.pdf b/tests/testthat/Rplots.pdf new file mode 100644 index 0000000000000000000000000000000000000000..78ca725403ebc6f7f44f952bfacd0dc52bada4e4 GIT binary patch literal 3611 zcmZ`+c{r479~L1~*`+zeON7E}#@H##SRz@olWDw|)0mlN-pM-lE!!7Llr^H|^GV1S zCE2o!Eu|FEiO3Q{sJ@xbIh}Kz@A}^Bdaw6-?&tTrfA{me|J=8{E!Ip0rHVnwM@>de zMKPm>Jg5j1pazi7{EE=mM}Q_&2nQ3$BrFbw07a}WS`CdssiAdHI%*h7*&iaiiQ1{MP@@vh@(dl5XpzF z0{v4B06GR!AOJMM!8jt>4*+d(eh>{%XBF53e>Jezzsvu#;rBYwir_;7+yPG(1DQ^O zS!JL#Quy*h4@pbe@2R*reb|)-`}9Ad(;K51=)4Gy%|zz%FEciL6>IgeUtz zY)v$n^+y8`LAiNZfsPKN`x2N(5 zz;SUqhWqge7rw|nMJ}Rmr`r-!ll{DTckEf>;39HNP^zko_idPQ=5U6+NFnBkY=}2C z@B?4A4i6nmY2o-@aH-x^7R6zkoie?=G3tR7@6tAK&XupV>B_#3vF=l|?2~nkMmuHF zmX+PZ)^Z>LofUMfKC+wEHwi7h8&jA{)e&w^N%uyIPw(2OM>ikB$H2$q4cp1@Dv@C) zH8~VB##G_zXpKw8~U4$Am@K>7_x?rH3ifEzs82b=m`(N+sk320Uvj_};Rma%?5 z%PTfw0g08~XkBv-&)Lgyw-l-w-Ao7|h;ew%?XYe9n$YVFawOerrt>q-Ni~Fd**}?7 z_B(QCY|O)IN5NSoaAt9{fIq_IbQsgV`Ss#&xS*9x?F<-Y;-y9M4JpMw>Fk@NeA)B9 zAYntA_ZwYmpU}2kb0T=j6x&^T0yvRX0e~*Za3%p(wjVxlbyH4jQ+-Ot~>FqbDvy98r~d>t`oNF z`*?NRjS~ZKm>P1;8cNJ?i9FhQ|B+A~hrk3Er;%df&U4H)=!HU<7){E4I0`?3(dJc+>h|6_w!>bHKR;4w3DILH7#r6qC}y7k116o4_u^bG zO1$IZHW9LwytGT+#MpvU%y6#I{-)&Z_}L=g8TT1Q_?HHby(YDVcK*VfVj;W-)%Z$V z-0lhyxvRWww0EV&innwvp|zz|qiY{srf`J`F2vfjUYQVbK^%)J6N(fR(!HLnCbaLd znQ?lM9G{TPor}UHoCL#{7o|$1>ys?f%R1dfc}z`FwkIy_ekv^%H+em)$f-!P__iId zZQjkJ!>5K7e?@#2vNk(=y?3B*h67gCMdl@|nmHF<>pw74H6ygDy(z{MQ-n`JrR|S6 zA$drwin9t?B{7ty6NhY1!If6J{9AWKaxQ6a8};rPBI-UOR4`OHlv^*25p!@7rrg&) z?V*1%@yL@?6K)g*iZex|4*5X%jo@O6K-EKDGL*Riw$8AXv4zW>Bp7$3%| z6lL_AX#X)(6ZKN=etW`=YO;S!wUSs;Ql4eCWxi$V({o}<2Z|&HxMn`A zqBn7y^i91}DyM8tHP0S;rupp1?TTmbZwBA|^vrVj8N)-vwYLIqjSm?T zvI((g-~IY=K>N-om_Hz`TXG>cLnmQ+Y$R5r7jqEf?7uK1*`GTyII=wSbny0|Zt-Ww zYyX)q%DdfUvK6~l!hfp6+PtvJWV)meACG9wOz*qtvx+K(HRxaI@(rIGVj9##+C%I@ z;zH&_)>rv9mA}e;J+Rrd>A<7QBg4bZQ_fSn_fx!bt7xld{MY!3)RfdSDVqD~NCUK! zI=@<<7D=P|tg+@fEmz+`Z)&e7{wdm8{dGXd=pM}~Ei2R=lmt4V_dpN#`^Me`mHvGv z_ZcViBuktS9?HyVB|>R#gDNp9yHy&M3tW*dZSDb8v$dQ40pvW=Ykx*Z{238{2mc}b z%AN8YmFM@{*Is%odyLoLnLf{Cko-s;wXg7jL;1f-pLy8Q{QO%?OmR$SQ7>+A;1Oz| za^w?FaytSa;F}j{tQV|PeqQ(e>d0t|>&LJcU&O^rt*%-ncNYh@uWYRRGM!pKTTk_v z^SnXYQ;V%_BH0Ie2Bw9^Y;g%N1oF)@J7UXj`Q4s2V)}WFc*$1qS3D>Bty!)qk)OO` zHa-dMPnZ<>&0(H4?#$F0t$h~wl=+4nH?9;>9dYry=68u5Zac;z-$&ju?09rJieXez z$Zhn@NFcg5+O)+$hGOB}xpuK}W$*Ov>A<+?{>c9MqV>lqtyysl=`=op-4%Qh`*9+6 zf&&jD8@#)sv$!i{n`FCX7qL0m5aGQLtyt(ke1=IzV3yV5MxjcQV zgON9Ctjg+$3#tnu`MhLaa>n9QaN0(${?KIw1IzQ)Z}RnXCiCsnmmMGNbKIR@wpliE zqxs}+>sJM9N(P6T47jeOXPlXRcd^NIn{phlFx56@G<)K%UTZ`v_d!hT?``+-4 z>8G2g`fuO36XHBqq3UMP+)nO|s)?;3oQ+VdR#gVW>Q#L`ZPJ|vu3oSF96slsES6;2 zrlna&y!k5erQ!?iI>JKfZEI<(AbD}Ca;qbj+M7SX%e zP=CDmR`rM0@VDU~q7u0ny=lF@DpysYa>|_&`m*=qmhSPOh@sc*#5?4&+AC6=333Ti z5!W{6Kerksg#PPpS~bIO!!v-lrnp38*5i%Nv&)0>^J#NZM}G;buI;!{HWk{vxGp_p z;6(HIEohYQ@rCOm1t;GZ7u*gova!B^%ThZqXrF|UoF?c*bvIc06fj(qpn z>UlRB_%LW2ttO_Y@y%m(d#K{t>6;H>qZ7U37GU!9Q^zFt=z*hSDLROSM{H)aQl zsMjx6E*?3S9X|7A|LUiOvJ{(waIW>(l`WP5|HCc*Va2t6+EBzFR@{`t@|6S<%ZsyZ z>t8Ep0?CL(Bm4;zE<034o5CIR3XD#R{ib>i@Nhy)`* z9|CI>jX(oHRT3HYfqVgw`a>k1W%r3h+~072I>`@5r3VmkbQl21eq<7KmNgCcgPF#5 z?w)_kB0*Hh7oi4dBh-FAfTo6qx(472{KU|#{KS3$(oYPf$qFU^z|bgGviT>bt@A&a zx(+Lv{G$$o{vSSd6iWNAI+%(h5Fsin!U3QIAp`=n0q{7 Date: Tue, 29 Oct 2024 19:14:57 +0100 Subject: [PATCH 37/49] ... --- tests/testthat/Rplots.pdf | Bin 3611 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/testthat/Rplots.pdf diff --git a/tests/testthat/Rplots.pdf b/tests/testthat/Rplots.pdf deleted file mode 100644 index 78ca725403ebc6f7f44f952bfacd0dc52bada4e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3611 zcmZ`+c{r479~L1~*`+zeON7E}#@H##SRz@olWDw|)0mlN-pM-lE!!7Llr^H|^GV1S zCE2o!Eu|FEiO3Q{sJ@xbIh}Kz@A}^Bdaw6-?&tTrfA{me|J=8{E!Ip0rHVnwM@>de zMKPm>Jg5j1pazi7{EE=mM}Q_&2nQ3$BrFbw07a}WS`CdssiAdHI%*h7*&iaiiQ1{MP@@vh@(dl5XpzF z0{v4B06GR!AOJMM!8jt>4*+d(eh>{%XBF53e>Jezzsvu#;rBYwir_;7+yPG(1DQ^O zS!JL#Quy*h4@pbe@2R*reb|)-`}9Ad(;K51=)4Gy%|zz%FEciL6>IgeUtz zY)v$n^+y8`LAiNZfsPKN`x2N(5 zz;SUqhWqge7rw|nMJ}Rmr`r-!ll{DTckEf>;39HNP^zko_idPQ=5U6+NFnBkY=}2C z@B?4A4i6nmY2o-@aH-x^7R6zkoie?=G3tR7@6tAK&XupV>B_#3vF=l|?2~nkMmuHF zmX+PZ)^Z>LofUMfKC+wEHwi7h8&jA{)e&w^N%uyIPw(2OM>ikB$H2$q4cp1@Dv@C) zH8~VB##G_zXpKw8~U4$Am@K>7_x?rH3ifEzs82b=m`(N+sk320Uvj_};Rma%?5 z%PTfw0g08~XkBv-&)Lgyw-l-w-Ao7|h;ew%?XYe9n$YVFawOerrt>q-Ni~Fd**}?7 z_B(QCY|O)IN5NSoaAt9{fIq_IbQsgV`Ss#&xS*9x?F<-Y;-y9M4JpMw>Fk@NeA)B9 zAYntA_ZwYmpU}2kb0T=j6x&^T0yvRX0e~*Za3%p(wjVxlbyH4jQ+-Ot~>FqbDvy98r~d>t`oNF z`*?NRjS~ZKm>P1;8cNJ?i9FhQ|B+A~hrk3Er;%df&U4H)=!HU<7){E4I0`?3(dJc+>h|6_w!>bHKR;4w3DILH7#r6qC}y7k116o4_u^bG zO1$IZHW9LwytGT+#MpvU%y6#I{-)&Z_}L=g8TT1Q_?HHby(YDVcK*VfVj;W-)%Z$V z-0lhyxvRWww0EV&innwvp|zz|qiY{srf`J`F2vfjUYQVbK^%)J6N(fR(!HLnCbaLd znQ?lM9G{TPor}UHoCL#{7o|$1>ys?f%R1dfc}z`FwkIy_ekv^%H+em)$f-!P__iId zZQjkJ!>5K7e?@#2vNk(=y?3B*h67gCMdl@|nmHF<>pw74H6ygDy(z{MQ-n`JrR|S6 zA$drwin9t?B{7ty6NhY1!If6J{9AWKaxQ6a8};rPBI-UOR4`OHlv^*25p!@7rrg&) z?V*1%@yL@?6K)g*iZex|4*5X%jo@O6K-EKDGL*Riw$8AXv4zW>Bp7$3%| z6lL_AX#X)(6ZKN=etW`=YO;S!wUSs;Ql4eCWxi$V({o}<2Z|&HxMn`A zqBn7y^i91}DyM8tHP0S;rupp1?TTmbZwBA|^vrVj8N)-vwYLIqjSm?T zvI((g-~IY=K>N-om_Hz`TXG>cLnmQ+Y$R5r7jqEf?7uK1*`GTyII=wSbny0|Zt-Ww zYyX)q%DdfUvK6~l!hfp6+PtvJWV)meACG9wOz*qtvx+K(HRxaI@(rIGVj9##+C%I@ z;zH&_)>rv9mA}e;J+Rrd>A<7QBg4bZQ_fSn_fx!bt7xld{MY!3)RfdSDVqD~NCUK! zI=@<<7D=P|tg+@fEmz+`Z)&e7{wdm8{dGXd=pM}~Ei2R=lmt4V_dpN#`^Me`mHvGv z_ZcViBuktS9?HyVB|>R#gDNp9yHy&M3tW*dZSDb8v$dQ40pvW=Ykx*Z{238{2mc}b z%AN8YmFM@{*Is%odyLoLnLf{Cko-s;wXg7jL;1f-pLy8Q{QO%?OmR$SQ7>+A;1Oz| za^w?FaytSa;F}j{tQV|PeqQ(e>d0t|>&LJcU&O^rt*%-ncNYh@uWYRRGM!pKTTk_v z^SnXYQ;V%_BH0Ie2Bw9^Y;g%N1oF)@J7UXj`Q4s2V)}WFc*$1qS3D>Bty!)qk)OO` zHa-dMPnZ<>&0(H4?#$F0t$h~wl=+4nH?9;>9dYry=68u5Zac;z-$&ju?09rJieXez z$Zhn@NFcg5+O)+$hGOB}xpuK}W$*Ov>A<+?{>c9MqV>lqtyysl=`=op-4%Qh`*9+6 zf&&jD8@#)sv$!i{n`FCX7qL0m5aGQLtyt(ke1=IzV3yV5MxjcQV zgON9Ctjg+$3#tnu`MhLaa>n9QaN0(${?KIw1IzQ)Z}RnXCiCsnmmMGNbKIR@wpliE zqxs}+>sJM9N(P6T47jeOXPlXRcd^NIn{phlFx56@G<)K%UTZ`v_d!hT?``+-4 z>8G2g`fuO36XHBqq3UMP+)nO|s)?;3oQ+VdR#gVW>Q#L`ZPJ|vu3oSF96slsES6;2 zrlna&y!k5erQ!?iI>JKfZEI<(AbD}Ca;qbj+M7SX%e zP=CDmR`rM0@VDU~q7u0ny=lF@DpysYa>|_&`m*=qmhSPOh@sc*#5?4&+AC6=333Ti z5!W{6Kerksg#PPpS~bIO!!v-lrnp38*5i%Nv&)0>^J#NZM}G;buI;!{HWk{vxGp_p z;6(HIEohYQ@rCOm1t;GZ7u*gova!B^%ThZqXrF|UoF?c*bvIc06fj(qpn z>UlRB_%LW2ttO_Y@y%m(d#K{t>6;H>qZ7U37GU!9Q^zFt=z*hSDLROSM{H)aQl zsMjx6E*?3S9X|7A|LUiOvJ{(waIW>(l`WP5|HCc*Va2t6+EBzFR@{`t@|6S<%ZsyZ z>t8Ep0?CL(Bm4;zE<034o5CIR3XD#R{ib>i@Nhy)`* z9|CI>jX(oHRT3HYfqVgw`a>k1W%r3h+~072I>`@5r3VmkbQl21eq<7KmNgCcgPF#5 z?w)_kB0*Hh7oi4dBh-FAfTo6qx(472{KU|#{KS3$(oYPf$qFU^z|bgGviT>bt@A&a zx(+Lv{G$$o{vSSd6iWNAI+%(h5Fsin!U3QIAp`=n0q{7 Date: Tue, 29 Oct 2024 19:36:03 +0100 Subject: [PATCH 38/49] ... --- R/AcqFunctionStochasticCB.R | 36 +++++++++---- R/OptimizerAsyncMboADBO.R | 21 +++----- man/mlr_acqfunctions_stochastic_cb.Rd | 14 +++-- man/mlr_optimizers_adbo.Rd | 16 +++--- tests/testthat/test_AcqFunctionStochasticCB.R | 53 +++++++++++++++++++ 5 files changed, 106 insertions(+), 34 deletions(-) diff --git a/R/AcqFunctionStochasticCB.R b/R/AcqFunctionStochasticCB.R index 7de4b184..ebb1884c 100644 --- a/R/AcqFunctionStochasticCB.R +++ b/R/AcqFunctionStochasticCB.R @@ -8,15 +8,18 @@ #' #' @description #' Lower / Upper Confidence Bound with lambda sampling and decay. -#' The initial lambda value is drawn from an uniform or exponential distribution with rate `1 / lambda`. +#' The initial lambda value is drawn from an uniform distribution between `min_lambda` and `max_lambda` or from an exponential distribution with rate `1 / lambda`. #' The lambda value is updated after each evaluation by the formula `lambda * exp(-rate * (t %% period))`. #' #' @section Parameters: +#' * `"lambda"` (`numeric(1)`)\cr +#' Lambda value for sampling from the exponential distribution. +#' Defaults to `1.96`. #' * `"min_lambda"` (`numeric(1)`)\cr -#' Minimum value of lambda. +#' Minimum value of lambda for sampling from the uniform distribution. #' Defaults to `0.01`. #' * `"max_lambda"` (`numeric(1)`)\cr -#' Maximum value of lambda. +#' Maximum value of lambda for sampling from the uniform distribution. #' Defaults to `10`. #' * `"distribution"` (`character(1)`)\cr #' Distribution to sample lambda from. @@ -52,6 +55,7 @@ AcqFunctionStochasticCB = R6Class("AcqFunctionStochasticCB", #' @param period (`integer(1)`). initialize = function( surrogate = NULL, + lambda = 1.96, min_lambda = 0.01, max_lambda = 10, distribution = "uniform", @@ -59,9 +63,19 @@ AcqFunctionStochasticCB = R6Class("AcqFunctionStochasticCB", period = NULL ) { assert_r6(surrogate, "SurrogateLearner", null.ok = TRUE) - private$.min_lambda = assert_number(min_lambda, lower = .Machine$double.neg.eps) - private$.max_lambda = assert_number(max_lambda, lower = .Machine$double.neg.eps) + private$.lambda = assert_number(lambda, lower = .Machine$double.neg.eps, null.ok = TRUE) + private$.min_lambda = assert_number(min_lambda, lower = .Machine$double.neg.eps, null.ok = TRUE) + private$.max_lambda = assert_number(max_lambda, lower = .Machine$double.neg.eps, null.ok = TRUE) private$.distribution = assert_choice(distribution, choices = c("uniform", "exponential")) + + if (private$.distribution == "uniform" && (is.null(private$.min_lambda) || is.null(private$.max_lambda))) { + stop("If `distribution` is 'uniform', `min_lambda` and `max_lambda` must be set.") + } + + if (private$.distribution == "exponential" && is.null(private$.lambda)) { + stop("If `distribution` is 'exponential', `lambda` must be set.") + } + private$.rate = assert_number(rate, lower = 0) private$.period = assert_int(period, lower = 1, null.ok = TRUE) @@ -84,12 +98,13 @@ AcqFunctionStochasticCB = R6Class("AcqFunctionStochasticCB", update = function() { # sample lambda if (is.null(self$constants$values$lambda)) { - fun = switch(private$.distribution, - uniform = runif, - exponential = rexp - ) - lambda = fun(1, private$.min_lambda, private$.max_lambda) + if (private$.distribution == "uniform") { + lambda = runif(1, private$.min_lambda, private$.max_lambda) + } else { + lambda = rexp(1, 1 / private$.lambda) + } + private$.lambda_0 = lambda self$constants$values$lambda = lambda } @@ -108,6 +123,7 @@ AcqFunctionStochasticCB = R6Class("AcqFunctionStochasticCB", ), private = list( + .lambda = NULL, .min_lambda = NULL, .max_lambda = NULL, .distribution = NULL, diff --git a/R/OptimizerAsyncMboADBO.R b/R/OptimizerAsyncMboADBO.R index ec1c326c..63da9aa1 100644 --- a/R/OptimizerAsyncMboADBO.R +++ b/R/OptimizerAsyncMboADBO.R @@ -11,12 +11,8 @@ #' #' @section Parameters: #' \describe{ -#' \item{`min_lambda`}{`numeric(1)`\cr -#' Minimum value of \eqn{\lambda}. -#' Defaults to `0.01`.} -#' \item{`max_lambda`}{`numeric(1)`\cr -#' Maximum value of \eqn{\lambda}. -#' Defaults to `10`.} +#' \item{`lambda`}{`numeric(1)`\cr +#' Lambda value for sampling from the exponential distribution.} #' \item{`rate`}{`numeric(1)`\cr #' Rate of the exponential decay.} #' \item{`period`}{`integer(1)`\cr @@ -38,6 +34,9 @@ #' If `NULL`, all rush workers set with [rush::rush_plan()] are used.} #' } #' +#' @references +#' * `r format_bib("egele_2023")` +#' #' @export OptimizerAsyncMboADBO = R6Class("OptimizerAsyncMboADBO", inherit = OptimizerAsyncMbo, @@ -48,9 +47,7 @@ OptimizerAsyncMboADBO = R6Class("OptimizerAsyncMboADBO", #' Creates a new instance of this [R6][R6::R6Class] class. initialize = function() { param_set = ps( - min_lambda = p_dbl(lower = 0, default = 0.01), - max_lambda = p_dbl(lower = 0, default = 10), - exponential_decay = p_lgl(default = TRUE), + lambda = p_dbl(lower = 0, default = 1.96), rate = p_dbl(lower = 0, default = 0.1), period = p_int(lower = 1L, default = 25L) ) @@ -62,8 +59,7 @@ OptimizerAsyncMboADBO = R6Class("OptimizerAsyncMboADBO", man = "mlr3mbo::OptimizerAsyncMboADBO") self$param_set$set_values( - min_lambda = 0.01, - max_lambda = 10, + lambda = 1.96, rate = 0.1, period = 25L) } @@ -74,8 +70,7 @@ OptimizerAsyncMboADBO = R6Class("OptimizerAsyncMboADBO", .optimize = function(inst) { self$acq_function = acqf("stochastic_cb", - min_lambda = self$param_set$values$min_lambda, - max_lambda = self$param_set$values$max_lambda, + lambda = self$param_set$values$lambda, rate = self$param_set$values$rate, period = self$param_set$values$period ) diff --git a/man/mlr_acqfunctions_stochastic_cb.Rd b/man/mlr_acqfunctions_stochastic_cb.Rd index e8588737..6ab76688 100644 --- a/man/mlr_acqfunctions_stochastic_cb.Rd +++ b/man/mlr_acqfunctions_stochastic_cb.Rd @@ -6,7 +6,7 @@ \title{Acquisition Function Stochastic Confidence Bound} \description{ Lower / Upper Confidence Bound with lambda sampling and decay. -The initial lambda value is drawn from an uniform or exponential distribution with rate \code{1 / lambda}. +The initial lambda value is drawn from an uniform distribution between \code{min_lambda} and \code{max_lambda} or from an exponential distribution with rate \code{1 / lambda}. The lambda value is updated after each evaluation by the formula \code{lambda * exp(-rate * (t \%\% period))}. } \section{Dictionary}{ @@ -22,11 +22,14 @@ acqf("stochastic_cb") \section{Parameters}{ \itemize{ +\item \code{"lambda"} (\code{numeric(1)})\cr +Lambda value for sampling from the exponential distribution. +Defaults to \code{1.96}. \item \code{"min_lambda"} (\code{numeric(1)})\cr -Minimum value of lambda. +Minimum value of lambda for sampling from the uniform distribution. Defaults to \code{0.01}. \item \code{"max_lambda"} (\code{numeric(1)})\cr -Maximum value of lambda. +Maximum value of lambda for sampling from the uniform distribution. Defaults to \code{10}. \item \code{"distribution"} (\code{character(1)})\cr Distribution to sample lambda from. @@ -101,6 +104,7 @@ Creates a new instance of this \link[R6:R6Class]{R6} class. \subsection{Usage}{ \if{html}{\out{
    }}\preformatted{AcqFunctionStochasticCB$new( surrogate = NULL, + lambda = 1.96, min_lambda = 0.01, max_lambda = 10, distribution = "uniform", @@ -114,6 +118,8 @@ Creates a new instance of this \link[R6:R6Class]{R6} class. \describe{ \item{\code{surrogate}}{(\code{NULL} | \link{SurrogateLearner}).} +\item{\code{lambda}}{(\code{numeric(1)}).} + \item{\code{min_lambda}}{(\code{numeric(1)}).} \item{\code{max_lambda}}{(\code{numeric(1)}).} @@ -123,8 +129,6 @@ Creates a new instance of this \link[R6:R6Class]{R6} class. \item{\code{rate}}{(\code{numeric(1)}).} \item{\code{period}}{(\code{integer(1)}).} - -\item{\code{lambda}}{(\code{numeric(1)}).} } \if{html}{\out{
    }} } diff --git a/man/mlr_optimizers_adbo.Rd b/man/mlr_optimizers_adbo.Rd index f417cae0..617ad6ba 100644 --- a/man/mlr_optimizers_adbo.Rd +++ b/man/mlr_optimizers_adbo.Rd @@ -15,12 +15,8 @@ ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to \section{Parameters}{ \describe{ -\item{\code{min_lambda}}{\code{numeric(1)}\cr -Minimum value of \eqn{\lambda}. -Defaults to \code{0.01}.} -\item{\code{max_lambda}}{\code{numeric(1)}\cr -Maximum value of \eqn{\lambda}. -Defaults to \code{10}.} +\item{\code{lambda}}{\code{numeric(1)}\cr +Lambda value for sampling from the exponential distribution.} \item{\code{rate}}{\code{numeric(1)}\cr Rate of the exponential decay.} \item{\code{period}}{\code{integer(1)}\cr @@ -43,6 +39,14 @@ If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush } } +\references{ +\itemize{ +\item Egele R, Guyon I, Vishwanath V, Balaprakash P (2023). +\dQuote{Asynchronous Decentralized Bayesian Optimization for Large Scale Hyperparameter Optimization.} +\emph{arXiv}. +\url{https://arxiv.org/abs/2207.00479}. +} +} \section{Super classes}{ \code{\link[bbotk:Optimizer]{bbotk::Optimizer}} -> \code{\link[bbotk:OptimizerAsync]{bbotk::OptimizerAsync}} -> \code{\link[mlr3mbo:OptimizerAsyncMbo]{mlr3mbo::OptimizerAsyncMbo}} -> \code{OptimizerAsyncMboADBO} } diff --git a/tests/testthat/test_AcqFunctionStochasticCB.R b/tests/testthat/test_AcqFunctionStochasticCB.R index 5760bd16..bc72d3b2 100644 --- a/tests/testthat/test_AcqFunctionStochasticCB.R +++ b/tests/testthat/test_AcqFunctionStochasticCB.R @@ -17,15 +17,68 @@ test_that("AcqFunctionStochasticCB works in defaults", { design_size = 5, acq_function = acq_function) + expect_data_table(optimizer$optimize(instance), nrows = 1) + expect_data_table(instance$archive$data, min.rows = 10) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "lambda_0", "lambda")) + + expect_rush_reset(instance$rush) +}) + +test("AcqFunctionStochasticCB works with uniform sampling", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 2) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 10), + ) + + acq_function = acqf("stochastic_cb", distribution = "uniform", min_lambda = 1, max_lambda = 3) + + optimizer = opt("async_mbo", + design_function = "sobol", + design_size = 5, + acq_function = acq_function) + + expect_data_table(optimizer$optimize(instance), nrows = 1) + expect_data_table(instance$archive$data, min.rows = 10) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "lambda_0", "lambda")) + expect_numeric(instance$archive$data$lambda, lower = 1, upper = 3) + + expect_rush_reset(instance$rush) +}) + +test_that("AcqFunctionStochasticCB works with exponential sampling", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 2) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 10), + ) + + acq_function = acqf("stochastic_cb", distribution = "exponential", lambda = 1.96) + optimizer = opt("async_mbo", + design_function = "sobol", + design_size = 5, + acq_function = acq_function) expect_data_table(optimizer$optimize(instance), nrows = 1) expect_data_table(instance$archive$data, min.rows = 10) expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "lambda_0", "lambda")) + expect_numeric(unique(instance$archive$data$lambda), len = 3) expect_rush_reset(instance$rush) }) + test_that("AcqFunctionStochasticCB works with lambda decay", { skip_on_cran() skip_if_not_installed("rush") From 80b4c0eb052c43996c64d64f28b9b8a1c3182be9 Mon Sep 17 00:00:00 2001 From: be-marc Date: Wed, 30 Oct 2024 08:07:49 +0100 Subject: [PATCH 39/49] ... --- tests/testthat/test_AcqFunctionStochasticCB.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test_AcqFunctionStochasticCB.R b/tests/testthat/test_AcqFunctionStochasticCB.R index bc72d3b2..188560f4 100644 --- a/tests/testthat/test_AcqFunctionStochasticCB.R +++ b/tests/testthat/test_AcqFunctionStochasticCB.R @@ -24,7 +24,7 @@ test_that("AcqFunctionStochasticCB works in defaults", { expect_rush_reset(instance$rush) }) -test("AcqFunctionStochasticCB works with uniform sampling", { +test_that("AcqFunctionStochasticCB works with uniform sampling", { skip_on_cran() skip_if_not_installed("rush") flush_redis() From fb360efc377d21bc52c101e496545fb45242616d Mon Sep 17 00:00:00 2001 From: be-marc Date: Wed, 30 Oct 2024 10:45:13 +0100 Subject: [PATCH 40/49] ... --- R/OptimizerAsyncMbo.R | 8 +++++++- R/OptimizerAsyncMboADBO.R | 11 +++++++---- man/mlr_optimizers_adbo.Rd | 11 +++++++---- man/mlr_optimizers_async_mbo.Rd | 8 +++++++- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/R/OptimizerAsyncMbo.R b/R/OptimizerAsyncMbo.R index dba1b28a..0adc990b 100644 --- a/R/OptimizerAsyncMbo.R +++ b/R/OptimizerAsyncMbo.R @@ -3,7 +3,13 @@ #' @name mlr_optimizers_async_mbo #' #' @description -#' `OptimizerAsyncMbo` class that implements asynchronous Model Based Optimization (MBO). +#' `OptimizerAsyncMbo` class that implements Asynchronous Model Based Optimization (AMBO). +#' AMBO starts multiple sequential MBO runs on different workers. +#' The worker communicate asynchronously through a shared archive. +#' The optimizer follows a modular layout in which the surrogate model, acquisition function, and acquisition optimizer can be changed. +#' The [SurrogateAsyncLearner] class is similar to [SurrogateLearner] but can impute results for ongoing evaluations. +#' The `AcqFunctionStochastic*` classes create varying acquisition functions on each worker, promoting different exploration-exploitation balances. +#' The [AcqOptimizer] classes remain consistent with those used in synchronous MBO. #' #' @section Parameters: #' \describe{ diff --git a/R/OptimizerAsyncMboADBO.R b/R/OptimizerAsyncMboADBO.R index 63da9aa1..3ab58d09 100644 --- a/R/OptimizerAsyncMboADBO.R +++ b/R/OptimizerAsyncMboADBO.R @@ -2,12 +2,15 @@ #' @name mlr_optimizers_adbo #' #' @description -#' Asynchronous Decentralized Bayesian Optimization (ADBO). +#' `OptimizerAsyncMboADBO` class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). +#' ADBO is a variant of Asynchronous Model Based Optimization (AMBO) that uses [AcqFunctionStochasticCB] with exponential lambda decay. #' #' @note -#' The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. -#' A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. -#' ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. +#' The lambda parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. +#' A large lambda value leads to more exploration, while a small lambda value leads to more exploitation. +#' The initial lambda value is drawn from an exponential distribution with rate `1 / lambda`. +#' ADBO can use periodic exponential decay to reduce lambda periodically with the formula `lambda * exp(-rate * (t %% period))`. +#' The surrogate model is always a random forest and die acquisition optimizer is random search with a budget of 10,000 evaluations. #' #' @section Parameters: #' \describe{ diff --git a/man/mlr_optimizers_adbo.Rd b/man/mlr_optimizers_adbo.Rd index 617ad6ba..cc75bfb0 100644 --- a/man/mlr_optimizers_adbo.Rd +++ b/man/mlr_optimizers_adbo.Rd @@ -5,12 +5,15 @@ \alias{OptimizerAsyncMboADBO} \title{Asynchronous Decentralized Bayesian Optimization} \description{ -Asynchronous Decentralized Bayesian Optimization (ADBO). +\code{OptimizerAsyncMboADBO} class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). +ADBO is a variant of Asynchronous Model Based Optimization (AMBO) that uses \link{AcqFunctionStochasticCB} with exponential lambda decay. } \note{ -The \eqn{\lambda} parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. -A large \eqn{\lambda} value leads to more exploration, while a small \eqn{\lambda} value leads to more exploitation. -ADBO can use periodic exponential decay to reduce \eqn{\lambda} periodically to the exploitation phase. +The lambda parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. +A large lambda value leads to more exploration, while a small lambda value leads to more exploitation. +The initial lambda value is drawn from an exponential distribution with rate \code{1 / lambda}. +ADBO can use periodic exponential decay to reduce lambda periodically with the formula \code{lambda * exp(-rate * (t \%\% period))}. +The surrogate model is always a random forest and die acquisition optimizer is random search with a budget of 10,000 evaluations. } \section{Parameters}{ diff --git a/man/mlr_optimizers_async_mbo.Rd b/man/mlr_optimizers_async_mbo.Rd index 3b655a9e..d73007a9 100644 --- a/man/mlr_optimizers_async_mbo.Rd +++ b/man/mlr_optimizers_async_mbo.Rd @@ -5,7 +5,13 @@ \alias{OptimizerAsyncMbo} \title{Asynchronous Model Based Optimization} \description{ -\code{OptimizerAsyncMbo} class that implements asynchronous Model Based Optimization (MBO). +\code{OptimizerAsyncMbo} class that implements Asynchronous Model Based Optimization (AMBO). +AMBO starts multiple sequential MBO runs on different workers. +The worker communicate asynchronously through a shared archive. +The optimizer follows a modular layout in which the surrogate model, acquisition function, and acquisition optimizer can be changed. +The \link{SurrogateAsyncLearner} class is similar to \link{SurrogateLearner} but can impute results for ongoing evaluations. +The \verb{AcqFunctionStochastic*} classes create varying acquisition functions on each worker, promoting different exploration-exploitation balances. +The \link{AcqOptimizer} classes remain consistent with those used in synchronous MBO. } \section{Parameters}{ From febd804511454e1f4670bd381efacffb8d0f13ac Mon Sep 17 00:00:00 2001 From: be-marc Date: Wed, 30 Oct 2024 10:54:11 +0100 Subject: [PATCH 41/49] ... --- R/OptimizerAsyncMbo.R | 2 +- man/mlr_optimizers_async_mbo.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/OptimizerAsyncMbo.R b/R/OptimizerAsyncMbo.R index 0adc990b..ba773a54 100644 --- a/R/OptimizerAsyncMbo.R +++ b/R/OptimizerAsyncMbo.R @@ -7,7 +7,7 @@ #' AMBO starts multiple sequential MBO runs on different workers. #' The worker communicate asynchronously through a shared archive. #' The optimizer follows a modular layout in which the surrogate model, acquisition function, and acquisition optimizer can be changed. -#' The [SurrogateAsyncLearner] class is similar to [SurrogateLearner] but can impute results for ongoing evaluations. +#' The [SurrogateLearnerAsync] class is similar to [SurrogateLearner] but can impute results for ongoing evaluations. #' The `AcqFunctionStochastic*` classes create varying acquisition functions on each worker, promoting different exploration-exploitation balances. #' The [AcqOptimizer] classes remain consistent with those used in synchronous MBO. #' diff --git a/man/mlr_optimizers_async_mbo.Rd b/man/mlr_optimizers_async_mbo.Rd index d73007a9..3386eaaf 100644 --- a/man/mlr_optimizers_async_mbo.Rd +++ b/man/mlr_optimizers_async_mbo.Rd @@ -9,7 +9,7 @@ AMBO starts multiple sequential MBO runs on different workers. The worker communicate asynchronously through a shared archive. The optimizer follows a modular layout in which the surrogate model, acquisition function, and acquisition optimizer can be changed. -The \link{SurrogateAsyncLearner} class is similar to \link{SurrogateLearner} but can impute results for ongoing evaluations. +The \link{SurrogateLearnerAsync} class is similar to \link{SurrogateLearner} but can impute results for ongoing evaluations. The \verb{AcqFunctionStochastic*} classes create varying acquisition functions on each worker, promoting different exploration-exploitation balances. The \link{AcqOptimizer} classes remain consistent with those used in synchronous MBO. } From 5eada459696ddc8631ceec7a60bf811c1a9d2cc0 Mon Sep 17 00:00:00 2001 From: be-marc Date: Thu, 31 Oct 2024 13:41:02 +0100 Subject: [PATCH 42/49] tuner --- DESCRIPTION | 1 + NAMESPACE | 1 + R/TunerAsyncMboADBO.R | 63 +++++++++++++++ man/mlr_tuners_adbo.Rd | 100 ++++++++++++++++++++++++ tests/testthat/test_TunerAsyncMboADBO.R | 29 +++++++ 5 files changed, 194 insertions(+) create mode 100644 R/TunerAsyncMboADBO.R create mode 100644 man/mlr_tuners_adbo.Rd create mode 100644 tests/testthat/test_TunerAsyncMboADBO.R diff --git a/DESCRIPTION b/DESCRIPTION index efbe5a81..767fe415 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -103,6 +103,7 @@ Collate: 'SurrogateLearnerAsync.R' 'SurrogateLearnerCollection.R' 'TunerAsyncMbo.R' + 'TunerAsyncMboADBO.R' 'TunerMbo.R' 'mlr_loop_functions.R' 'bayesopt_ego.R' diff --git a/NAMESPACE b/NAMESPACE index d79f3751..10f868b4 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -30,6 +30,7 @@ export(SurrogateLearner) export(SurrogateLearnerAsync) export(SurrogateLearnerCollection) export(TunerAsyncMbo) +export(TunerAsyncMboADBO) export(TunerMbo) export(acqf) export(acqfs) diff --git a/R/TunerAsyncMboADBO.R b/R/TunerAsyncMboADBO.R new file mode 100644 index 00000000..4ca2ec42 --- /dev/null +++ b/R/TunerAsyncMboADBO.R @@ -0,0 +1,63 @@ +#' @title Asynchronous Decentralized Bayesian Optimization +#' @name mlr_tuners_adbo +#' +#' @description +#' `TunerAsyncMboADBO` class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). +#' ADBO is a variant of Asynchronous Model Based Optimization (AMBO) that uses [AcqFunctionStochasticCB] with exponential lambda decay. +#' +#' @note +#' The lambda parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. +#' A large lambda value leads to more exploration, while a small lambda value leads to more exploitation. +#' The initial lambda value is drawn from an exponential distribution with rate `1 / lambda`. +#' ADBO can use periodic exponential decay to reduce lambda periodically with the formula `lambda * exp(-rate * (t %% period))`. +#' The surrogate model is always a random forest and die acquisition optimizer is random search with a budget of 10,000 evaluations. +#' +#' @section Parameters: +#' \describe{ +#' \item{`lambda`}{`numeric(1)`\cr +#' Lambda value for sampling from the exponential distribution.} +#' \item{`rate`}{`numeric(1)`\cr +#' Rate of the exponential decay.} +#' \item{`period`}{`integer(1)`\cr +#' Period of the exponential decay.} +#' \item{`initial_design_size`}{`integer(1)`\cr +#' Size of the initial design. +#' Defaults to `100`.} +#' +#' \item{`initial_design`}{`data.table::data.table()`\cr +#' Initial design of the optimization. +#' If `NULL`, a design of size `design_size` is generated with `design_function`.} +#' \item{`design_size`}{`integer(1)`\cr +#' Size of the initial design.} +#' \item{`design_function`}{`character(1)`\cr +#' Function to generate the initial design. +#' One of `c("random", "sobol", "lhs")`.} +#' \item{`n_workers`}{`integer(1)`\cr +#' Number of parallel workers. +#' If `NULL`, all rush workers set with [rush::rush_plan()] are used.} +#' } +#' +#' +#' @references +#' * `r format_bib("egele_2023")` +#' +#' @export +TunerAsyncMboADBO = R6Class("TunerAsyncMboADBO", + inherit = mlr3tuning::TunerAsyncFromOptimizerAsync, + public = list( + + #' @description + #' Creates a new instance of this [R6][R6::R6Class] class. + initialize = function() { + optimizer = OptimizerAsyncMboADBO$new() + + super$initialize( + optimizer = optimizer, + man = "mlr3tuning::mlr_tuners_adbo" + ) + } + ) +) + +#' @include aaa.R +tuners[["adbo"]] = TunerAsyncMboADBO diff --git a/man/mlr_tuners_adbo.Rd b/man/mlr_tuners_adbo.Rd new file mode 100644 index 00000000..f1cac05a --- /dev/null +++ b/man/mlr_tuners_adbo.Rd @@ -0,0 +1,100 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/TunerAsyncMboADBO.R +\name{mlr_tuners_adbo} +\alias{mlr_tuners_adbo} +\alias{TunerAsyncMboADBO} +\title{Asynchronous Decentralized Bayesian Optimization} +\description{ +\code{TunerAsyncMboADBO} class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). +ADBO is a variant of Asynchronous Model Based Optimization (AMBO) that uses \link{AcqFunctionStochasticCB} with exponential lambda decay. +} +\note{ +The lambda parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. +A large lambda value leads to more exploration, while a small lambda value leads to more exploitation. +The initial lambda value is drawn from an exponential distribution with rate \code{1 / lambda}. +ADBO can use periodic exponential decay to reduce lambda periodically with the formula \code{lambda * exp(-rate * (t \%\% period))}. +The surrogate model is always a random forest and die acquisition optimizer is random search with a budget of 10,000 evaluations. +} +\section{Parameters}{ + +\describe{ +\item{\code{lambda}}{\code{numeric(1)}\cr +Lambda value for sampling from the exponential distribution.} +\item{\code{rate}}{\code{numeric(1)}\cr +Rate of the exponential decay.} +\item{\code{period}}{\code{integer(1)}\cr +Period of the exponential decay.} +\item{\code{initial_design_size}}{\code{integer(1)}\cr +Size of the initial design. +Defaults to \code{100}.} + +\item{\code{initial_design}}{\code{data.table::data.table()}\cr +Initial design of the optimization. +If \code{NULL}, a design of size \code{design_size} is generated with \code{design_function}.} +\item{\code{design_size}}{\code{integer(1)}\cr +Size of the initial design.} +\item{\code{design_function}}{\code{character(1)}\cr +Function to generate the initial design. +One of \code{c("random", "sobol", "lhs")}.} +\item{\code{n_workers}}{\code{integer(1)}\cr +Number of parallel workers. +If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush_plan()}} are used.} +} +} + +\references{ +\itemize{ +\item Egele R, Guyon I, Vishwanath V, Balaprakash P (2023). +\dQuote{Asynchronous Decentralized Bayesian Optimization for Large Scale Hyperparameter Optimization.} +\emph{arXiv}. +\url{https://arxiv.org/abs/2207.00479}. +} +} +\section{Super classes}{ +\code{\link[mlr3tuning:Tuner]{mlr3tuning::Tuner}} -> \code{\link[mlr3tuning:TunerAsync]{mlr3tuning::TunerAsync}} -> \code{\link[mlr3tuning:TunerAsyncFromOptimizerAsync]{mlr3tuning::TunerAsyncFromOptimizerAsync}} -> \code{TunerAsyncMboADBO} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-TunerAsyncMboADBO-new}{\code{TunerAsyncMboADBO$new()}} +\item \href{#method-TunerAsyncMboADBO-clone}{\code{TunerAsyncMboADBO$clone()}} +} +} +\if{html}{\out{ +
    Inherited methods + +
    +}} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TunerAsyncMboADBO-new}{}}} +\subsection{Method \code{new()}}{ +Creates a new instance of this \link[R6:R6Class]{R6} class. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{TunerAsyncMboADBO$new()}\if{html}{\out{
    }} +} + +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TunerAsyncMboADBO-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{TunerAsyncMboADBO$clone(deep = FALSE)}\if{html}{\out{
    }} +} + +\subsection{Arguments}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
    }} +} +} +} diff --git a/tests/testthat/test_TunerAsyncMboADBO.R b/tests/testthat/test_TunerAsyncMboADBO.R new file mode 100644 index 00000000..63e84623 --- /dev/null +++ b/tests/testthat/test_TunerAsyncMboADBO.R @@ -0,0 +1,29 @@ + +test_that("async mbo tuner works", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + learner = lrn("classif.rpart", + minsplit = to_tune(2, 128), + cp = to_tune(1e-04, 1e-1)) + + rush::rush_plan(n_workers = 4) + instance = ti_async( + task = tsk("pima"), + learner = learner, + resampling = rsmp("cv", folds = 3), + measure = msr("classif.ce"), + terminator = trm("evals", n_evals = 20), + store_benchmark_result = FALSE + ) + + tuner = tnr("adbo", design_size = 10) + + expect_data_table(tuner$optimize(instance), nrows = 1) + expect_data_table(instance$archive$data, min.rows = 10) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "lambda_0", "lambda")) + + expect_rush_reset(instance$rush) +}) + From 824b586e109c42ebcef97fe26a974ef28abd488b Mon Sep 17 00:00:00 2001 From: be-marc Date: Tue, 5 Nov 2024 18:04:07 +0100 Subject: [PATCH 43/49] prefix columns with acq_ --- R/AcqFunctionStochasticCB.R | 2 +- R/AcqFunctionStochasticEI.R | 2 +- tests/testthat/test_AcqFunctionStochasticCB.R | 18 +++++++++--------- tests/testthat/test_AcqFunctionStochasticEI.R | 10 +++++----- tests/testthat/test_OptimizerAsyncMbo.R | 2 +- tests/testthat/test_OptimizerAsyncMboADBO.R | 2 +- tests/testthat/test_TunerAsyncMbo.R | 2 +- tests/testthat/test_TunerAsyncMboADBO.R | 2 +- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/R/AcqFunctionStochasticCB.R b/R/AcqFunctionStochasticCB.R index ebb1884c..a5e20e08 100644 --- a/R/AcqFunctionStochasticCB.R +++ b/R/AcqFunctionStochasticCB.R @@ -135,7 +135,7 @@ AcqFunctionStochasticCB = R6Class("AcqFunctionStochasticCB", .fun = function(xdt, lambda) { p = self$surrogate$predict(xdt) cb = p$mean - self$surrogate_max_to_min * lambda * p$se - data.table(acq_cb = cb, lambda = lambda, lambda_0 = private$.lambda_0) + data.table(acq_cb = cb, acq_lambda = lambda, acq_lambda_0 = private$.lambda_0) } ) ) diff --git a/R/AcqFunctionStochasticEI.R b/R/AcqFunctionStochasticEI.R index 5938acf6..5897033c 100644 --- a/R/AcqFunctionStochasticEI.R +++ b/R/AcqFunctionStochasticEI.R @@ -103,7 +103,7 @@ AcqFunctionStochasticEI = R6Class("AcqFunctionStochasticEI", d_norm = d / se ei = d * pnorm(d_norm) + se * dnorm(d_norm) ei = ifelse(se < 1e-20, 0, ei) - data.table(acq_ei = ei, epsilon = epsilon, epsilon_0 = private$.epsilon_0) + data.table(acq_ei = ei, acq_epsilon = epsilon, acq_epsilon_0 = private$.epsilon_0) } ) ) diff --git a/tests/testthat/test_AcqFunctionStochasticCB.R b/tests/testthat/test_AcqFunctionStochasticCB.R index 188560f4..267565bc 100644 --- a/tests/testthat/test_AcqFunctionStochasticCB.R +++ b/tests/testthat/test_AcqFunctionStochasticCB.R @@ -19,7 +19,7 @@ test_that("AcqFunctionStochasticCB works in defaults", { expect_data_table(optimizer$optimize(instance), nrows = 1) expect_data_table(instance$archive$data, min.rows = 10) - expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "lambda_0", "lambda")) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) expect_rush_reset(instance$rush) }) @@ -45,8 +45,8 @@ test_that("AcqFunctionStochasticCB works with uniform sampling", { expect_data_table(optimizer$optimize(instance), nrows = 1) expect_data_table(instance$archive$data, min.rows = 10) - expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "lambda_0", "lambda")) - expect_numeric(instance$archive$data$lambda, lower = 1, upper = 3) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) + expect_numeric(instance$archive$data$acq_lambda, lower = 1, upper = 3) expect_rush_reset(instance$rush) }) @@ -72,8 +72,8 @@ test_that("AcqFunctionStochasticCB works with exponential sampling", { expect_data_table(optimizer$optimize(instance), nrows = 1) expect_data_table(instance$archive$data, min.rows = 10) - expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "lambda_0", "lambda")) - expect_numeric(unique(instance$archive$data$lambda), len = 3) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) + expect_numeric(unique(instance$archive$data$acq_lambda), len = 3) expect_rush_reset(instance$rush) }) @@ -100,9 +100,9 @@ test_that("AcqFunctionStochasticCB works with lambda decay", { expect_data_table(optimizer$optimize(instance), nrows = 1) expect_data_table(instance$archive$data, min.rows = 10) - expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "lambda_0", "lambda")) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) - expect_numeric(-instance$archive$data$lambda, sorted = TRUE) + expect_numeric(-instance$archive$data$acq_lambda, sorted = TRUE) expect_rush_reset(instance$rush) }) @@ -128,9 +128,9 @@ test_that("AcqFunctionStochasticCB works with periodic lambda decay", { expect_data_table(optimizer$optimize(instance), nrows = 1) expect_data_table(instance$archive$data, min.rows = 10) - expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "lambda_0", "lambda")) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) - expect_numeric(unique(instance$archive$data$lambda), len = 3) + expect_numeric(unique(instance$archive$data$acq_lambda), len = 3) expect_rush_reset(instance$rush) }) diff --git a/tests/testthat/test_AcqFunctionStochasticEI.R b/tests/testthat/test_AcqFunctionStochasticEI.R index 106bbaa2..37e5e70b 100644 --- a/tests/testthat/test_AcqFunctionStochasticEI.R +++ b/tests/testthat/test_AcqFunctionStochasticEI.R @@ -19,8 +19,8 @@ test_that("AcqFunctionStochasticEI works in defaults", { expect_data_table(optimizer$optimize(instance), nrows = 1) expect_data_table(instance$archive$data, min.rows = 10) - expect_names(names(instance$archive$data), must.include = c(".already_evaluated", "acq_ei", "epsilon_0", "epsilon")) - expect_numeric(-instance$archive$data$epsilon, sorted = TRUE) + expect_names(names(instance$archive$data), must.include = c(".already_evaluated", "acq_ei", "acq_epsilon_0", "acq_epsilon")) + expect_numeric(-instance$archive$data$acq_epsilon, sorted = TRUE) expect_rush_reset(instance$rush) }) @@ -46,7 +46,7 @@ test_that("AcqFunctionStochasticEI works with multiple workers", { expect_data_table(optimizer$optimize(instance), nrows = 1) expect_data_table(instance$archive$data, min.rows = 10) - expect_names(names(instance$archive$data), must.include = c(".already_evaluated", "acq_ei", "epsilon_0", "epsilon")) + expect_names(names(instance$archive$data), must.include = c(".already_evaluated", "acq_ei", "acq_epsilon_0", "acq_epsilon")) expect_rush_reset(instance$rush) }) @@ -73,8 +73,8 @@ test_that("AcqFunctionStochasticEI works with periodic epsilon decay", { expect_data_table(optimizer$optimize(instance), nrows = 1) expect_data_table(instance$archive$data, min.rows = 10) - expect_names(names(instance$archive$data), must.include = c(".already_evaluated", "acq_ei", "epsilon_0", "epsilon")) - expect_numeric(unique(instance$archive$data$epsilon), len = 3) + expect_names(names(instance$archive$data), must.include = c(".already_evaluated", "acq_ei", "acq_epsilon_0", "acq_epsilon")) + expect_numeric(unique(instance$archive$data$acq_epsilon), len = 3) expect_rush_reset(instance$rush) }) diff --git a/tests/testthat/test_OptimizerAsyncMbo.R b/tests/testthat/test_OptimizerAsyncMbo.R index 68a24e8d..cd480f74 100644 --- a/tests/testthat/test_OptimizerAsyncMbo.R +++ b/tests/testthat/test_OptimizerAsyncMbo.R @@ -13,7 +13,7 @@ test_that("async optimizer works in defaults", { expect_data_table(optimizer$optimize(instance), nrows = 1) expect_data_table(instance$archive$data, min.rows = 10) - expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "lambda_0", "lambda")) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) expect_rush_reset(instance$rush) }) diff --git a/tests/testthat/test_OptimizerAsyncMboADBO.R b/tests/testthat/test_OptimizerAsyncMboADBO.R index 8ca098dc..746bb1b3 100644 --- a/tests/testthat/test_OptimizerAsyncMboADBO.R +++ b/tests/testthat/test_OptimizerAsyncMboADBO.R @@ -13,7 +13,7 @@ test_that("adbo works in defaults", { expect_data_table(optimizer$optimize(instance), nrows = 1) expect_data_table(instance$archive$data, min.rows = 20) - expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "lambda_0", "lambda")) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) expect_rush_reset(instance$rush) }) diff --git a/tests/testthat/test_TunerAsyncMbo.R b/tests/testthat/test_TunerAsyncMbo.R index da58a5d0..dce8021e 100644 --- a/tests/testthat/test_TunerAsyncMbo.R +++ b/tests/testthat/test_TunerAsyncMbo.R @@ -22,7 +22,7 @@ test_that("async mbo tuner works", { expect_data_table(tuner$optimize(instance), nrows = 1) expect_data_table(instance$archive$data, min.rows = 10) - expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "lambda_0", "lambda")) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) expect_rush_reset(instance$rush) }) diff --git a/tests/testthat/test_TunerAsyncMboADBO.R b/tests/testthat/test_TunerAsyncMboADBO.R index 63e84623..8dc51a53 100644 --- a/tests/testthat/test_TunerAsyncMboADBO.R +++ b/tests/testthat/test_TunerAsyncMboADBO.R @@ -22,7 +22,7 @@ test_that("async mbo tuner works", { expect_data_table(tuner$optimize(instance), nrows = 1) expect_data_table(instance$archive$data, min.rows = 10) - expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "lambda_0", "lambda")) + expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) expect_rush_reset(instance$rush) }) From e3be42c52f55c4cccfe391214257abbb2a820488 Mon Sep 17 00:00:00 2001 From: be-marc Date: Thu, 7 Nov 2024 19:32:18 +0100 Subject: [PATCH 44/49] ... --- DESCRIPTION | 6 ++++-- NAMESPACE | 4 ++-- R/{OptimizerAsyncMboADBO.R => OptimizerADBO.R} | 8 ++++---- R/OptimizerAsyncMbo.R | 2 +- R/{TunerAsyncMboADBO.R => TunerADBO.R} | 8 ++++---- 5 files changed, 15 insertions(+), 13 deletions(-) rename R/{OptimizerAsyncMboADBO.R => OptimizerADBO.R} (92%) rename R/{TunerAsyncMboADBO.R => TunerADBO.R} (90%) diff --git a/DESCRIPTION b/DESCRIPTION index 767fe415..c0fa3a16 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -66,6 +66,8 @@ Suggests: rush, stringi, testthat (>= 3.0.0) +Remotes: + mlr-org/bbotk ByteCompile: no Encoding: UTF-8 Config/testthat/edition: 3 @@ -92,7 +94,7 @@ Collate: 'AcqOptimizer.R' 'aaa.R' 'OptimizerAsyncMbo.R' - 'OptimizerAsyncMboADBO.R' + 'OptimizerADBO.R' 'OptimizerMbo.R' 'mlr_result_assigners.R' 'ResultAssigner.R' @@ -103,7 +105,7 @@ Collate: 'SurrogateLearnerAsync.R' 'SurrogateLearnerCollection.R' 'TunerAsyncMbo.R' - 'TunerAsyncMboADBO.R' + 'TunerADBO.R' 'TunerMbo.R' 'mlr_loop_functions.R' 'bayesopt_ego.R' diff --git a/NAMESPACE b/NAMESPACE index 10f868b4..83f5e392 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -20,7 +20,7 @@ export(AcqFunctionStochasticCB) export(AcqFunctionStochasticEI) export(AcqOptimizer) export(OptimizerAsyncMbo) -export(OptimizerAsyncMboADBO) +export(OptimizerADBO) export(OptimizerMbo) export(ResultAssigner) export(ResultAssignerArchive) @@ -30,7 +30,7 @@ export(SurrogateLearner) export(SurrogateLearnerAsync) export(SurrogateLearnerCollection) export(TunerAsyncMbo) -export(TunerAsyncMboADBO) +export(TunerADBO) export(TunerMbo) export(acqf) export(acqfs) diff --git a/R/OptimizerAsyncMboADBO.R b/R/OptimizerADBO.R similarity index 92% rename from R/OptimizerAsyncMboADBO.R rename to R/OptimizerADBO.R index 3ab58d09..0270cf38 100644 --- a/R/OptimizerAsyncMboADBO.R +++ b/R/OptimizerADBO.R @@ -2,7 +2,7 @@ #' @name mlr_optimizers_adbo #' #' @description -#' `OptimizerAsyncMboADBO` class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). +#' `OptimizerADBO` class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). #' ADBO is a variant of Asynchronous Model Based Optimization (AMBO) that uses [AcqFunctionStochasticCB] with exponential lambda decay. #' #' @note @@ -41,7 +41,7 @@ #' * `r format_bib("egele_2023")` #' #' @export -OptimizerAsyncMboADBO = R6Class("OptimizerAsyncMboADBO", +OptimizerADBO = R6Class("OptimizerADBO", inherit = OptimizerAsyncMbo, public = list( @@ -59,7 +59,7 @@ OptimizerAsyncMboADBO = R6Class("OptimizerAsyncMboADBO", id = "adbo", param_set = param_set, label = "Asynchronous Decentralized Bayesian Optimization", - man = "mlr3mbo::OptimizerAsyncMboADBO") + man = "mlr3mbo::OptimizerADBO") self$param_set$set_values( lambda = 1.96, @@ -90,4 +90,4 @@ OptimizerAsyncMboADBO = R6Class("OptimizerAsyncMboADBO", ) #' @include aaa.R -optimizers[["adbo"]] = OptimizerAsyncMboADBO +optimizers[["adbo"]] = OptimizerADBO diff --git a/R/OptimizerAsyncMbo.R b/R/OptimizerAsyncMbo.R index ba773a54..8ce8e18d 100644 --- a/R/OptimizerAsyncMbo.R +++ b/R/OptimizerAsyncMbo.R @@ -60,7 +60,7 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", acq_optimizer = NULL, param_set = NULL, param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), - properties = c("dependencies", "single-crit"), + properties = c("dependencies", "single-crit", "async"), packages = c("mlr3mbo", "rush"), label = "Asynchronous Model Based Optimization", man = "mlr3mbo::OptimizerAsyncMbo" diff --git a/R/TunerAsyncMboADBO.R b/R/TunerADBO.R similarity index 90% rename from R/TunerAsyncMboADBO.R rename to R/TunerADBO.R index 4ca2ec42..21e7900c 100644 --- a/R/TunerAsyncMboADBO.R +++ b/R/TunerADBO.R @@ -2,7 +2,7 @@ #' @name mlr_tuners_adbo #' #' @description -#' `TunerAsyncMboADBO` class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). +#' `TunerADBO` class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). #' ADBO is a variant of Asynchronous Model Based Optimization (AMBO) that uses [AcqFunctionStochasticCB] with exponential lambda decay. #' #' @note @@ -42,14 +42,14 @@ #' * `r format_bib("egele_2023")` #' #' @export -TunerAsyncMboADBO = R6Class("TunerAsyncMboADBO", +TunerADBO = R6Class("TunerADBO", inherit = mlr3tuning::TunerAsyncFromOptimizerAsync, public = list( #' @description #' Creates a new instance of this [R6][R6::R6Class] class. initialize = function() { - optimizer = OptimizerAsyncMboADBO$new() + optimizer = OptimizerADBO$new() super$initialize( optimizer = optimizer, @@ -60,4 +60,4 @@ TunerAsyncMboADBO = R6Class("TunerAsyncMboADBO", ) #' @include aaa.R -tuners[["adbo"]] = TunerAsyncMboADBO +tuners[["adbo"]] = TunerADBO From 75b6b306bd8e74951461f3abf7039fffe0a153b0 Mon Sep 17 00:00:00 2001 From: be-marc Date: Thu, 7 Nov 2024 19:46:37 +0100 Subject: [PATCH 45/49] archive design --- DESCRIPTION | 4 +-- NAMESPACE | 4 +-- R/OptimizerAsyncMbo.R | 5 +++- man/mlr_optimizers_adbo.Rd | 24 ++++++++--------- man/mlr_optimizers_async_mbo.Rd | 2 +- man/mlr_tuners_adbo.Rd | 24 ++++++++--------- ...zerAsyncMboADBO.R => test_OptimizerADBO.R} | 0 tests/testthat/test_OptimizerAsyncMbo.R | 27 +++++++++++++++++++ ...t_TunerAsyncMboADBO.R => test_TunerADBO.R} | 0 9 files changed, 60 insertions(+), 30 deletions(-) rename tests/testthat/{test_OptimizerAsyncMboADBO.R => test_OptimizerADBO.R} (100%) rename tests/testthat/{test_TunerAsyncMboADBO.R => test_TunerADBO.R} (100%) diff --git a/DESCRIPTION b/DESCRIPTION index c0fa3a16..b0d0955b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -93,8 +93,8 @@ Collate: 'AcqFunctionStochasticEI.R' 'AcqOptimizer.R' 'aaa.R' - 'OptimizerAsyncMbo.R' 'OptimizerADBO.R' + 'OptimizerAsyncMbo.R' 'OptimizerMbo.R' 'mlr_result_assigners.R' 'ResultAssigner.R' @@ -104,8 +104,8 @@ Collate: 'SurrogateLearner.R' 'SurrogateLearnerAsync.R' 'SurrogateLearnerCollection.R' - 'TunerAsyncMbo.R' 'TunerADBO.R' + 'TunerAsyncMbo.R' 'TunerMbo.R' 'mlr_loop_functions.R' 'bayesopt_ego.R' diff --git a/NAMESPACE b/NAMESPACE index 83f5e392..fde8732f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -19,8 +19,8 @@ export(AcqFunctionSmsEgo) export(AcqFunctionStochasticCB) export(AcqFunctionStochasticEI) export(AcqOptimizer) -export(OptimizerAsyncMbo) export(OptimizerADBO) +export(OptimizerAsyncMbo) export(OptimizerMbo) export(ResultAssigner) export(ResultAssignerArchive) @@ -29,8 +29,8 @@ export(Surrogate) export(SurrogateLearner) export(SurrogateLearnerAsync) export(SurrogateLearnerCollection) -export(TunerAsyncMbo) export(TunerADBO) +export(TunerAsyncMbo) export(TunerMbo) export(acqf) export(acqfs) diff --git a/R/OptimizerAsyncMbo.R b/R/OptimizerAsyncMbo.R index 8ce8e18d..fb42fceb 100644 --- a/R/OptimizerAsyncMbo.R +++ b/R/OptimizerAsyncMbo.R @@ -102,7 +102,10 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", pv = self$param_set$values # initial design - design = if (is.null(pv$initial_design)) { + design = if (inst$archive$n_evals) { + lg$debug("Using archive with %s evaluations as initial design", inst$archive$n_evals) + NULL + } else if (is.null(pv$initial_design)) { # generate initial design generate_design = switch(pv$design_function, "random" = generate_design_random, diff --git a/man/mlr_optimizers_adbo.Rd b/man/mlr_optimizers_adbo.Rd index cc75bfb0..279fc75a 100644 --- a/man/mlr_optimizers_adbo.Rd +++ b/man/mlr_optimizers_adbo.Rd @@ -1,11 +1,11 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/OptimizerAsyncMboADBO.R +% Please edit documentation in R/OptimizerADBO.R \name{mlr_optimizers_adbo} \alias{mlr_optimizers_adbo} -\alias{OptimizerAsyncMboADBO} +\alias{OptimizerADBO} \title{Asynchronous Decentralized Bayesian Optimization} \description{ -\code{OptimizerAsyncMboADBO} class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). +\code{OptimizerADBO} class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). ADBO is a variant of Asynchronous Model Based Optimization (AMBO) that uses \link{AcqFunctionStochasticCB} with exponential lambda decay. } \note{ @@ -51,13 +51,13 @@ If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush } } \section{Super classes}{ -\code{\link[bbotk:Optimizer]{bbotk::Optimizer}} -> \code{\link[bbotk:OptimizerAsync]{bbotk::OptimizerAsync}} -> \code{\link[mlr3mbo:OptimizerAsyncMbo]{mlr3mbo::OptimizerAsyncMbo}} -> \code{OptimizerAsyncMboADBO} +\code{\link[bbotk:Optimizer]{bbotk::Optimizer}} -> \code{\link[bbotk:OptimizerAsync]{bbotk::OptimizerAsync}} -> \code{\link[mlr3mbo:OptimizerAsyncMbo]{mlr3mbo::OptimizerAsyncMbo}} -> \code{OptimizerADBO} } \section{Methods}{ \subsection{Public methods}{ \itemize{ -\item \href{#method-OptimizerAsyncMboADBO-new}{\code{OptimizerAsyncMboADBO$new()}} -\item \href{#method-OptimizerAsyncMboADBO-clone}{\code{OptimizerAsyncMboADBO$clone()}} +\item \href{#method-OptimizerADBO-new}{\code{OptimizerADBO$new()}} +\item \href{#method-OptimizerADBO-clone}{\code{OptimizerADBO$clone()}} } } \if{html}{\out{ @@ -71,22 +71,22 @@ If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush }} \if{html}{\out{
    }} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-OptimizerAsyncMboADBO-new}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OptimizerADBO-new}{}}} \subsection{Method \code{new()}}{ Creates a new instance of this \link[R6:R6Class]{R6} class. \subsection{Usage}{ -\if{html}{\out{
    }}\preformatted{OptimizerAsyncMboADBO$new()}\if{html}{\out{
    }} +\if{html}{\out{
    }}\preformatted{OptimizerADBO$new()}\if{html}{\out{
    }} } } \if{html}{\out{
    }} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-OptimizerAsyncMboADBO-clone}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OptimizerADBO-clone}{}}} \subsection{Method \code{clone()}}{ The objects of this class are cloneable with this method. \subsection{Usage}{ -\if{html}{\out{
    }}\preformatted{OptimizerAsyncMboADBO$clone(deep = FALSE)}\if{html}{\out{
    }} +\if{html}{\out{
    }}\preformatted{OptimizerADBO$clone(deep = FALSE)}\if{html}{\out{
    }} } \subsection{Arguments}{ diff --git a/man/mlr_optimizers_async_mbo.Rd b/man/mlr_optimizers_async_mbo.Rd index 3386eaaf..83554d96 100644 --- a/man/mlr_optimizers_async_mbo.Rd +++ b/man/mlr_optimizers_async_mbo.Rd @@ -94,7 +94,7 @@ Creates a new instance of this \link[R6:R6Class]{R6} class. acq_optimizer = NULL, param_set = NULL, param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), - properties = c("dependencies", "single-crit"), + properties = c("dependencies", "single-crit", "async"), packages = c("mlr3mbo", "rush"), label = "Asynchronous Model Based Optimization", man = "mlr3mbo::OptimizerAsyncMbo" diff --git a/man/mlr_tuners_adbo.Rd b/man/mlr_tuners_adbo.Rd index f1cac05a..90fb6f0d 100644 --- a/man/mlr_tuners_adbo.Rd +++ b/man/mlr_tuners_adbo.Rd @@ -1,11 +1,11 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/TunerAsyncMboADBO.R +% Please edit documentation in R/TunerADBO.R \name{mlr_tuners_adbo} \alias{mlr_tuners_adbo} -\alias{TunerAsyncMboADBO} +\alias{TunerADBO} \title{Asynchronous Decentralized Bayesian Optimization} \description{ -\code{TunerAsyncMboADBO} class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). +\code{TunerADBO} class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). ADBO is a variant of Asynchronous Model Based Optimization (AMBO) that uses \link{AcqFunctionStochasticCB} with exponential lambda decay. } \note{ @@ -51,13 +51,13 @@ If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush } } \section{Super classes}{ -\code{\link[mlr3tuning:Tuner]{mlr3tuning::Tuner}} -> \code{\link[mlr3tuning:TunerAsync]{mlr3tuning::TunerAsync}} -> \code{\link[mlr3tuning:TunerAsyncFromOptimizerAsync]{mlr3tuning::TunerAsyncFromOptimizerAsync}} -> \code{TunerAsyncMboADBO} +\code{\link[mlr3tuning:Tuner]{mlr3tuning::Tuner}} -> \code{\link[mlr3tuning:TunerAsync]{mlr3tuning::TunerAsync}} -> \code{\link[mlr3tuning:TunerAsyncFromOptimizerAsync]{mlr3tuning::TunerAsyncFromOptimizerAsync}} -> \code{TunerADBO} } \section{Methods}{ \subsection{Public methods}{ \itemize{ -\item \href{#method-TunerAsyncMboADBO-new}{\code{TunerAsyncMboADBO$new()}} -\item \href{#method-TunerAsyncMboADBO-clone}{\code{TunerAsyncMboADBO$clone()}} +\item \href{#method-TunerADBO-new}{\code{TunerADBO$new()}} +\item \href{#method-TunerADBO-clone}{\code{TunerADBO$clone()}} } } \if{html}{\out{ @@ -71,22 +71,22 @@ If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush }} \if{html}{\out{
    }} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-TunerAsyncMboADBO-new}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TunerADBO-new}{}}} \subsection{Method \code{new()}}{ Creates a new instance of this \link[R6:R6Class]{R6} class. \subsection{Usage}{ -\if{html}{\out{
    }}\preformatted{TunerAsyncMboADBO$new()}\if{html}{\out{
    }} +\if{html}{\out{
    }}\preformatted{TunerADBO$new()}\if{html}{\out{
    }} } } \if{html}{\out{
    }} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-TunerAsyncMboADBO-clone}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TunerADBO-clone}{}}} \subsection{Method \code{clone()}}{ The objects of this class are cloneable with this method. \subsection{Usage}{ -\if{html}{\out{
    }}\preformatted{TunerAsyncMboADBO$clone(deep = FALSE)}\if{html}{\out{
    }} +\if{html}{\out{
    }}\preformatted{TunerADBO$clone(deep = FALSE)}\if{html}{\out{
    }} } \subsection{Arguments}{ diff --git a/tests/testthat/test_OptimizerAsyncMboADBO.R b/tests/testthat/test_OptimizerADBO.R similarity index 100% rename from tests/testthat/test_OptimizerAsyncMboADBO.R rename to tests/testthat/test_OptimizerADBO.R diff --git a/tests/testthat/test_OptimizerAsyncMbo.R b/tests/testthat/test_OptimizerAsyncMbo.R index cd480f74..1693e47c 100644 --- a/tests/testthat/test_OptimizerAsyncMbo.R +++ b/tests/testthat/test_OptimizerAsyncMbo.R @@ -18,3 +18,30 @@ test_that("async optimizer works in defaults", { expect_rush_reset(instance$rush) }) +test_that("async optimizer works with evaluations in archive", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 2) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 10), + ) + + optimizer = opt("async_random_search") + optimizer$optimize(instance) + + + instance$terminator$param_set$values$n_evals = 30 + + optimizer = opt("async_mbo") + optimizer$optimize(instance) + + instance$archive + expect_data_table(instance$archive$data[is.na(get("acq_cb"))], min.rows = 10) + expect_data_table(instance$archive$data[!is.na(get("acq_cb"))], min.rows = 20) + + expect_rush_reset(instance$rush) +}) diff --git a/tests/testthat/test_TunerAsyncMboADBO.R b/tests/testthat/test_TunerADBO.R similarity index 100% rename from tests/testthat/test_TunerAsyncMboADBO.R rename to tests/testthat/test_TunerADBO.R From a4463210c82f4927d5593c0193df6010a48f77c3 Mon Sep 17 00:00:00 2001 From: be-marc Date: Thu, 7 Nov 2024 19:57:22 +0100 Subject: [PATCH 46/49] relaxe --- tests/testthat/test_OptimizerAsyncMbo.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test_OptimizerAsyncMbo.R b/tests/testthat/test_OptimizerAsyncMbo.R index 1693e47c..2b611f9c 100644 --- a/tests/testthat/test_OptimizerAsyncMbo.R +++ b/tests/testthat/test_OptimizerAsyncMbo.R @@ -34,7 +34,7 @@ test_that("async optimizer works with evaluations in archive", { optimizer$optimize(instance) - instance$terminator$param_set$values$n_evals = 30 + instance$terminator$param_set$values$n_evals = 40 optimizer = opt("async_mbo") optimizer$optimize(instance) From a79d86312c91bd781e8753f41b18aaf2445184c1 Mon Sep 17 00:00:00 2001 From: Lennart Schneider Date: Fri, 15 Nov 2024 08:34:07 +0100 Subject: [PATCH 47/49] refactor: minor refactor changes to proposed async extensions --- R/AcqFunction.R | 8 + R/AcqFunctionMulti.R | 2 +- R/AcqFunctionSmsEgo.R | 18 +- R/AcqFunctionStochasticCB.R | 76 +++++++-- R/AcqFunctionStochasticEI.R | 61 ++++++- R/AcqOptimizer.R | 28 +-- R/OptimizerADBO.R | 85 +++++++--- R/OptimizerAsyncMbo.R | 300 ++++++++++++++++++++++++--------- R/OptimizerMbo.R | 98 ++++++----- R/ResultAssigner.R | 2 +- R/ResultAssignerArchive.R | 4 +- R/ResultAssignerSurrogate.R | 12 +- R/Surrogate.R | 54 +++++- R/SurrogateLearner.R | 40 ++++- R/SurrogateLearnerAsync.R | 89 ---------- R/SurrogateLearnerCollection.R | 72 +++++++- R/TunerADBO.R | 169 +++++++++++++++---- R/TunerAsyncMbo.R | 178 +++++++++++++++---- R/TunerMbo.R | 7 +- R/bayesopt_ego.R | 4 +- R/bayesopt_emo.R | 4 +- R/bayesopt_mpcl.R | 4 +- R/bayesopt_parego.R | 6 +- R/bayesopt_smsego.R | 4 +- R/bibentries.R | 14 +- R/mbo_defaults.R | 33 ++-- R/sugar.R | 4 +- 27 files changed, 977 insertions(+), 399 deletions(-) delete mode 100644 R/SurrogateLearnerAsync.R diff --git a/R/AcqFunction.R b/R/AcqFunction.R index 2dfb2a6f..261bc1f8 100644 --- a/R/AcqFunction.R +++ b/R/AcqFunction.R @@ -73,6 +73,14 @@ AcqFunction = R6Class("AcqFunction", # FIXME: at some point we may want to make this an AB to a private$.update }, + #' @description + #' Reset the acquisition function. + #' + #' Can be implemented by subclasses. + reset = function() { + # FIXME: at some point we may want to make this an AB to a private$.reset + }, + #' @description #' Evaluates multiple input values on the objective function. #' diff --git a/R/AcqFunctionMulti.R b/R/AcqFunctionMulti.R index 5b9543e3..f148e6ba 100644 --- a/R/AcqFunctionMulti.R +++ b/R/AcqFunctionMulti.R @@ -16,7 +16,7 @@ #' If acquisition functions have not been initialized with a surrogate, the surrogate passed during construction or lazy initialization #' will be used for all acquisition functions. #' -#' For optimization, [AcqOptimizer] can be used as for any other [AcqFunction], however, the [bbotk::Optimizer] wrapped within the [AcqOptimizer] +#' For optimization, [AcqOptimizer] can be used as for any other [AcqFunction], however, the [bbotk::OptimizerBatch] wrapped within the [AcqOptimizer] #' must support multi-objective optimization as indicated via the `multi-crit` property. #' #' @family Acquisition Function diff --git a/R/AcqFunctionSmsEgo.R b/R/AcqFunctionSmsEgo.R index 5f30efd1..b2865aae 100644 --- a/R/AcqFunctionSmsEgo.R +++ b/R/AcqFunctionSmsEgo.R @@ -18,6 +18,11 @@ #' In the case of being `NULL`, an epsilon vector is maintained dynamically as #' described in Horn et al. (2015). #' +#' @section Note: +#' * This acquisition function always also returns its current epsilon values in a list column (`acq_epsilon`). +#' This value will be logged into the [bbotk::ArchiveBatch] of the [bbotk::OptimInstanceBatch] of the [AcqOptimizer] and +#' therefore also in the [bbotk::Archive] of the actual [bbotk::OptimInstance] that is to be optimized. +#' #' @references #' * `r format_bib("ponweiser_2008")` #' * `r format_bib("horn_2015")` @@ -78,7 +83,7 @@ AcqFunctionSmsEgo = R6Class("AcqFunctionSmsEgo", #' @field progress (`numeric(1)`)\cr #' Optimization progress (typically, the number of function evaluations left). - #' Note that this requires the [bbotk::OptimInstance] to be terminated via a [bbotk::TerminatorEvals]. + #' Note that this requires the [bbotk::OptimInstanceBatch] to be terminated via a [bbotk::TerminatorEvals]. progress = NULL, #' @description @@ -94,7 +99,7 @@ AcqFunctionSmsEgo = R6Class("AcqFunctionSmsEgo", constants = ps( lambda = p_dbl(lower = 0, default = 1), - epsilon = p_dbl(lower = 0, default = NULL, special_vals = list(NULL)) # for NULL, it will be calculated dynamically + epsilon = p_dbl(lower = 0, default = NULL, special_vals = list(NULL)) # if NULL, it will be calculated dynamically ) constants$values$lambda = lambda constants$values$epsilon = epsilon @@ -140,6 +145,13 @@ AcqFunctionSmsEgo = R6Class("AcqFunctionSmsEgo", } else { self$epsilon = self$constants$values$epsilon } + }, + + #' @description + #' Reset the acquisition function. + #' Resets `epsilon`. + reset = function() { + self$epsilon = NULL } ), @@ -163,7 +175,7 @@ AcqFunctionSmsEgo = R6Class("AcqFunctionSmsEgo", # allocate memory for adding points to front for HV calculation in C front2 = t(rbind(self$ys_front, 0)) sms = .Call("c_sms_indicator", PACKAGE = "mlr3mbo", cbs, self$ys_front, front2, self$epsilon, self$ref_point) # note that the negative indicator is returned from C - data.table(acq_smsego = sms) + data.table(acq_smsego = sms, acq_epsilon = list(self$epsilon)) } ) ) diff --git a/R/AcqFunctionStochasticCB.R b/R/AcqFunctionStochasticCB.R index a5e20e08..b8d66ebf 100644 --- a/R/AcqFunctionStochasticCB.R +++ b/R/AcqFunctionStochasticCB.R @@ -8,21 +8,24 @@ #' #' @description #' Lower / Upper Confidence Bound with lambda sampling and decay. -#' The initial lambda value is drawn from an uniform distribution between `min_lambda` and `max_lambda` or from an exponential distribution with rate `1 / lambda`. -#' The lambda value is updated after each evaluation by the formula `lambda * exp(-rate * (t %% period))`. +#' The initial \eqn{\lambda} is drawn from an uniform distribution between `min_lambda` and `max_lambda` or from an exponential distribution with rate `1 / lambda`. +#' \eqn{\lambda} is updated after each update by the formula `lambda * exp(-rate * (t %% period))`, where `t` is the number of times the acquisition function has been updated. +#' +#' While this acquisition function usually would be used within an asynchronous optimizer, e.g., [OptimizerAsyncMbo], +#' it can in principle also be used in synchronous optimizers, e.g., [OptimizerMbo]. #' #' @section Parameters: #' * `"lambda"` (`numeric(1)`)\cr -#' Lambda value for sampling from the exponential distribution. +#' \eqn{\lambda} value for sampling from the exponential distribution. #' Defaults to `1.96`. #' * `"min_lambda"` (`numeric(1)`)\cr -#' Minimum value of lambda for sampling from the uniform distribution. +#' Minimum value of \eqn{\lambda}for sampling from the uniform distribution. #' Defaults to `0.01`. #' * `"max_lambda"` (`numeric(1)`)\cr -#' Maximum value of lambda for sampling from the uniform distribution. +#' Maximum value of \eqn{\lambda} for sampling from the uniform distribution. #' Defaults to `10`. #' * `"distribution"` (`character(1)`)\cr -#' Distribution to sample lambda from. +#' Distribution to sample \eqn{\lambda} from. #' One of `c("uniform", "exponential")`. #' Defaults to `uniform`. #' * `"rate"` (`numeric(1)`)\cr @@ -30,7 +33,12 @@ #' Defaults to `0` i.e. no decay. #' * `"period"` (`integer(1)`)\cr #' Period of the exponential decay. -#' Defaults to `NULL` i.e. the decay has no period. +#' Defaults to `NULL`, i.e., the decay has no period. +#' +#' @section Note: +#' * This acquisition function always also returns its current (`acq_lambda`) and original (`acq_lambda_0`) \eqn{\lambda}. +#' These values will be logged into the [bbotk::ArchiveBatch] of the [bbotk::OptimInstanceBatch] of the [AcqOptimizer] and +#' therefore also in the [bbotk::Archive] of the actual [bbotk::OptimInstance] that is to be optimized. #' #' @references #' * `r format_bib("snoek_2012")` @@ -38,6 +46,38 @@ #' #' @family Acquisition Function #' @export +#' @examples +#' if (requireNamespace("mlr3learners") & +#' requireNamespace("DiceKriging") & +#' requireNamespace("rgenoud")) { +#' library(bbotk) +#' library(paradox) +#' library(mlr3learners) +#' library(data.table) +#' +#' fun = function(xs) { +#' list(y = xs$x ^ 2) +#' } +#' domain = ps(x = p_dbl(lower = -10, upper = 10)) +#' codomain = ps(y = p_dbl(tags = "minimize")) +#' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) +#' +#' instance = OptimInstanceBatchSingleCrit$new( +#' objective = objective, +#' terminator = trm("evals", n_evals = 5)) +#' +#' instance$eval_batch(data.table(x = c(-6, -5, 3, 9))) +#' +#' learner = default_gp() +#' +#' surrogate = srlrn(learner, archive = instance$archive) +#' +#' acq_function = acqf("stochastic_cb", surrogate = surrogate, lambda = 3) +#' +#' acq_function$surrogate$update() +#' acq_function$update() +#' acq_function$eval_dt(data.table(x = c(-1, 0, 1))) +#' } AcqFunctionStochasticCB = R6Class("AcqFunctionStochasticCB", inherit = AcqFunction, @@ -52,7 +92,7 @@ AcqFunctionStochasticCB = R6Class("AcqFunctionStochasticCB", #' @param max_lambda (`numeric(1)`). #' @param distribution (`character(1)`). #' @param rate (`numeric(1)`). - #' @param period (`integer(1)`). + #' @param period (`NULL` | `integer(1)`). initialize = function( surrogate = NULL, lambda = 1.96, @@ -69,19 +109,17 @@ AcqFunctionStochasticCB = R6Class("AcqFunctionStochasticCB", private$.distribution = assert_choice(distribution, choices = c("uniform", "exponential")) if (private$.distribution == "uniform" && (is.null(private$.min_lambda) || is.null(private$.max_lambda))) { - stop("If `distribution` is 'uniform', `min_lambda` and `max_lambda` must be set.") + stop('If `distribution` is "uniform", `min_lambda` and `max_lambda` must be set.') } if (private$.distribution == "exponential" && is.null(private$.lambda)) { - stop("If `distribution` is 'exponential', `lambda` must be set.") + stop('If `distribution` is "exponential", `lambda` must be set.') } private$.rate = assert_number(rate, lower = 0) private$.period = assert_int(period, lower = 1, null.ok = TRUE) - constants = ps( - lambda = p_dbl(lower = 0) - ) + constants = ps(lambda = p_dbl(lower = 0)) super$initialize("acq_cb", constants = constants, @@ -117,8 +155,15 @@ AcqFunctionStochasticCB = R6Class("AcqFunctionStochasticCB", rate = private$.rate self$constants$values$lambda = lambda_0 * exp(-rate * t) - private$.t = t + 1 + private$.t = t + 1L } + }, + + #' @description + #' Reset the acquisition function. + #' Resets the private update counter `.t` used within the epsilon decay. + reset = function() { + private$.t = 0L } ), @@ -129,9 +174,8 @@ AcqFunctionStochasticCB = R6Class("AcqFunctionStochasticCB", .distribution = NULL, .rate = NULL, .period = NULL, - .t = 0, .lambda_0 = NULL, - + .t = 0L, .fun = function(xdt, lambda) { p = self$surrogate$predict(xdt) cb = p$mean - self$surrogate_max_to_min * lambda * p$se diff --git a/R/AcqFunctionStochasticEI.R b/R/AcqFunctionStochasticEI.R index 5897033c..9b3770a3 100644 --- a/R/AcqFunctionStochasticEI.R +++ b/R/AcqFunctionStochasticEI.R @@ -8,6 +8,10 @@ #' #' @description #' Expected Improvement with epsilon decay. +#' \eqn{\epsilon} is updated after each update by the formula `epsilon * exp(-rate * (t %% period))` where `t` is the number of times the acquisition function has been updated. +#' +#' While this acquisition function usually would be used within an asynchronous optimizer, e.g., [OptimizerAsyncMbo], +#' it can in principle also be used in synchronous optimizers, e.g., [OptimizerMbo]. #' #' @section Parameters: #' * `"epsilon"` (`numeric(1)`)\cr @@ -20,13 +24,50 @@ #' Defaults to `0.05`. #' * `"period"` (`integer(1)`)\cr #' Period of the exponential decay. -#' Defaults to `NULL` i.e. the decay has no period. +#' Defaults to `NULL`, i.e., the decay has no period. +#' +#' @section Note: +#' * This acquisition function always also returns its current (`acq_epsilon`) and original (`acq_epsilon_0`) \eqn{\epsilon}. +#' These values will be logged into the [bbotk::ArchiveBatch] of the [bbotk::OptimInstanceBatch] of the [AcqOptimizer] and +#' therefore also in the [bbotk::Archive] of the actual [bbotk::OptimInstance] that is to be optimized. #' #' @references #' * `r format_bib("jones_1998")` #' #' @family Acquisition Function #' @export +#' @examples +#' if (requireNamespace("mlr3learners") & +#' requireNamespace("DiceKriging") & +#' requireNamespace("rgenoud")) { +#' library(bbotk) +#' library(paradox) +#' library(mlr3learners) +#' library(data.table) +#' +#' fun = function(xs) { +#' list(y = xs$x ^ 2) +#' } +#' domain = ps(x = p_dbl(lower = -10, upper = 10)) +#' codomain = ps(y = p_dbl(tags = "minimize")) +#' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) +#' +#' instance = OptimInstanceBatchSingleCrit$new( +#' objective = objective, +#' terminator = trm("evals", n_evals = 5)) +#' +#' instance$eval_batch(data.table(x = c(-6, -5, 3, 9))) +#' +#' learner = default_gp() +#' +#' surrogate = srlrn(learner, archive = instance$archive) +#' +#' acq_function = acqf("stochastic_ei", surrogate = surrogate) +#' +#' acq_function$surrogate$update() +#' acq_function$update() +#' acq_function$eval_dt(data.table(x = c(-1, 0, 1))) +#' } AcqFunctionStochasticEI = R6Class("AcqFunctionStochasticEI", inherit = AcqFunction, @@ -43,7 +84,7 @@ AcqFunctionStochasticEI = R6Class("AcqFunctionStochasticEI", #' @param surrogate (`NULL` | [SurrogateLearner]). #' @param epsilon (`numeric(1)`). #' @param rate (`numeric(1)`). - #' @param period (`integer(1)`). + #' @param period (`NULL` | `integer(1)`). initialize = function( surrogate = NULL, epsilon = 0.1, @@ -55,9 +96,7 @@ AcqFunctionStochasticEI = R6Class("AcqFunctionStochasticEI", private$.rate = assert_number(rate, lower = 0, finite = TRUE) private$.period = assert_int(period, lower = 1, null.ok = TRUE) - constants = ps( - epsilon = p_dbl(lower = 0) - ) + constants = ps(epsilon = p_dbl(lower = 0, default = 0.1)) super$initialize("acq_ei", constants = constants, @@ -82,7 +121,14 @@ AcqFunctionStochasticEI = R6Class("AcqFunctionStochasticEI", rate = private$.rate self$constants$values$epsilon = epsilon_0 * exp(-rate * t) - private$.t = t + 1 + private$.t = t + 1L + }, + + #' @description + #' Reset the acquisition function. + #' Resets the private update counter `.t` used within the epsilon decay. + reset = function() { + private$.t = 0L } ), @@ -90,8 +136,7 @@ AcqFunctionStochasticEI = R6Class("AcqFunctionStochasticEI", .rate = NULL, .period = NULL, .epsilon_0 = NULL, - .t = 0, - + .t = 0L, .fun = function(xdt, epsilon) { if (is.null(self$y_best)) { stop("$y_best is not set. Missed to call $update()?") diff --git a/R/AcqOptimizer.R b/R/AcqOptimizer.R index ae95d76e..1802ff85 100644 --- a/R/AcqOptimizer.R +++ b/R/AcqOptimizer.R @@ -2,7 +2,7 @@ #' #' @description #' Optimizer for [AcqFunction]s which performs the acquisition function optimization. -#' Wraps an [bbotk::Optimizer] and [bbotk::Terminator]. +#' Wraps an [bbotk::OptimizerBatch] and [bbotk::Terminator]. #' #' @section Parameters: #' \describe{ @@ -10,9 +10,9 @@ #' Number of candidate points to propose. #' Note that this does not affect how the acquisition function itself is calculated (e.g., setting `n_candidates > 1` will not #' result in computing the q- or multi-Expected Improvement) but rather the top `n_candidates` are selected from the -#' [bbotk::Archive] of the acquisition function [bbotk::OptimInstance]. +#' [bbotk::ArchiveBatch] of the acquisition function [bbotk::OptimInstanceBatch]. #' Note that setting `n_candidates > 1` is usually not a sensible idea but it is still supported for experimental reasons. -#' Note that in the case of the acquisition function [bbotk::OptimInstance] being multi-criteria, due to using an [AcqFunctionMulti], +#' Note that in the case of the acquisition function [bbotk::OptimInstanceBatch] being multi-criteria, due to using an [AcqFunctionMulti], #' selection of the best candidates is performed via non-dominated-sorting. #' Default is `1`. #' } @@ -89,7 +89,7 @@ AcqOptimizer = R6Class("AcqOptimizer", public = list( - #' @field optimizer ([bbotk::Optimizer]). + #' @field optimizer ([bbotk::OptimizerBatch]). optimizer = NULL, #' @field terminator ([bbotk::Terminator]). @@ -104,7 +104,7 @@ AcqOptimizer = R6Class("AcqOptimizer", #' @description #' Creates a new instance of this [R6][R6::R6Class] class. #' - #' @param optimizer ([bbotk::Optimizer]). + #' @param optimizer ([bbotk::OptimizerBatch]). #' @param terminator ([bbotk::Terminator]). #' @param acq_function (`NULL` | [AcqFunction]). #' @param callbacks (`NULL` | list of [mlr3misc::Callback]) @@ -150,10 +150,10 @@ AcqOptimizer = R6Class("AcqOptimizer", optimize = function() { is_multi_acq_function = self$acq_function$codomain$length > 1L - logger = lgr::get_logger("bbotk") - old_threshold = logger$threshold - logger$set_threshold(self$param_set$values$logging_level) - on.exit(logger$set_threshold(old_threshold)) + lg = lgr::get_logger("bbotk") + old_threshold = lg$threshold + lg$set_threshold(self$param_set$values$logging_level) + on.exit(lg$set_threshold(old_threshold)) if (is_multi_acq_function) { instance = OptimInstanceBatchMultiCrit$new(objective = self$acq_function, search_space = self$acq_function$domain, terminator = self$terminator, check_values = FALSE, callbacks = self$callbacks) @@ -215,11 +215,18 @@ AcqOptimizer = R6Class("AcqOptimizer", # setcolorder(xdt, c(instance$archive$cols_x, "x_domain", instance$objective$id)) #} xdt[, -c("timestamp", "batch_nr")] # drop timestamp and batch_nr information from the candidates + }, + + #' @description + #' Reset the acquisition function optimizer. + #' + #' Currently not used. + reset = function() { + } ), active = list( - #' @template field_print_id print_id = function(rhs) { if (missing(rhs)) { @@ -240,7 +247,6 @@ AcqOptimizer = R6Class("AcqOptimizer", ), private = list( - .param_set = NULL, deep_clone = function(name, value) { diff --git a/R/OptimizerADBO.R b/R/OptimizerADBO.R index 0270cf38..8565cdf0 100644 --- a/R/OptimizerADBO.R +++ b/R/OptimizerADBO.R @@ -5,47 +5,77 @@ #' `OptimizerADBO` class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). #' ADBO is a variant of Asynchronous Model Based Optimization (AMBO) that uses [AcqFunctionStochasticCB] with exponential lambda decay. #' +#' Currently, only single-objective optimization is supported and [OptimizerADBO] is considered an experimental feature and API might be subject to changes. +#' #' @note -#' The lambda parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. +#' The lambda parameter of the confidence bound acquisition function controls the trade-off between exploration and exploitation. #' A large lambda value leads to more exploration, while a small lambda value leads to more exploitation. -#' The initial lambda value is drawn from an exponential distribution with rate `1 / lambda`. -#' ADBO can use periodic exponential decay to reduce lambda periodically with the formula `lambda * exp(-rate * (t %% period))`. -#' The surrogate model is always a random forest and die acquisition optimizer is random search with a budget of 10,000 evaluations. +#' The initial lambda value of the acquisition function used on each worker is drawn from an exponential distribution with rate `1 / lambda`. +#' ADBO can use periodic exponential decay to reduce lambda periodically for a given time step `t` with the formula `lambda * exp(-rate * (t %% period))`. +#' The [SurrogateLearner] is configured to use a random forest and the [AcqOptimizer] is a random search with a batch size of 1000 and a budget of 10000 evaluations. #' #' @section Parameters: #' \describe{ #' \item{`lambda`}{`numeric(1)`\cr -#' Lambda value for sampling from the exponential distribution.} +#' Value used for sampling the lambda for each worker from an exponential distribution.} #' \item{`rate`}{`numeric(1)`\cr #' Rate of the exponential decay.} #' \item{`period`}{`integer(1)`\cr #' Period of the exponential decay.} -#' \item{`initial_design_size`}{`integer(1)`\cr -#' Size of the initial design. -#' Defaults to `100`.} -#' #' \item{`initial_design`}{`data.table::data.table()`\cr #' Initial design of the optimization. -#' If `NULL`, a design of size `design_size` is generated with `design_function`.} +#' If `NULL`, a design of size `design_size` is generated with the specified `design_function`. +#' Default is `NULL`.} #' \item{`design_size`}{`integer(1)`\cr -#' Size of the initial design.} +#' Size of the initial design if it is to be generated. +#' Default is `100`.} #' \item{`design_function`}{`character(1)`\cr -#' Function to generate the initial design. -#' One of `c("random", "sobol", "lhs")`.} +#' Sampling function to generate the initial design. +#' Can be `random` [paradox::generate_design_random], `lhs` [paradox::generate_design_lhs], or `sobol` [paradox::generate_design_sobol]. +#' Default is `sobol`.} #' \item{`n_workers`}{`integer(1)`\cr #' Number of parallel workers. -#' If `NULL`, all rush workers set with [rush::rush_plan()] are used.} +#' If `NULL`, all rush workers specified via [rush::rush_plan()] are used. +#' Default is `NULL`.} #' } #' #' @references #' * `r format_bib("egele_2023")` #' #' @export +#' @examples +#' \donttest{ +#' if (requireNamespace("rush") & +#' requireNamespace("mlr3learners") & +#' requireNamespace("DiceKriging") & +#' requireNamespace("rgenoud")) { +#' +#' library(bbotk) +#' library(paradox) +#' library(mlr3learners) +#' +#' fun = function(xs) { +#' list(y = xs$x ^ 2) +#' } +#' domain = ps(x = p_dbl(lower = -10, upper = 10)) +#' codomain = ps(y = p_dbl(tags = "minimize")) +#' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) +#' +#' instance = OptimInstanceAsyncSingleCrit$new( +#' objective = objective, +#' terminator = trm("evals", n_evals = 10)) +#' +#' rush::rush_plan(n_workers=2) +#' +#' optimizer = opt("adbo", design_size = 4, n_workers = 2) +#' +#' optimizer$optimize(instance) +#' } +#' } OptimizerADBO = R6Class("OptimizerADBO", inherit = OptimizerAsyncMbo, public = list( - #' @description #' Creates a new instance of this [R6][R6::R6Class] class. initialize = function() { @@ -65,26 +95,29 @@ OptimizerADBO = R6Class("OptimizerADBO", lambda = 1.96, rate = 0.1, period = 25L) - } - ), - - private = list( + }, - .optimize = function(inst) { - - self$acq_function = acqf("stochastic_cb", + #' @description + #' Performs the optimization on an [bbotk::OptimInstanceAsyncSingleCrit] until termination. + #' The single evaluations will be written into the [bbotk::ArchiveAsync]. + #' The result will be written into the instance object. + #' + #' @param inst ([bbotk::OptimInstanceAsyncSingleCrit]). + #' @return [data.table::data.table()] + optimize = function(inst) { + self$acq_function = AcqFunctionStochasticCB$new( lambda = self$param_set$values$lambda, rate = self$param_set$values$rate, period = self$param_set$values$period ) - self$acq_optimizer = acqo( + self$surrogate = default_surrogate(inst, force_random_forest = TRUE) + + self$acq_optimizer = AcqOptimizer$new( optimizer = opt("random_search", batch_size = 1000L), terminator = trm("evals", n_evals = 10000L)) - self$surrogate = default_surrogate(inst, force_rf = TRUE) - - super$.optimize(inst) + super$optimize(inst) } ) ) diff --git a/R/OptimizerAsyncMbo.R b/R/OptimizerAsyncMbo.R index fb42fceb..c7b202dc 100644 --- a/R/OptimizerAsyncMbo.R +++ b/R/OptimizerAsyncMbo.R @@ -5,63 +5,121 @@ #' @description #' `OptimizerAsyncMbo` class that implements Asynchronous Model Based Optimization (AMBO). #' AMBO starts multiple sequential MBO runs on different workers. -#' The worker communicate asynchronously through a shared archive. +#' The worker communicate asynchronously through a shared archive relying on the \pkg{rush} package. #' The optimizer follows a modular layout in which the surrogate model, acquisition function, and acquisition optimizer can be changed. -#' The [SurrogateLearnerAsync] class is similar to [SurrogateLearner] but can impute results for ongoing evaluations. -#' The `AcqFunctionStochastic*` classes create varying acquisition functions on each worker, promoting different exploration-exploitation balances. -#' The [AcqOptimizer] classes remain consistent with those used in synchronous MBO. +#' The [SurrogateLearner] will impute missing values due to pending evaluations. +#' A stochastic [AcqFunction], e.g., [AcqFunctionStochasticEI] or [AcqFunctionStochasticCB] is used to create varying versions of the acquisition +#' function on each worker, promoting different exploration-exploitation trade-offs. +#' The [AcqOptimizer] class remains consistent with the one used in synchronous MBO. +#' +#' In contrast to [OptimizerMbo], no [loop_function] can be specified that determines the AMBO flavor as `OptimizerAsyncMbo` simply relies on +#' a surrogate update, acquisition function update and acquisition function optimization step as an internal loop. +#' +#' Currently, only single-objective optimization is supported and `OptimizerAsyncMbo` is considered an experimental feature and API might be subject to changes. +#' +#' Note that in general the [SurrogateLearner] is updated one final time on all available data after the optimization process has terminated. +#' However, in certain scenarios this is not always possible or meaningful. +#' It is therefore recommended to manually inspect the [SurrogateLearner] after optimization if it is to be used, e.g., for visualization purposes to make +#' sure that it has been properly updated on all available data. +#' If this final update of the [SurrogateLearner] could not be performed successfully, a warning will be logged. +#' +#' By specifying a [ResultAssigner], one can alter how the final result is determined after optimization, e.g., +#' simply based on the evaluations logged in the archive [ResultAssignerArchive] or based on the [Surrogate] via [ResultAssignerSurrogate]. +#' +#' @section Archive: +#' The [bbotk::ArchiveAsync] holds the following additional columns that are specific to AMBO algorithms: +#' * `acq_function$id` (`numeric(1)`)\cr +#' The value of the acquisition function. +#' * `".already_evaluated"` (`logical(1))`\cr +#' Whether this point was already evaluated. Depends on the `skip_already_evaluated` parameter of the [AcqOptimizer]. +#' +#' If the [bbotk::ArchiveAsync] does not contain any evaluations prior to optimization, an initial design is needed. +#' If the `initial_design` parameter is specified to be a `data.table`, this data will be used. +#' Otherwise, if it is `NULL`, an initial design of size `design_size` will be generated based on the `generate_design` sampling function. +#' See also the parameters below. #' #' @section Parameters: #' \describe{ #' \item{`initial_design`}{`data.table::data.table()`\cr #' Initial design of the optimization. -#' If `NULL`, a design of size `design_size` is generated with `design_function`.} +#' If `NULL`, a design of size `design_size` is generated with the specified `design_function`. +#' Default is `NULL`.} #' \item{`design_size`}{`integer(1)`\cr -#' Size of the initial design.} +#' Size of the initial design if it is to be generated. +#' Default is `100`.} #' \item{`design_function`}{`character(1)`\cr -#' Function to generate the initial design. -#' One of `c("random", "sobol", "lhs")`.} +#' Sampling function to generate the initial design. +#' Can be `random` [paradox::generate_design_random], `lhs` [paradox::generate_design_lhs], or `sobol` [paradox::generate_design_sobol]. +#' Default is `sobol`.} #' \item{`n_workers`}{`integer(1)`\cr #' Number of parallel workers. -#' If `NULL`, all rush workers set with [rush::rush_plan()] are used.} +#' If `NULL`, all rush workers specified via [rush::rush_plan()] are used. +#' Default is `NULL`.} #' } #' -#' @template param_id -#' @template param_surrogate -#' @template param_acq_function -#' @template param_acq_optimizer -#' @template param_label -#' @template param_man +#' @export +#' @examples +#' \donttest{ +#' if (requireNamespace("rush") & +#' requireNamespace("mlr3learners") & +#' requireNamespace("DiceKriging") & +#' requireNamespace("rgenoud")) { #' -#' @param param_set [paradox::ParamSet]\cr -#' Set of control parameters. +#' library(bbotk) +#' library(paradox) +#' library(mlr3learners) #' -#' @export +#' fun = function(xs) { +#' list(y = xs$x ^ 2) +#' } +#' domain = ps(x = p_dbl(lower = -10, upper = 10)) +#' codomain = ps(y = p_dbl(tags = "minimize")) +#' objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) +#' +#' instance = OptimInstanceAsyncSingleCrit$new( +#' objective = objective, +#' terminator = trm("evals", n_evals = 10)) +#' +#' rush::rush_plan(n_workers=2) +#' +#' optimizer = opt("async_mbo", design_size = 4, n_workers = 2) +#' +#' optimizer$optimize(instance) +#' } +#' } OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", - inherit = OptimizerAsync, + inherit = bbotk::OptimizerAsync, public = list( - #' @description #' Creates a new instance of this [R6][R6::R6Class] class. #' - #' @param param_classes (`character()`)\cr - #' Supported parameter classes that the optimizer can optimize, as given in the [`paradox::ParamSet`] `$class` field. - #' @param properties (`character()`)\cr - #' Set of properties of the optimizer. - #' Must be a subset of [`bbotk_reflections$optimizer_properties`][bbotk_reflections]. - #' @param packages (`character()`)\cr - #' Set of required packages. - #' A warning is signaled by the constructor if at least one of the packages is not installed, but loaded (not attached) later on-demand via [requireNamespace()]. + #' If `surrogate` is `NULL` and the `acq_function$surrogate` field is populated, this [SurrogateLearner] is used. + #' Otherwise, `default_surrogate(instance)` is used. + #' If `acq_function` is `NULL` and the `acq_optimizer$acq_function` field is populated, this [AcqFunction] is used (and therefore its `$surrogate` if populated; see above). + #' Otherwise `default_acqfunction(instance)` is used. + #' If `acq_optimizer` is `NULL`, `default_acqoptimizer(instance)` is used. + #' + #' Even if already initialized, the `surrogate$archive` field will always be overwritten by the [bbotk::ArchiveAsync] of the current [bbotk::OptimInstanceAsyncSingleCrit] to be optimized. + #' + #' For more information on default values for `surrogate`, `acq_function`, `acq_optimizer` and `result_assigner`, see `?mbo_defaults`. + #' + #' @template param_id + #' @template param_surrogate + #' @template param_acq_function + #' @template param_acq_optimizer + #' @template param_result_assigner + #' @template param_label + #' @param param_set ([paradox::ParamSet])\cr + #' Set of control parameters. + #' @template param_man initialize = function( id = "async_mbo", surrogate = NULL, acq_function = NULL, acq_optimizer = NULL, + result_assigner = NULL, param_set = NULL, - param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), - properties = c("dependencies", "single-crit", "async"), - packages = c("mlr3mbo", "rush"), label = "Asynchronous Model Based Optimization", man = "mlr3mbo::OptimizerAsyncMbo" ) { @@ -78,27 +136,84 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", super$initialize("async_mbo", param_set = param_set, - param_classes = param_classes, - properties = properties, - packages = packages, + param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), # is replaced with dynamic AB after construction + properties = c("dependencies", "single-crit"), # is replaced with dynamic AB after construction + packages = c("mlr3mbo", "rush"), # is replaced with dynamic AB after construction label = label, man = man) self$surrogate = assert_r6(surrogate, classes = "Surrogate", null.ok = TRUE) self$acq_function = assert_r6(acq_function, classes = "AcqFunction", null.ok = TRUE) self$acq_optimizer = assert_r6(acq_optimizer, classes = "AcqOptimizer", null.ok = TRUE) + self$result_assigner = assert_r6(result_assigner, classes = "ResultAssigner", null.ok = TRUE) + }, + + #' @description + #' Print method. + #' + #' @return (`character()`). + print = function() { + catn(format(self), if (is.na(self$label)) "" else paste0(": ", self$label)) + #catn(str_indent("* Parameters:", as_short_string(self$param_set$values))) + catn(str_indent("* Parameter classes:", self$param_classes)) + catn(str_indent("* Properties:", self$properties)) + catn(str_indent("* Packages:", self$packages)) + catn(str_indent("* Surrogate:", if (is.null(self$surrogate)) "-" else self$surrogate$print_id)) + catn(str_indent("* Acquisition Function:", if (is.null(self$acq_function)) "-" else class(self$acq_function)[1L])) + catn(str_indent("* Acquisition Function Optimizer:", if (is.null(self$acq_optimizer)) "-" else self$acq_optimizer$print_id)) + catn(str_indent("* Result Assigner:", if (is.null(self$result_assigner)) "-" else class(self$result_assigner)[1L])) }, + #' @description + #' Reset the optimizer. + #' Sets the following fields to `NULL`: + #' `surrogate`, `acq_function`, `acq_optimizer`,`result_assigner` + #' Resets parameter values `design_size` and `design_function` to their defaults. + reset = function() { + private$.surrogate = NULL + private$.acq_function = NULL + private$.acq_optimizer = NULL + private$.result_assigner = NULL + self$param_set$set_values(design_size = 100L, design_function = "sobol") + }, #' @description - #' Performs the optimization on a [OptimInstanceAsyncSingleCrit] or [OptimInstanceAsyncMultiCrit] until termination. - #' The single evaluations will be written into the [ArchiveAsync]. + #' Performs the optimization on an [bbotk::OptimInstanceAsyncSingleCrit] until termination. + #' The single evaluations will be written into the [bbotk::ArchiveAsync]. #' The result will be written into the instance object. #' - #' @param inst ([OptimInstanceAsyncSingleCrit] | [OptimInstanceAsyncMultiCrit]). - #' + #' @param inst ([bbotk::OptimInstanceAsyncSingleCrit]). #' @return [data.table::data.table()] optimize = function(inst) { + if (is.null(self$acq_function)) { + self$acq_function = self$acq_optimizer$acq_function %??% default_acqfunction(inst) + } + + if (is.null(self$surrogate)) { # acq_function$surrogate has precedence + self$surrogate = self$acq_function$surrogate %??% default_surrogate(inst) + } + + if (is.null(self$acq_optimizer)) { + self$acq_optimizer = default_acqoptimizer(self$acq_function) + } + + if (is.null(self$result_assigner)) { + self$result_assigner = default_result_assigner(inst) + } + + self$surrogate$reset() + self$acq_function$reset() + self$acq_optimizer$reset() + + self$surrogate$archive = inst$archive + self$acq_function$surrogate = self$surrogate + self$acq_optimizer$acq_function = self$acq_function + + # FIXME: if result_assigner is for example ResultAssignerSurrogate the surrogate won't be set automatically + + check_packages_installed(self$packages, msg = sprintf("Package '%%s' required but not installed for Optimizer '%s'", format(self))) + + lg = lgr::get_logger("bbotk") pv = self$param_set$values # initial design @@ -129,7 +244,7 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", if (missing(rhs)) { private$.surrogate } else { - private$.surrogate = assert_r6(rhs, classes = "Surrogate", null.ok = TRUE) + private$.surrogate = assert_r6(rhs, classes = "SurrogateLearner", null.ok = TRUE) } }, @@ -151,38 +266,56 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", } }, + #' @template field_result_assigner + result_assigner = function(rhs) { + if (missing(rhs)) { + private$.result_assigner + } else { + private$.result_assigner = assert_r6(rhs, classes = "ResultAssigner", null.ok = TRUE) + } + }, + #' @template field_param_classes param_classes = function(rhs) { - assert_ro_binding(rhs) - param_classes_surrogate = c("logical" = "ParamLgl", "integer" = "ParamInt", "numeric" = "ParamDbl", "factor" = "ParamFct") - if (!is.null(self$surrogate)) { - param_classes_surrogate = param_classes_surrogate[c("logical", "integer", "numeric", "factor") %in% self$surrogate$feature_types] # surrogate has precedence over acq_function$surrogate - } - param_classes_acq_opt = if (!is.null(self$acq_optimizer)) { - self$acq_optimizer$optimizer$param_classes + if (missing(rhs)) { + param_classes_surrogate = c("logical" = "ParamLgl", "integer" = "ParamInt", "numeric" = "ParamDbl", "factor" = "ParamFct") + if (!is.null(self$surrogate)) { + param_classes_surrogate = param_classes_surrogate[c("logical", "integer", "numeric", "factor") %in% self$surrogate$feature_types] # surrogate has precedence over acq_function$surrogate + } + param_classes_acq_opt = if (!is.null(self$acq_optimizer)) { + self$acq_optimizer$optimizer$param_classes + } else { + c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct") + } + unname(intersect(param_classes_surrogate, param_classes_acq_opt)) } else { - c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct") + stop("$param_classes is read-only.") } - unname(intersect(param_classes_surrogate, param_classes_acq_opt)) }, #' @template field_properties properties = function(rhs) { - assert_ro_binding(rhs) - - properties_surrogate = "dependencies" - if (!is.null(self$surrogate)) { - if ("missings" %nin% self$surrogate$properties) { - properties_surrogate = character() + if (missing(rhs)) { + properties_loop_function = "single-crit" + properties_surrogate = "dependencies" + if (!is.null(self$surrogate)) { + if ("missings" %nin% self$surrogate$properties) { + properties_surrogate = character() + } } + unname(c(properties_surrogate, properties_loop_function)) + } else { + stop("$properties is read-only.") } - unname(c(properties_surrogate)) }, #' @template field_packages packages = function(rhs) { - assert_ro_binding(rhs) - union("mlr3mbo", c(self$acq_function$packages, self$surrogate$packages, self$acq_optimizer$optimizer$packages)) + if (missing(rhs)) { + union(c("mlr3mbo", "rush"), c(self$acq_function$packages, self$surrogate$packages, self$acq_optimizer$optimizer$packages, self$result_assigner$packages)) + } else { + stop("$packages is read-only.") + } } ), @@ -190,44 +323,47 @@ OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", .surrogate = NULL, .acq_function = NULL, .acq_optimizer = NULL, + .result_assigner = NULL, .optimize = function(inst) { - pv = self$param_set$values - search_space = inst$search_space - archive = inst$archive - - if (is.null(self$acq_function)) { - self$acq_function = self$acq_optimizer$acq_function %??% default_acqfunction(inst) - } - - if (is.null(self$surrogate)) { - self$surrogate = self$acq_function$surrogate %??% default_surrogate(inst) - } - - if (is.null(self$acq_optimizer)) { - self$acq_optimizer = default_acqoptimizer(self$acq_function) - } - - self$surrogate$archive = inst$archive - self$acq_function$surrogate = self$surrogate - self$acq_optimizer$acq_function = self$acq_function - + lg = lgr::get_logger("bbotk") lg$debug("Optimizer '%s' evaluates the initial design", self$id) get_private(inst)$.eval_queue() - lg$debug("Optimizer '%s' starts the tuning phase", self$id) + lg$debug("Optimizer '%s' starts the optimization phase", self$id) # actual loop while (!inst$is_terminated) { # sample - self$acq_function$surrogate$update() - self$acq_function$update() - xdt = self$acq_optimizer$optimize() - xs = transpose_list(xdt)[[1]] + xs = tryCatch({ + self$acq_function$surrogate$update() + self$acq_function$update() + xdt = self$acq_optimizer$optimize() + transpose_list(xdt)[[1L]] + }, mbo_error = function(mbo_error_condition) { + lg$info(paste0(class(mbo_error_condition), collapse = " / ")) + lg$info("Proposing a randomly sampled point") + xdt = generate_design_random(inst$search_space, n = 1L)$data + transpose_list(xdt)[[1L]] + }) # eval get_private(inst)$.eval_point(xs) } + + on.exit({ + tryCatch( + { + self$surrogate$update() + }, surrogate_update_error = function(error_condition) { + lg$warn("Could not update the surrogate a final time after the optimization process has terminated.") + } + ) + }) + }, + + .assign_result = function(inst) { + self$result_assigner$assign_result(inst) } ) ) diff --git a/R/OptimizerMbo.R b/R/OptimizerMbo.R index fa1c7547..5f4f80b4 100644 --- a/R/OptimizerMbo.R +++ b/R/OptimizerMbo.R @@ -13,7 +13,7 @@ #' #' Detailed descriptions of different MBO flavors are provided in the documentation of the respective [loop_function]. #' -#' Termination is handled via a [bbotk::Terminator] part of the [bbotk::OptimInstance] to be optimized. +#' Termination is handled via a [bbotk::Terminator] part of the [bbotk::OptimInstanceBatch] to be optimized. #' #' Note that in general the [Surrogate] is updated one final time on all available data after the optimization process has terminated. #' However, in certain scenarios this is not always possible or meaningful, e.g., when using [bayesopt_parego()] for multi-objective optimization @@ -22,11 +22,14 @@ #' sure that it has been properly updated on all available data. #' If this final update of the [Surrogate] could not be performed successfully, a warning will be logged. #' +#' By specifying a [ResultAssigner], one can alter how the final result is determined after optimization, e.g., +#' simply based on the evaluations logged in the archive [ResultAssignerArchive] or based on the [Surrogate] via [ResultAssignerSurrogate]. +#' #' @section Archive: -#' The [bbotk::Archive] holds the following additional columns that are specific to MBO algorithms: -#' * `[acq_function$id]` (`numeric(1)`)\cr +#' The [bbotk::ArchiveBatch] holds the following additional columns that are specific to MBO algorithms: +#' * `acq_function$id` (`numeric(1)`)\cr #' The value of the acquisition function. -#' * `.already_evaluated` (`logical(1))`\cr +#' * `".already_evaluated"` (`logical(1))`\cr #' Whether this point was already evaluated. Depends on the `skip_already_evaluated` parameter of the [AcqOptimizer]. #' @export #' @examples @@ -97,13 +100,13 @@ OptimizerMbo = R6Class("OptimizerMbo", #' #' If `surrogate` is `NULL` and the `acq_function$surrogate` field is populated, this [Surrogate] is used. #' Otherwise, `default_surrogate(instance)` is used. - #' If `acq_function` is NULL and the `acq_optimizer$acq_function` field is populated, this [AcqFunction] is used (and therefore its `$surrogate` if populated; see above). + #' If `acq_function` is `NULL` and the `acq_optimizer$acq_function` field is populated, this [AcqFunction] is used (and therefore its `$surrogate` if populated; see above). #' Otherwise `default_acqfunction(instance)` is used. - #' If `acq_optimizer` is NULL, `default_acqoptimizer(instance)` is used. + #' If `acq_optimizer` is `NULL`, `default_acqoptimizer(instance)` is used. #' - #' Even if already initialized, the `surrogate$archive` field will always be overwritten by the [bbotk::Archive] of the current [bbotk::OptimInstance] to be optimized. + #' Even if already initialized, the `surrogate$archive` field will always be overwritten by the [bbotk::ArchiveBatch] of the current [bbotk::OptimInstanceBatch] to be optimized. #' - #' For more information on default values for `loop_function`, `surrogate`, `acq_function` and `acq_optimizer`, see `?mbo_defaults`. + #' For more information on default values for `surrogate`, `acq_function`, `acq_optimizer` and `result_assigner`, see `?mbo_defaults`. #' #' @template param_loop_function #' @template param_surrogate @@ -146,6 +149,7 @@ OptimizerMbo = R6Class("OptimizerMbo", catn(str_indent("* Surrogate:", if (is.null(self$surrogate)) "-" else self$surrogate$print_id)) catn(str_indent("* Acquisition Function:", if (is.null(self$acq_function)) "-" else class(self$acq_function)[1L])) catn(str_indent("* Acquisition Function Optimizer:", if (is.null(self$acq_optimizer)) "-" else self$acq_optimizer$print_id)) + catn(str_indent("* Result Assigner:", if (is.null(self$result_assigner)) "-" else class(self$result_assigner)[1L])) }, #' @description @@ -159,6 +163,50 @@ OptimizerMbo = R6Class("OptimizerMbo", private$.acq_optimizer = NULL private$.args = NULL private$.result_assigner = NULL + }, + + #' @description + #' Performs the optimization and writes optimization result into [bbotk::OptimInstanceBatch]. + #' The optimization result is returned but the complete optimization path is stored in [bbotk::ArchiveBatch] of [bbotk::OptimInstanceBatch]. + #' + #' @param inst ([bbotk::OptimInstanceBatch]). + #' @return [data.table::data.table]. + optimize = function(inst) { + # FIXME: this needs more checks for edge cases like eips or loop_function bayesopt_parego then default_surrogate should use one learner + + if (is.null(self$loop_function)) { + self$loop_function = default_loop_function(inst) + } + + if (is.null(self$acq_function)) { # acq_optimizer$acq_function has precedence + self$acq_function = self$acq_optimizer$acq_function %??% default_acqfunction(inst) + } + + if (is.null(self$surrogate)) { # acq_function$surrogate has precedence + self$surrogate = self$acq_function$surrogate %??% default_surrogate(inst) + } + + if (is.null(self$acq_optimizer)) { + self$acq_optimizer = default_acqoptimizer(self$acq_function) + } + + if (is.null(self$result_assigner)) { + self$result_assigner = default_result_assigner(inst) + } + + self$surrogate$reset() + self$acq_function$reset() + self$acq_optimizer$reset() + + self$surrogate$archive = inst$archive + self$acq_function$surrogate = self$surrogate + self$acq_optimizer$acq_function = self$acq_function + + # FIXME: if result_assigner is for example ResultAssignerSurrogate the surrogate won't be set automatically + + check_packages_installed(self$packages, msg = sprintf("Package '%%s' required but not installed for Optimizer '%s'", format(self))) + + optimize_batch_default(inst, self) } ), @@ -280,36 +328,6 @@ OptimizerMbo = R6Class("OptimizerMbo", .result_assigner = NULL, .optimize = function(inst) { - # FIXME: this needs more checks for edge cases like eips or loop_function bayesopt_parego then default_surrogate should use one learner - - if (is.null(self$loop_function)) { - self$loop_function = default_loop_function(inst) - } - - if (is.null(self$acq_function)) { # acq_optimizer$acq_function has precedence - self$acq_function = self$acq_optimizer$acq_function %??% default_acqfunction(inst) - } - - if (is.null(self$surrogate)) { # acq_function$surrogate has precedence - self$surrogate = self$acq_function$surrogate %??% default_surrogate(inst) - } - - if (is.null(self$acq_optimizer)) { - self$acq_optimizer = default_acqoptimizer(self$acq_function) - } - - if (is.null(self$result_assigner)) { - self$result_assigner = default_result_assigner(inst) - } - - self$surrogate$archive = inst$archive - self$acq_function$surrogate = self$surrogate - self$acq_optimizer$acq_function = self$acq_function - - # FIXME: if result_assigner is for example ResultAssignerSurrogate the surrogate won't be set automatically - - check_packages_installed(self$packages, msg = sprintf("Package '%%s' required but not installed for Optimizer '%s'", format(self))) - invoke(self$loop_function, instance = inst, surrogate = self$surrogate, acq_function = self$acq_function, acq_optimizer = self$acq_optimizer, .args = self$args) on.exit({ @@ -317,8 +335,8 @@ OptimizerMbo = R6Class("OptimizerMbo", { self$surrogate$update() }, surrogate_update_error = function(error_condition) { - logger = lgr::get_logger("bbotk") - logger$warn("Could not update the surrogate a final time after the optimization process has terminated.") + lg = lgr::get_logger("bbotk") + lg$warn("Could not update the surrogate a final time after the optimization process has terminated.") } ) }) diff --git a/R/ResultAssigner.R b/R/ResultAssigner.R index e6f6997f..bcb664a6 100644 --- a/R/ResultAssigner.R +++ b/R/ResultAssigner.R @@ -28,7 +28,7 @@ ResultAssigner = R6Class("ResultAssigner", #' @description #' Assigns the result, i.e., the final point(s) to the instance. #' - #' @param instance ([bbotk::OptimInstanceBatchSingleCrit] | [bbotk::OptimInstanceBatchMultiCrit])\cr + #' @param instance ([bbotk::OptimInstanceBatchSingleCrit] | [bbotk::OptimInstanceBatchMultiCrit] |[bbotk::OptimInstanceAsyncSingleCrit] | [bbotk::OptimInstanceAsyncMultiCrit])\cr #' The [bbotk::OptimInstance] the final result should be assigned to. assign_result = function(instance) { stop("Abstract.") diff --git a/R/ResultAssignerArchive.R b/R/ResultAssignerArchive.R index c9266fdb..bca3d030 100644 --- a/R/ResultAssignerArchive.R +++ b/R/ResultAssignerArchive.R @@ -26,12 +26,12 @@ ResultAssignerArchive = R6Class("ResultAssignerArchive", #' @description #' Assigns the result, i.e., the final point(s) to the instance. #' - #' @param instance ([bbotk::OptimInstanceBatchSingleCrit] | [bbotk::OptimInstanceBatchMultiCrit])\cr + #' @param instance ([bbotk::OptimInstanceBatchSingleCrit] | [bbotk::OptimInstanceBatchMultiCrit] |[bbotk::OptimInstanceAsyncSingleCrit] | [bbotk::OptimInstanceAsyncMultiCrit])\cr #' The [bbotk::OptimInstance] the final result should be assigned to. assign_result = function(instance) { xydt = instance$archive$best() xdt = xydt[, instance$search_space$ids(), with = FALSE] - if (inherits(instance, "OptimInstanceBatchMultiCrit")) { + if (inherits(instance, c("OptimInstanceBatchMultiCrit", "OptimInstanceAsyncMultiCrit"))) { ydt = xydt[, instance$archive$cols_y, with = FALSE] instance$assign_result(xdt, ydt, xydt = xydt) } diff --git a/R/ResultAssignerSurrogate.R b/R/ResultAssignerSurrogate.R index 4870947a..5f509fb0 100644 --- a/R/ResultAssignerSurrogate.R +++ b/R/ResultAssignerSurrogate.R @@ -7,7 +7,7 @@ #' Result assigner that chooses the final point(s) based on a surrogate mean prediction of all evaluated points in the [bbotk::Archive]. #' This is especially useful in the case of noisy objective functions. #' -#' In the case of operating on an [bbotk::OptimInstanceBatchMultiCrit] the [SurrogateLearnerCollection] must use as many learners as there are objective functions. +#' In the case of operating on an [bbotk::OptimInstanceBatchMultiCrit] or [bbotk::OptimInstanceAsyncMultiCrit] the [SurrogateLearnerCollection] must use as many learners as there are objective functions. #' #' @family Result Assigner #' @export @@ -32,15 +32,15 @@ ResultAssignerSurrogate = R6Class("ResultAssignerSurrogate", #' Assigns the result, i.e., the final point(s) to the instance. #' If `$surrogate` is `NULL`, `default_surrogate(instance)` is used and also assigned to `$surrogate`. #' - #' @param instance ([bbotk::OptimInstanceBatchSingleCrit] | [bbotk::OptimInstanceBatchMultiCrit])\cr + #' @param instance ([bbotk::OptimInstanceBatchSingleCrit] | [bbotk::OptimInstanceBatchMultiCrit] |[bbotk::OptimInstanceAsyncSingleCrit] | [bbotk::OptimInstanceAsyncMultiCrit])\cr #' The [bbotk::OptimInstance] the final result should be assigned to. assign_result = function(instance) { if (is.null(self$surrogate)) { self$surrogate = default_surrogate(instance) } - if (inherits(instance, "OptimInstanceBatchSingleCrit")) { + if (inherits(instance, c("OptimInstanceBatchSingleCrit", "OptimInstanceAsyncSingleCrit"))) { assert_r6(self$surrogate, classes = "SurrogateLearner") - } else if (inherits(instance, "OptimInstanceBatchMultiCrit")) { + } else if (inherits(instance, c("OptimInstanceBatchMultiCrit", "OptimInstanceAsyncMultiCrit"))) { assert_r6(self$surrogate, classes = "SurrogateLearnerCollection") if (self$surrogate$n_learner != instance$objective$ydim) { stopf("Surrogate used within the result assigner uses %i learners but the optimization instance has %i objective functions", self$surrogate$n_learner, instance$objective$ydim) @@ -63,9 +63,9 @@ ResultAssignerSurrogate = R6Class("ResultAssignerSurrogate", best = xydt[, archive_tmp$cols_x, with = FALSE] # ys are still the ones originally evaluated - best_y = if (inherits(instance, "OptimInstanceBatchSingleCrit")) { + best_y = if (inherits(instance, c("OptimInstanceBatchSingleCrit", "OptimInstanceAsyncSingleCrit"))) { unlist(archive$data[best, on = archive$cols_x][, archive$cols_y, with = FALSE]) - } else if (inherits(instance, "OptimInstanceBatchMultiCrit")) { + } else if (inherits(instance, c("OptimInstanceBatchMultiCrit", "OptimInstanceAsyncMultiCrit"))) { archive$data[best, on = archive$cols_x][, archive$cols_y, with = FALSE] } instance$assign_result(xdt = best, best_y, xydt = xydt) diff --git a/R/Surrogate.R b/R/Surrogate.R index a3c6e2c0..00d2752f 100644 --- a/R/Surrogate.R +++ b/R/Surrogate.R @@ -36,25 +36,48 @@ Surrogate = R6Class("Surrogate", #' @description #' Train learner with new data. - #' Subclasses must implement `$private.update()`. + #' Subclasses must implement `private.update()` and `private.update_async()`. #' #' @return `NULL`. update = function() { if (is.null(self$archive)) stop("Archive must be set during construction or manually prior before calling $update().") if (self$param_set$values$catch_errors) { - tryCatch(private$.update(), - error = function(error_condition) { - lg$warn(error_condition$message) - stop(set_class(list(message = error_condition$message, call = NULL), - classes = c("surrogate_update_error", "mbo_error", "error", "condition"))) - } - ) + if (self$archive_is_async) { + tryCatch(private$.update_async(), + error = function(error_condition) { + lg$warn(error_condition$message) + stop(set_class(list(message = error_condition$message, call = NULL), + classes = c("surrogate_update_error", "mbo_error", "error", "condition"))) + } + ) + } else { + tryCatch(private$.update(), + error = function(error_condition) { + lg$warn(error_condition$message) + stop(set_class(list(message = error_condition$message, call = NULL), + classes = c("surrogate_update_error", "mbo_error", "error", "condition"))) + } + ) + } } else { - private$.update() + if (self$archive_is_async) { + private$.update_async() + } else { + private$.update() + } } invisible(NULL) }, + #' @description + #' Reset the surrogate model. + #' Subclasses must implement `private$.reset()`. + #' + #' @return `NULL` + reset = function() { + private$.reset() + }, + #' @description #' Predict mean response and standard error. #' Must be implemented by subclasses. @@ -106,6 +129,15 @@ Surrogate = R6Class("Surrogate", } }, + #' @template field_archive_surrogate_is_async + archive_is_async = function(rhs) { + if (missing(rhs)) { + inherits(private$.archive, "ArchiveAsync") + } else { + stop("$archive_is_async is read-only.") + } + }, + #' @template field_n_learner_surrogate n_learner = function() { stop("Abstract.") @@ -207,6 +239,10 @@ Surrogate = R6Class("Surrogate", stop("Abstract.") }, + .update_async = function() { + stop("Abstract.") + }, + deep_clone = function(name, value) { switch(name, .param_set = value$clone(deep = TRUE), diff --git a/R/SurrogateLearner.R b/R/SurrogateLearner.R index fb5681e4..18abc334 100644 --- a/R/SurrogateLearner.R +++ b/R/SurrogateLearner.R @@ -26,6 +26,11 @@ #' the failed acquisition function optimization (as a result of the failed surrogate) appropriately by, e.g., proposing a randomly sampled point for evaluation? #' Default is `TRUE`. #' } +#' \item{`impute_method`}{`character(1)`\cr +#' Method to impute missing values in the case of updating on an asynchronous [bbotk::ArchiveAsync] with pending evaluations. +#' Can be `"mean"` to use mean imputation or `"random"` to sample values uniformly at random between the empirical minimum and maximum. +#' Default is `"random"`. +#' } #' } #' #' @export @@ -87,9 +92,10 @@ SurrogateLearner = R6Class("SurrogateLearner", assert_insample_perf = p_lgl(), perf_measure = p_uty(custom_check = function(x) check_r6(x, classes = "MeasureRegr")), # FIXME: actually want check_measure perf_threshold = p_dbl(lower = -Inf, upper = Inf), - catch_errors = p_lgl() + catch_errors = p_lgl(), + impute_method = p_fct(c("mean", "random"), default = "random") ) - ps$values = list(assert_insample_perf = FALSE, catch_errors = TRUE) + ps$values = list(assert_insample_perf = FALSE, catch_errors = TRUE, impute_method = "random") ps$add_dep("perf_measure", on = "assert_insample_perf", cond = CondEqual$new(TRUE)) ps$add_dep("perf_threshold", on = "assert_insample_perf", cond = CondEqual$new(TRUE)) @@ -226,6 +232,36 @@ SurrogateLearner = R6Class("SurrogateLearner", } }, + # Train learner with new data. + # Operates on an asynchronous archive and performs imputation as needed. + # Also calculates the insample performance based on the `perf_measure` hyperparameter if `assert_insample_perf = TRUE`. + .update_async = function() { + xydt = self$archive$rush$fetch_tasks_with_state(states = c("queued", "running", "finished"))[, c(self$cols_x, self$cols_y, "state"), with = FALSE] + if (self$param_set$values$impute_method == "mean") { + mean_y = mean(xydt[[self$cols_y]], na.rm = TRUE) + xydt[c("queued", "running"), (self$cols_y) := mean_y, on = "state"] + } else if (self$param_set$values$impute_method == "random") { + min_y = min(xydt[[self$cols_y]], na.rm = TRUE) + max_y = max(xydt[[self$cols_y]], na.rm = TRUE) + xydt[c("queued", "running"), (self$cols_y) := runif(.N, min = min_y, max = max_y), on = "state"] + } + set(xydt, j = "state", value = NULL) + + task = TaskRegr$new(id = "surrogate_task", backend = xydt, target = self$cols_y) + assert_learnable(task, learner = self$learner) + self$learner$train(task) + + if (self$param_set$values$assert_insample_perf) { + measure = assert_measure(self$param_set$values$perf_measure %??% mlr_measures$get("regr.rsq"), task = task, learner = self$learner) + private$.insample_perf = self$learner$predict(task)$score(measure, task = task, learner = self$learner) + self$assert_insample_perf + } + }, + + .reset = function() { + self$learner$reset() + }, + deep_clone = function(name, value) { switch(name, learner = value$clone(deep = TRUE), diff --git a/R/SurrogateLearnerAsync.R b/R/SurrogateLearnerAsync.R deleted file mode 100644 index 6b7de4b2..00000000 --- a/R/SurrogateLearnerAsync.R +++ /dev/null @@ -1,89 +0,0 @@ -#' @title Surrogate Model Containing a Single Learner -#' -#' @description -#' Surrogate model containing a single [mlr3::LearnerRegr]. -#' The surrogate model is updated on the [mlr3tuning::ArchiveAsyncTuning]. -#' Unevaluated points are imputed with the mean or random values. -#' -#' @section Parameters: -#' \describe{ -#' \item{`assert_insample_perf`}{`logical(1)`\cr -#' Should the insample performance of the [mlr3::LearnerRegr] be asserted after updating the surrogate? -#' If the assertion fails (i.e., the insample performance based on the `perf_measure` does not meet the -#' `perf_threshold`), an error is thrown. -#' Default is `FALSE`. -#' } -#' \item{`perf_measure`}{[mlr3::MeasureRegr]\cr -#' Performance measure which should be use to assert the insample performance of the [mlr3::LearnerRegr]. -#' Only relevant if `assert_insample_perf = TRUE`. -#' Default is [mlr3::mlr_measures_regr.rsq]. -#' } -#' \item{`perf_threshold`}{`numeric(1)`\cr -#' Threshold the insample performance of the [mlr3::LearnerRegr] should be asserted against. -#' Only relevant if `assert_insample_perf = TRUE`. -#' Default is `0`. -#' } -#' \item{`catch_errors`}{`logical(1)`\cr -#' Should errors during updating the surrogate be caught and propagated to the `loop_function` which can then handle -#' the failed acquisition function optimization (as a result of the failed surrogate) appropriately by, e.g., proposing a randomly sampled point for evaluation? -#' Default is `TRUE`. -#' } -#' \item{`impute_method`}{`character(1)`\cr -#' Method to impute missing values in the surrogate model. -#' } -#' } -#' -#' @export -SurrogateLearnerAsync = R6Class("SurrogateLearnerAsync", - inherit = SurrogateLearner, - - public = list( - - #' @description - #' Creates a new instance of this [R6][R6::R6Class] class. - #' - #' @param learner ([mlr3::LearnerRegr]). - #' @template param_archive_surrogate - #' @template param_col_y_surrogate - #' @template param_cols_x_surrogate - initialize = function(learner, archive = NULL, cols_x = NULL, col_y = NULL) { - super$initialize(learner = learner, archive = archive, cols_x = cols_x, col_y = col_y) - - extra_ps = ps( - impute_method = p_fct(c("mean", "random"), default = "random") - ) - - private$.param_set = c(private$.param_set, extra_ps) - private$.param_set$set_values(impute_method = "random") - } - ), - - private = list( - .update = function() { - xydt = self$archive$rush$fetch_tasks_with_state(states = c("queued", "running", "finished"))[, c(self$cols_x, self$cols_y, "state"), with = FALSE] - - - if (self$param_set$values$impute_method == "mean") { - setnafill(xydt, type = "const", fill = mean(xydt[[self$cols_y]], na.rm = TRUE), cols = self$cols_y) - } else if (self$param_set$values$impute_method == "random") { - walk(self$cols_y, function(col) { - min = min(xydt[[col]], na.rm = TRUE) - max = max(xydt[[col]], na.rm = TRUE) - xydt[c("queued", "running"), (col) := runif(.N, min, max), on = "state"] - }) - } - set(xydt, j = "state", value = NULL) - - task = TaskRegr$new(id = "surrogate_task", backend = xydt, target = self$cols_y) - assert_learnable(task, learner = self$learner) - self$learner$train(task) - - if (self$param_set$values$assert_insample_perf) { - measure = assert_measure(self$param_set$values$perf_measure %??% mlr_measures$get("regr.rsq"), task = task, learner = self$learner) - private$.insample_perf = self$learner$predict(task)$score(measure, task = task, learner = self$learner) - self$assert_insample_perf - } - } - ) -) - diff --git a/R/SurrogateLearnerCollection.R b/R/SurrogateLearnerCollection.R index 7ae73662..74aacff4 100644 --- a/R/SurrogateLearnerCollection.R +++ b/R/SurrogateLearnerCollection.R @@ -28,6 +28,11 @@ #' the failed acquisition function optimization (as a result of the failed surrogate) appropriately by, e.g., proposing a randomly sampled point for evaluation? #' Default is `TRUE`. #' } +#' \item{`impute_method`}{`character(1)`\cr +#' Method to impute missing values in the case of updating on an asynchronous [bbotk::ArchiveAsync] with pending evaluations. +#' Can be `"mean"` to use mean imputation or `"random"` to sample values uniformly at random between the empirical minimum and maximum. +#' Default is `"random"`. +#' } #' } #' #' @export @@ -64,6 +69,8 @@ #' #' surrogate$learner #' +#' surrogate$learner[["y1"]]$model +#' #' surrogate$learner[["y2"]]$model #' } SurrogateLearnerCollection = R6Class("SurrogateLearnerCollection", @@ -100,9 +107,10 @@ SurrogateLearnerCollection = R6Class("SurrogateLearnerCollection", assert_insample_perf = p_lgl(), perf_measures = p_uty(custom_check = function(x) check_list(x, types = "MeasureRegr", any.missing = FALSE, len = length(learners))), # FIXME: actually want check_measures perf_thresholds = p_uty(custom_check = function(x) check_double(x, lower = -Inf, upper = Inf, any.missing = FALSE, len = length(learners))), - catch_errors = p_lgl() + catch_errors = p_lgl(), + impute_method = p_fct(c("mean", "random"), default = "random") ) - ps$values = list(assert_insample_perf = FALSE, catch_errors = TRUE) + ps$values = list(assert_insample_perf = FALSE, catch_errors = TRUE, impute_method = "random") ps$add_dep("perf_measures", on = "assert_insample_perf", cond = CondEqual$new(TRUE)) ps$add_dep("perf_thresholds", on = "assert_insample_perf", cond = CondEqual$new(TRUE)) @@ -259,6 +267,66 @@ SurrogateLearnerCollection = R6Class("SurrogateLearnerCollection", } }, + # Train learner with new data. + # Operates on an asynchronous archive and performs imputation as needed. + # Also calculates the insample performance based on the `perf_measures` hyperparameter if `assert_insample_perf = TRUE`. + .update_async = function() { + assert_true((length(self$cols_y) == length(self$learner)) || length(self$cols_y) == 1L) # either as many cols_y as learner or only one + one_to_multiple = length(self$cols_y) == 1L + + xydt = self$archive$rush$fetch_tasks_with_state(states = c("queued", "running", "finished"))[, c(self$cols_x, self$cols_y, "state"), with = FALSE] + if (self$param_set$values$impute_method == "mean") { + walk(self$cols_y, function(col) { + mean_y = mean(xydt[[col]], na.rm = TRUE) + xydt[c("queued", "running"), (col) := mean_y, on = "state"] + }) + } else if (self$param_set$values$impute_method == "random") { + walk(self$cols_y, function(col) { + min_y = min(xydt[[col]], na.rm = TRUE) + max_y = max(xydt[[col]], na.rm = TRUE) + xydt[c("queued", "running"), (col) := runif(.N, min = min_y, max = max_y), on = "state"] + }) + } + set(xydt, j = "state", value = NULL) + + features = setdiff(names(xydt), self$cols_y) + tasks = lapply(self$cols_y, function(col_y) { + # if this turns out to be a bottleneck, we can also operate on a single task here + task = TaskRegr$new(id = paste0("surrogate_task_", col_y), backend = xydt[, c(features, col_y), with = FALSE], target = col_y) + task + }) + if (one_to_multiple) { + tasks = replicate(length(self$learner), tasks[[1L]]) + } + pmap(list(learner = self$learner, task = tasks), .f = function(learner, task) { + assert_learnable(task, learner = learner) + learner$train(task) + invisible(NULL) + }) + + if (one_to_multiple) { + names(self$learner) = rep(self$cols_y, length(self$learner)) + } else { + names(self$learner) = self$cols_y + } + + if (self$param_set$values$assert_insample_perf) { + private$.insample_perf = setNames(pmap_dbl(list(learner = self$learner, task = tasks, perf_measure = self$param_set$values$perf_measures %??% replicate(self$n_learner, mlr_measures$get("regr.rsq"), simplify = FALSE)), + .f = function(learner, task, perf_measure) { + assert_measure(perf_measure, task = task, learner = learner) + learner$predict(task)$score(perf_measure, task = task, learner = learner) + } + ), nm = map_chr(self$param_set$values$perf_measures, "id")) + self$assert_insample_perf + } + }, + + .reset = function() { + for (learner in self$learner) { + learner$reset() + } + }, + deep_clone = function(name, value) { switch(name, learner = map(value, function(x) x$clone(deep = TRUE)), diff --git a/R/TunerADBO.R b/R/TunerADBO.R index 21e7900c..4ce392f8 100644 --- a/R/TunerADBO.R +++ b/R/TunerADBO.R @@ -1,61 +1,164 @@ -#' @title Asynchronous Decentralized Bayesian Optimization +#' @title TunerAsync using Asynchronous Decentralized Bayesian Optimization #' @name mlr_tuners_adbo #' #' @description #' `TunerADBO` class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). #' ADBO is a variant of Asynchronous Model Based Optimization (AMBO) that uses [AcqFunctionStochasticCB] with exponential lambda decay. -#' -#' @note -#' The lambda parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. -#' A large lambda value leads to more exploration, while a small lambda value leads to more exploitation. -#' The initial lambda value is drawn from an exponential distribution with rate `1 / lambda`. -#' ADBO can use periodic exponential decay to reduce lambda periodically with the formula `lambda * exp(-rate * (t %% period))`. -#' The surrogate model is always a random forest and die acquisition optimizer is random search with a budget of 10,000 evaluations. +#' This is a minimal interface internally passing on to [OptimizerAsyncMbo]. +#' For additional information and documentation see [OptimizerAsyncMbo]. #' #' @section Parameters: #' \describe{ -#' \item{`lambda`}{`numeric(1)`\cr -#' Lambda value for sampling from the exponential distribution.} -#' \item{`rate`}{`numeric(1)`\cr -#' Rate of the exponential decay.} -#' \item{`period`}{`integer(1)`\cr -#' Period of the exponential decay.} -#' \item{`initial_design_size`}{`integer(1)`\cr -#' Size of the initial design. -#' Defaults to `100`.} -#' #' \item{`initial_design`}{`data.table::data.table()`\cr #' Initial design of the optimization. -#' If `NULL`, a design of size `design_size` is generated with `design_function`.} +#' If `NULL`, a design of size `design_size` is generated with the specified `design_function`. +#' Default is `NULL`.} #' \item{`design_size`}{`integer(1)`\cr -#' Size of the initial design.} +#' Size of the initial design if it is to be generated. +#' Default is `100`.} #' \item{`design_function`}{`character(1)`\cr -#' Function to generate the initial design. -#' One of `c("random", "sobol", "lhs")`.} +#' Sampling function to generate the initial design. +#' Can be `random` [paradox::generate_design_random], `lhs` [paradox::generate_design_lhs], or `sobol` [paradox::generate_design_sobol]. +#' Default is `sobol`.} #' \item{`n_workers`}{`integer(1)`\cr #' Number of parallel workers. -#' If `NULL`, all rush workers set with [rush::rush_plan()] are used.} +#' If `NULL`, all rush workers specified via [rush::rush_plan()] are used. +#' Default is `NULL`.} #' } #' -#' #' @references #' * `r format_bib("egele_2023")` #' #' @export +#' @examples +#' \donttest{ +#' if (requireNamespace("rush") & +#' requireNamespace("mlr3learners") & +#' requireNamespace("DiceKriging") & +#' requireNamespace("rgenoud")) { +#' +#' library(mlr3) +#' library(mlr3tuning) +#' +#' # single-objective +#' task = tsk("wine") +#' learner = lrn("classif.rpart", cp = to_tune(lower = 1e-4, upper = 1, logscale = TRUE)) +#' resampling = rsmp("cv", folds = 3) +#' measure = msr("classif.acc") +#' +#' instance = TuningInstanceAsyncSingleCrit$new( +#' task = task, +#' learner = learner, +#' resampling = resampling, +#' measure = measure, +#' terminator = trm("evals", n_evals = 10)) +#' +#' rush::rush_plan(n_workers=2) +#' +#' tnr("adbo", design_size = 4, n_workers = 2)$optimize(instance) +#' } +#' } TunerADBO = R6Class("TunerADBO", inherit = mlr3tuning::TunerAsyncFromOptimizerAsync, + public = list( + #' @description + #' Creates a new instance of this [R6][R6::R6Class] class. + initialize = function() { + optimizer = OptimizerADBO$new() + + super$initialize(optimizer = optimizer, man = "mlr3mbo::TunerADBO") + }, + + #' @description + #' Print method. + #' + #' @return (`character()`). + print = function() { + catn(format(self), if (is.na(self$label)) "" else paste0(": ", self$label)) + #catn(str_indent("* Parameters:", as_short_string(self$param_set$values))) + catn(str_indent("* Parameter classes:", self$param_classes)) + catn(str_indent("* Properties:", self$properties)) + catn(str_indent("* Packages:", self$packages)) + catn(str_indent("* Surrogate:", if (is.null(self$surrogate)) "-" else self$surrogate$print_id)) + catn(str_indent("* Acquisition Function:", if (is.null(self$acq_function)) "-" else class(self$acq_function)[1L])) + catn(str_indent("* Acquisition Function Optimizer:", if (is.null(self$acq_optimizer)) "-" else self$acq_optimizer$print_id)) + catn(str_indent("* Result Assigner:", if (is.null(self$result_assigner)) "-" else class(self$result_assigner)[1L])) + }, + + #' @description + #' Reset the tuner. + #' Sets the following fields to `NULL`: + #' `surrogate`, `acq_function`, `acq_optimizer`, `result_assigner` + #' Resets parameter values `design_size` and `design_function` to their defaults. + reset = function() { + private$.optimizer$reset() + } + ), + + active = list( + #' @template field_surrogate + surrogate = function(rhs) { + if (missing(rhs)) { + private$.optimizer$surrogate + } else { + private$.optimizer$surrogate = assert_r6(rhs, classes = "Surrogate", null.ok = TRUE) + } + }, + + #' @template field_acq_function + acq_function = function(rhs) { + if (missing(rhs)) { + private$.optimizer$acq_function + } else { + private$.optimizer$acq_function = assert_r6(rhs, classes = "AcqFunction", null.ok = TRUE) + } + }, + + #' @template field_acq_optimizer + acq_optimizer = function(rhs) { + if (missing(rhs)) { + private$.optimizer$acq_optimizer + } else { + private$.optimizer$acq_optimizer = assert_r6(rhs, classes = "AcqOptimizer", null.ok = TRUE) + } + }, + + #' @template field_result_assigner + result_assigner = function(rhs) { + if (missing(rhs)) { + private$.optimizer$result_assigner + } else { + private$.optimizer$result_assigner = assert_r6(rhs, classes = "ResultAssigner", null.ok = TRUE) + } + }, + + #' @template field_param_classes + param_classes = function(rhs) { + if (missing(rhs)) { + private$.optimizer$param_classes + } else { + stop("$param_classes is read-only.") + } + }, - #' @description - #' Creates a new instance of this [R6][R6::R6Class] class. - initialize = function() { - optimizer = OptimizerADBO$new() + #' @template field_properties + properties = function(rhs) { + if (missing(rhs)) { + private$.optimizer$properties + } else { + stop("$properties is read-only.") + } + }, - super$initialize( - optimizer = optimizer, - man = "mlr3tuning::mlr_tuners_adbo" - ) - } + #' @template field_packages + packages = function(rhs) { + if (missing(rhs)) { + private$.optimizer$packages + } else { + stop("$packages is read-only.") + } + } ) ) diff --git a/R/TunerAsyncMbo.R b/R/TunerAsyncMbo.R index 70afae28..fe9560d5 100644 --- a/R/TunerAsyncMbo.R +++ b/R/TunerAsyncMbo.R @@ -1,57 +1,175 @@ -#' @title Asynchronous Model Based Tuning +#' @title TunerAsync using Asynchronous Model Based Optimization #' #' @include OptimizerAsyncMbo.R #' @name mlr_tuners_async_mbo #' #' @description -#' `TunerAsyncMbo` class that implements asynchronous Model Based Tuning (MBO). +#' `TunerAsyncMbo` class that implements Asynchronous Model Based Optimization (AMBO). +#' This is a minimal interface internally passing on to [OptimizerAsyncMbo]. +#' For additional information and documentation see [OptimizerAsyncMbo]. #' #' @section Parameters: #' \describe{ #' \item{`initial_design`}{`data.table::data.table()`\cr #' Initial design of the optimization. -#' If `NULL`, a design of size `design_size` is generated with `design_function`.} +#' If `NULL`, a design of size `design_size` is generated with the specified `design_function`. +#' Default is `NULL`.} #' \item{`design_size`}{`integer(1)`\cr -#' Size of the initial design.} +#' Size of the initial design if it is to be generated. +#' Default is `100`.} #' \item{`design_function`}{`character(1)`\cr -#' Function to generate the initial design. -#' One of `c("random", "sobol", "lhs")`.} +#' Sampling function to generate the initial design. +#' Can be `random` [paradox::generate_design_random], `lhs` [paradox::generate_design_lhs], or `sobol` [paradox::generate_design_sobol]. +#' Default is `sobol`.} #' \item{`n_workers`}{`integer(1)`\cr #' Number of parallel workers. -#' If `NULL`, all rush workers set with [rush::rush_plan()] are used.} +#' If `NULL`, all rush workers specified via [rush::rush_plan()] are used. +#' Default is `NULL`.} #' } #' -#' @template param_surrogate -#' @template param_acq_function -#' @template param_acq_optimizer +#' @export +#' @examples +#' \donttest{ +#' if (requireNamespace("rush") & +#' requireNamespace("mlr3learners") & +#' requireNamespace("DiceKriging") & +#' requireNamespace("rgenoud")) { #' -#' @param param_set [paradox::ParamSet]\cr -#' Set of control parameters. +#' library(mlr3) +#' library(mlr3tuning) #' -#' @export +#' # single-objective +#' task = tsk("wine") +#' learner = lrn("classif.rpart", cp = to_tune(lower = 1e-4, upper = 1, logscale = TRUE)) +#' resampling = rsmp("cv", folds = 3) +#' measure = msr("classif.acc") +#' +#' instance = TuningInstanceAsyncSingleCrit$new( +#' task = task, +#' learner = learner, +#' resampling = resampling, +#' measure = measure, +#' terminator = trm("evals", n_evals = 10)) +#' +#' rush::rush_plan(n_workers=2) +#' +#' tnr("async_mbo", design_size = 4, n_workers = 2)$optimize(instance) +#' } +#' } TunerAsyncMbo = R6Class("TunerAsyncMbo", inherit = mlr3tuning::TunerAsyncFromOptimizerAsync, + public = list( + #' @description + #' Creates a new instance of this [R6][R6::R6Class] class. + #' For more information on default values for `surrogate`, `acq_function`, `acq_optimizer`, and `result_assigner`, see `?mbo_defaults`. + #' + #' Note that all the parameters below are simply passed to the [OptimizerAsyncMbo] and + #' the respective fields are simply (settable) active bindings to the fields of the [OptimizerAsyncMbo]. + #' + #' @template param_surrogate + #' @template param_acq_function + #' @template param_acq_optimizer + #' @template param_result_assigner + #' @param param_set ([paradox::ParamSet])\cr + #' Set of control parameters. + initialize = function(surrogate = NULL, acq_function = NULL, acq_optimizer = NULL, param_set = NULL) { + optimizer = OptimizerAsyncMbo$new(surrogate = surrogate, acq_function = acq_function, acq_optimizer = acq_optimizer, param_set = param_set) + + super$initialize(optimizer = optimizer, man = "mlr3mbo::TunerAsyncMbo") + }, - #' @description - #' Creates a new instance of this [R6][R6::R6Class] class. - initialize = function(surrogate = NULL, acq_function = NULL, acq_optimizer = NULL, param_set = NULL) { - optimizer = OptimizerAsyncMbo$new( - surrogate = surrogate, - acq_function = acq_function, - acq_optimizer = acq_optimizer, - param_set = param_set - ) - - super$initialize( - optimizer = optimizer, - man = "mlr3tuning::mlr_tuners_async_mbo" - ) - } + #' @description + #' Print method. + #' + #' @return (`character()`). + print = function() { + catn(format(self), if (is.na(self$label)) "" else paste0(": ", self$label)) + #catn(str_indent("* Parameters:", as_short_string(self$param_set$values))) + catn(str_indent("* Parameter classes:", self$param_classes)) + catn(str_indent("* Properties:", self$properties)) + catn(str_indent("* Packages:", self$packages)) + catn(str_indent("* Surrogate:", if (is.null(self$surrogate)) "-" else self$surrogate$print_id)) + catn(str_indent("* Acquisition Function:", if (is.null(self$acq_function)) "-" else class(self$acq_function)[1L])) + catn(str_indent("* Acquisition Function Optimizer:", if (is.null(self$acq_optimizer)) "-" else self$acq_optimizer$print_id)) + catn(str_indent("* Result Assigner:", if (is.null(self$result_assigner)) "-" else class(self$result_assigner)[1L])) + }, + + #' @description + #' Reset the tuner. + #' Sets the following fields to `NULL`: + #' `surrogate`, `acq_function`, `acq_optimizer`, `result_assigner` + #' Resets parameter values `design_size` and `design_function` to their defaults. + reset = function() { + private$.optimizer$reset() + } + ), + + active = list( + #' @template field_surrogate + surrogate = function(rhs) { + if (missing(rhs)) { + private$.optimizer$surrogate + } else { + private$.optimizer$surrogate = assert_r6(rhs, classes = "Surrogate", null.ok = TRUE) + } + }, + + #' @template field_acq_function + acq_function = function(rhs) { + if (missing(rhs)) { + private$.optimizer$acq_function + } else { + private$.optimizer$acq_function = assert_r6(rhs, classes = "AcqFunction", null.ok = TRUE) + } + }, + + #' @template field_acq_optimizer + acq_optimizer = function(rhs) { + if (missing(rhs)) { + private$.optimizer$acq_optimizer + } else { + private$.optimizer$acq_optimizer = assert_r6(rhs, classes = "AcqOptimizer", null.ok = TRUE) + } + }, + + #' @template field_result_assigner + result_assigner = function(rhs) { + if (missing(rhs)) { + private$.optimizer$result_assigner + } else { + private$.optimizer$result_assigner = assert_r6(rhs, classes = "ResultAssigner", null.ok = TRUE) + } + }, + + #' @template field_param_classes + param_classes = function(rhs) { + if (missing(rhs)) { + private$.optimizer$param_classes + } else { + stop("$param_classes is read-only.") + } + }, + + #' @template field_properties + properties = function(rhs) { + if (missing(rhs)) { + private$.optimizer$properties + } else { + stop("$properties is read-only.") + } + }, + + #' @template field_packages + packages = function(rhs) { + if (missing(rhs)) { + private$.optimizer$packages + } else { + stop("$packages is read-only.") + } + } ) ) -mlr_tuners$add("async_mbo", TunerAsyncMbo) - #' @include aaa.R tuners[["async_mbo"]] = TunerAsyncMbo diff --git a/R/TunerMbo.R b/R/TunerMbo.R index fe778ea9..25a804a0 100644 --- a/R/TunerMbo.R +++ b/R/TunerMbo.R @@ -1,5 +1,6 @@ #' @title TunerBatch using Model Based Optimization #' +#' @include OptimizerMbo.R #' @name mlr_tuners_mbo #' #' @description @@ -55,7 +56,7 @@ TunerMbo = R6Class("TunerMbo", public = list( #' @description #' Creates a new instance of this [R6][R6::R6Class] class. - #' For more information on default values for `loop_function`, `surrogate`, `acq_function` and `acq_optimizer`, see `?mbo_defaults`. + #' For more information on default values for `loop_function`, `surrogate`, `acq_function`, `acq_optimizer`, and `result_assigner`, see `?mbo_defaults`. #' #' Note that all the parameters below are simply passed to the [OptimizerMbo] and #' the respective fields are simply (settable) active bindings to the fields of the [OptimizerMbo]. @@ -67,7 +68,8 @@ TunerMbo = R6Class("TunerMbo", #' @template param_args #' @template param_result_assigner initialize = function(loop_function = NULL, surrogate = NULL, acq_function = NULL, acq_optimizer = NULL, args = NULL, result_assigner = NULL) { - super$initialize(optimizer = OptimizerMbo$new(loop_function = loop_function, surrogate = surrogate, acq_function = acq_function, acq_optimizer = acq_optimizer, args = args, result_assigner = result_assigner), man = "mlr3mbo::TunerMbo") + optimizer = OptimizerMbo$new(loop_function = loop_function, surrogate = surrogate, acq_function = acq_function, acq_optimizer = acq_optimizer, args = args, result_assigner = result_assigner) + super$initialize(optimizer = optimizer, man = "mlr3mbo::TunerMbo") }, #' @description @@ -84,6 +86,7 @@ TunerMbo = R6Class("TunerMbo", catn(str_indent("* Surrogate:", if (is.null(self$surrogate)) "-" else self$surrogate$print_id)) catn(str_indent("* Acquisition Function:", if (is.null(self$acq_function)) "-" else class(self$acq_function)[1L])) catn(str_indent("* Acquisition Function Optimizer:", if (is.null(self$acq_optimizer)) "-" else self$acq_optimizer$print_id)) + catn(str_indent("* Result Assigner:", if (is.null(self$result_assigner)) "-" else class(self$result_assigner)[1L])) }, #' @description diff --git a/R/bayesopt_ego.R b/R/bayesopt_ego.R index ca3d5736..5dff5359 100644 --- a/R/bayesopt_ego.R +++ b/R/bayesopt_ego.R @@ -14,7 +14,7 @@ #' The [bbotk::OptimInstanceBatchSingleCrit] to be optimized. #' @param init_design_size (`NULL` | `integer(1)`)\cr #' Size of the initial design. -#' If `NULL` and the [bbotk::Archive] contains no evaluations, \code{4 * d} is used with \code{d} being the +#' If `NULL` and the [bbotk::ArchiveBatch] contains no evaluations, \code{4 * d} is used with \code{d} being the #' dimensionality of the search space. #' Points are generated via a Sobol sequence. #' @param surrogate ([Surrogate])\cr @@ -34,7 +34,7 @@ #' @note #' * The `acq_function$surrogate`, even if already populated, will always be overwritten by the `surrogate`. #' * The `acq_optimizer$acq_function`, even if already populated, will always be overwritten by `acq_function`. -#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::Archive] of the [bbotk::OptimInstanceBatchSingleCrit]. +#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::ArchiveBatch] of the [bbotk::OptimInstanceBatchSingleCrit]. #' #' @return invisible(instance)\cr #' The original instance is modified in-place and returned invisible. diff --git a/R/bayesopt_emo.R b/R/bayesopt_emo.R index c1e96ec8..e6c97baf 100644 --- a/R/bayesopt_emo.R +++ b/R/bayesopt_emo.R @@ -15,7 +15,7 @@ #' The [bbotk::OptimInstanceBatchMultiCrit] to be optimized. #' @param init_design_size (`NULL` | `integer(1)`)\cr #' Size of the initial design. -#' If `NULL` and the [bbotk::Archive] contains no evaluations, \code{4 * d} is used with \code{d} being the +#' If `NULL` and the [bbotk::ArchiveBatch] contains no evaluations, \code{4 * d} is used with \code{d} being the #' dimensionality of the search space. #' Points are generated via a Sobol sequence. #' @param surrogate ([SurrogateLearnerCollection])\cr @@ -34,7 +34,7 @@ #' @note #' * The `acq_function$surrogate`, even if already populated, will always be overwritten by the `surrogate`. #' * The `acq_optimizer$acq_function`, even if already populated, will always be overwritten by `acq_function`. -#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::Archive] of the [bbotk::OptimInstanceBatchMultiCrit]. +#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::ArchiveBatch] of the [bbotk::OptimInstanceBatchMultiCrit]. #' #' @return invisible(instance)\cr #' The original instance is modified in-place and returned invisible. diff --git a/R/bayesopt_mpcl.R b/R/bayesopt_mpcl.R index 59b4850e..13cc2186 100644 --- a/R/bayesopt_mpcl.R +++ b/R/bayesopt_mpcl.R @@ -16,7 +16,7 @@ #' The [bbotk::OptimInstanceBatchSingleCrit] to be optimized. #' @param init_design_size (`NULL` | `integer(1)`)\cr #' Size of the initial design. -#' If `NULL` and the [bbotk::Archive] contains no evaluations, \code{4 * d} is used with \code{d} being the +#' If `NULL` and the [bbotk::ArchiveBatch] contains no evaluations, \code{4 * d} is used with \code{d} being the #' dimensionality of the search space. #' Points are generated via a Sobol sequence. #' @param surrogate ([Surrogate])\cr @@ -42,7 +42,7 @@ #' @note #' * The `acq_function$surrogate`, even if already populated, will always be overwritten by the `surrogate`. #' * The `acq_optimizer$acq_function`, even if already populated, will always be overwritten by `acq_function`. -#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::Archive] of the [bbotk::OptimInstanceBatchSingleCrit]. +#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::ArchiveBatch] of the [bbotk::OptimInstanceBatchSingleCrit]. #' * To make use of parallel evaluations in the case of `q > 1, the objective #' function of the [bbotk::OptimInstanceBatchSingleCrit] must be implemented accordingly. #' diff --git a/R/bayesopt_parego.R b/R/bayesopt_parego.R index 744eecb2..c89d9145 100644 --- a/R/bayesopt_parego.R +++ b/R/bayesopt_parego.R @@ -15,7 +15,7 @@ #' The [bbotk::OptimInstanceBatchMultiCrit] to be optimized. #' @param init_design_size (`NULL` | `integer(1)`)\cr #' Size of the initial design. -#' If `NULL` and the [bbotk::Archive] contains no evaluations, \code{4 * d} is used with \code{d} being the +#' If `NULL` and the [bbotk::ArchiveBatch] contains no evaluations, \code{4 * d} is used with \code{d} being the #' dimensionality of the search space. #' Points are generated via a Sobol sequence. #' @param surrogate ([SurrogateLearner])\cr @@ -44,9 +44,9 @@ #' @note #' * The `acq_function$surrogate`, even if already populated, will always be overwritten by the `surrogate`. #' * The `acq_optimizer$acq_function`, even if already populated, will always be overwritten by `acq_function`. -#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::Archive] of the [bbotk::OptimInstanceBatchMultiCrit]. +#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::ArchiveBatch] of the [bbotk::OptimInstanceBatchMultiCrit]. #' * The scalarizations of the objective function values are stored as the `y_scal` column in the -#' [bbotk::Archive] of the [bbotk::OptimInstanceBatchMultiCrit]. +#' [bbotk::ArchiveBatch] of the [bbotk::OptimInstanceBatchMultiCrit]. #' * To make use of parallel evaluations in the case of `q > 1, the objective #' function of the [bbotk::OptimInstanceBatchMultiCrit] must be implemented accordingly. #' diff --git a/R/bayesopt_smsego.R b/R/bayesopt_smsego.R index 3f7ee08f..8d53eae9 100644 --- a/R/bayesopt_smsego.R +++ b/R/bayesopt_smsego.R @@ -14,7 +14,7 @@ #' The [bbotk::OptimInstanceBatchMultiCrit] to be optimized. #' @param init_design_size (`NULL` | `integer(1)`)\cr #' Size of the initial design. -#' If `NULL` and the [bbotk::Archive] contains no evaluations, \code{4 * d} is used with \code{d} being the +#' If `NULL` and the [bbotk::ArchiveBatch] contains no evaluations, \code{4 * d} is used with \code{d} being the #' dimensionality of the search space. #' Points are generated via a Sobol sequence. #' @param surrogate ([SurrogateLearnerCollection])\cr @@ -33,7 +33,7 @@ #' @note #' * The `acq_function$surrogate`, even if already populated, will always be overwritten by the `surrogate`. #' * The `acq_optimizer$acq_function`, even if already populated, will always be overwritten by `acq_function`. -#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::Archive] of the [bbotk::OptimInstanceBatchMultiCrit]. +#' * The `surrogate$archive`, even if already populated, will always be overwritten by the [bbotk::ArchiveBatch] of the [bbotk::OptimInstanceBatchMultiCrit]. #' * Due to the iterative computation of the epsilon within the [mlr_acqfunctions_smsego], requires the [bbotk::Terminator] of #' the [bbotk::OptimInstanceBatchMultiCrit] to be a [bbotk::TerminatorEvals]. #' diff --git a/R/bibentries.R b/R/bibentries.R index e6ae6909..467d7879 100644 --- a/R/bibentries.R +++ b/R/bibentries.R @@ -111,7 +111,7 @@ bibentries = c( title = "A Multicriteria Generalization of Bayesian Global Optimization", author = "Emmerich, Michael and Yang, Kaifeng and Deutz, Andr{\\'e} and Wang, Hao and Fonseca, Carlos M.", editor = "Pardalos, Panos M. and Zhigljavsky, Anatoly and {\\v{Z}}ilinskas, Julius", - bookTitle = "Advances in Stochastic and Deterministic Global Optimization", + booktitle = "Advances in Stochastic and Deterministic Global Optimization", year = "2016", publisher = "Springer International Publishing", address = "Cham", @@ -127,11 +127,11 @@ bibentries = c( pages = "90--103" ), - egele_2023 = bibentry("article", - title = "Asynchronous Decentralized Bayesian Optimization for Large Scale Hyperparameter Optimization", - author = "Romain Egele and Isabelle Guyon and Venkatram Vishwanath and Prasanna Balaprakash", - year = "2023", - journal = "arXiv", - url = "https://arxiv.org/abs/2207.00479" + egele_2023 = bibentry("inproceedings", + title = "Asynchronous Decentralized Bayesian Optimization for Large Scale Hyperparameter Optimization", + author = "Egel{\\'e}, Romain and Guyon, Isabelle and Vishwanath, Venkatram and Balaprakash, Prasanna", + booktitle = "2023 IEEE 19th International Conference on e-Science (e-Science)", + year = "2023", + pages = "1--10" ) ) diff --git a/R/mbo_defaults.R b/R/mbo_defaults.R index 9627071f..5ec06411 100644 --- a/R/mbo_defaults.R +++ b/R/mbo_defaults.R @@ -31,6 +31,8 @@ default_loop_function = function(instance) { bayesopt_ego } else if (inherits(instance, "OptimInstanceBatchMultiCrit")) { bayesopt_smsego + } else { + stopf("There are no loop functions for %s.", class(instance)[1L]) } } @@ -127,11 +129,8 @@ default_rf = function(noisy = FALSE) { #' In the case of dependencies, the following learner is used as a fallback: #' \code{lrn("regr.featureless")}. #' -#' If the instance is of class [bbotk::OptimInstanceBatchSingleCrit] the learner is wrapped as a -#' [SurrogateLearner]. -#' -#' If the instance is of class [bbotk::OptimInstanceBatchMultiCrit] multiple deep clones of the learner are -#' wrapped as a [SurrogateLearnerCollection]. +#' If `n_learner` is `1`, the learner is wrapped as a [SurrogateLearner]. +#' Otherwise, if `n_learner` is larger than `1`, multiple deep clones of the learner are wrapped as a [SurrogateLearnerCollection]. #' #' @references #' * `r format_bib("ding_2010")` @@ -141,21 +140,21 @@ default_rf = function(noisy = FALSE) { #' @param learner (`NULL` | [mlr3::Learner]). #' If specified, this learner will be used instead of the defaults described above. #' @param n_learner (`NULL` | `integer(1)`). -#' Number of learners to be considered in the construction of the [SurrogateLearner] or [SurrogateLearnerCollection]. +#' Number of learners to be considered in the construction of the [Surrogate]. #' If not specified will be based on the number of objectives as stated by the instance. -#' @param force_rf (`logical(1)`). +#' @param force_random_forest (`logical(1)`). #' If `TRUE`, a random forest is constructed even if the parameter space is numeric-only. #' @return [Surrogate] #' @family mbo_defaults #' @export -default_surrogate = function(instance, learner = NULL, n_learner = NULL, force_rf = FALSE) { - assert_multi_class(instance, c("OptimInstance", "OptimInstanceAsync")) +default_surrogate = function(instance, learner = NULL, n_learner = NULL, force_random_forest = FALSE) { + assert_multi_class(instance, c("OptimInstance", "OptimInstanceBatch", "OptimInstanceAsync")) assert_r6(learner, "Learner", null.ok = TRUE) assert_int(n_learner, lower = 1L, null.ok = TRUE) noisy = "noisy" %in% instance$objective$properties if (is.null(learner)) { - is_mixed_space = !all(instance$search_space$class %in% c("ParamDbl", "ParamInt")) || force_rf + is_mixed_space = !all(instance$search_space$class %in% c("ParamDbl", "ParamInt")) || force_random_forest has_deps = nrow(instance$search_space$deps) > 0L learner = if (!is_mixed_space) { default_gp(noisy) @@ -190,11 +189,9 @@ default_surrogate = function(instance, learner = NULL, n_learner = NULL, force_r } if (is.null(n_learner)) n_learner = length(instance$archive$cols_y) - if (n_learner == 1L && inherits(instance, "OptimInstanceBatch")) { + if (n_learner == 1L) { SurrogateLearner$new(learner) - } else if (n_learner == 1L && inherits(instance, "OptimInstanceAsync")) { - SurrogateLearnerAsync$new(learner) - } else { + } else { learners = replicate(n_learner, learner$clone(deep = TRUE), simplify = FALSE) SurrogateLearnerCollection$new(learners) } @@ -204,10 +201,12 @@ default_surrogate = function(instance, learner = NULL, n_learner = NULL, force_r #' #' @description #' Chooses a default acquisition function, i.e. the criterion used to propose future points. -#' For single-objective optimization, defaults to [mlr_acqfunctions_ei]. -#' For multi-objective optimization, defaults to [mlr_acqfunctions_smsego]. +#' For synchronous single-objective optimization, defaults to [mlr_acqfunctions_ei]. +#' For synchronous multi-objective optimization, defaults to [mlr_acqfunctions_smsego]. +#' For asynchronous single-objective optimization, defaults to [mlr_acqfunctions_stochastic_cb]. #' #' @param instance ([bbotk::OptimInstance]). +#' An object that inherits from [bbotk::OptimInstance]. #' @return [AcqFunction] #' @family mbo_defaults #' @export @@ -219,6 +218,8 @@ default_acqfunction = function(instance) { AcqFunctionStochasticCB$new() } else if (inherits(instance, "OptimInstanceBatchMultiCrit")) { AcqFunctionSmsEgo$new() + } else if (inherits(instance, "OptimInstanceAsyncMultiCrit")) { + stopf("Currently, there is no default acquisition function for %s.", class(instance)[1L]) } } diff --git a/R/sugar.R b/R/sugar.R index 0cc43de3..5dee7717 100644 --- a/R/sugar.R +++ b/R/sugar.R @@ -108,8 +108,8 @@ acqfs = function(.keys, ...) { #' @description #' This function allows to construct an [AcqOptimizer] in the spirit #' of `mlr_sugar` from \CRANpkg{mlr3}. -#' @param optimizer ([bbotk::Optimizer])\cr -#' [bbotk::Optimizer] that is to be used. +#' @param optimizer ([bbotk::OptimizerBatch])\cr +#' [bbotk::OptimizerBatch] that is to be used. #' @param terminator ([bbotk::Terminator])\cr #' [bbotk::Terminator] that is to be used. #' @param acq_function (`NULL` | [AcqFunction])\cr From 86f2e2870471fc2f6634ae81eca9ebd181b9cfa7 Mon Sep 17 00:00:00 2001 From: Lennart Schneider Date: Fri, 15 Nov 2024 08:35:18 +0100 Subject: [PATCH 48/49] man and tests: fix docs and test --- DESCRIPTION | 1 - NAMESPACE | 1 - .../field_archive_surrogate_is_async.R | 2 + man-roxygen/field_properties.R | 2 +- man/AcqFunction.Rd | 13 ++ man/AcqOptimizer.Rd | 23 ++- man/ResultAssigner.Rd | 2 +- man/Surrogate.Rd | 20 ++- man/SurrogateLearner.Rd | 6 + man/SurrogateLearnerAsync.Rd | 106 ------------ man/SurrogateLearnerCollection.Rd | 8 + man/acqo.Rd | 4 +- man/default_acqfunction.Rd | 8 +- man/default_surrogate.Rd | 18 ++- man/mlr_acqfunctions_aei.Rd | 1 + man/mlr_acqfunctions_cb.Rd | 1 + man/mlr_acqfunctions_ehvi.Rd | 1 + man/mlr_acqfunctions_ehvigh.Rd | 1 + man/mlr_acqfunctions_ei.Rd | 1 + man/mlr_acqfunctions_eips.Rd | 1 + man/mlr_acqfunctions_mean.Rd | 1 + man/mlr_acqfunctions_multi.Rd | 3 +- man/mlr_acqfunctions_pi.Rd | 1 + man/mlr_acqfunctions_sd.Rd | 1 + man/mlr_acqfunctions_smsego.Rd | 23 ++- man/mlr_acqfunctions_stochastic_cb.Rd | 78 +++++++-- man/mlr_acqfunctions_stochastic_ei.Rd | 62 ++++++- man/mlr_loop_functions_ego.Rd | 4 +- man/mlr_loop_functions_emo.Rd | 4 +- man/mlr_loop_functions_mpcl.Rd | 4 +- man/mlr_loop_functions_parego.Rd | 6 +- man/mlr_loop_functions_smsego.Rd | 4 +- man/mlr_optimizers_adbo.Rd | 92 ++++++++--- man/mlr_optimizers_async_mbo.Rd | 153 ++++++++++++++---- man/mlr_optimizers_mbo.Rd | 44 +++-- man/mlr_result_assigners_archive.Rd | 2 +- man/mlr_result_assigners_surrogate.Rd | 4 +- man/mlr_tuners_adbo.Rd | 132 +++++++++++---- man/mlr_tuners_async_mbo.Rd | 118 ++++++++++++-- man/mlr_tuners_mbo.Rd | 4 +- tests/testthat/test_AcqFunctionSmsEgo.R | 5 +- tests/testthat/test_AcqFunctionStochasticCB.R | 50 +++--- tests/testthat/test_AcqFunctionStochasticEI.R | 32 ++-- tests/testthat/test_OptimizerADBO.R | 12 +- tests/testthat/test_OptimizerAsyncMbo.R | 20 +-- tests/testthat/test_SurrogateLearner.R | 3 +- .../test_SurrogateLearnerCollection.R | 3 +- tests/testthat/test_TunerADBO.R | 17 +- tests/testthat/test_TunerAsyncMbo.R | 17 +- 49 files changed, 786 insertions(+), 333 deletions(-) create mode 100644 man-roxygen/field_archive_surrogate_is_async.R delete mode 100644 man/SurrogateLearnerAsync.Rd diff --git a/DESCRIPTION b/DESCRIPTION index b0d0955b..47a032c2 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -102,7 +102,6 @@ Collate: 'ResultAssignerSurrogate.R' 'Surrogate.R' 'SurrogateLearner.R' - 'SurrogateLearnerAsync.R' 'SurrogateLearnerCollection.R' 'TunerADBO.R' 'TunerAsyncMbo.R' diff --git a/NAMESPACE b/NAMESPACE index fde8732f..e5b96257 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -27,7 +27,6 @@ export(ResultAssignerArchive) export(ResultAssignerSurrogate) export(Surrogate) export(SurrogateLearner) -export(SurrogateLearnerAsync) export(SurrogateLearnerCollection) export(TunerADBO) export(TunerAsyncMbo) diff --git a/man-roxygen/field_archive_surrogate_is_async.R b/man-roxygen/field_archive_surrogate_is_async.R new file mode 100644 index 00000000..f835cec9 --- /dev/null +++ b/man-roxygen/field_archive_surrogate_is_async.R @@ -0,0 +1,2 @@ +#' @field archive_is_async (`bool(1)``)\cr +#' Whether the [bbotk::Archive] is an asynchronous one. diff --git a/man-roxygen/field_properties.R b/man-roxygen/field_properties.R index c9fc652b..299eb381 100644 --- a/man-roxygen/field_properties.R +++ b/man-roxygen/field_properties.R @@ -2,4 +2,4 @@ #' Set of properties of the optimizer. #' Must be a subset of [`bbotk_reflections$optimizer_properties`][bbotk::bbotk_reflections]. #' MBO in principle is very flexible and by default we assume that the optimizer has all properties. -#' When fully initialized, properties are determined based on the `loop_function` and `surrogate`. +#' When fully initialized, properties are determined based on the loop, e.g., the `loop_function`, and `surrogate`. diff --git a/man/AcqFunction.Rd b/man/AcqFunction.Rd index d102b453..24f48a24 100644 --- a/man/AcqFunction.Rd +++ b/man/AcqFunction.Rd @@ -69,6 +69,7 @@ Set of required packages.} \itemize{ \item \href{#method-AcqFunction-new}{\code{AcqFunction$new()}} \item \href{#method-AcqFunction-update}{\code{AcqFunction$update()}} +\item \href{#method-AcqFunction-reset}{\code{AcqFunction$reset()}} \item \href{#method-AcqFunction-eval_many}{\code{AcqFunction$eval_many()}} \item \href{#method-AcqFunction-eval_dt}{\code{AcqFunction$eval_dt()}} \item \href{#method-AcqFunction-clone}{\code{AcqFunction$clone()}} @@ -147,6 +148,18 @@ Can be implemented by subclasses. \if{html}{\out{
    }}\preformatted{AcqFunction$update()}\if{html}{\out{
    }} } +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AcqFunction-reset}{}}} +\subsection{Method \code{reset()}}{ +Reset the acquisition function. + +Can be implemented by subclasses. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{AcqFunction$reset()}\if{html}{\out{
    }} +} + } \if{html}{\out{
    }} \if{html}{\out{}} diff --git a/man/AcqOptimizer.Rd b/man/AcqOptimizer.Rd index 035c6d6e..ed484834 100644 --- a/man/AcqOptimizer.Rd +++ b/man/AcqOptimizer.Rd @@ -5,7 +5,7 @@ \title{Acquisition Function Optimizer} \description{ Optimizer for \link{AcqFunction}s which performs the acquisition function optimization. -Wraps an \link[bbotk:Optimizer]{bbotk::Optimizer} and \link[bbotk:Terminator]{bbotk::Terminator}. +Wraps an \link[bbotk:OptimizerBatch]{bbotk::OptimizerBatch} and \link[bbotk:Terminator]{bbotk::Terminator}. } \section{Parameters}{ @@ -14,9 +14,9 @@ Wraps an \link[bbotk:Optimizer]{bbotk::Optimizer} and \link[bbotk:Terminator]{bb Number of candidate points to propose. Note that this does not affect how the acquisition function itself is calculated (e.g., setting \code{n_candidates > 1} will not result in computing the q- or multi-Expected Improvement) but rather the top \code{n_candidates} are selected from the -\link[bbotk:Archive]{bbotk::Archive} of the acquisition function \link[bbotk:OptimInstance]{bbotk::OptimInstance}. +\link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} of the acquisition function \link[bbotk:OptimInstanceBatch]{bbotk::OptimInstanceBatch}. Note that setting \code{n_candidates > 1} is usually not a sensible idea but it is still supported for experimental reasons. -Note that in the case of the acquisition function \link[bbotk:OptimInstance]{bbotk::OptimInstance} being multi-criteria, due to using an \link{AcqFunctionMulti}, +Note that in the case of the acquisition function \link[bbotk:OptimInstanceBatch]{bbotk::OptimInstanceBatch} being multi-criteria, due to using an \link{AcqFunctionMulti}, selection of the best candidates is performed via non-dominated-sorting. Default is \code{1}. } @@ -94,7 +94,7 @@ if (requireNamespace("mlr3learners") & \section{Public fields}{ \if{html}{\out{
    }} \describe{ -\item{\code{optimizer}}{(\link[bbotk:Optimizer]{bbotk::Optimizer}).} +\item{\code{optimizer}}{(\link[bbotk:OptimizerBatch]{bbotk::OptimizerBatch}).} \item{\code{terminator}}{(\link[bbotk:Terminator]{bbotk::Terminator}).} @@ -122,6 +122,7 @@ Set of hyperparameters.} \item \href{#method-AcqOptimizer-format}{\code{AcqOptimizer$format()}} \item \href{#method-AcqOptimizer-print}{\code{AcqOptimizer$print()}} \item \href{#method-AcqOptimizer-optimize}{\code{AcqOptimizer$optimize()}} +\item \href{#method-AcqOptimizer-reset}{\code{AcqOptimizer$reset()}} \item \href{#method-AcqOptimizer-clone}{\code{AcqOptimizer$clone()}} } } @@ -137,7 +138,7 @@ Creates a new instance of this \link[R6:R6Class]{R6} class. \subsection{Arguments}{ \if{html}{\out{
    }} \describe{ -\item{\code{optimizer}}{(\link[bbotk:Optimizer]{bbotk::Optimizer}).} +\item{\code{optimizer}}{(\link[bbotk:OptimizerBatch]{bbotk::OptimizerBatch}).} \item{\code{terminator}}{(\link[bbotk:Terminator]{bbotk::Terminator}).} @@ -186,6 +187,18 @@ Optimize the acquisition function. \subsection{Returns}{ \code{\link[data.table:data.table]{data.table::data.table()}} with 1 row per candidate. } +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AcqOptimizer-reset}{}}} +\subsection{Method \code{reset()}}{ +Reset the acquisition function optimizer. + +Currently not used. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{AcqOptimizer$reset()}\if{html}{\out{
    }} +} + } \if{html}{\out{
    }} \if{html}{\out{}} diff --git a/man/ResultAssigner.Rd b/man/ResultAssigner.Rd index 01e0211a..864d27a6 100644 --- a/man/ResultAssigner.Rd +++ b/man/ResultAssigner.Rd @@ -74,7 +74,7 @@ Assigns the result, i.e., the final point(s) to the instance. \subsection{Arguments}{ \if{html}{\out{
    }} \describe{ -\item{\code{instance}}{(\link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} | \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit})\cr +\item{\code{instance}}{(\link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} | \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} |\link[bbotk:OptimInstanceAsyncSingleCrit]{bbotk::OptimInstanceAsyncSingleCrit} | \link[bbotk:OptimInstanceAsyncMultiCrit]{bbotk::OptimInstanceAsyncMultiCrit})\cr The \link[bbotk:OptimInstance]{bbotk::OptimInstance} the final result should be assigned to.} } \if{html}{\out{
    }} diff --git a/man/Surrogate.Rd b/man/Surrogate.Rd index db339406..03a341a8 100644 --- a/man/Surrogate.Rd +++ b/man/Surrogate.Rd @@ -25,6 +25,9 @@ Id used when printing.} \item{\code{archive}}{(\link[bbotk:Archive]{bbotk::Archive} | \code{NULL})\cr \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstance]{bbotk::OptimInstance}.} +\item{\code{archive_is_async}}{(`bool(1)``)\cr +Whether the \link[bbotk:Archive]{bbotk::Archive} is an asynchronous one.} + \item{\code{n_learner}}{(\code{integer(1)})\cr Returns the number of surrogate models.} @@ -67,6 +70,7 @@ Retrieves the currently active predict type, e.g. \code{"response"}.} \itemize{ \item \href{#method-Surrogate-new}{\code{Surrogate$new()}} \item \href{#method-Surrogate-update}{\code{Surrogate$update()}} +\item \href{#method-Surrogate-reset}{\code{Surrogate$reset()}} \item \href{#method-Surrogate-predict}{\code{Surrogate$predict()}} \item \href{#method-Surrogate-format}{\code{Surrogate$format()}} \item \href{#method-Surrogate-print}{\code{Surrogate$print()}} @@ -110,7 +114,7 @@ Parameter space description depending on the subclass.} \if{latex}{\out{\hypertarget{method-Surrogate-update}{}}} \subsection{Method \code{update()}}{ Train learner with new data. -Subclasses must implement \verb{$private.update()}. +Subclasses must implement \code{private.update()} and \code{private.update_async()}. \subsection{Usage}{ \if{html}{\out{
    }}\preformatted{Surrogate$update()}\if{html}{\out{
    }} } @@ -120,6 +124,20 @@ Subclasses must implement \verb{$private.update()}. } } \if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Surrogate-reset}{}}} +\subsection{Method \code{reset()}}{ +Reset the surrogate model. +Subclasses must implement \code{private$.reset()}. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{Surrogate$reset()}\if{html}{\out{
    }} +} + +\subsection{Returns}{ +\code{NULL} +} +} +\if{html}{\out{
    }} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-Surrogate-predict}{}}} \subsection{Method \code{predict()}}{ diff --git a/man/SurrogateLearner.Rd b/man/SurrogateLearner.Rd index 27f6f69d..b2531b5b 100644 --- a/man/SurrogateLearner.Rd +++ b/man/SurrogateLearner.Rd @@ -30,6 +30,11 @@ Should errors during updating the surrogate be caught and propagated to the \cod the failed acquisition function optimization (as a result of the failed surrogate) appropriately by, e.g., proposing a randomly sampled point for evaluation? Default is \code{TRUE}. } +\item{\code{impute_method}}{\code{character(1)}\cr +Method to impute missing values in the case of updating on an asynchronous \link[bbotk:ArchiveAsync]{bbotk::ArchiveAsync} with pending evaluations. +Can be \code{"mean"} to use mean imputation or \code{"random"} to sample values uniformly at random between the empirical minimum and maximum. +Default is \code{"random"}. +} } } @@ -110,6 +115,7 @@ Retrieves the currently active predict type, e.g. \code{"response"}.} diff --git a/man/SurrogateLearnerAsync.Rd b/man/SurrogateLearnerAsync.Rd deleted file mode 100644 index 5a2b4303..00000000 --- a/man/SurrogateLearnerAsync.Rd +++ /dev/null @@ -1,106 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/SurrogateLearnerAsync.R -\name{SurrogateLearnerAsync} -\alias{SurrogateLearnerAsync} -\title{Surrogate Model Containing a Single Learner} -\description{ -Surrogate model containing a single \link[mlr3:LearnerRegr]{mlr3::LearnerRegr}. -The surrogate model is updated on the \link[mlr3tuning:ArchiveAsyncTuning]{mlr3tuning::ArchiveAsyncTuning}. -Unevaluated points are imputed with the mean or random values. -} -\section{Parameters}{ - -\describe{ -\item{\code{assert_insample_perf}}{\code{logical(1)}\cr -Should the insample performance of the \link[mlr3:LearnerRegr]{mlr3::LearnerRegr} be asserted after updating the surrogate? -If the assertion fails (i.e., the insample performance based on the \code{perf_measure} does not meet the -\code{perf_threshold}), an error is thrown. -Default is \code{FALSE}. -} -\item{\code{perf_measure}}{\link[mlr3:MeasureRegr]{mlr3::MeasureRegr}\cr -Performance measure which should be use to assert the insample performance of the \link[mlr3:LearnerRegr]{mlr3::LearnerRegr}. -Only relevant if \code{assert_insample_perf = TRUE}. -Default is \link[mlr3:mlr_measures_regr.rsq]{mlr3::mlr_measures_regr.rsq}. -} -\item{\code{perf_threshold}}{\code{numeric(1)}\cr -Threshold the insample performance of the \link[mlr3:LearnerRegr]{mlr3::LearnerRegr} should be asserted against. -Only relevant if \code{assert_insample_perf = TRUE}. -Default is \code{0}. -} -\item{\code{catch_errors}}{\code{logical(1)}\cr -Should errors during updating the surrogate be caught and propagated to the \code{loop_function} which can then handle -the failed acquisition function optimization (as a result of the failed surrogate) appropriately by, e.g., proposing a randomly sampled point for evaluation? -Default is \code{TRUE}. -} -\item{\code{impute_method}}{\code{character(1)}\cr -Method to impute missing values in the surrogate model. -} -} -} - -\section{Super classes}{ -\code{\link[mlr3mbo:Surrogate]{mlr3mbo::Surrogate}} -> \code{\link[mlr3mbo:SurrogateLearner]{mlr3mbo::SurrogateLearner}} -> \code{SurrogateLearnerAsync} -} -\section{Methods}{ -\subsection{Public methods}{ -\itemize{ -\item \href{#method-SurrogateLearnerAsync-new}{\code{SurrogateLearnerAsync$new()}} -\item \href{#method-SurrogateLearnerAsync-clone}{\code{SurrogateLearnerAsync$clone()}} -} -} -\if{html}{\out{ -
    Inherited methods - -
    -}} -\if{html}{\out{
    }} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-SurrogateLearnerAsync-new}{}}} -\subsection{Method \code{new()}}{ -Creates a new instance of this \link[R6:R6Class]{R6} class. -\subsection{Usage}{ -\if{html}{\out{
    }}\preformatted{SurrogateLearnerAsync$new(learner, archive = NULL, cols_x = NULL, col_y = NULL)}\if{html}{\out{
    }} -} - -\subsection{Arguments}{ -\if{html}{\out{
    }} -\describe{ -\item{\code{learner}}{(\link[mlr3:LearnerRegr]{mlr3::LearnerRegr}).} - -\item{\code{archive}}{(\link[bbotk:Archive]{bbotk::Archive} | \code{NULL})\cr -\link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstance]{bbotk::OptimInstance}.} - -\item{\code{cols_x}}{(\code{character()} | \code{NULL})\cr -Column id's of variables that should be used as features. -By default, automatically inferred based on the archive.} - -\item{\code{col_y}}{(\code{character(1)} | \code{NULL})\cr -Column id of variable that should be used as a target. -By default, automatically inferred based on the archive.} -} -\if{html}{\out{
    }} -} -} -\if{html}{\out{
    }} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-SurrogateLearnerAsync-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
    }}\preformatted{SurrogateLearnerAsync$clone(deep = FALSE)}\if{html}{\out{
    }} -} - -\subsection{Arguments}{ -\if{html}{\out{
    }} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
    }} -} -} -} diff --git a/man/SurrogateLearnerCollection.Rd b/man/SurrogateLearnerCollection.Rd index 83783625..74c232a7 100644 --- a/man/SurrogateLearnerCollection.Rd +++ b/man/SurrogateLearnerCollection.Rd @@ -32,6 +32,11 @@ Should errors during updating the surrogate be caught and propagated to the \cod the failed acquisition function optimization (as a result of the failed surrogate) appropriately by, e.g., proposing a randomly sampled point for evaluation? Default is \code{TRUE}. } +\item{\code{impute_method}}{\code{character(1)}\cr +Method to impute missing values in the case of updating on an asynchronous \link[bbotk:ArchiveAsync]{bbotk::ArchiveAsync} with pending evaluations. +Can be \code{"mean"} to use mean imputation or \code{"random"} to sample values uniformly at random between the empirical minimum and maximum. +Default is \code{"random"}. +} } } @@ -68,6 +73,8 @@ if (requireNamespace("mlr3learners") & surrogate$learner + surrogate$learner[["y1"]]$model + surrogate$learner[["y2"]]$model } } @@ -116,6 +123,7 @@ Retrieves the currently active predict type, e.g. \code{"response"}.} diff --git a/man/acqo.Rd b/man/acqo.Rd index 43114a0c..b697cf1c 100644 --- a/man/acqo.Rd +++ b/man/acqo.Rd @@ -7,8 +7,8 @@ acqo(optimizer, terminator, acq_function = NULL, callbacks = NULL, ...) } \arguments{ -\item{optimizer}{(\link[bbotk:Optimizer]{bbotk::Optimizer})\cr -\link[bbotk:Optimizer]{bbotk::Optimizer} that is to be used.} +\item{optimizer}{(\link[bbotk:OptimizerBatch]{bbotk::OptimizerBatch})\cr +\link[bbotk:OptimizerBatch]{bbotk::OptimizerBatch} that is to be used.} \item{terminator}{(\link[bbotk:Terminator]{bbotk::Terminator})\cr \link[bbotk:Terminator]{bbotk::Terminator} that is to be used.} diff --git a/man/default_acqfunction.Rd b/man/default_acqfunction.Rd index 66f6ddaa..e7b53720 100644 --- a/man/default_acqfunction.Rd +++ b/man/default_acqfunction.Rd @@ -7,15 +7,17 @@ default_acqfunction(instance) } \arguments{ -\item{instance}{(\link[bbotk:OptimInstance]{bbotk::OptimInstance}).} +\item{instance}{(\link[bbotk:OptimInstance]{bbotk::OptimInstance}). +An object that inherits from \link[bbotk:OptimInstance]{bbotk::OptimInstance}.} } \value{ \link{AcqFunction} } \description{ Chooses a default acquisition function, i.e. the criterion used to propose future points. -For single-objective optimization, defaults to \link{mlr_acqfunctions_ei}. -For multi-objective optimization, defaults to \link{mlr_acqfunctions_smsego}. +For synchronous single-objective optimization, defaults to \link{mlr_acqfunctions_ei}. +For synchronous multi-objective optimization, defaults to \link{mlr_acqfunctions_smsego}. +For asynchronous single-objective optimization, defaults to \link{mlr_acqfunctions_stochastic_cb}. } \seealso{ Other mbo_defaults: diff --git a/man/default_surrogate.Rd b/man/default_surrogate.Rd index 839793c3..c783861e 100644 --- a/man/default_surrogate.Rd +++ b/man/default_surrogate.Rd @@ -4,7 +4,12 @@ \alias{default_surrogate} \title{Default Surrogate} \usage{ -default_surrogate(instance, learner = NULL, n_learner = NULL, force_rf = FALSE) +default_surrogate( + instance, + learner = NULL, + n_learner = NULL, + force_random_forest = FALSE +) } \arguments{ \item{instance}{(\link[bbotk:OptimInstance]{bbotk::OptimInstance})\cr @@ -14,10 +19,10 @@ An object that inherits from \link[bbotk:OptimInstance]{bbotk::OptimInstance}.} If specified, this learner will be used instead of the defaults described above.} \item{n_learner}{(\code{NULL} | \code{integer(1)}). -Number of learners to be considered in the construction of the \link{SurrogateLearner} or \link{SurrogateLearnerCollection}. +Number of learners to be considered in the construction of the \link{Surrogate}. If not specified will be based on the number of objectives as stated by the instance.} -\item{force_rf}{(\code{logical(1)}). +\item{force_random_forest}{(\code{logical(1)}). If \code{TRUE}, a random forest is constructed even if the parameter space is numeric-only.} } \value{ @@ -47,11 +52,8 @@ Out of range imputation makes sense for tree-based methods and is usually hard t In the case of dependencies, the following learner is used as a fallback: \code{lrn("regr.featureless")}. -If the instance is of class \link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} the learner is wrapped as a -\link{SurrogateLearner}. - -If the instance is of class \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} multiple deep clones of the learner are -wrapped as a \link{SurrogateLearnerCollection}. +If \code{n_learner} is \code{1}, the learner is wrapped as a \link{SurrogateLearner}. +Otherwise, if \code{n_learner} is larger than \code{1}, multiple deep clones of the learner are wrapped as a \link{SurrogateLearnerCollection}. } \references{ \itemize{ diff --git a/man/mlr_acqfunctions_aei.Rd b/man/mlr_acqfunctions_aei.Rd index dfdbee55..7c7c2c79 100644 --- a/man/mlr_acqfunctions_aei.Rd +++ b/man/mlr_acqfunctions_aei.Rd @@ -127,6 +127,7 @@ This corresponds to the \code{nugget} estimate when using a \link[mlr3learners:m
  • bbotk::Objective$print()
  • mlr3mbo::AcqFunction$eval_dt()
  • mlr3mbo::AcqFunction$eval_many()
  • +
  • mlr3mbo::AcqFunction$reset()
  • }} diff --git a/man/mlr_acqfunctions_cb.Rd b/man/mlr_acqfunctions_cb.Rd index 9cf4ab51..72493345 100644 --- a/man/mlr_acqfunctions_cb.Rd +++ b/man/mlr_acqfunctions_cb.Rd @@ -102,6 +102,7 @@ Other Acquisition Function:
  • bbotk::Objective$print()
  • mlr3mbo::AcqFunction$eval_dt()
  • mlr3mbo::AcqFunction$eval_many()
  • +
  • mlr3mbo::AcqFunction$reset()
  • mlr3mbo::AcqFunction$update()
  • diff --git a/man/mlr_acqfunctions_ehvi.Rd b/man/mlr_acqfunctions_ehvi.Rd index 32650a7d..895e84e8 100644 --- a/man/mlr_acqfunctions_ehvi.Rd +++ b/man/mlr_acqfunctions_ehvi.Rd @@ -106,6 +106,7 @@ Signs are corrected with respect to assuming minimization of objectives.}
  • bbotk::Objective$print()
  • mlr3mbo::AcqFunction$eval_dt()
  • mlr3mbo::AcqFunction$eval_many()
  • +
  • mlr3mbo::AcqFunction$reset()
  • }} diff --git a/man/mlr_acqfunctions_ehvigh.Rd b/man/mlr_acqfunctions_ehvigh.Rd index f122a903..cc7e5a52 100644 --- a/man/mlr_acqfunctions_ehvigh.Rd +++ b/man/mlr_acqfunctions_ehvigh.Rd @@ -125,6 +125,7 @@ Nodes are scaled by a factor of \code{sqrt(2)} and weights are normalized under
  • bbotk::Objective$print()
  • mlr3mbo::AcqFunction$eval_dt()
  • mlr3mbo::AcqFunction$eval_many()
  • +
  • mlr3mbo::AcqFunction$reset()
  • }} diff --git a/man/mlr_acqfunctions_ei.Rd b/man/mlr_acqfunctions_ei.Rd index dd7bc9d9..d07624be 100644 --- a/man/mlr_acqfunctions_ei.Rd +++ b/man/mlr_acqfunctions_ei.Rd @@ -115,6 +115,7 @@ In the case of maximization, this already includes the necessary change of sign.
  • bbotk::Objective$print()
  • mlr3mbo::AcqFunction$eval_dt()
  • mlr3mbo::AcqFunction$eval_many()
  • +
  • mlr3mbo::AcqFunction$reset()
  • }} diff --git a/man/mlr_acqfunctions_eips.Rd b/man/mlr_acqfunctions_eips.Rd index ba79851b..fd7df6d9 100644 --- a/man/mlr_acqfunctions_eips.Rd +++ b/man/mlr_acqfunctions_eips.Rd @@ -121,6 +121,7 @@ In the case of maximization, this already includes the necessary change of sign.
  • bbotk::Objective$print()
  • mlr3mbo::AcqFunction$eval_dt()
  • mlr3mbo::AcqFunction$eval_many()
  • +
  • mlr3mbo::AcqFunction$reset()
  • }} diff --git a/man/mlr_acqfunctions_mean.Rd b/man/mlr_acqfunctions_mean.Rd index e201f9b2..cd6a7400 100644 --- a/man/mlr_acqfunctions_mean.Rd +++ b/man/mlr_acqfunctions_mean.Rd @@ -87,6 +87,7 @@ Other Acquisition Function:
  • bbotk::Objective$print()
  • mlr3mbo::AcqFunction$eval_dt()
  • mlr3mbo::AcqFunction$eval_many()
  • +
  • mlr3mbo::AcqFunction$reset()
  • mlr3mbo::AcqFunction$update()
  • diff --git a/man/mlr_acqfunctions_multi.Rd b/man/mlr_acqfunctions_multi.Rd index d3e78c09..4cd43abd 100644 --- a/man/mlr_acqfunctions_multi.Rd +++ b/man/mlr_acqfunctions_multi.Rd @@ -14,7 +14,7 @@ the surrogate is the same for all acquisition functions. If acquisition functions have not been initialized with a surrogate, the surrogate passed during construction or lazy initialization will be used for all acquisition functions. -For optimization, \link{AcqOptimizer} can be used as for any other \link{AcqFunction}, however, the \link[bbotk:Optimizer]{bbotk::Optimizer} wrapped within the \link{AcqOptimizer} +For optimization, \link{AcqOptimizer} can be used as for any other \link{AcqFunction}, however, the \link[bbotk:OptimizerBatch]{bbotk::OptimizerBatch} wrapped within the \link{AcqOptimizer} must support multi-objective optimization as indicated via the \code{multi-crit} property. } \section{Dictionary}{ @@ -115,6 +115,7 @@ Points to the ids of the individual acquisition functions.}
  • bbotk::Objective$print()
  • mlr3mbo::AcqFunction$eval_dt()
  • mlr3mbo::AcqFunction$eval_many()
  • +
  • mlr3mbo::AcqFunction$reset()
  • }} diff --git a/man/mlr_acqfunctions_pi.Rd b/man/mlr_acqfunctions_pi.Rd index a7b8f179..a388c9f8 100644 --- a/man/mlr_acqfunctions_pi.Rd +++ b/man/mlr_acqfunctions_pi.Rd @@ -104,6 +104,7 @@ In the case of maximization, this already includes the necessary change of sign.
  • bbotk::Objective$print()
  • mlr3mbo::AcqFunction$eval_dt()
  • mlr3mbo::AcqFunction$eval_many()
  • +
  • mlr3mbo::AcqFunction$reset()
  • }} diff --git a/man/mlr_acqfunctions_sd.Rd b/man/mlr_acqfunctions_sd.Rd index d80fe16b..c40e4515 100644 --- a/man/mlr_acqfunctions_sd.Rd +++ b/man/mlr_acqfunctions_sd.Rd @@ -87,6 +87,7 @@ Other Acquisition Function:
  • bbotk::Objective$print()
  • mlr3mbo::AcqFunction$eval_dt()
  • mlr3mbo::AcqFunction$eval_many()
  • +
  • mlr3mbo::AcqFunction$reset()
  • mlr3mbo::AcqFunction$update()
  • diff --git a/man/mlr_acqfunctions_smsego.Rd b/man/mlr_acqfunctions_smsego.Rd index 53de144f..4e4118b5 100644 --- a/man/mlr_acqfunctions_smsego.Rd +++ b/man/mlr_acqfunctions_smsego.Rd @@ -23,6 +23,15 @@ described in Horn et al. (2015). } } +\section{Note}{ + +\itemize{ +\item This acquisition function always also returns its current epsilon values in a list column (\code{acq_epsilon}). +This value will be logged into the \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} of the \link[bbotk:OptimInstanceBatch]{bbotk::OptimInstanceBatch} of the \link{AcqOptimizer} and +therefore also in the \link[bbotk:Archive]{bbotk::Archive} of the actual \link[bbotk:OptimInstance]{bbotk::OptimInstance} that is to be optimized. +} +} + \examples{ if (requireNamespace("mlr3learners") & requireNamespace("DiceKriging") & @@ -104,7 +113,7 @@ Epsilon used for the additive epsilon dominance.} \item{\code{progress}}{(\code{numeric(1)})\cr Optimization progress (typically, the number of function evaluations left). -Note that this requires the \link[bbotk:OptimInstance]{bbotk::OptimInstance} to be terminated via a \link[bbotk:mlr_terminators_evals]{bbotk::TerminatorEvals}.} +Note that this requires the \link[bbotk:OptimInstanceBatch]{bbotk::OptimInstanceBatch} to be terminated via a \link[bbotk:mlr_terminators_evals]{bbotk::TerminatorEvals}.} } \if{html}{\out{
    }} } @@ -113,6 +122,7 @@ Note that this requires the \link[bbotk:OptimInstance]{bbotk::OptimInstance} to \itemize{ \item \href{#method-AcqFunctionSmsEgo-new}{\code{AcqFunctionSmsEgo$new()}} \item \href{#method-AcqFunctionSmsEgo-update}{\code{AcqFunctionSmsEgo$update()}} +\item \href{#method-AcqFunctionSmsEgo-reset}{\code{AcqFunctionSmsEgo$reset()}} \item \href{#method-AcqFunctionSmsEgo-clone}{\code{AcqFunctionSmsEgo$clone()}} } } @@ -158,6 +168,17 @@ Update the acquisition function and set \code{ys_front}, \code{ref_point} and \c \if{html}{\out{
    }}\preformatted{AcqFunctionSmsEgo$update()}\if{html}{\out{
    }} } +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AcqFunctionSmsEgo-reset}{}}} +\subsection{Method \code{reset()}}{ +Reset the acquisition function. +Resets \code{epsilon}. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{AcqFunctionSmsEgo$reset()}\if{html}{\out{
    }} +} + } \if{html}{\out{
    }} \if{html}{\out{}} diff --git a/man/mlr_acqfunctions_stochastic_cb.Rd b/man/mlr_acqfunctions_stochastic_cb.Rd index 6ab76688..19255f06 100644 --- a/man/mlr_acqfunctions_stochastic_cb.Rd +++ b/man/mlr_acqfunctions_stochastic_cb.Rd @@ -6,8 +6,11 @@ \title{Acquisition Function Stochastic Confidence Bound} \description{ Lower / Upper Confidence Bound with lambda sampling and decay. -The initial lambda value is drawn from an uniform distribution between \code{min_lambda} and \code{max_lambda} or from an exponential distribution with rate \code{1 / lambda}. -The lambda value is updated after each evaluation by the formula \code{lambda * exp(-rate * (t \%\% period))}. +The initial \eqn{\lambda} is drawn from an uniform distribution between \code{min_lambda} and \code{max_lambda} or from an exponential distribution with rate \code{1 / lambda}. +\eqn{\lambda} is updated after each update by the formula \code{lambda * exp(-rate * (t \%\% period))}, where \code{t} is the number of times the acquisition function has been updated. + +While this acquisition function usually would be used within an asynchronous optimizer, e.g., \link{OptimizerAsyncMbo}, +it can in principle also be used in synchronous optimizers, e.g., \link{OptimizerMbo}. } \section{Dictionary}{ @@ -23,16 +26,16 @@ acqf("stochastic_cb") \itemize{ \item \code{"lambda"} (\code{numeric(1)})\cr -Lambda value for sampling from the exponential distribution. +\eqn{\lambda} value for sampling from the exponential distribution. Defaults to \code{1.96}. \item \code{"min_lambda"} (\code{numeric(1)})\cr -Minimum value of lambda for sampling from the uniform distribution. +Minimum value of \eqn{\lambda}for sampling from the uniform distribution. Defaults to \code{0.01}. \item \code{"max_lambda"} (\code{numeric(1)})\cr -Maximum value of lambda for sampling from the uniform distribution. +Maximum value of \eqn{\lambda} for sampling from the uniform distribution. Defaults to \code{10}. \item \code{"distribution"} (\code{character(1)})\cr -Distribution to sample lambda from. +Distribution to sample \eqn{\lambda} from. One of \code{c("uniform", "exponential")}. Defaults to \code{uniform}. \item \code{"rate"} (\code{numeric(1)})\cr @@ -40,19 +43,60 @@ Rate of the exponential decay. Defaults to \code{0} i.e. no decay. \item \code{"period"} (\code{integer(1)})\cr Period of the exponential decay. -Defaults to \code{NULL} i.e. the decay has no period. +Defaults to \code{NULL}, i.e., the decay has no period. +} +} + +\section{Note}{ + +\itemize{ +\item This acquisition function always also returns its current (\code{acq_lambda}) and original (\code{acq_lambda_0}) \eqn{\lambda}. +These values will be logged into the \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} of the \link[bbotk:OptimInstanceBatch]{bbotk::OptimInstanceBatch} of the \link{AcqOptimizer} and +therefore also in the \link[bbotk:Archive]{bbotk::Archive} of the actual \link[bbotk:OptimInstance]{bbotk::OptimInstance} that is to be optimized. } } +\examples{ +if (requireNamespace("mlr3learners") & + requireNamespace("DiceKriging") & + requireNamespace("rgenoud")) { + library(bbotk) + library(paradox) + library(mlr3learners) + library(data.table) + + fun = function(xs) { + list(y = xs$x ^ 2) + } + domain = ps(x = p_dbl(lower = -10, upper = 10)) + codomain = ps(y = p_dbl(tags = "minimize")) + objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) + + instance = OptimInstanceBatchSingleCrit$new( + objective = objective, + terminator = trm("evals", n_evals = 5)) + + instance$eval_batch(data.table(x = c(-6, -5, 3, 9))) + + learner = default_gp() + + surrogate = srlrn(learner, archive = instance$archive) + + acq_function = acqf("stochastic_cb", surrogate = surrogate, lambda = 3) + + acq_function$surrogate$update() + acq_function$update() + acq_function$eval_dt(data.table(x = c(-1, 0, 1))) +} +} \references{ \itemize{ \item Snoek, Jasper, Larochelle, Hugo, Adams, P R (2012). \dQuote{Practical Bayesian Optimization of Machine Learning Algorithms.} In Pereira F, Burges CJC, Bottou L, Weinberger KQ (eds.), \emph{Advances in Neural Information Processing Systems}, volume 25, 2951--2959. -\item Egele R, Guyon I, Vishwanath V, Balaprakash P (2023). +\item Egelé, Romain, Guyon, Isabelle, Vishwanath, Venkatram, Balaprakash, Prasanna (2023). \dQuote{Asynchronous Decentralized Bayesian Optimization for Large Scale Hyperparameter Optimization.} -\emph{arXiv}. -\url{https://arxiv.org/abs/2207.00479}. +In \emph{2023 IEEE 19th International Conference on e-Science (e-Science)}, 1--10. } } \seealso{ @@ -81,6 +125,7 @@ Other Acquisition Function: \itemize{ \item \href{#method-AcqFunctionStochasticCB-new}{\code{AcqFunctionStochasticCB$new()}} \item \href{#method-AcqFunctionStochasticCB-update}{\code{AcqFunctionStochasticCB$update()}} +\item \href{#method-AcqFunctionStochasticCB-reset}{\code{AcqFunctionStochasticCB$reset()}} \item \href{#method-AcqFunctionStochasticCB-clone}{\code{AcqFunctionStochasticCB$clone()}} } } @@ -128,7 +173,7 @@ Creates a new instance of this \link[R6:R6Class]{R6} class. \item{\code{rate}}{(\code{numeric(1)}).} -\item{\code{period}}{(\code{integer(1)}).} +\item{\code{period}}{(\code{NULL} | \code{integer(1)}).} } \if{html}{\out{
    }} } @@ -143,6 +188,17 @@ Samples and decays lambda. \if{html}{\out{
    }}\preformatted{AcqFunctionStochasticCB$update()}\if{html}{\out{
    }} } +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AcqFunctionStochasticCB-reset}{}}} +\subsection{Method \code{reset()}}{ +Reset the acquisition function. +Resets the private update counter \code{.t} used within the epsilon decay. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{AcqFunctionStochasticCB$reset()}\if{html}{\out{
    }} +} + } \if{html}{\out{
    }} \if{html}{\out{}} diff --git a/man/mlr_acqfunctions_stochastic_ei.Rd b/man/mlr_acqfunctions_stochastic_ei.Rd index 6957410b..83f40c99 100644 --- a/man/mlr_acqfunctions_stochastic_ei.Rd +++ b/man/mlr_acqfunctions_stochastic_ei.Rd @@ -6,6 +6,10 @@ \title{Acquisition Function Stochastic Expected Improvement} \description{ Expected Improvement with epsilon decay. +\eqn{\epsilon} is updated after each update by the formula \code{epsilon * exp(-rate * (t \%\% period))} where \code{t} is the number of times the acquisition function has been updated. + +While this acquisition function usually would be used within an asynchronous optimizer, e.g., \link{OptimizerAsyncMbo}, +it can in principle also be used in synchronous optimizers, e.g., \link{OptimizerMbo}. } \section{Dictionary}{ @@ -29,10 +33,52 @@ Defaults to \code{0.1}. Defaults to \code{0.05}. \item \code{"period"} (\code{integer(1)})\cr Period of the exponential decay. -Defaults to \code{NULL} i.e. the decay has no period. +Defaults to \code{NULL}, i.e., the decay has no period. +} +} + +\section{Note}{ + +\itemize{ +\item This acquisition function always also returns its current (\code{acq_epsilon}) and original (\code{acq_epsilon_0}) \eqn{\epsilon}. +These values will be logged into the \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} of the \link[bbotk:OptimInstanceBatch]{bbotk::OptimInstanceBatch} of the \link{AcqOptimizer} and +therefore also in the \link[bbotk:Archive]{bbotk::Archive} of the actual \link[bbotk:OptimInstance]{bbotk::OptimInstance} that is to be optimized. } } +\examples{ +if (requireNamespace("mlr3learners") & + requireNamespace("DiceKriging") & + requireNamespace("rgenoud")) { + library(bbotk) + library(paradox) + library(mlr3learners) + library(data.table) + + fun = function(xs) { + list(y = xs$x ^ 2) + } + domain = ps(x = p_dbl(lower = -10, upper = 10)) + codomain = ps(y = p_dbl(tags = "minimize")) + objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) + + instance = OptimInstanceBatchSingleCrit$new( + objective = objective, + terminator = trm("evals", n_evals = 5)) + + instance$eval_batch(data.table(x = c(-6, -5, 3, 9))) + + learner = default_gp() + + surrogate = srlrn(learner, archive = instance$archive) + + acq_function = acqf("stochastic_ei", surrogate = surrogate) + + acq_function$surrogate$update() + acq_function$update() + acq_function$eval_dt(data.table(x = c(-1, 0, 1))) +} +} \references{ \itemize{ \item Jones, R. D, Schonlau, Matthias, Welch, J. W (1998). @@ -75,6 +121,7 @@ In the case of maximization, this already includes the necessary change of sign. \itemize{ \item \href{#method-AcqFunctionStochasticEI-new}{\code{AcqFunctionStochasticEI$new()}} \item \href{#method-AcqFunctionStochasticEI-update}{\code{AcqFunctionStochasticEI$update()}} +\item \href{#method-AcqFunctionStochasticEI-reset}{\code{AcqFunctionStochasticEI$reset()}} \item \href{#method-AcqFunctionStochasticEI-clone}{\code{AcqFunctionStochasticEI$clone()}} } } @@ -113,7 +160,7 @@ Creates a new instance of this \link[R6:R6Class]{R6} class. \item{\code{rate}}{(\code{numeric(1)}).} -\item{\code{period}}{(\code{integer(1)}).} +\item{\code{period}}{(\code{NULL} | \code{integer(1)}).} } \if{html}{\out{
    }} } @@ -129,6 +176,17 @@ Decays epsilon. \if{html}{\out{
    }}\preformatted{AcqFunctionStochasticEI$update()}\if{html}{\out{
    }} } +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AcqFunctionStochasticEI-reset}{}}} +\subsection{Method \code{reset()}}{ +Reset the acquisition function. +Resets the private update counter \code{.t} used within the epsilon decay. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{AcqFunctionStochasticEI$reset()}\if{html}{\out{
    }} +} + } \if{html}{\out{
    }} \if{html}{\out{}} diff --git a/man/mlr_loop_functions_ego.Rd b/man/mlr_loop_functions_ego.Rd index 72b7c6fc..d1310edf 100644 --- a/man/mlr_loop_functions_ego.Rd +++ b/man/mlr_loop_functions_ego.Rd @@ -30,7 +30,7 @@ Typically a \link{SurrogateLearner}.} \item{init_design_size}{(\code{NULL} | \code{integer(1)})\cr Size of the initial design. -If \code{NULL} and the \link[bbotk:Archive]{bbotk::Archive} contains no evaluations, \code{4 * d} is used with \code{d} being the +If \code{NULL} and the \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} contains no evaluations, \code{4 * d} is used with \code{d} being the dimensionality of the search space. Points are generated via a Sobol sequence.} @@ -56,7 +56,7 @@ is chosen based on optimizing the acquisition function. \itemize{ \item The \code{acq_function$surrogate}, even if already populated, will always be overwritten by the \code{surrogate}. \item The \code{acq_optimizer$acq_function}, even if already populated, will always be overwritten by \code{acq_function}. -\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit}. +\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} of the \link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit}. } } \examples{ diff --git a/man/mlr_loop_functions_emo.Rd b/man/mlr_loop_functions_emo.Rd index 6ce0885a..41b2e005 100644 --- a/man/mlr_loop_functions_emo.Rd +++ b/man/mlr_loop_functions_emo.Rd @@ -29,7 +29,7 @@ The \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} \item{init_design_size}{(\code{NULL} | \code{integer(1)})\cr Size of the initial design. -If \code{NULL} and the \link[bbotk:Archive]{bbotk::Archive} contains no evaluations, \code{4 * d} is used with \code{d} being the +If \code{NULL} and the \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} contains no evaluations, \code{4 * d} is used with \code{d} being the dimensionality of the search space. Points are generated via a Sobol sequence.} @@ -56,7 +56,7 @@ is chosen based on optimizing the acquisition function. \itemize{ \item The \code{acq_function$surrogate}, even if already populated, will always be overwritten by the \code{surrogate}. \item The \code{acq_optimizer$acq_function}, even if already populated, will always be overwritten by \code{acq_function}. -\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit}. +\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} of the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit}. } } \examples{ diff --git a/man/mlr_loop_functions_mpcl.Rd b/man/mlr_loop_functions_mpcl.Rd index 1ee43738..738e3d2e 100644 --- a/man/mlr_loop_functions_mpcl.Rd +++ b/man/mlr_loop_functions_mpcl.Rd @@ -32,7 +32,7 @@ Typically a \link{SurrogateLearner}.} \item{init_design_size}{(\code{NULL} | \code{integer(1)})\cr Size of the initial design. -If \code{NULL} and the \link[bbotk:Archive]{bbotk::Archive} contains no evaluations, \code{4 * d} is used with \code{d} being the +If \code{NULL} and the \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} contains no evaluations, \code{4 * d} is used with \code{d} being the dimensionality of the search space. Points are generated via a Sobol sequence.} @@ -68,7 +68,7 @@ This is repeated \code{q - 1} times to obtain a total of \code{q} candidates tha \itemize{ \item The \code{acq_function$surrogate}, even if already populated, will always be overwritten by the \code{surrogate}. \item The \code{acq_optimizer$acq_function}, even if already populated, will always be overwritten by \code{acq_function}. -\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit}. +\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} of the \link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit}. \item To make use of parallel evaluations in the case of `q > 1, the objective function of the \link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} must be implemented accordingly. } diff --git a/man/mlr_loop_functions_parego.Rd b/man/mlr_loop_functions_parego.Rd index 8f8606d0..3037e09c 100644 --- a/man/mlr_loop_functions_parego.Rd +++ b/man/mlr_loop_functions_parego.Rd @@ -32,7 +32,7 @@ The \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} \item{init_design_size}{(\code{NULL} | \code{integer(1)})\cr Size of the initial design. -If \code{NULL} and the \link[bbotk:Archive]{bbotk::Archive} contains no evaluations, \code{4 * d} is used with \code{d} being the +If \code{NULL} and the \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} contains no evaluations, \code{4 * d} is used with \code{d} being the dimensionality of the search space. Points are generated via a Sobol sequence.} @@ -72,9 +72,9 @@ these scalarized values and optimizing the acquisition function. \itemize{ \item The \code{acq_function$surrogate}, even if already populated, will always be overwritten by the \code{surrogate}. \item The \code{acq_optimizer$acq_function}, even if already populated, will always be overwritten by \code{acq_function}. -\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit}. +\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} of the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit}. \item The scalarizations of the objective function values are stored as the \code{y_scal} column in the -\link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit}. +\link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} of the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit}. \item To make use of parallel evaluations in the case of `q > 1, the objective function of the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} must be implemented accordingly. } diff --git a/man/mlr_loop_functions_smsego.Rd b/man/mlr_loop_functions_smsego.Rd index ab9c3199..6c15ff20 100644 --- a/man/mlr_loop_functions_smsego.Rd +++ b/man/mlr_loop_functions_smsego.Rd @@ -29,7 +29,7 @@ The \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} \item{init_design_size}{(\code{NULL} | \code{integer(1)})\cr Size of the initial design. -If \code{NULL} and the \link[bbotk:Archive]{bbotk::Archive} contains no evaluations, \code{4 * d} is used with \code{d} being the +If \code{NULL} and the \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} contains no evaluations, \code{4 * d} is used with \code{d} being the dimensionality of the search space. Points are generated via a Sobol sequence.} @@ -55,7 +55,7 @@ updated and the next candidate is chosen based on optimizing the acquisition fun \itemize{ \item The \code{acq_function$surrogate}, even if already populated, will always be overwritten by the \code{surrogate}. \item The \code{acq_optimizer$acq_function}, even if already populated, will always be overwritten by \code{acq_function}. -\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit}. +\item The \code{surrogate$archive}, even if already populated, will always be overwritten by the \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} of the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit}. \item Due to the iterative computation of the epsilon within the \link{mlr_acqfunctions_smsego}, requires the \link[bbotk:Terminator]{bbotk::Terminator} of the \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} to be a \link[bbotk:mlr_terminators_evals]{bbotk::TerminatorEvals}. } diff --git a/man/mlr_optimizers_adbo.Rd b/man/mlr_optimizers_adbo.Rd index 279fc75a..ed7ab0a5 100644 --- a/man/mlr_optimizers_adbo.Rd +++ b/man/mlr_optimizers_adbo.Rd @@ -7,47 +7,78 @@ \description{ \code{OptimizerADBO} class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). ADBO is a variant of Asynchronous Model Based Optimization (AMBO) that uses \link{AcqFunctionStochasticCB} with exponential lambda decay. + +Currently, only single-objective optimization is supported and \link{OptimizerADBO} is considered an experimental feature and API might be subject to changes. } \note{ -The lambda parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. +The lambda parameter of the confidence bound acquisition function controls the trade-off between exploration and exploitation. A large lambda value leads to more exploration, while a small lambda value leads to more exploitation. -The initial lambda value is drawn from an exponential distribution with rate \code{1 / lambda}. -ADBO can use periodic exponential decay to reduce lambda periodically with the formula \code{lambda * exp(-rate * (t \%\% period))}. -The surrogate model is always a random forest and die acquisition optimizer is random search with a budget of 10,000 evaluations. +The initial lambda value of the acquisition function used on each worker is drawn from an exponential distribution with rate \code{1 / lambda}. +ADBO can use periodic exponential decay to reduce lambda periodically for a given time step \code{t} with the formula \code{lambda * exp(-rate * (t \%\% period))}. +The \link{SurrogateLearner} is configured to use a random forest and the \link{AcqOptimizer} is a random search with a batch size of 1000 and a budget of 10000 evaluations. } \section{Parameters}{ \describe{ \item{\code{lambda}}{\code{numeric(1)}\cr -Lambda value for sampling from the exponential distribution.} +Value used for sampling the lambda for each worker from an exponential distribution.} \item{\code{rate}}{\code{numeric(1)}\cr Rate of the exponential decay.} \item{\code{period}}{\code{integer(1)}\cr Period of the exponential decay.} -\item{\code{initial_design_size}}{\code{integer(1)}\cr -Size of the initial design. -Defaults to \code{100}.} - \item{\code{initial_design}}{\code{data.table::data.table()}\cr Initial design of the optimization. -If \code{NULL}, a design of size \code{design_size} is generated with \code{design_function}.} +If \code{NULL}, a design of size \code{design_size} is generated with the specified \code{design_function}. +Default is \code{NULL}.} \item{\code{design_size}}{\code{integer(1)}\cr -Size of the initial design.} +Size of the initial design if it is to be generated. +Default is \code{100}.} \item{\code{design_function}}{\code{character(1)}\cr -Function to generate the initial design. -One of \code{c("random", "sobol", "lhs")}.} +Sampling function to generate the initial design. +Can be \code{random} \link[paradox:generate_design_random]{paradox::generate_design_random}, \code{lhs} \link[paradox:generate_design_lhs]{paradox::generate_design_lhs}, or \code{sobol} \link[paradox:generate_design_sobol]{paradox::generate_design_sobol}. +Default is \code{sobol}.} \item{\code{n_workers}}{\code{integer(1)}\cr Number of parallel workers. -If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush_plan()}} are used.} +If \code{NULL}, all rush workers specified via \code{\link[rush:rush_plan]{rush::rush_plan()}} are used. +Default is \code{NULL}.} } } +\examples{ +\donttest{ +if (requireNamespace("rush") & + requireNamespace("mlr3learners") & + requireNamespace("DiceKriging") & + requireNamespace("rgenoud")) { + + library(bbotk) + library(paradox) + library(mlr3learners) + + fun = function(xs) { + list(y = xs$x ^ 2) + } + domain = ps(x = p_dbl(lower = -10, upper = 10)) + codomain = ps(y = p_dbl(tags = "minimize")) + objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) + + instance = OptimInstanceAsyncSingleCrit$new( + objective = objective, + terminator = trm("evals", n_evals = 10)) + + rush::rush_plan(n_workers=2) + + optimizer = opt("adbo", design_size = 4, n_workers = 2) + + optimizer$optimize(instance) +} +} +} \references{ \itemize{ -\item Egele R, Guyon I, Vishwanath V, Balaprakash P (2023). +\item Egelé, Romain, Guyon, Isabelle, Vishwanath, Venkatram, Balaprakash, Prasanna (2023). \dQuote{Asynchronous Decentralized Bayesian Optimization for Large Scale Hyperparameter Optimization.} -\emph{arXiv}. -\url{https://arxiv.org/abs/2207.00479}. +In \emph{2023 IEEE 19th International Conference on e-Science (e-Science)}, 1--10. } } \section{Super classes}{ @@ -57,6 +88,7 @@ If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush \subsection{Public methods}{ \itemize{ \item \href{#method-OptimizerADBO-new}{\code{OptimizerADBO$new()}} +\item \href{#method-OptimizerADBO-optimize}{\code{OptimizerADBO$optimize()}} \item \href{#method-OptimizerADBO-clone}{\code{OptimizerADBO$clone()}} } } @@ -65,8 +97,8 @@ If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush }} @@ -79,6 +111,28 @@ Creates a new instance of this \link[R6:R6Class]{R6} class. \if{html}{\out{
    }}\preformatted{OptimizerADBO$new()}\if{html}{\out{
    }} } +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OptimizerADBO-optimize}{}}} +\subsection{Method \code{optimize()}}{ +Performs the optimization on an \link[bbotk:OptimInstanceAsyncSingleCrit]{bbotk::OptimInstanceAsyncSingleCrit} until termination. +The single evaluations will be written into the \link[bbotk:ArchiveAsync]{bbotk::ArchiveAsync}. +The result will be written into the instance object. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{OptimizerADBO$optimize(inst)}\if{html}{\out{
    }} +} + +\subsection{Arguments}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{inst}}{(\link[bbotk:OptimInstanceAsyncSingleCrit]{bbotk::OptimInstanceAsyncSingleCrit}).} +} +\if{html}{\out{
    }} +} +\subsection{Returns}{ +\code{\link[data.table:data.table]{data.table::data.table()}} +} } \if{html}{\out{
    }} \if{html}{\out{}} diff --git a/man/mlr_optimizers_async_mbo.Rd b/man/mlr_optimizers_async_mbo.Rd index 83554d96..d7dc16bf 100644 --- a/man/mlr_optimizers_async_mbo.Rd +++ b/man/mlr_optimizers_async_mbo.Rd @@ -7,29 +7,94 @@ \description{ \code{OptimizerAsyncMbo} class that implements Asynchronous Model Based Optimization (AMBO). AMBO starts multiple sequential MBO runs on different workers. -The worker communicate asynchronously through a shared archive. +The worker communicate asynchronously through a shared archive relying on the \pkg{rush} package. The optimizer follows a modular layout in which the surrogate model, acquisition function, and acquisition optimizer can be changed. -The \link{SurrogateLearnerAsync} class is similar to \link{SurrogateLearner} but can impute results for ongoing evaluations. -The \verb{AcqFunctionStochastic*} classes create varying acquisition functions on each worker, promoting different exploration-exploitation balances. -The \link{AcqOptimizer} classes remain consistent with those used in synchronous MBO. +The \link{SurrogateLearner} will impute missing values due to pending evaluations. +A stochastic \link{AcqFunction}, e.g., \link{AcqFunctionStochasticEI} or \link{AcqFunctionStochasticCB} is used to create varying versions of the acquisition +function on each worker, promoting different exploration-exploitation trade-offs. +The \link{AcqOptimizer} class remains consistent with the one used in synchronous MBO. + +In contrast to \link{OptimizerMbo}, no \link{loop_function} can be specified that determines the AMBO flavor as \code{OptimizerAsyncMbo} simply relies on +a surrogate update, acquisition function update and acquisition function optimization step as an internal loop. + +Currently, only single-objective optimization is supported and \code{OptimizerAsyncMbo} is considered an experimental feature and API might be subject to changes. + +Note that in general the \link{SurrogateLearner} is updated one final time on all available data after the optimization process has terminated. +However, in certain scenarios this is not always possible or meaningful. +It is therefore recommended to manually inspect the \link{SurrogateLearner} after optimization if it is to be used, e.g., for visualization purposes to make +sure that it has been properly updated on all available data. +If this final update of the \link{SurrogateLearner} could not be performed successfully, a warning will be logged. + +By specifying a \link{ResultAssigner}, one can alter how the final result is determined after optimization, e.g., +simply based on the evaluations logged in the archive \link{ResultAssignerArchive} or based on the \link{Surrogate} via \link{ResultAssignerSurrogate}. } +\section{Archive}{ + +The \link[bbotk:ArchiveAsync]{bbotk::ArchiveAsync} holds the following additional columns that are specific to AMBO algorithms: +\itemize{ +\item \code{acq_function$id} (\code{numeric(1)})\cr +The value of the acquisition function. +\item \code{".already_evaluated"} (\verb{logical(1))}\cr +Whether this point was already evaluated. Depends on the \code{skip_already_evaluated} parameter of the \link{AcqOptimizer}. +} + +If the \link[bbotk:ArchiveAsync]{bbotk::ArchiveAsync} does not contain any evaluations prior to optimization, an initial design is needed. +If the \code{initial_design} parameter is specified to be a \code{data.table}, this data will be used. +Otherwise, if it is \code{NULL}, an initial design of size \code{design_size} will be generated based on the \code{generate_design} sampling function. +See also the parameters below. +} + \section{Parameters}{ \describe{ \item{\code{initial_design}}{\code{data.table::data.table()}\cr Initial design of the optimization. -If \code{NULL}, a design of size \code{design_size} is generated with \code{design_function}.} +If \code{NULL}, a design of size \code{design_size} is generated with the specified \code{design_function}. +Default is \code{NULL}.} \item{\code{design_size}}{\code{integer(1)}\cr -Size of the initial design.} +Size of the initial design if it is to be generated. +Default is \code{100}.} \item{\code{design_function}}{\code{character(1)}\cr -Function to generate the initial design. -One of \code{c("random", "sobol", "lhs")}.} +Sampling function to generate the initial design. +Can be \code{random} \link[paradox:generate_design_random]{paradox::generate_design_random}, \code{lhs} \link[paradox:generate_design_lhs]{paradox::generate_design_lhs}, or \code{sobol} \link[paradox:generate_design_sobol]{paradox::generate_design_sobol}. +Default is \code{sobol}.} \item{\code{n_workers}}{\code{integer(1)}\cr Number of parallel workers. -If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush_plan()}} are used.} +If \code{NULL}, all rush workers specified via \code{\link[rush:rush_plan]{rush::rush_plan()}} are used. +Default is \code{NULL}.} } } +\examples{ +\donttest{ +if (requireNamespace("rush") & + requireNamespace("mlr3learners") & + requireNamespace("DiceKriging") & + requireNamespace("rgenoud")) { + + library(bbotk) + library(paradox) + library(mlr3learners) + + fun = function(xs) { + list(y = xs$x ^ 2) + } + domain = ps(x = p_dbl(lower = -10, upper = 10)) + codomain = ps(y = p_dbl(tags = "minimize")) + objective = ObjectiveRFun$new(fun = fun, domain = domain, codomain = codomain) + + instance = OptimInstanceAsyncSingleCrit$new( + objective = objective, + terminator = trm("evals", n_evals = 10)) + + rush::rush_plan(n_workers=2) + + optimizer = opt("async_mbo", design_size = 4, n_workers = 2) + + optimizer$optimize(instance) +} +} +} \section{Super classes}{ \code{\link[bbotk:Optimizer]{bbotk::Optimizer}} -> \code{\link[bbotk:OptimizerAsync]{bbotk::OptimizerAsync}} -> \code{OptimizerAsyncMbo} } @@ -45,6 +110,9 @@ The acquisition function.} \item{\code{acq_optimizer}}{(\link{AcqOptimizer} | \code{NULL})\cr The acquisition function optimizer.} +\item{\code{result_assigner}}{(\link{ResultAssigner} | \code{NULL})\cr +The result assigner.} + \item{\code{param_classes}}{(\code{character()})\cr Supported parameter classes that the optimizer can optimize. Determined based on the \code{surrogate} and the \code{acq_optimizer}. @@ -55,7 +123,7 @@ This corresponds to the values given by a \link[paradox:ParamSet]{paradox::Param Set of properties of the optimizer. Must be a subset of \code{\link[bbotk:bbotk_reflections]{bbotk_reflections$optimizer_properties}}. MBO in principle is very flexible and by default we assume that the optimizer has all properties. -When fully initialized, properties are determined based on the \code{loop_function} and \code{surrogate}.} +When fully initialized, properties are determined based on the loop, e.g., the \code{loop_function}, and \code{surrogate}.} \item{\code{packages}}{(\code{character()})\cr Set of required packages. @@ -68,6 +136,8 @@ Required packages are determined based on the \code{acq_function}, \code{surroga \subsection{Public methods}{ \itemize{ \item \href{#method-OptimizerAsyncMbo-new}{\code{OptimizerAsyncMbo$new()}} +\item \href{#method-OptimizerAsyncMbo-print}{\code{OptimizerAsyncMbo$print()}} +\item \href{#method-OptimizerAsyncMbo-reset}{\code{OptimizerAsyncMbo$reset()}} \item \href{#method-OptimizerAsyncMbo-optimize}{\code{OptimizerAsyncMbo$optimize()}} \item \href{#method-OptimizerAsyncMbo-clone}{\code{OptimizerAsyncMbo$clone()}} } @@ -77,7 +147,6 @@ Required packages are determined based on the \code{acq_function}, \code{surroga }} @@ -86,16 +155,24 @@ Required packages are determined based on the \code{acq_function}, \code{surroga \if{latex}{\out{\hypertarget{method-OptimizerAsyncMbo-new}{}}} \subsection{Method \code{new()}}{ Creates a new instance of this \link[R6:R6Class]{R6} class. + +If \code{surrogate} is \code{NULL} and the \code{acq_function$surrogate} field is populated, this \link{SurrogateLearner} is used. +Otherwise, \code{default_surrogate(instance)} is used. +If \code{acq_function} is \code{NULL} and the \code{acq_optimizer$acq_function} field is populated, this \link{AcqFunction} is used (and therefore its \verb{$surrogate} if populated; see above). +Otherwise \code{default_acqfunction(instance)} is used. +If \code{acq_optimizer} is \code{NULL}, \code{default_acqoptimizer(instance)} is used. + +Even if already initialized, the \code{surrogate$archive} field will always be overwritten by the \link[bbotk:ArchiveAsync]{bbotk::ArchiveAsync} of the current \link[bbotk:OptimInstanceAsyncSingleCrit]{bbotk::OptimInstanceAsyncSingleCrit} to be optimized. + +For more information on default values for \code{surrogate}, \code{acq_function}, \code{acq_optimizer} and \code{result_assigner}, see \code{?mbo_defaults}. \subsection{Usage}{ \if{html}{\out{
    }}\preformatted{OptimizerAsyncMbo$new( id = "async_mbo", surrogate = NULL, acq_function = NULL, acq_optimizer = NULL, + result_assigner = NULL, param_set = NULL, - param_classes = c("ParamLgl", "ParamInt", "ParamDbl", "ParamFct"), - properties = c("dependencies", "single-crit", "async"), - packages = c("mlr3mbo", "rush"), label = "Asynchronous Model Based Optimization", man = "mlr3mbo::OptimizerAsyncMbo" )}\if{html}{\out{
    }} @@ -116,19 +193,11 @@ The acquisition function.} \item{\code{acq_optimizer}}{(\link{AcqOptimizer} | \code{NULL})\cr The acquisition function optimizer.} -\item{\code{param_set}}{\link[paradox:ParamSet]{paradox::ParamSet}\cr -Set of control parameters.} - -\item{\code{param_classes}}{(\code{character()})\cr -Supported parameter classes that the optimizer can optimize, as given in the \code{\link[paradox:ParamSet]{paradox::ParamSet}} \verb{$class} field.} +\item{\code{result_assigner}}{(\link{ResultAssigner} | \code{NULL})\cr +The result assigner.} -\item{\code{properties}}{(\code{character()})\cr -Set of properties of the optimizer. -Must be a subset of \code{\link[=bbotk_reflections]{bbotk_reflections$optimizer_properties}}.} - -\item{\code{packages}}{(\code{character()})\cr -Set of required packages. -A warning is signaled by the constructor if at least one of the packages is not installed, but loaded (not attached) later on-demand via \code{\link[=requireNamespace]{requireNamespace()}}.} +\item{\code{param_set}}{(\link[paradox:ParamSet]{paradox::ParamSet})\cr +Set of control parameters.} \item{\code{label}}{(\code{character(1)})\cr Label for this object. @@ -140,13 +209,39 @@ The referenced help package can be opened via method \verb{$help()}.} } \if{html}{\out{}} } +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OptimizerAsyncMbo-print}{}}} +\subsection{Method \code{print()}}{ +Print method. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{OptimizerAsyncMbo$print()}\if{html}{\out{
    }} +} + +\subsection{Returns}{ +(\code{character()}). +} +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OptimizerAsyncMbo-reset}{}}} +\subsection{Method \code{reset()}}{ +Reset the optimizer. +Sets the following fields to \code{NULL}: +\code{surrogate}, \code{acq_function}, \code{acq_optimizer},\code{result_assigner} +Resets parameter values \code{design_size} and \code{design_function} to their defaults. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{OptimizerAsyncMbo$reset()}\if{html}{\out{
    }} +} + } \if{html}{\out{
    }} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-OptimizerAsyncMbo-optimize}{}}} \subsection{Method \code{optimize()}}{ -Performs the optimization on a \link{OptimInstanceAsyncSingleCrit} or \link{OptimInstanceAsyncMultiCrit} until termination. -The single evaluations will be written into the \link{ArchiveAsync}. +Performs the optimization on an \link[bbotk:OptimInstanceAsyncSingleCrit]{bbotk::OptimInstanceAsyncSingleCrit} until termination. +The single evaluations will be written into the \link[bbotk:ArchiveAsync]{bbotk::ArchiveAsync}. The result will be written into the instance object. \subsection{Usage}{ \if{html}{\out{
    }}\preformatted{OptimizerAsyncMbo$optimize(inst)}\if{html}{\out{
    }} @@ -155,7 +250,7 @@ The result will be written into the instance object. \subsection{Arguments}{ \if{html}{\out{
    }} \describe{ -\item{\code{inst}}{(\link{OptimInstanceAsyncSingleCrit} | \link{OptimInstanceAsyncMultiCrit}).} +\item{\code{inst}}{(\link[bbotk:OptimInstanceAsyncSingleCrit]{bbotk::OptimInstanceAsyncSingleCrit}).} } \if{html}{\out{
    }} } diff --git a/man/mlr_optimizers_mbo.Rd b/man/mlr_optimizers_mbo.Rd index 680bc3f7..3f5cf30c 100644 --- a/man/mlr_optimizers_mbo.Rd +++ b/man/mlr_optimizers_mbo.Rd @@ -15,7 +15,7 @@ By optimizing a comparably cheap to evaluate acquisition function defined on the Detailed descriptions of different MBO flavors are provided in the documentation of the respective \link{loop_function}. -Termination is handled via a \link[bbotk:Terminator]{bbotk::Terminator} part of the \link[bbotk:OptimInstance]{bbotk::OptimInstance} to be optimized. +Termination is handled via a \link[bbotk:Terminator]{bbotk::Terminator} part of the \link[bbotk:OptimInstanceBatch]{bbotk::OptimInstanceBatch} to be optimized. Note that in general the \link{Surrogate} is updated one final time on all available data after the optimization process has terminated. However, in certain scenarios this is not always possible or meaningful, e.g., when using \code{\link[=bayesopt_parego]{bayesopt_parego()}} for multi-objective optimization @@ -23,14 +23,17 @@ which uses a surrogate that relies on a scalarization of the objectives. It is therefore recommended to manually inspect the \link{Surrogate} after optimization if it is to be used, e.g., for visualization purposes to make sure that it has been properly updated on all available data. If this final update of the \link{Surrogate} could not be performed successfully, a warning will be logged. + +By specifying a \link{ResultAssigner}, one can alter how the final result is determined after optimization, e.g., +simply based on the evaluations logged in the archive \link{ResultAssignerArchive} or based on the \link{Surrogate} via \link{ResultAssignerSurrogate}. } \section{Archive}{ -The \link[bbotk:Archive]{bbotk::Archive} holds the following additional columns that are specific to MBO algorithms: +The \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} holds the following additional columns that are specific to MBO algorithms: \itemize{ -\item \verb{[acq_function$id]} (\code{numeric(1)})\cr +\item \code{acq_function$id} (\code{numeric(1)})\cr The value of the acquisition function. -\item \code{.already_evaluated} (\verb{logical(1))}\cr +\item \code{".already_evaluated"} (\verb{logical(1))}\cr Whether this point was already evaluated. Depends on the \code{skip_already_evaluated} parameter of the \link{AcqOptimizer}. } } @@ -130,7 +133,7 @@ This corresponds to the values given by a \link[paradox:ParamSet]{paradox::Param Set of properties of the optimizer. Must be a subset of \code{\link[bbotk:bbotk_reflections]{bbotk_reflections$optimizer_properties}}. MBO in principle is very flexible and by default we assume that the optimizer has all properties. -When fully initialized, properties are determined based on the \code{loop_function} and \code{surrogate}.} +When fully initialized, properties are determined based on the loop, e.g., the \code{loop_function}, and \code{surrogate}.} \item{\code{packages}}{(\code{character()})\cr Set of required packages. @@ -145,6 +148,7 @@ Required packages are determined based on the \code{acq_function}, \code{surroga \item \href{#method-OptimizerMbo-new}{\code{OptimizerMbo$new()}} \item \href{#method-OptimizerMbo-print}{\code{OptimizerMbo$print()}} \item \href{#method-OptimizerMbo-reset}{\code{OptimizerMbo$reset()}} +\item \href{#method-OptimizerMbo-optimize}{\code{OptimizerMbo$optimize()}} \item \href{#method-OptimizerMbo-clone}{\code{OptimizerMbo$clone()}} } } @@ -153,7 +157,6 @@ Required packages are determined based on the \code{acq_function}, \code{surroga }} @@ -165,13 +168,13 @@ Creates a new instance of this \link[R6:R6Class]{R6} class. If \code{surrogate} is \code{NULL} and the \code{acq_function$surrogate} field is populated, this \link{Surrogate} is used. Otherwise, \code{default_surrogate(instance)} is used. -If \code{acq_function} is NULL and the \code{acq_optimizer$acq_function} field is populated, this \link{AcqFunction} is used (and therefore its \verb{$surrogate} if populated; see above). +If \code{acq_function} is \code{NULL} and the \code{acq_optimizer$acq_function} field is populated, this \link{AcqFunction} is used (and therefore its \verb{$surrogate} if populated; see above). Otherwise \code{default_acqfunction(instance)} is used. -If \code{acq_optimizer} is NULL, \code{default_acqoptimizer(instance)} is used. +If \code{acq_optimizer} is \code{NULL}, \code{default_acqoptimizer(instance)} is used. -Even if already initialized, the \code{surrogate$archive} field will always be overwritten by the \link[bbotk:Archive]{bbotk::Archive} of the current \link[bbotk:OptimInstance]{bbotk::OptimInstance} to be optimized. +Even if already initialized, the \code{surrogate$archive} field will always be overwritten by the \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} of the current \link[bbotk:OptimInstanceBatch]{bbotk::OptimInstanceBatch} to be optimized. -For more information on default values for \code{loop_function}, \code{surrogate}, \code{acq_function} and \code{acq_optimizer}, see \code{?mbo_defaults}. +For more information on default values for \code{surrogate}, \code{acq_function}, \code{acq_optimizer} and \code{result_assigner}, see \code{?mbo_defaults}. \subsection{Usage}{ \if{html}{\out{
    }}\preformatted{OptimizerMbo$new( loop_function = NULL, @@ -232,6 +235,27 @@ Sets the following fields to \code{NULL}: \if{html}{\out{
    }}\preformatted{OptimizerMbo$reset()}\if{html}{\out{
    }} } +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-OptimizerMbo-optimize}{}}} +\subsection{Method \code{optimize()}}{ +Performs the optimization and writes optimization result into \link[bbotk:OptimInstanceBatch]{bbotk::OptimInstanceBatch}. +The optimization result is returned but the complete optimization path is stored in \link[bbotk:ArchiveBatch]{bbotk::ArchiveBatch} of \link[bbotk:OptimInstanceBatch]{bbotk::OptimInstanceBatch}. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{OptimizerMbo$optimize(inst)}\if{html}{\out{
    }} +} + +\subsection{Arguments}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{inst}}{(\link[bbotk:OptimInstanceBatch]{bbotk::OptimInstanceBatch}).} +} +\if{html}{\out{
    }} +} +\subsection{Returns}{ +\link[data.table:data.table]{data.table::data.table}. +} } \if{html}{\out{
    }} \if{html}{\out{}} diff --git a/man/mlr_result_assigners_archive.Rd b/man/mlr_result_assigners_archive.Rd index aa453a08..9bda8c60 100644 --- a/man/mlr_result_assigners_archive.Rd +++ b/man/mlr_result_assigners_archive.Rd @@ -68,7 +68,7 @@ Assigns the result, i.e., the final point(s) to the instance. \subsection{Arguments}{ \if{html}{\out{
    }} \describe{ -\item{\code{instance}}{(\link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} | \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit})\cr +\item{\code{instance}}{(\link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} | \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} |\link[bbotk:OptimInstanceAsyncSingleCrit]{bbotk::OptimInstanceAsyncSingleCrit} | \link[bbotk:OptimInstanceAsyncMultiCrit]{bbotk::OptimInstanceAsyncMultiCrit})\cr The \link[bbotk:OptimInstance]{bbotk::OptimInstance} the final result should be assigned to.} } \if{html}{\out{
    }} diff --git a/man/mlr_result_assigners_surrogate.Rd b/man/mlr_result_assigners_surrogate.Rd index 68d57759..0d38d082 100644 --- a/man/mlr_result_assigners_surrogate.Rd +++ b/man/mlr_result_assigners_surrogate.Rd @@ -8,7 +8,7 @@ Result assigner that chooses the final point(s) based on a surrogate mean prediction of all evaluated points in the \link[bbotk:Archive]{bbotk::Archive}. This is especially useful in the case of noisy objective functions. -In the case of operating on an \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} the \link{SurrogateLearnerCollection} must use as many learners as there are objective functions. +In the case of operating on an \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} or \link[bbotk:OptimInstanceAsyncMultiCrit]{bbotk::OptimInstanceAsyncMultiCrit} the \link{SurrogateLearnerCollection} must use as many learners as there are objective functions. } \examples{ result_assigner = ras("surrogate") @@ -82,7 +82,7 @@ If \verb{$surrogate} is \code{NULL}, \code{default_surrogate(instance)} is used \subsection{Arguments}{ \if{html}{\out{
    }} \describe{ -\item{\code{instance}}{(\link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} | \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit})\cr +\item{\code{instance}}{(\link[bbotk:OptimInstanceBatchSingleCrit]{bbotk::OptimInstanceBatchSingleCrit} | \link[bbotk:OptimInstanceBatchMultiCrit]{bbotk::OptimInstanceBatchMultiCrit} |\link[bbotk:OptimInstanceAsyncSingleCrit]{bbotk::OptimInstanceAsyncSingleCrit} | \link[bbotk:OptimInstanceAsyncMultiCrit]{bbotk::OptimInstanceAsyncMultiCrit})\cr The \link[bbotk:OptimInstance]{bbotk::OptimInstance} the final result should be assigned to.} } \if{html}{\out{
    }} diff --git a/man/mlr_tuners_adbo.Rd b/man/mlr_tuners_adbo.Rd index 90fb6f0d..19369a35 100644 --- a/man/mlr_tuners_adbo.Rd +++ b/man/mlr_tuners_adbo.Rd @@ -3,60 +3,113 @@ \name{mlr_tuners_adbo} \alias{mlr_tuners_adbo} \alias{TunerADBO} -\title{Asynchronous Decentralized Bayesian Optimization} +\title{TunerAsync using Asynchronous Decentralized Bayesian Optimization} \description{ \code{TunerADBO} class that implements Asynchronous Decentralized Bayesian Optimization (ADBO). ADBO is a variant of Asynchronous Model Based Optimization (AMBO) that uses \link{AcqFunctionStochasticCB} with exponential lambda decay. -} -\note{ -The lambda parameter of the upper confidence bound acquisition function controls the trade-off between exploration and exploitation. -A large lambda value leads to more exploration, while a small lambda value leads to more exploitation. -The initial lambda value is drawn from an exponential distribution with rate \code{1 / lambda}. -ADBO can use periodic exponential decay to reduce lambda periodically with the formula \code{lambda * exp(-rate * (t \%\% period))}. -The surrogate model is always a random forest and die acquisition optimizer is random search with a budget of 10,000 evaluations. +This is a minimal interface internally passing on to \link{OptimizerAsyncMbo}. +For additional information and documentation see \link{OptimizerAsyncMbo}. } \section{Parameters}{ \describe{ -\item{\code{lambda}}{\code{numeric(1)}\cr -Lambda value for sampling from the exponential distribution.} -\item{\code{rate}}{\code{numeric(1)}\cr -Rate of the exponential decay.} -\item{\code{period}}{\code{integer(1)}\cr -Period of the exponential decay.} -\item{\code{initial_design_size}}{\code{integer(1)}\cr -Size of the initial design. -Defaults to \code{100}.} - \item{\code{initial_design}}{\code{data.table::data.table()}\cr Initial design of the optimization. -If \code{NULL}, a design of size \code{design_size} is generated with \code{design_function}.} +If \code{NULL}, a design of size \code{design_size} is generated with the specified \code{design_function}. +Default is \code{NULL}.} \item{\code{design_size}}{\code{integer(1)}\cr -Size of the initial design.} +Size of the initial design if it is to be generated. +Default is \code{100}.} \item{\code{design_function}}{\code{character(1)}\cr -Function to generate the initial design. -One of \code{c("random", "sobol", "lhs")}.} +Sampling function to generate the initial design. +Can be \code{random} \link[paradox:generate_design_random]{paradox::generate_design_random}, \code{lhs} \link[paradox:generate_design_lhs]{paradox::generate_design_lhs}, or \code{sobol} \link[paradox:generate_design_sobol]{paradox::generate_design_sobol}. +Default is \code{sobol}.} \item{\code{n_workers}}{\code{integer(1)}\cr Number of parallel workers. -If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush_plan()}} are used.} +If \code{NULL}, all rush workers specified via \code{\link[rush:rush_plan]{rush::rush_plan()}} are used. +Default is \code{NULL}.} } } +\examples{ +\donttest{ +if (requireNamespace("rush") & + requireNamespace("mlr3learners") & + requireNamespace("DiceKriging") & + requireNamespace("rgenoud")) { + + library(mlr3) + library(mlr3tuning) + + # single-objective + task = tsk("wine") + learner = lrn("classif.rpart", cp = to_tune(lower = 1e-4, upper = 1, logscale = TRUE)) + resampling = rsmp("cv", folds = 3) + measure = msr("classif.acc") + + instance = TuningInstanceAsyncSingleCrit$new( + task = task, + learner = learner, + resampling = resampling, + measure = measure, + terminator = trm("evals", n_evals = 10)) + + rush::rush_plan(n_workers=2) + + tnr("adbo", design_size = 4, n_workers = 2)$optimize(instance) +} +} +} \references{ \itemize{ -\item Egele R, Guyon I, Vishwanath V, Balaprakash P (2023). +\item Egelé, Romain, Guyon, Isabelle, Vishwanath, Venkatram, Balaprakash, Prasanna (2023). \dQuote{Asynchronous Decentralized Bayesian Optimization for Large Scale Hyperparameter Optimization.} -\emph{arXiv}. -\url{https://arxiv.org/abs/2207.00479}. +In \emph{2023 IEEE 19th International Conference on e-Science (e-Science)}, 1--10. } } \section{Super classes}{ \code{\link[mlr3tuning:Tuner]{mlr3tuning::Tuner}} -> \code{\link[mlr3tuning:TunerAsync]{mlr3tuning::TunerAsync}} -> \code{\link[mlr3tuning:TunerAsyncFromOptimizerAsync]{mlr3tuning::TunerAsyncFromOptimizerAsync}} -> \code{TunerADBO} } +\section{Active bindings}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{surrogate}}{(\link{Surrogate} | \code{NULL})\cr +The surrogate.} + +\item{\code{acq_function}}{(\link{AcqFunction} | \code{NULL})\cr +The acquisition function.} + +\item{\code{acq_optimizer}}{(\link{AcqOptimizer} | \code{NULL})\cr +The acquisition function optimizer.} + +\item{\code{result_assigner}}{(\link{ResultAssigner} | \code{NULL})\cr +The result assigner.} + +\item{\code{param_classes}}{(\code{character()})\cr +Supported parameter classes that the optimizer can optimize. +Determined based on the \code{surrogate} and the \code{acq_optimizer}. +This corresponds to the values given by a \link[paradox:ParamSet]{paradox::ParamSet}'s +\verb{$class} field.} + +\item{\code{properties}}{(\code{character()})\cr +Set of properties of the optimizer. +Must be a subset of \code{\link[bbotk:bbotk_reflections]{bbotk_reflections$optimizer_properties}}. +MBO in principle is very flexible and by default we assume that the optimizer has all properties. +When fully initialized, properties are determined based on the loop, e.g., the \code{loop_function}, and \code{surrogate}.} + +\item{\code{packages}}{(\code{character()})\cr +Set of required packages. +A warning is signaled prior to optimization if at least one of the packages is not installed, but loaded (not attached) later on-demand via \code{\link[=requireNamespace]{requireNamespace()}}. +Required packages are determined based on the \code{acq_function}, \code{surrogate} and the \code{acq_optimizer}.} +} +\if{html}{\out{
    }} +} \section{Methods}{ \subsection{Public methods}{ \itemize{ \item \href{#method-TunerADBO-new}{\code{TunerADBO$new()}} +\item \href{#method-TunerADBO-print}{\code{TunerADBO$print()}} +\item \href{#method-TunerADBO-reset}{\code{TunerADBO$reset()}} \item \href{#method-TunerADBO-clone}{\code{TunerADBO$clone()}} } } @@ -65,7 +118,6 @@ If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush @@ -79,6 +131,32 @@ Creates a new instance of this \link[R6:R6Class]{R6} class. \if{html}{\out{
    }}\preformatted{TunerADBO$new()}\if{html}{\out{
    }} } +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TunerADBO-print}{}}} +\subsection{Method \code{print()}}{ +Print method. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{TunerADBO$print()}\if{html}{\out{
    }} +} + +\subsection{Returns}{ +(\code{character()}). +} +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TunerADBO-reset}{}}} +\subsection{Method \code{reset()}}{ +Reset the tuner. +Sets the following fields to \code{NULL}: +\code{surrogate}, \code{acq_function}, \code{acq_optimizer}, \code{result_assigner} +Resets parameter values \code{design_size} and \code{design_function} to their defaults. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{TunerADBO$reset()}\if{html}{\out{
    }} +} + } \if{html}{\out{
    }} \if{html}{\out{}} diff --git a/man/mlr_tuners_async_mbo.Rd b/man/mlr_tuners_async_mbo.Rd index 3dbe203c..b7f15d1a 100644 --- a/man/mlr_tuners_async_mbo.Rd +++ b/man/mlr_tuners_async_mbo.Rd @@ -3,34 +3,105 @@ \name{mlr_tuners_async_mbo} \alias{mlr_tuners_async_mbo} \alias{TunerAsyncMbo} -\title{Asynchronous Model Based Tuning} +\title{TunerAsync using Asynchronous Model Based Optimization} \description{ -\code{TunerAsyncMbo} class that implements asynchronous Model Based Tuning (MBO). +\code{TunerAsyncMbo} class that implements Asynchronous Model Based Optimization (AMBO). +This is a minimal interface internally passing on to \link{OptimizerAsyncMbo}. +For additional information and documentation see \link{OptimizerAsyncMbo}. } \section{Parameters}{ \describe{ \item{\code{initial_design}}{\code{data.table::data.table()}\cr Initial design of the optimization. -If \code{NULL}, a design of size \code{design_size} is generated with \code{design_function}.} +If \code{NULL}, a design of size \code{design_size} is generated with the specified \code{design_function}. +Default is \code{NULL}.} \item{\code{design_size}}{\code{integer(1)}\cr -Size of the initial design.} +Size of the initial design if it is to be generated. +Default is \code{100}.} \item{\code{design_function}}{\code{character(1)}\cr -Function to generate the initial design. -One of \code{c("random", "sobol", "lhs")}.} +Sampling function to generate the initial design. +Can be \code{random} \link[paradox:generate_design_random]{paradox::generate_design_random}, \code{lhs} \link[paradox:generate_design_lhs]{paradox::generate_design_lhs}, or \code{sobol} \link[paradox:generate_design_sobol]{paradox::generate_design_sobol}. +Default is \code{sobol}.} \item{\code{n_workers}}{\code{integer(1)}\cr Number of parallel workers. -If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush_plan()}} are used.} +If \code{NULL}, all rush workers specified via \code{\link[rush:rush_plan]{rush::rush_plan()}} are used. +Default is \code{NULL}.} } } +\examples{ +\donttest{ +if (requireNamespace("rush") & + requireNamespace("mlr3learners") & + requireNamespace("DiceKriging") & + requireNamespace("rgenoud")) { + + library(mlr3) + library(mlr3tuning) + + # single-objective + task = tsk("wine") + learner = lrn("classif.rpart", cp = to_tune(lower = 1e-4, upper = 1, logscale = TRUE)) + resampling = rsmp("cv", folds = 3) + measure = msr("classif.acc") + + instance = TuningInstanceAsyncSingleCrit$new( + task = task, + learner = learner, + resampling = resampling, + measure = measure, + terminator = trm("evals", n_evals = 10)) + + rush::rush_plan(n_workers=2) + + tnr("async_mbo", design_size = 4, n_workers = 2)$optimize(instance) +} +} +} \section{Super classes}{ \code{\link[mlr3tuning:Tuner]{mlr3tuning::Tuner}} -> \code{\link[mlr3tuning:TunerAsync]{mlr3tuning::TunerAsync}} -> \code{\link[mlr3tuning:TunerAsyncFromOptimizerAsync]{mlr3tuning::TunerAsyncFromOptimizerAsync}} -> \code{TunerAsyncMbo} } +\section{Active bindings}{ +\if{html}{\out{
    }} +\describe{ +\item{\code{surrogate}}{(\link{Surrogate} | \code{NULL})\cr +The surrogate.} + +\item{\code{acq_function}}{(\link{AcqFunction} | \code{NULL})\cr +The acquisition function.} + +\item{\code{acq_optimizer}}{(\link{AcqOptimizer} | \code{NULL})\cr +The acquisition function optimizer.} + +\item{\code{result_assigner}}{(\link{ResultAssigner} | \code{NULL})\cr +The result assigner.} + +\item{\code{param_classes}}{(\code{character()})\cr +Supported parameter classes that the optimizer can optimize. +Determined based on the \code{surrogate} and the \code{acq_optimizer}. +This corresponds to the values given by a \link[paradox:ParamSet]{paradox::ParamSet}'s +\verb{$class} field.} + +\item{\code{properties}}{(\code{character()})\cr +Set of properties of the optimizer. +Must be a subset of \code{\link[bbotk:bbotk_reflections]{bbotk_reflections$optimizer_properties}}. +MBO in principle is very flexible and by default we assume that the optimizer has all properties. +When fully initialized, properties are determined based on the loop, e.g., the \code{loop_function}, and \code{surrogate}.} + +\item{\code{packages}}{(\code{character()})\cr +Set of required packages. +A warning is signaled prior to optimization if at least one of the packages is not installed, but loaded (not attached) later on-demand via \code{\link[=requireNamespace]{requireNamespace()}}. +Required packages are determined based on the \code{acq_function}, \code{surrogate} and the \code{acq_optimizer}.} +} +\if{html}{\out{
    }} +} \section{Methods}{ \subsection{Public methods}{ \itemize{ \item \href{#method-TunerAsyncMbo-new}{\code{TunerAsyncMbo$new()}} +\item \href{#method-TunerAsyncMbo-print}{\code{TunerAsyncMbo$print()}} +\item \href{#method-TunerAsyncMbo-reset}{\code{TunerAsyncMbo$reset()}} \item \href{#method-TunerAsyncMbo-clone}{\code{TunerAsyncMbo$clone()}} } } @@ -39,7 +110,6 @@ If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush @@ -49,6 +119,10 @@ If \code{NULL}, all rush workers set with \code{\link[rush:rush_plan]{rush::rush \if{latex}{\out{\hypertarget{method-TunerAsyncMbo-new}{}}} \subsection{Method \code{new()}}{ Creates a new instance of this \link[R6:R6Class]{R6} class. +For more information on default values for \code{surrogate}, \code{acq_function}, \code{acq_optimizer}, and \code{result_assigner}, see \code{?mbo_defaults}. + +Note that all the parameters below are simply passed to the \link{OptimizerAsyncMbo} and +the respective fields are simply (settable) active bindings to the fields of the \link{OptimizerAsyncMbo}. \subsection{Usage}{ \if{html}{\out{
    }}\preformatted{TunerAsyncMbo$new( surrogate = NULL, @@ -70,11 +144,37 @@ The acquisition function.} \item{\code{acq_optimizer}}{(\link{AcqOptimizer} | \code{NULL})\cr The acquisition function optimizer.} -\item{\code{param_set}}{\link[paradox:ParamSet]{paradox::ParamSet}\cr +\item{\code{param_set}}{(\link[paradox:ParamSet]{paradox::ParamSet})\cr Set of control parameters.} } \if{html}{\out{
    }} } +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TunerAsyncMbo-print}{}}} +\subsection{Method \code{print()}}{ +Print method. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{TunerAsyncMbo$print()}\if{html}{\out{
    }} +} + +\subsection{Returns}{ +(\code{character()}). +} +} +\if{html}{\out{
    }} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TunerAsyncMbo-reset}{}}} +\subsection{Method \code{reset()}}{ +Reset the tuner. +Sets the following fields to \code{NULL}: +\code{surrogate}, \code{acq_function}, \code{acq_optimizer}, \code{result_assigner} +Resets parameter values \code{design_size} and \code{design_function} to their defaults. +\subsection{Usage}{ +\if{html}{\out{
    }}\preformatted{TunerAsyncMbo$reset()}\if{html}{\out{
    }} +} + } \if{html}{\out{
    }} \if{html}{\out{}} diff --git a/man/mlr_tuners_mbo.Rd b/man/mlr_tuners_mbo.Rd index 6e5261f8..a8249d6c 100644 --- a/man/mlr_tuners_mbo.Rd +++ b/man/mlr_tuners_mbo.Rd @@ -86,7 +86,7 @@ This corresponds to the values given by a \link[paradox:ParamSet]{paradox::Param Set of properties of the optimizer. Must be a subset of \code{\link[bbotk:bbotk_reflections]{bbotk_reflections$optimizer_properties}}. MBO in principle is very flexible and by default we assume that the optimizer has all properties. -When fully initialized, properties are determined based on the \code{loop_function} and \code{surrogate}.} +When fully initialized, properties are determined based on the loop, e.g., the \code{loop_function}, and \code{surrogate}.} \item{\code{packages}}{(\code{character()})\cr Set of required packages. @@ -118,7 +118,7 @@ Required packages are determined based on the \code{acq_function}, \code{surroga \if{latex}{\out{\hypertarget{method-TunerMbo-new}{}}} \subsection{Method \code{new()}}{ Creates a new instance of this \link[R6:R6Class]{R6} class. -For more information on default values for \code{loop_function}, \code{surrogate}, \code{acq_function} and \code{acq_optimizer}, see \code{?mbo_defaults}. +For more information on default values for \code{loop_function}, \code{surrogate}, \code{acq_function}, \code{acq_optimizer}, and \code{result_assigner}, see \code{?mbo_defaults}. Note that all the parameters below are simply passed to the \link{OptimizerMbo} and the respective fields are simply (settable) active bindings to the fields of the \link{OptimizerMbo}. diff --git a/tests/testthat/test_AcqFunctionSmsEgo.R b/tests/testthat/test_AcqFunctionSmsEgo.R index b6cdece9..e4f40728 100644 --- a/tests/testthat/test_AcqFunctionSmsEgo.R +++ b/tests/testthat/test_AcqFunctionSmsEgo.R @@ -25,7 +25,8 @@ test_that("AcqFunctionSmsEgo works", { acqf$progress = 1 acqf$update() res = acqf$eval_dt(xdt) - expect_data_table(res, ncols = 1L, nrows = 5L, any.missing = FALSE) - expect_named(res, acqf$id) + expect_data_table(res, ncols = 2L, nrows = 5L, any.missing = FALSE) + expect_named(res) + expect_setequal(colnames(res), c(acqf$id, "acq_epsilon")) }) diff --git a/tests/testthat/test_AcqFunctionStochasticCB.R b/tests/testthat/test_AcqFunctionStochasticCB.R index 267565bc..802e3aa6 100644 --- a/tests/testthat/test_AcqFunctionStochasticCB.R +++ b/tests/testthat/test_AcqFunctionStochasticCB.R @@ -3,11 +3,11 @@ test_that("AcqFunctionStochasticCB works in defaults", { skip_if_not_installed("rush") flush_redis() - rush::rush_plan(n_workers = 2) + rush::rush_plan(n_workers = 1L) instance = oi_async( objective = OBJ_2D, search_space = PS_2D, - terminator = trm("evals", n_evals = 10), + terminator = trm("evals", n_evals = 10L), ) acq_function = acqf("stochastic_cb") @@ -17,8 +17,8 @@ test_that("AcqFunctionStochasticCB works in defaults", { design_size = 5, acq_function = acq_function) - expect_data_table(optimizer$optimize(instance), nrows = 1) - expect_data_table(instance$archive$data, min.rows = 10) + expect_data_table(optimizer$optimize(instance), nrows = 1L) + expect_data_table(instance$archive$data, min.rows = 10L) expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) expect_rush_reset(instance$rush) @@ -29,22 +29,22 @@ test_that("AcqFunctionStochasticCB works with uniform sampling", { skip_if_not_installed("rush") flush_redis() - rush::rush_plan(n_workers = 2) + rush::rush_plan(n_workers = 2L) instance = oi_async( objective = OBJ_2D, search_space = PS_2D, - terminator = trm("evals", n_evals = 10), + terminator = trm("evals", n_evals = 10L), ) acq_function = acqf("stochastic_cb", distribution = "uniform", min_lambda = 1, max_lambda = 3) optimizer = opt("async_mbo", design_function = "sobol", - design_size = 5, + design_size = 5L, acq_function = acq_function) - expect_data_table(optimizer$optimize(instance), nrows = 1) - expect_data_table(instance$archive$data, min.rows = 10) + expect_data_table(optimizer$optimize(instance), nrows = 1L) + expect_data_table(instance$archive$data, min.rows = 10L) expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) expect_numeric(instance$archive$data$acq_lambda, lower = 1, upper = 3) @@ -56,11 +56,11 @@ test_that("AcqFunctionStochasticCB works with exponential sampling", { skip_if_not_installed("rush") flush_redis() - rush::rush_plan(n_workers = 2) + rush::rush_plan(n_workers = 2L) instance = oi_async( objective = OBJ_2D, search_space = PS_2D, - terminator = trm("evals", n_evals = 10), + terminator = trm("evals", n_evals = 10L), ) acq_function = acqf("stochastic_cb", distribution = "exponential", lambda = 1.96) @@ -70,10 +70,10 @@ test_that("AcqFunctionStochasticCB works with exponential sampling", { design_size = 5, acq_function = acq_function) - expect_data_table(optimizer$optimize(instance), nrows = 1) - expect_data_table(instance$archive$data, min.rows = 10) + expect_data_table(optimizer$optimize(instance), nrows = 1L) + expect_data_table(instance$archive$data, min.rows = 10L) expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) - expect_numeric(unique(instance$archive$data$acq_lambda), len = 3) + expect_numeric(unique(instance$archive$data$acq_lambda), len = 3L) expect_rush_reset(instance$rush) }) @@ -84,22 +84,22 @@ test_that("AcqFunctionStochasticCB works with lambda decay", { skip_if_not_installed("rush") flush_redis() - rush::rush_plan(n_workers = 1) + rush::rush_plan(n_workers = 1L) instance = oi_async( objective = OBJ_2D, search_space = PS_2D, - terminator = trm("evals", n_evals = 10), + terminator = trm("evals", n_evals = 10L), ) acq_function = acqf("stochastic_cb", rate = 0.5) optimizer = opt("async_mbo", design_function = "sobol", - design_size = 5, + design_size = 5L, acq_function = acq_function) - expect_data_table(optimizer$optimize(instance), nrows = 1) - expect_data_table(instance$archive$data, min.rows = 10) + expect_data_table(optimizer$optimize(instance), nrows = 1L) + expect_data_table(instance$archive$data, min.rows = 10L) expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) expect_numeric(-instance$archive$data$acq_lambda, sorted = TRUE) @@ -112,25 +112,25 @@ test_that("AcqFunctionStochasticCB works with periodic lambda decay", { skip_if_not_installed("rush") flush_redis() - rush::rush_plan(n_workers = 1) + rush::rush_plan(n_workers = 1L) instance = oi_async( objective = OBJ_2D, search_space = PS_2D, - terminator = trm("evals", n_evals = 10), + terminator = trm("evals", n_evals = 10L), ) acq_function = acqf("stochastic_cb", rate = 0.5, period = 2) optimizer = opt("async_mbo", design_function = "sobol", - design_size = 5, + design_size = 5L, acq_function = acq_function) - expect_data_table(optimizer$optimize(instance), nrows = 1) - expect_data_table(instance$archive$data, min.rows = 10) + expect_data_table(optimizer$optimize(instance), nrows = 1L) + expect_data_table(instance$archive$data, min.rows = 10L) expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) - expect_numeric(unique(instance$archive$data$acq_lambda), len = 3) + expect_numeric(unique(instance$archive$data$acq_lambda), len = 3L) expect_rush_reset(instance$rush) }) diff --git a/tests/testthat/test_AcqFunctionStochasticEI.R b/tests/testthat/test_AcqFunctionStochasticEI.R index 37e5e70b..8629d0b3 100644 --- a/tests/testthat/test_AcqFunctionStochasticEI.R +++ b/tests/testthat/test_AcqFunctionStochasticEI.R @@ -3,22 +3,22 @@ test_that("AcqFunctionStochasticEI works in defaults", { skip_if_not_installed("rush") flush_redis() - rush::rush_plan(n_workers = 1) + rush::rush_plan(n_workers = 1L) instance = oi_async( objective = OBJ_2D, search_space = PS_2D, - terminator = trm("evals", n_evals = 10), + terminator = trm("evals", n_evals = 10L), ) acq_function = acqf("stochastic_ei") optimizer = opt("async_mbo", design_function = "sobol", - design_size = 5, + design_size = 5L, acq_function = acq_function) - expect_data_table(optimizer$optimize(instance), nrows = 1) - expect_data_table(instance$archive$data, min.rows = 10) + expect_data_table(optimizer$optimize(instance), nrows = 1L) + expect_data_table(instance$archive$data, min.rows = 10L) expect_names(names(instance$archive$data), must.include = c(".already_evaluated", "acq_ei", "acq_epsilon_0", "acq_epsilon")) expect_numeric(-instance$archive$data$acq_epsilon, sorted = TRUE) @@ -30,22 +30,22 @@ test_that("AcqFunctionStochasticEI works with multiple workers", { skip_if_not_installed("rush") flush_redis() - rush::rush_plan(n_workers = 2) + rush::rush_plan(n_workers = 2L) instance = oi_async( objective = OBJ_2D, search_space = PS_2D, - terminator = trm("evals", n_evals = 20), + terminator = trm("evals", n_evals = 20L), ) acq_function = acqf("stochastic_ei") optimizer = opt("async_mbo", design_function = "sobol", - design_size = 5, + design_size = 5L, acq_function = acq_function) - expect_data_table(optimizer$optimize(instance), nrows = 1) - expect_data_table(instance$archive$data, min.rows = 10) + expect_data_table(optimizer$optimize(instance), nrows = 1L) + expect_data_table(instance$archive$data, min.rows = 10L) expect_names(names(instance$archive$data), must.include = c(".already_evaluated", "acq_ei", "acq_epsilon_0", "acq_epsilon")) expect_rush_reset(instance$rush) @@ -57,24 +57,24 @@ test_that("AcqFunctionStochasticEI works with periodic epsilon decay", { skip_if_not_installed("rush") flush_redis() - rush::rush_plan(n_workers = 1) + rush::rush_plan(n_workers = 1L) instance = oi_async( objective = OBJ_2D, search_space = PS_2D, - terminator = trm("evals", n_evals = 10), + terminator = trm("evals", n_evals = 10L), ) acq_function = acqf("stochastic_ei", rate = 0.5, period = 2) optimizer = opt("async_mbo", design_function = "sobol", - design_size = 5, + design_size = 5L, acq_function = acq_function) - expect_data_table(optimizer$optimize(instance), nrows = 1) - expect_data_table(instance$archive$data, min.rows = 10) + expect_data_table(optimizer$optimize(instance), nrows = 1L) + expect_data_table(instance$archive$data, min.rows = 10L) expect_names(names(instance$archive$data), must.include = c(".already_evaluated", "acq_ei", "acq_epsilon_0", "acq_epsilon")) - expect_numeric(unique(instance$archive$data$acq_epsilon), len = 3) + expect_numeric(unique(instance$archive$data$acq_epsilon), len = 3L) expect_rush_reset(instance$rush) }) diff --git a/tests/testthat/test_OptimizerADBO.R b/tests/testthat/test_OptimizerADBO.R index 746bb1b3..6952fd77 100644 --- a/tests/testthat/test_OptimizerADBO.R +++ b/tests/testthat/test_OptimizerADBO.R @@ -1,18 +1,18 @@ -test_that("adbo works in defaults", { +test_that("OptimizerADBO works in defaults", { skip_on_cran() skip_if_not_installed("rush") flush_redis() - rush::rush_plan(n_workers = 2) + rush::rush_plan(n_workers = 2L) instance = oi_async( objective = OBJ_2D, search_space = PS_2D, - terminator = trm("evals", n_evals = 20), + terminator = trm("evals", n_evals = 20L), ) - optimizer = opt("adbo", design_function = "sobol", design_size = 5) + optimizer = opt("adbo", design_function = "sobol", design_size = 5L) - expect_data_table(optimizer$optimize(instance), nrows = 1) - expect_data_table(instance$archive$data, min.rows = 20) + expect_data_table(optimizer$optimize(instance), nrows = 1L) + expect_data_table(instance$archive$data, min.rows = 20L) expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) expect_rush_reset(instance$rush) diff --git a/tests/testthat/test_OptimizerAsyncMbo.R b/tests/testthat/test_OptimizerAsyncMbo.R index 2b611f9c..6f4afdc2 100644 --- a/tests/testthat/test_OptimizerAsyncMbo.R +++ b/tests/testthat/test_OptimizerAsyncMbo.R @@ -1,40 +1,40 @@ -test_that("async optimizer works in defaults", { +test_that("OptimizerAsyncMbo works in defaults", { skip_on_cran() skip_if_not_installed("rush") flush_redis() - rush::rush_plan(n_workers = 2) + rush::rush_plan(n_workers = 2L) instance = oi_async( objective = OBJ_2D, search_space = PS_2D, - terminator = trm("evals", n_evals = 10), + terminator = trm("evals", n_evals = 10L), ) - optimizer = opt("async_mbo", design_function = "sobol", design_size = 5) + optimizer = opt("async_mbo", design_function = "sobol", design_size = 5L) - expect_data_table(optimizer$optimize(instance), nrows = 1) - expect_data_table(instance$archive$data, min.rows = 10) + expect_data_table(optimizer$optimize(instance), nrows = 1L) + expect_data_table(instance$archive$data, min.rows = 10L) expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) expect_rush_reset(instance$rush) }) -test_that("async optimizer works with evaluations in archive", { +test_that("OptimizerAsyncMbo works with evaluations in archive", { skip_on_cran() skip_if_not_installed("rush") flush_redis() - rush::rush_plan(n_workers = 2) + rush::rush_plan(n_workers = 2L) instance = oi_async( objective = OBJ_2D, search_space = PS_2D, - terminator = trm("evals", n_evals = 10), + terminator = trm("evals", n_evals = 10L), ) optimizer = opt("async_random_search") optimizer$optimize(instance) - instance$terminator$param_set$values$n_evals = 40 + instance$terminator$param_set$values$n_evals = 40L optimizer = opt("async_mbo") optimizer$optimize(instance) diff --git a/tests/testthat/test_SurrogateLearner.R b/tests/testthat/test_SurrogateLearner.R index 08f46d7e..5dae4d03 100644 --- a/tests/testthat/test_SurrogateLearner.R +++ b/tests/testthat/test_SurrogateLearner.R @@ -50,11 +50,12 @@ test_that("param_set", { inst = MAKE_INST_1D() surrogate = SurrogateLearner$new(learner = REGR_FEATURELESS, archive = inst$archive) expect_r6(surrogate$param_set, "ParamSet") - expect_setequal(surrogate$param_set$ids(), c("assert_insample_perf", "perf_measure", "perf_threshold", "catch_errors")) + expect_setequal(surrogate$param_set$ids(), c("assert_insample_perf", "perf_measure", "perf_threshold", "catch_errors", "impute_method")) expect_equal(surrogate$param_set$class[["assert_insample_perf"]], "ParamLgl") expect_equal(surrogate$param_set$class[["perf_measure"]], "ParamUty") expect_equal(surrogate$param_set$class[["perf_threshold"]], "ParamDbl") expect_equal(surrogate$param_set$class[["catch_errors"]], "ParamLgl") + expect_equal(surrogate$param_set$class[["impute_method"]], "ParamFct") expect_error({surrogate$param_set = list()}, regexp = "param_set is read-only.") }) diff --git a/tests/testthat/test_SurrogateLearnerCollection.R b/tests/testthat/test_SurrogateLearnerCollection.R index aa050284..b95ecf55 100644 --- a/tests/testthat/test_SurrogateLearnerCollection.R +++ b/tests/testthat/test_SurrogateLearnerCollection.R @@ -60,11 +60,12 @@ test_that("param_set", { inst = MAKE_INST(OBJ_1D_2, PS_1D, trm("evals", n_evals = 5L)) surrogate = SurrogateLearnerCollection$new(learner = list(REGR_FEATURELESS, REGR_FEATURELESS$clone(deep = TRUE)), archive = inst$archive) expect_r6(surrogate$param_set, "ParamSet") - expect_setequal(surrogate$param_set$ids(), c("assert_insample_perf", "perf_measures", "perf_thresholds", "catch_errors")) + expect_setequal(surrogate$param_set$ids(), c("assert_insample_perf", "perf_measures", "perf_thresholds", "catch_errors", "impute_method")) expect_equal(surrogate$param_set$class[["assert_insample_perf"]], "ParamLgl") expect_equal(surrogate$param_set$class[["perf_measures"]], "ParamUty") expect_equal(surrogate$param_set$class[["perf_thresholds"]], "ParamUty") expect_equal(surrogate$param_set$class[["catch_errors"]], "ParamLgl") + expect_equal(surrogate$param_set$class[["impute_method"]], "ParamFct") expect_error({surrogate$param_set = list()}, regexp = "param_set is read-only.") }) diff --git a/tests/testthat/test_TunerADBO.R b/tests/testthat/test_TunerADBO.R index 8dc51a53..53c50d3f 100644 --- a/tests/testthat/test_TunerADBO.R +++ b/tests/testthat/test_TunerADBO.R @@ -1,27 +1,26 @@ - -test_that("async mbo tuner works", { +test_that("TunerADBO works", { skip_on_cran() skip_if_not_installed("rush") flush_redis() learner = lrn("classif.rpart", - minsplit = to_tune(2, 128), + minsplit = to_tune(2L, 128L), cp = to_tune(1e-04, 1e-1)) - rush::rush_plan(n_workers = 4) + rush::rush_plan(n_workers = 4L) instance = ti_async( task = tsk("pima"), learner = learner, - resampling = rsmp("cv", folds = 3), + resampling = rsmp("cv", folds = 3L), measure = msr("classif.ce"), - terminator = trm("evals", n_evals = 20), + terminator = trm("evals", n_evals = 20L), store_benchmark_result = FALSE ) - tuner = tnr("adbo", design_size = 10) + tuner = tnr("adbo", design_size = 10L) - expect_data_table(tuner$optimize(instance), nrows = 1) - expect_data_table(instance$archive$data, min.rows = 10) + expect_data_table(tuner$optimize(instance), nrows = 1L) + expect_data_table(instance$archive$data, min.rows = 10L) expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) expect_rush_reset(instance$rush) diff --git a/tests/testthat/test_TunerAsyncMbo.R b/tests/testthat/test_TunerAsyncMbo.R index dce8021e..31d6da9a 100644 --- a/tests/testthat/test_TunerAsyncMbo.R +++ b/tests/testthat/test_TunerAsyncMbo.R @@ -1,27 +1,26 @@ - -test_that("async mbo tuner works", { +test_that("TunerAsyncMbo works", { skip_on_cran() skip_if_not_installed("rush") flush_redis() learner = lrn("classif.rpart", - minsplit = to_tune(2, 128), + minsplit = to_tune(2L, 128L), cp = to_tune(1e-04, 1e-1)) - rush::rush_plan(n_workers = 4) + rush::rush_plan(n_workers = 4L) instance = ti_async( task = tsk("pima"), learner = learner, - resampling = rsmp("cv", folds = 3), + resampling = rsmp("cv", folds = 3L), measure = msr("classif.ce"), - terminator = trm("evals", n_evals = 20), + terminator = trm("evals", n_evals = 20L), store_benchmark_result = FALSE ) - tuner = tnr("async_mbo", design_size = 4) + tuner = tnr("async_mbo", design_size = 4L) - expect_data_table(tuner$optimize(instance), nrows = 1) - expect_data_table(instance$archive$data, min.rows = 10) + expect_data_table(tuner$optimize(instance), nrows = 1L) + expect_data_table(instance$archive$data, min.rows = 10L) expect_names(names(instance$archive$data), must.include = c("acq_cb", ".already_evaluated", "acq_lambda_0", "acq_lambda")) expect_rush_reset(instance$rush) From 1408c3ec8e3361d2d9fd9d5fbd4229f3663356cf Mon Sep 17 00:00:00 2001 From: Lennart Schneider Date: Fri, 15 Nov 2024 11:45:41 +0100 Subject: [PATCH 49/49] docs: --- DESCRIPTION | 2 -- NEWS.md | 6 ++++++ R/AcqFunctionSmsEgo.R | 2 +- R/OptimizerMbo.R | 2 +- R/TunerADBO.R | 2 ++ R/TunerAsyncMbo.R | 2 ++ 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 47a032c2..2b949150 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -66,8 +66,6 @@ Suggests: rush, stringi, testthat (>= 3.0.0) -Remotes: - mlr-org/bbotk ByteCompile: no Encoding: UTF-8 Config/testthat/edition: 3 diff --git a/NEWS.md b/NEWS.md index 16fd1198..ace7e6d2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ # mlr3mbo (development version) +* refactor: refactored `SurrogateLearner` and `SurrogateLearnerCollection` to allow updating on an asynchronous `Archive` +* feat: added experimental `OptimizerAsyncMbo`, `OptimizerADBO`, `TunerAsyncMbo`, and `TunerADBO` that allow for asynchronous optimization +* feat: added `AcqFunctionStochasticCB` and `AcqFunctionStochasticEI` that are useful for asynchronous optimization +* doc: minor changes to highlight differences between batch and asynchronous objects related to asynchronous support +* refactor: `AcqFunction`s and `AcqOptimizer` gained a `reset()` method. + # mlr3mbo 0.2.6 * refactor: Extract internal tuned values in instance. diff --git a/R/AcqFunctionSmsEgo.R b/R/AcqFunctionSmsEgo.R index b2865aae..20c1c503 100644 --- a/R/AcqFunctionSmsEgo.R +++ b/R/AcqFunctionSmsEgo.R @@ -20,7 +20,7 @@ #' #' @section Note: #' * This acquisition function always also returns its current epsilon values in a list column (`acq_epsilon`). -#' This value will be logged into the [bbotk::ArchiveBatch] of the [bbotk::OptimInstanceBatch] of the [AcqOptimizer] and +#' These values will be logged into the [bbotk::ArchiveBatch] of the [bbotk::OptimInstanceBatch] of the [AcqOptimizer] and #' therefore also in the [bbotk::Archive] of the actual [bbotk::OptimInstance] that is to be optimized. #' #' @references diff --git a/R/OptimizerMbo.R b/R/OptimizerMbo.R index 5f4f80b4..745f87a4 100644 --- a/R/OptimizerMbo.R +++ b/R/OptimizerMbo.R @@ -106,7 +106,7 @@ OptimizerMbo = R6Class("OptimizerMbo", #' #' Even if already initialized, the `surrogate$archive` field will always be overwritten by the [bbotk::ArchiveBatch] of the current [bbotk::OptimInstanceBatch] to be optimized. #' - #' For more information on default values for `surrogate`, `acq_function`, `acq_optimizer` and `result_assigner`, see `?mbo_defaults`. + #' For more information on default values for `loop_function`, `surrogate`, `acq_function`, `acq_optimizer` and `result_assigner`, see `?mbo_defaults`. #' #' @template param_loop_function #' @template param_surrogate diff --git a/R/TunerADBO.R b/R/TunerADBO.R index 4ce392f8..cac3c0fc 100644 --- a/R/TunerADBO.R +++ b/R/TunerADBO.R @@ -7,6 +7,8 @@ #' This is a minimal interface internally passing on to [OptimizerAsyncMbo]. #' For additional information and documentation see [OptimizerAsyncMbo]. #' +#' Currently, only single-objective optimization is supported and `TunerADBO` is considered an experimental feature and API might be subject to changes. +#' #' @section Parameters: #' \describe{ #' \item{`initial_design`}{`data.table::data.table()`\cr diff --git a/R/TunerAsyncMbo.R b/R/TunerAsyncMbo.R index fe9560d5..3843cbbc 100644 --- a/R/TunerAsyncMbo.R +++ b/R/TunerAsyncMbo.R @@ -8,6 +8,8 @@ #' This is a minimal interface internally passing on to [OptimizerAsyncMbo]. #' For additional information and documentation see [OptimizerAsyncMbo]. #' +#' Currently, only single-objective optimization is supported and `TunerAsyncMbo` is considered an experimental feature and API might be subject to changes. +#' #' @section Parameters: #' \describe{ #' \item{`initial_design`}{`data.table::data.table()`\cr