Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add asynchronous decentralized bayesian optimization #145

Merged
merged 58 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
0604202
draft
be-marc Feb 8, 2024
d4b56b9
fix: add start time
be-marc Feb 8, 2024
71fdc66
refactor: remove rush debug
be-marc Feb 9, 2024
762b136
fix: add callback
be-marc Feb 9, 2024
6a62d75
fix: transformation
be-marc Feb 9, 2024
c62f7c8
fix: xdomain
be-marc Feb 9, 2024
d92dc55
refactor: kill workers
be-marc Feb 10, 2024
2bb45a9
feat: cache archive
be-marc Feb 10, 2024
2f05764
chore: debug
be-marc Feb 10, 2024
0ed1969
chore: import rush
be-marc Feb 12, 2024
b7217e4
feat: add logging
be-marc Feb 12, 2024
6982d12
refactor: use optimize_decentralized()
be-marc Feb 12, 2024
0363e5d
feat: add exponential decay
be-marc Feb 19, 2024
c48a484
feat: add min-max imputation
be-marc Feb 19, 2024
c8784aa
feat: add n_worker parameter
be-marc Feb 22, 2024
b97bd18
draft
be-marc Apr 26, 2024
e890f13
Merge branch 'main' into adbo
be-marc Apr 26, 2024
3787ec9
draft
be-marc Apr 28, 2024
5a03bf7
refactor: remove stage
be-marc Apr 29, 2024
d77fad8
fix: description
be-marc Apr 29, 2024
170d650
feat: add n_worker argument
be-marc May 1, 2024
3bd95b0
fix: imports
be-marc May 1, 2024
43c2189
fix: tests
be-marc May 1, 2024
056e50e
ci: add redis
be-marc May 1, 2024
106c58b
Merge remote-tracking branch 'origin/main' into adbo
sumny Jun 21, 2024
81959dc
Merge branch 'main' into adbo
be-marc Jul 1, 2024
66f93eb
Merge branch 'main' into adbo
be-marc Jul 1, 2024
86f9659
Merge branch 'main' into adbo
be-marc Jul 25, 2024
a661667
refactor: add SurrogateLearnerAsync
be-marc Aug 18, 2024
1e0a31d
Merge remote-tracking branch 'origin/main' into adbo
sumny Aug 20, 2024
4abb9c6
feat: add OptimizerAsyncMbo
be-marc Aug 22, 2024
a55f037
tests: add tests
be-marc Aug 22, 2024
475152a
update
be-marc Aug 27, 2024
5a8f2ae
...
be-marc Sep 11, 2024
782040b
compatibility: mlr3 0.21.0
be-marc Sep 11, 2024
b43d542
...
be-marc Sep 11, 2024
51d9c0d
Merge branch 'compat_mlr3' into adbo
be-marc Sep 13, 2024
367583c
...
be-marc Sep 13, 2024
c7caa61
feat: n_workers
be-marc Sep 13, 2024
0709d10
docs: update
be-marc Sep 14, 2024
e943099
...
be-marc Oct 28, 2024
4518ec2
Merge branch 'main' into adbo
be-marc Oct 28, 2024
15fc62a
...
be-marc Oct 28, 2024
9ebd2d0
refactor
be-marc Oct 29, 2024
6b1dda6
...
be-marc Oct 29, 2024
70b5a76
...
be-marc Oct 29, 2024
80b4c0e
...
be-marc Oct 30, 2024
fb360ef
...
be-marc Oct 30, 2024
febd804
...
be-marc Oct 30, 2024
5eada45
tuner
be-marc Oct 31, 2024
824b586
prefix columns with acq_
be-marc Nov 5, 2024
e3be42c
...
be-marc Nov 7, 2024
75b6b30
archive design
be-marc Nov 7, 2024
a446321
relaxe
be-marc Nov 7, 2024
a79d863
refactor: minor refactor changes to proposed async extensions
sumny Nov 15, 2024
86f2e28
man and tests: fix docs and test
sumny Nov 15, 2024
dd2fb9b
Merge remote-tracking branch 'origin/main' into adbo
sumny Nov 15, 2024
1408c3e
docs:
sumny Nov 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .lintr
Original file line number Diff line number Diff line change
Expand Up @@ -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")
)
14 changes: 11 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -62,6 +62,8 @@ Suggests:
ranger,
rgenoud,
rpart,
redux,
rush,
stringi,
testthat (>= 3.0.0)
ByteCompile: no
Expand All @@ -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'
Expand All @@ -95,6 +101,8 @@ Collate:
'Surrogate.R'
'SurrogateLearner.R'
'SurrogateLearnerCollection.R'
'TunerADBO.R'
'TunerAsyncMbo.R'
'TunerMbo.R'
'mlr_loop_functions.R'
'bayesopt_ego.R'
Expand Down
7 changes: 7 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,20 @@ export(AcqFunctionMulti)
export(AcqFunctionPI)
export(AcqFunctionSD)
export(AcqFunctionSmsEgo)
export(AcqFunctionStochasticCB)
export(AcqFunctionStochasticEI)
export(AcqOptimizer)
export(OptimizerADBO)
export(OptimizerAsyncMbo)
export(OptimizerMbo)
export(ResultAssigner)
export(ResultAssignerArchive)
export(ResultAssignerSurrogate)
export(Surrogate)
export(SurrogateLearner)
export(SurrogateLearnerCollection)
export(TunerADBO)
export(TunerAsyncMbo)
export(TunerMbo)
export(acqf)
export(acqfs)
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
8 changes: 8 additions & 0 deletions R/AcqFunction.R
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#'
Expand Down
2 changes: 1 addition & 1 deletion R/AcqFunctionMulti.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 15 additions & 3 deletions R/AcqFunctionSmsEgo.R
Original file line number Diff line number Diff line change
Expand Up @@ -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")`
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
}
),

Expand All @@ -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))
}
)
)
Expand Down
188 changes: 188 additions & 0 deletions R/AcqFunctionStochasticCB.R
Original file line number Diff line number Diff line change
@@ -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)

Loading
Loading