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 0424c1fb..2b949150 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -39,16 +39,16 @@ 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), checkmate (>= 2.0.0), data.table, lgr (>= 0.3.4), - mlr3 (>= 0.21.0), + mlr3 (>= 0.21.1), mlr3misc (>= 0.11.0), - mlr3tuning (>= 1.0.2), - paradox (>= 1.0.0), + paradox (>= 1.0.1), spacefillr, R6 (>= 2.4.1) Suggests: @@ -62,6 +62,8 @@ Suggests: ranger, rgenoud, rpart, + redux, + rush, stringi, testthat (>= 3.0.0) ByteCompile: no @@ -85,8 +87,12 @@ Collate: 'AcqFunctionPI.R' 'AcqFunctionSD.R' 'AcqFunctionSmsEgo.R' + 'AcqFunctionStochasticCB.R' + 'AcqFunctionStochasticEI.R' 'AcqOptimizer.R' 'aaa.R' + 'OptimizerADBO.R' + 'OptimizerAsyncMbo.R' 'OptimizerMbo.R' 'mlr_result_assigners.R' 'ResultAssigner.R' @@ -95,6 +101,8 @@ Collate: 'Surrogate.R' 'SurrogateLearner.R' 'SurrogateLearnerCollection.R' + 'TunerADBO.R' + 'TunerAsyncMbo.R' 'TunerMbo.R' 'mlr_loop_functions.R' 'bayesopt_ego.R' diff --git a/NAMESPACE b/NAMESPACE index 9b8e3b96..e5b96257 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,7 +16,11 @@ export(AcqFunctionMulti) export(AcqFunctionPI) export(AcqFunctionSD) export(AcqFunctionSmsEgo) +export(AcqFunctionStochasticCB) +export(AcqFunctionStochasticEI) export(AcqOptimizer) +export(OptimizerADBO) +export(OptimizerAsyncMbo) export(OptimizerMbo) export(ResultAssigner) export(ResultAssignerArchive) @@ -24,6 +28,8 @@ export(ResultAssignerSurrogate) export(Surrogate) export(SurrogateLearner) export(SurrogateLearnerCollection) +export(TunerADBO) +export(TunerAsyncMbo) export(TunerMbo) export(acqf) export(acqfs) @@ -58,6 +64,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/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/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..20c1c503 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`). +#' 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("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 new file mode 100644 index 00000000..b8d66ebf --- /dev/null +++ b/R/AcqFunctionStochasticCB.R @@ -0,0 +1,188 @@ +#' @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 \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 +#' \eqn{\lambda} value for sampling from the exponential distribution. +#' Defaults to `1.96`. +#' * `"min_lambda"` (`numeric(1)`)\cr +#' Minimum value of \eqn{\lambda}for sampling from the uniform distribution. +#' Defaults to `0.01`. +#' * `"max_lambda"` (`numeric(1)`)\cr +#' Maximum value of \eqn{\lambda} for sampling from the uniform distribution. +#' Defaults to `10`. +#' * `"distribution"` (`character(1)`)\cr +#' Distribution to sample \eqn{\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. +#' +#' @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")` +#' * `r format_bib("egele_2023")` +#' +#' @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, + + 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 (`NULL` | `integer(1)`). + initialize = function( + surrogate = NULL, + lambda = 1.96, + min_lambda = 0.01, + max_lambda = 10, + distribution = "uniform", + rate = 0, + period = NULL + ) { + assert_r6(surrogate, "SurrogateLearner", null.ok = TRUE) + 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) + + 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)) { + + 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 + } + + # 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 + 1L + } + }, + + #' @description + #' Reset the acquisition function. + #' Resets the private update counter `.t` used within the epsilon decay. + reset = function() { + private$.t = 0L + } + ), + + private = list( + .lambda = NULL, + .min_lambda = NULL, + .max_lambda = NULL, + .distribution = NULL, + .rate = NULL, + .period = NULL, + .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 + data.table(acq_cb = cb, acq_lambda = lambda, acq_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..9b3770a3 --- /dev/null +++ b/R/AcqFunctionStochasticEI.R @@ -0,0 +1,157 @@ +#' @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. +#' \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 +#' \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. +#' +#' @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, + + 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 (`NULL` | `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, default = 0.1)) + + 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 + 1L + }, + + #' @description + #' Reset the acquisition function. + #' Resets the private update counter `.t` used within the epsilon decay. + reset = function() { + private$.t = 0L + } + ), + + private = list( + .rate = NULL, + .period = NULL, + .epsilon_0 = NULL, + .t = 0L, + .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, acq_epsilon = epsilon, acq_epsilon_0 = private$.epsilon_0) + } + ) +) + +mlr_acqfunctions$add("stochastic_ei", AcqFunctionStochasticEI) + 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 new file mode 100644 index 00000000..8565cdf0 --- /dev/null +++ b/R/OptimizerADBO.R @@ -0,0 +1,126 @@ +#' @title Asynchronous Decentralized Bayesian Optimization +#' @name mlr_optimizers_adbo +#' +#' @description +#' `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 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 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 +#' 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`}{`data.table::data.table()`\cr +#' Initial design of the optimization. +#' 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 if it is to be generated. +#' Default is `100`.} +#' \item{`design_function`}{`character(1)`\cr +#' 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 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() { + param_set = ps( + lambda = p_dbl(lower = 0, default = 1.96), + 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::OptimizerADBO") + + self$param_set$set_values( + lambda = 1.96, + rate = 0.1, + period = 25L) + }, + + #' @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$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)) + + super$optimize(inst) + } + ) +) + +#' @include aaa.R +optimizers[["adbo"]] = OptimizerADBO diff --git a/R/OptimizerAsyncMbo.R b/R/OptimizerAsyncMbo.R new file mode 100644 index 00000000..c7b202dc --- /dev/null +++ b/R/OptimizerAsyncMbo.R @@ -0,0 +1,372 @@ +#' @title Asynchronous Model Based Optimization +#' +#' @name mlr_optimizers_async_mbo +#' +#' @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 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 [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 the specified `design_function`. +#' Default is `NULL`.} +#' \item{`design_size`}{`integer(1)`\cr +#' Size of the initial design if it is to be generated. +#' Default is `100`.} +#' \item{`design_function`}{`character(1)`\cr +#' 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 specified via [rush::rush_plan()] are used. +#' Default is `NULL`.} +#' } +#' +#' @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("async_mbo", design_size = 4, n_workers = 2) +#' +#' optimizer$optimize(instance) +#' } +#' } +OptimizerAsyncMbo = R6Class("OptimizerAsyncMbo", + inherit = bbotk::OptimizerAsync, + + public = list( + #' @description + #' Creates a new instance of this [R6][R6::R6Class] class. + #' + #' 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, + label = "Asynchronous Model Based Optimization", + man = "mlr3mbo::OptimizerAsyncMbo" + ) { + + default_param_set = ps( + initial_design = p_uty(), + 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 = 100L, design_function = "sobol") + + super$initialize("async_mbo", + param_set = param_set, + 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 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) { + 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 + 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, + "sobol" = generate_design_sobol, + "lhs" = generate_design_lhs) + + lg$debug("Generating sobol design with size %s", pv$design_size) + 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 + } + 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 = "SurrogateLearner", 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_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) { + 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 { + stop("$param_classes is read-only.") + } + }, + + #' @template field_properties + properties = function(rhs) { + 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.") + } + }, + + #' @template field_packages + packages = function(rhs) { + 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.") + } + } + ), + + private = list( + .surrogate = NULL, + .acq_function = NULL, + .acq_optimizer = NULL, + .result_assigner = NULL, + + .optimize = function(inst) { + 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 optimization phase", self$id) + + # actual loop + while (!inst$is_terminated) { + # sample + 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) + } + ) +) + +#' @include aaa.R +optimizers[["async_mbo"]] = OptimizerAsyncMbo diff --git a/R/OptimizerMbo.R b/R/OptimizerMbo.R index fa1c7547..745f87a4 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 `loop_function`, `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 12370377..da70d173 100644 --- a/R/ResultAssignerArchive.R +++ b/R/ResultAssignerArchive.R @@ -26,23 +26,21 @@ 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() cols_x = instance$archive$cols_x cols_y = instance$archive$cols_y - xdt = xydt[, cols_x, with = FALSE] extra = xydt[, !c(cols_x, cols_y), with = FALSE] - - if (inherits(instance, "OptimInstanceBatchMultiCrit")) { + if (inherits(instance, c("OptimInstanceBatchMultiCrit", "OptimInstanceAsyncMultiCrit"))) { ydt = xydt[, cols_y, with = FALSE] instance$assign_result(xdt, ydt, extra = extra) } else { y = unlist(xydt[, cols_y, with = FALSE]) - instance$assign_result(xdt, y, extra = extra) + instance$assign_result(xdt = xdt, y = y, extra = extra) } } ), diff --git a/R/ResultAssignerSurrogate.R b/R/ResultAssignerSurrogate.R index 59c3aa95..3f84e382 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) @@ -60,16 +60,18 @@ ResultAssignerSurrogate = R6Class("ResultAssignerSurrogate", archive_tmp = archive$clone(deep = TRUE) archive_tmp$data[, self$surrogate$cols_y := means] xydt = archive_tmp$best() - extra = xydt[, !c(archive_tmp$cols_x, archive_tmp$cols_y), with = FALSE] - best = xydt[, archive_tmp$cols_x, with = FALSE] + cols_x = archive_tmp$cols_x + cols_y = archive_tmp$cols_y + best = xydt[, cols_x, with = FALSE] + extra = xydt[, !c(cols_x, cols_y), with = FALSE] # ys are still the ones originally evaluated - best_y = if (inherits(instance, "OptimInstanceBatchSingleCrit")) { - unlist(archive$data[best, on = archive$cols_x][, archive$cols_y, with = FALSE]) - } else if (inherits(instance, "OptimInstanceBatchMultiCrit")) { - archive$data[best, on = archive$cols_x][, archive$cols_y, with = FALSE] + best_y = if (inherits(instance, c("OptimInstanceBatchSingleCrit", "OptimInstanceAsyncSingleCrit"))) { + unlist(archive$data[best, on = cols_x][, cols_y, with = FALSE]) + } else if (inherits(instance, c("OptimInstanceBatchMultiCrit", "OptimInstanceAsyncMultiCrit"))) { + archive$data[best, on = cols_x][, cols_y, with = FALSE] } - instance$assign_result(xdt = best, best_y, extra = extra) + instance$assign_result(xdt = best, y = best_y, extra = extra) } ), 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/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 new file mode 100644 index 00000000..cac3c0fc --- /dev/null +++ b/R/TunerADBO.R @@ -0,0 +1,168 @@ +#' @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. +#' 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 +#' Initial design of the optimization. +#' 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 if it is to be generated. +#' Default is `100`.} +#' \item{`design_function`}{`character(1)`\cr +#' 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 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.") + } + }, + + #' @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.") + } + } + ) +) + +#' @include aaa.R +tuners[["adbo"]] = TunerADBO diff --git a/R/TunerAsyncMbo.R b/R/TunerAsyncMbo.R new file mode 100644 index 00000000..3843cbbc --- /dev/null +++ b/R/TunerAsyncMbo.R @@ -0,0 +1,177 @@ +#' @title TunerAsync using Asynchronous Model Based Optimization +#' +#' @include OptimizerAsyncMbo.R +#' @name mlr_tuners_async_mbo +#' +#' @description +#' `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]. +#' +#' 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 +#' Initial design of the optimization. +#' 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 if it is to be generated. +#' Default is `100`.} +#' \item{`design_function`}{`character(1)`\cr +#' 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 specified via [rush::rush_plan()] are used. +#' Default is `NULL`.} +#' } +#' +#' @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("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 + #' 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.") + } + } + ) +) + +#' @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 68efb918..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", @@ -125,6 +125,13 @@ bibentries = c( booktitle = "Parallel Problem Solving from Nature -- PPSN XVII", year = "2022", pages = "90--103" + ), + + 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 e6bcb7f1..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,19 +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_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) { - 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")) + 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,7 +191,7 @@ 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) { SurrogateLearner$new(learner) - } else { + } else { learners = replicate(n_learner, learner$clone(deep = TRUE), simplify = FALSE) SurrogateLearnerCollection$new(learners) } @@ -200,10 +201,12 @@ default_surrogate = function(instance, learner = NULL, n_learner = NULL) { #' #' @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 @@ -211,8 +214,12 @@ default_acqfunction = function(instance) { assert_r6(instance, classes = "OptimInstance") if (inherits(instance, "OptimInstanceBatchSingleCrit")) { AcqFunctionEI$new() + } else if (inherits(instance, "OptimInstanceAsyncSingleCrit")) { + 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 diff --git a/R/zzz.R b/R/zzz.R index 003b52c1..a0e1e22d 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -9,7 +9,7 @@ #' @import lgr #' @import mlr3 #' @import mlr3tuning -#' @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/attic/OptimizerADBO.R b/attic/OptimizerADBO.R new file mode 100644 index 00000000..3cdd4754 --- /dev/null +++ b/attic/OptimizerADBO.R @@ -0,0 +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), + 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/attic/TunerADBO.R b/attic/TunerADBO.R new file mode 100644 index 00000000..6c89074f --- /dev/null +++ b/attic/TunerADBO.R @@ -0,0 +1,48 @@ +#' @title Asynchronous Decentralized Bayesian Optimization +#' @name mlr_tuners_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.} +#' } +#' +#' @export +TunerADBO = R6Class("TunerADBO", + inherit = mlr3tuning::TunerAsyncFromOptimizerAsync, + 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/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/attic/test_TunerADBO.R b/attic/test_TunerADBO.R new file mode 100644 index 00000000..5d677bff --- /dev/null +++ b/attic/test_TunerADBO.R @@ -0,0 +1,110 @@ +test_that("adbo 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 = 4) + tuner$optimize(instance) + + expect_data_table(instance$archive$data, min.rows = 20L) + expect_rush_reset(instance$rush) +}) + +test_that("adbo works with transformation functions", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + learner = lrn("classif.rpart", + minsplit = to_tune(2, 128, logscale = TRUE), + cp = to_tune(1e-04, 1e-1, logscale = TRUE)) + + 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 + ) + + optimizer = tnr("adbo", 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", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + 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()) + + 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 + ) + + optimizer = tnr("adbo", 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", { + skip_on_cran() + skip_if_not_installed("rush") + skip_if_not_installed("mlr3pipelines") + flush_redis() + library(mlr3pipelines) + + 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")) + ) + + rush::rush_plan(n_workers = 2) + instance = ti_async( + 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", design_size = 4) + optimizer$optimize(instance) + + expect_data_table(instance$archive$data, min.rows = 20) + expect_rush_reset(instance$rush) +}) 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-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..24f48a24 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}{ @@ -67,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()}} @@ -145,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/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 25e416d3..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) +default_surrogate( + instance, + learner = NULL, + n_learner = NULL, + force_random_forest = FALSE +) } \arguments{ \item{instance}{(\link[bbotk:OptimInstance]{bbotk::OptimInstance})\cr @@ -14,8 +19,11 @@ 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_random_forest}{(\code{logical(1)}). +If \code{TRUE}, a random forest is constructed even if the parameter space is numeric-only.} } \value{ \link{Surrogate} @@ -44,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.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..7c7c2c79 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}{ @@ -125,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 e38ee1fd..72493345 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}{ @@ -100,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 30c11e73..895e84e8 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}{ @@ -104,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 0601b421..cc7e5a52 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}{ @@ -123,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 07bc12a9..d07624be 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}{ @@ -113,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 44d7b507..fd7df6d9 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}{ @@ -119,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 fdcd77e2..cd6a7400 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}{ @@ -85,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 344632ff..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}{ @@ -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}{ @@ -113,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 e4ec11f8..a388c9f8 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}{ @@ -102,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 7dd8184f..c40e4515 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}{ @@ -85,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 314da30e..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") & @@ -80,7 +89,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}{ @@ -102,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{
    }} } @@ -111,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()}} } } @@ -156,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 new file mode 100644 index 00000000..19255f06 --- /dev/null +++ b/man/mlr_acqfunctions_stochastic_cb.Rd @@ -0,0 +1,220 @@ +% 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 \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}{ + +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{"lambda"} (\code{numeric(1)})\cr +\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 \eqn{\lambda}for sampling from the uniform distribution. +Defaults to \code{0.01}. +\item \code{"max_lambda"} (\code{numeric(1)})\cr +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 \eqn{\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. +} +} + +\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 Egelé, Romain, Guyon, Isabelle, Vishwanath, Venkatram, Balaprakash, Prasanna (2023). +\dQuote{Asynchronous Decentralized Bayesian Optimization for Large Scale Hyperparameter Optimization.} +In \emph{2023 IEEE 19th International Conference on e-Science (e-Science)}, 1--10. +} +} +\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-reset}{\code{AcqFunctionStochasticCB$reset()}} +\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, + lambda = 1.96, + 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{lambda}}{(\code{numeric(1)}).} + +\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{NULL} | \code{integer(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-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{}} +\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..83f40c99 --- /dev/null +++ b/man/mlr_acqfunctions_stochastic_ei.Rd @@ -0,0 +1,208 @@ +% 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. +\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}{ + +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. +} +} + +\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). +\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-reset}{\code{AcqFunctionStochasticEI$reset()}} +\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{NULL} | \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-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{}} +\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_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 new file mode 100644 index 00000000..ed7ab0a5 --- /dev/null +++ b/man/mlr_optimizers_adbo.Rd @@ -0,0 +1,154 @@ +% 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{ +\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 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 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 +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}}{\code{data.table::data.table()}\cr +Initial design of the optimization. +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 if it is to be generated. +Default is \code{100}.} +\item{\code{design_function}}{\code{character(1)}\cr +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 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 Egelé, Romain, Guyon, Isabelle, Vishwanath, Venkatram, Balaprakash, Prasanna (2023). +\dQuote{Asynchronous Decentralized Bayesian Optimization for Large Scale Hyperparameter Optimization.} +In \emph{2023 IEEE 19th International Conference on e-Science (e-Science)}, 1--10. +} +} +\section{Super classes}{ +\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-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 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{}} +\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..d7dc16bf --- /dev/null +++ b/man/mlr_optimizers_async_mbo.Rd @@ -0,0 +1,278 @@ +% 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 (AMBO). +AMBO starts multiple sequential MBO runs on different workers. +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{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 the specified \code{design_function}. +Default is \code{NULL}.} +\item{\code{design_size}}{\code{integer(1)}\cr +Size of the initial design if it is to be generated. +Default is \code{100}.} +\item{\code{design_function}}{\code{character(1)}\cr +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 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} +} +\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-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()}} +} +} +\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. + +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, + 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.} + +\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_set}}{(\link[paradox:ParamSet]{paradox::ParamSet})\cr +Set of control parameters.} + +\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{
    }} +} +} +\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 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{
    }} +} + +\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{}} +\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_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 new file mode 100644 index 00000000..19369a35 --- /dev/null +++ b/man/mlr_tuners_adbo.Rd @@ -0,0 +1,178 @@ +% 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{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. +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 the specified \code{design_function}. +Default is \code{NULL}.} +\item{\code{design_size}}{\code{integer(1)}\cr +Size of the initial design if it is to be generated. +Default is \code{100}.} +\item{\code{design_function}}{\code{character(1)}\cr +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 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 Egelé, Romain, Guyon, Isabelle, Vishwanath, Venkatram, Balaprakash, Prasanna (2023). +\dQuote{Asynchronous Decentralized Bayesian Optimization for Large Scale Hyperparameter Optimization.} +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()}} +} +} +\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-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{}} +\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/man/mlr_tuners_async_mbo.Rd b/man/mlr_tuners_async_mbo.Rd new file mode 100644 index 00000000..b7f15d1a --- /dev/null +++ b/man/mlr_tuners_async_mbo.Rd @@ -0,0 +1,196 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/TunerAsyncMbo.R +\name{mlr_tuners_async_mbo} +\alias{mlr_tuners_async_mbo} +\alias{TunerAsyncMbo} +\title{TunerAsync using Asynchronous Model Based Optimization} +\description{ +\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 the specified \code{design_function}. +Default is \code{NULL}.} +\item{\code{design_size}}{\code{integer(1)}\cr +Size of the initial design if it is to be generated. +Default is \code{100}.} +\item{\code{design_function}}{\code{character(1)}\cr +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 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()}} +} +} +\if{html}{\out{ +
    Inherited methods + +
    +}} +\if{html}{\out{
    }} +\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. +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, + 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{}} +\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{}} +\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{TunerAsyncMbo$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_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/helper.R b/tests/testthat/helper.R index 41a886c8..c137dbb1 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -201,6 +201,19 @@ 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() +} + sortnames = function(x) { if (!is.null(names(x))) { x = x[order(names(x), decreasing = TRUE)] 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 new file mode 100644 index 00000000..802e3aa6 --- /dev/null +++ b/tests/testthat/test_AcqFunctionStochasticCB.R @@ -0,0 +1,136 @@ +test_that("AcqFunctionStochasticCB works in defaults", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 1L) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 10L), + ) + + acq_function = acqf("stochastic_cb") + + optimizer = opt("async_mbo", + design_function = "sobol", + design_size = 5, + acq_function = acq_function) + + 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("AcqFunctionStochasticCB works with uniform sampling", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 2L) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + 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 = 5L, + acq_function = acq_function) + + 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) + + 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 = 2L) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 10L), + ) + + 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 = 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 = 3L) + + expect_rush_reset(instance$rush) +}) + + +test_that("AcqFunctionStochasticCB works with lambda decay", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 1L) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 10L), + ) + + acq_function = acqf("stochastic_cb", rate = 0.5) + + optimizer = opt("async_mbo", + design_function = "sobol", + design_size = 5L, + acq_function = acq_function) + + 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) + + expect_rush_reset(instance$rush) +}) + +test_that("AcqFunctionStochasticCB works with periodic lambda decay", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 1L) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + 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 = 5L, + acq_function = acq_function) + + 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 = 3L) + + expect_rush_reset(instance$rush) +}) diff --git a/tests/testthat/test_AcqFunctionStochasticEI.R b/tests/testthat/test_AcqFunctionStochasticEI.R new file mode 100644 index 00000000..8629d0b3 --- /dev/null +++ b/tests/testthat/test_AcqFunctionStochasticEI.R @@ -0,0 +1,80 @@ +test_that("AcqFunctionStochasticEI works in defaults", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 1L) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 10L), + ) + + acq_function = acqf("stochastic_ei") + + optimizer = opt("async_mbo", + design_function = "sobol", + design_size = 5L, + acq_function = acq_function) + + 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) + + expect_rush_reset(instance$rush) +}) + +test_that("AcqFunctionStochasticEI works with multiple workers", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 2L) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 20L), + ) + + acq_function = acqf("stochastic_ei") + + optimizer = opt("async_mbo", + design_function = "sobol", + design_size = 5L, + acq_function = acq_function) + + 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) +}) + + +test_that("AcqFunctionStochasticEI works with periodic epsilon decay", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 1L) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + 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 = 5L, + acq_function = acq_function) + + 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 = 3L) + + expect_rush_reset(instance$rush) +}) diff --git a/tests/testthat/test_OptimizerADBO.R b/tests/testthat/test_OptimizerADBO.R new file mode 100644 index 00000000..6952fd77 --- /dev/null +++ b/tests/testthat/test_OptimizerADBO.R @@ -0,0 +1,20 @@ +test_that("OptimizerADBO works in defaults", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 2L) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 20L), + ) + optimizer = opt("adbo", design_function = "sobol", design_size = 5L) + + 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 new file mode 100644 index 00000000..6f4afdc2 --- /dev/null +++ b/tests/testthat/test_OptimizerAsyncMbo.R @@ -0,0 +1,47 @@ +test_that("OptimizerAsyncMbo works in defaults", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 2L) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 10L), + ) + optimizer = opt("async_mbo", design_function = "sobol", design_size = 5L) + + 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("OptimizerAsyncMbo works with evaluations in archive", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + rush::rush_plan(n_workers = 2L) + instance = oi_async( + objective = OBJ_2D, + search_space = PS_2D, + terminator = trm("evals", n_evals = 10L), + ) + + optimizer = opt("async_random_search") + optimizer$optimize(instance) + + + instance$terminator$param_set$values$n_evals = 40L + + 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_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 new file mode 100644 index 00000000..53c50d3f --- /dev/null +++ b/tests/testthat/test_TunerADBO.R @@ -0,0 +1,28 @@ +test_that("TunerADBO works", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + learner = lrn("classif.rpart", + minsplit = to_tune(2L, 128L), + cp = to_tune(1e-04, 1e-1)) + + rush::rush_plan(n_workers = 4L) + instance = ti_async( + task = tsk("pima"), + learner = learner, + resampling = rsmp("cv", folds = 3L), + measure = msr("classif.ce"), + terminator = trm("evals", n_evals = 20L), + store_benchmark_result = FALSE + ) + + tuner = tnr("adbo", design_size = 10L) + + 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 new file mode 100644 index 00000000..31d6da9a --- /dev/null +++ b/tests/testthat/test_TunerAsyncMbo.R @@ -0,0 +1,28 @@ +test_that("TunerAsyncMbo works", { + skip_on_cran() + skip_if_not_installed("rush") + flush_redis() + + learner = lrn("classif.rpart", + minsplit = to_tune(2L, 128L), + cp = to_tune(1e-04, 1e-1)) + + rush::rush_plan(n_workers = 4L) + instance = ti_async( + task = tsk("pima"), + learner = learner, + resampling = rsmp("cv", folds = 3L), + measure = msr("classif.ce"), + terminator = trm("evals", n_evals = 20L), + store_benchmark_result = FALSE + ) + + tuner = tnr("async_mbo", design_size = 4L) + + 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) +}) +