diff --git a/DESCRIPTION b/DESCRIPTION
index df043bf3..ffc831b7 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,7 +1,7 @@
Type: Package
Package: mlr3mbo
Title: Flexible Bayesian Optimization
-Version: 0.2.3.9000
+Version: 0.2.4.9000
Authors@R: c(
person("Lennart", "Schneider", , "lennart.sch@web.de", role = c("cre", "aut"),
comment = c(ORCID = "0000-0003-4152-5308")),
@@ -66,13 +66,14 @@ Suggests:
rpart,
stringi,
testthat (>= 3.0.0)
+Remotes: mlr-org/bbotk
ByteCompile: no
Encoding: UTF-8
Config/testthat/edition: 3
Config/testthat/parallel: false
NeedsCompilation: yes
Roxygen: list(markdown = TRUE, r6 = TRUE)
-RoxygenNote: 7.3.1
+RoxygenNote: 7.3.2
Collate:
'mlr_acqfunctions.R'
'AcqFunction.R'
diff --git a/NEWS.md b/NEWS.md
index 57766688..cb1beef6 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,5 +1,9 @@
# mlr3mbo (development version)
+# mlr3mbo 0.2.4
+
+* fix: Improve runtime of `AcqOptimizer` by setting `check_values = FALSE`.
+
# mlr3mbo 0.2.3
* compatibility: Work with new bbotk and mlr3tuning version 1.0.0.
diff --git a/R/AcqFunctionCB.R b/R/AcqFunctionCB.R
index 14a980c3..81328e05 100644
--- a/R/AcqFunctionCB.R
+++ b/R/AcqFunctionCB.R
@@ -76,8 +76,8 @@ AcqFunctionCB = R6Class("AcqFunctionCB",
constants = list(...)
lambda = constants$lambda
p = self$surrogate$predict(xdt)
- res = p$mean - self$surrogate_max_to_min * lambda * p$se
- data.table(acq_cb = res)
+ cb = p$mean - self$surrogate_max_to_min * lambda * p$se
+ data.table(acq_cb = cb)
}
)
)
diff --git a/R/AcqFunctionEI.R b/R/AcqFunctionEI.R
index 1782b972..f03ec39b 100644
--- a/R/AcqFunctionEI.R
+++ b/R/AcqFunctionEI.R
@@ -9,6 +9,13 @@
#' @description
#' Expected Improvement.
#'
+#' @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` (standard Expected Improvement).
+#'
#' @references
#' * `r format_bib("jones_1998")`
#'
@@ -60,9 +67,15 @@ AcqFunctionEI = R6Class("AcqFunctionEI",
#' Creates a new instance of this [R6][R6::R6Class] class.
#'
#' @param surrogate (`NULL` | [SurrogateLearner]).
- initialize = function(surrogate = NULL) {
+ #' @param epsilon (`numeric(1)`).
+ initialize = function(surrogate = NULL, epsilon = 0) {
assert_r6(surrogate, "SurrogateLearner", null.ok = TRUE)
- super$initialize("acq_ei", surrogate = surrogate, requires_predict_type_se = TRUE, direction = "maximize", label = "Expected Improvement", man = "mlr3mbo::mlr_acqfunctions_ei")
+ assert_number(epsilon, lower = 0, finite = TRUE)
+
+ constants = ps(epsilon = p_dbl(lower = 0, default = 0))
+ constants$values$epsilon = epsilon
+
+ super$initialize("acq_ei", constants = constants, surrogate = surrogate, requires_predict_type_se = TRUE, direction = "maximize", label = "Expected Improvement", man = "mlr3mbo::mlr_acqfunctions_ei")
},
#' @description
@@ -73,14 +86,16 @@ AcqFunctionEI = R6Class("AcqFunctionEI",
),
private = list(
- .fun = function(xdt) {
+ .fun = function(xdt, ...) {
if (is.null(self$y_best)) {
stop("$y_best is not set. Missed to call $update()?")
}
+ constants = list(...)
+ epsilon = constants$epsilon
p = self$surrogate$predict(xdt)
mu = p$mean
se = p$se
- d = self$y_best - self$surrogate_max_to_min * mu
+ 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)
diff --git a/R/AcqOptimizer.R b/R/AcqOptimizer.R
index 68d6cd84..f9f0f5d6 100644
--- a/R/AcqOptimizer.R
+++ b/R/AcqOptimizer.R
@@ -93,16 +93,21 @@ AcqOptimizer = R6Class("AcqOptimizer",
#' @field acq_function ([AcqFunction]).
acq_function = NULL,
+ #' @field callbacks (`NULL` | list of [mlr3misc::Callback]).
+ callbacks = NULL,
+
#' @description
#' Creates a new instance of this [R6][R6::R6Class] class.
#'
#' @param optimizer ([bbotk::Optimizer]).
#' @param terminator ([bbotk::Terminator]).
#' @param acq_function (`NULL` | [AcqFunction]).
- initialize = function(optimizer, terminator, acq_function = NULL) {
+ #' @param callbacks (`NULL` | list of [mlr3misc::Callback])
+ initialize = function(optimizer, terminator, acq_function = NULL, callbacks = NULL) {
self$optimizer = assert_r6(optimizer, "Optimizer")
self$terminator = assert_r6(terminator, "Terminator")
self$acq_function = assert_r6(acq_function, "AcqFunction", null.ok = TRUE)
+ self$callbacks = assert_callbacks(as_callbacks(callbacks))
ps = ps(
n_candidates = p_int(lower = 1, default = 1L),
logging_level = p_fct(levels = c("fatal", "error", "warn", "info", "debug", "trace"), default = "warn"),
@@ -146,7 +151,7 @@ AcqOptimizer = R6Class("AcqOptimizer",
logger$set_threshold(self$param_set$values$logging_level)
on.exit(logger$set_threshold(old_threshold))
- instance = OptimInstanceBatchSingleCrit$new(objective = self$acq_function, search_space = self$acq_function$domain, terminator = self$terminator, check_values = FALSE)
+ instance = OptimInstanceBatchSingleCrit$new(objective = self$acq_function, search_space = self$acq_function$domain, terminator = self$terminator, check_values = FALSE, callbacks = self$callbacks)
# warmstart
if (self$param_set$values$warmstart) {
diff --git a/R/sugar.R b/R/sugar.R
index ac9279c6..69c6a8b0 100644
--- a/R/sugar.R
+++ b/R/sugar.R
@@ -20,7 +20,7 @@
#' @param cols_y (`NULL` | `character()`)\cr
#' Column id(s) in the [bbotk::Archive] that should be used as a target.
#' If a list of [mlr3::LearnerRegr] is provided as the `learner` argument and `cols_y` is
-#' specified as well, as many column names as learners must be provided.
+#' specified as well, as many column names as learners must be provided.
#' Can also be `NULL` in which case this is automatically inferred based on the archive.
#' @param ... (named `list()`)\cr
#' Named arguments passed to the constructor, to be set as parameters in the
@@ -90,6 +90,8 @@ acqf = function(.key, ...) {
#' @param acq_function (`NULL` | [AcqFunction])\cr
#' [AcqFunction] that is to be used.
#' Can also be `NULL`.
+#' @param callbacks (`NULL` | list of [mlr3misc::Callback])
+#' Callbacks used during acquisition function optimization.
#' @param ... (named `list()`)\cr
#' Named arguments passed to the constructor, to be set as parameters in the
#' [paradox::ParamSet].
@@ -101,9 +103,9 @@ acqf = function(.key, ...) {
#' library(bbotk)
#' acqo(opt("random_search"), trm("evals"), catch_errors = FALSE)
#' @export
-acqo = function(optimizer, terminator, acq_function = NULL, ...) {
+acqo = function(optimizer, terminator, acq_function = NULL, callbacks = NULL, ...) {
dots = list(...)
- acqopt = AcqOptimizer$new(optimizer = optimizer, terminator = terminator, acq_function = acq_function)
+ acqopt = AcqOptimizer$new(optimizer = optimizer, terminator = terminator, acq_function = acq_function, callbacks = callbacks)
acqopt$param_set$values = insert_named(acqopt$param_set$values, dots)
acqopt
}
diff --git a/man/AcqOptimizer.Rd b/man/AcqOptimizer.Rd
index f0f8d9b3..b4c143b1 100644
--- a/man/AcqOptimizer.Rd
+++ b/man/AcqOptimizer.Rd
@@ -94,6 +94,8 @@ if (requireNamespace("mlr3learners") &
\item{\code{terminator}}{(\link[bbotk:Terminator]{bbotk::Terminator}).}
\item{\code{acq_function}}{(\link{AcqFunction}).}
+
+\item{\code{callbacks}}{(\code{NULL} | list of \link[mlr3misc:Callback]{mlr3misc::Callback}).}
}
\if{html}{\out{}}
}
@@ -124,7 +126,7 @@ Set of hyperparameters.}
\subsection{Method \code{new()}}{
Creates a new instance of this \link[R6:R6Class]{R6} class.
\subsection{Usage}{
-\if{html}{\out{
}}\preformatted{AcqOptimizer$new(optimizer, terminator, acq_function = NULL)}\if{html}{\out{
}}
+\if{html}{\out{}}\preformatted{AcqOptimizer$new(optimizer, terminator, acq_function = NULL, callbacks = NULL)}\if{html}{\out{
}}
}
\subsection{Arguments}{
@@ -135,6 +137,8 @@ Creates a new instance of this \link[R6:R6Class]{R6} class.
\item{\code{terminator}}{(\link[bbotk:Terminator]{bbotk::Terminator}).}
\item{\code{acq_function}}{(\code{NULL} | \link{AcqFunction}).}
+
+\item{\code{callbacks}}{(\code{NULL} | list of \link[mlr3misc:Callback]{mlr3misc::Callback})}
}
\if{html}{\out{}}
}
diff --git a/man/acqo.Rd b/man/acqo.Rd
index 9455152e..43114a0c 100644
--- a/man/acqo.Rd
+++ b/man/acqo.Rd
@@ -4,7 +4,7 @@
\alias{acqo}
\title{Syntactic Sugar Acquisition Function Optimizer Construction}
\usage{
-acqo(optimizer, terminator, acq_function = NULL, ...)
+acqo(optimizer, terminator, acq_function = NULL, callbacks = NULL, ...)
}
\arguments{
\item{optimizer}{(\link[bbotk:Optimizer]{bbotk::Optimizer})\cr
@@ -17,6 +17,9 @@ acqo(optimizer, terminator, acq_function = NULL, ...)
\link{AcqFunction} that is to be used.
Can also be \code{NULL}.}
+\item{callbacks}{(\code{NULL} | list of \link[mlr3misc:Callback]{mlr3misc::Callback})
+Callbacks used during acquisition function optimization.}
+
\item{...}{(named \code{list()})\cr
Named arguments passed to the constructor, to be set as parameters in the
\link[paradox:ParamSet]{paradox::ParamSet}.}
diff --git a/man/mlr_acqfunctions_ei.Rd b/man/mlr_acqfunctions_ei.Rd
index 700e72ab..4357d844 100644
--- a/man/mlr_acqfunctions_ei.Rd
+++ b/man/mlr_acqfunctions_ei.Rd
@@ -17,6 +17,17 @@ acqf("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} (standard Expected Improvement).
+}
+}
+
\examples{
if (requireNamespace("mlr3learners") &
requireNamespace("DiceKriging") &
@@ -110,13 +121,15 @@ In the case of maximization, this already includes the necessary change of sign.
\subsection{Method \code{new()}}{
Creates a new instance of this \link[R6:R6Class]{R6} class.
\subsection{Usage}{
-\if{html}{\out{}}\preformatted{AcqFunctionEI$new(surrogate = NULL)}\if{html}{\out{
}}
+\if{html}{\out{}}\preformatted{AcqFunctionEI$new(surrogate = NULL, epsilon = 0)}\if{html}{\out{
}}
}
\subsection{Arguments}{
\if{html}{\out{}}
\describe{
\item{\code{surrogate}}{(\code{NULL} | \link{SurrogateLearner}).}
+
+\item{\code{epsilon}}{(\code{numeric(1)}).}
}
\if{html}{\out{
}}
}
diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml
index 55a84b5d..b72e56b5 100644
--- a/pkgdown/_pkgdown.yml
+++ b/pkgdown/_pkgdown.yml
@@ -16,8 +16,8 @@ toc:
navbar:
structure:
- left: [reference, news, book]
- right: [github, mattermost, stackoverflow, rss, lightswitch]
+ left: [reference, intro, news, book]
+ right: [search, github, mattermost, stackoverflow, rss, lightswitch]
components:
home: ~
reference:
diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R
index b3ea492d..aba5c6e6 100644
--- a/tests/testthat/helper.R
+++ b/tests/testthat/helper.R
@@ -39,12 +39,18 @@ PS_1D_MIXED_DEPS = PS_1D_MIXED$clone(deep = TRUE)
PS_1D_MIXED_DEPS$add_dep("x2", on = "x4", cond = CondEqual$new(TRUE))
FUN_1D_MIXED = function(xs) {
+ if (is.null(xs$x2)) {
+ xs$x2 = "a"
+ }
list(y = (xs$x1 - switch(xs$x2, "a" = 0, "b" = 1, "c" = 2)) %% xs$x3 + (if (xs$x4) xs$x1 else pi))
}
OBJ_1D_MIXED = ObjectiveRFun$new(fun = FUN_1D_MIXED, domain = PS_1D_MIXED, properties = "single-crit")
OBJ_1D_MIXED_DEPS = ObjectiveRFun$new(fun = FUN_1D_MIXED, domain = PS_1D_MIXED_DEPS, properties = "single-crit")
FUN_1D_2_MIXED = function(xs) {
+ if (is.null(xs$x2)) {
+ xs$x2 = "a"
+ }
list(y1 = (xs$x1 - switch(xs$x2, "a" = 0, "b" = 1, "c" = 2)) %% xs$x3 + (if (xs$x4) xs$x1 else pi), y2 = xs$x1)
}
OBJ_1D_2_MIXED = ObjectiveRFun$new(fun = FUN_1D_2_MIXED, domain = PS_1D_MIXED, codomain = FUN_1D_2_CODOMAIN, properties = "multi-crit")
diff --git a/tests/testthat/test_AcqFunctionCB.R b/tests/testthat/test_AcqFunctionCB.R
index 1749b3d2..eecaa100 100644
--- a/tests/testthat/test_AcqFunctionCB.R
+++ b/tests/testthat/test_AcqFunctionCB.R
@@ -12,6 +12,9 @@ test_that("AcqFunctionCB works", {
expect_learner(acqf$surrogate$learner)
expect_true(acqf$requires_predict_type_se)
+ expect_r6(acqf$constants, "ParamSet")
+ expect_equal(acqf$constants$ids(), "lambda")
+
design = MAKE_DESIGN(inst)
inst$eval_batch(design)
diff --git a/tests/testthat/test_AcqFunctionEHVIGH.R b/tests/testthat/test_AcqFunctionEHVIGH.R
index 801fd841..58273cca 100644
--- a/tests/testthat/test_AcqFunctionEHVIGH.R
+++ b/tests/testthat/test_AcqFunctionEHVIGH.R
@@ -15,6 +15,9 @@ test_that("AcqFunctionEHVIGH works", {
expect_true(acqf$requires_predict_type_se)
expect_setequal(acqf$packages, c("emoa", "fastGHQuad"))
+ expect_r6(acqf$constants, "ParamSet")
+ expect_equal(acqf$constants$ids(), c("k", "r"))
+
design = MAKE_DESIGN(inst)
inst$eval_batch(design)
diff --git a/tests/testthat/test_AcqFunctionEI.R b/tests/testthat/test_AcqFunctionEI.R
index 59eae9ef..dd8be11a 100644
--- a/tests/testthat/test_AcqFunctionEI.R
+++ b/tests/testthat/test_AcqFunctionEI.R
@@ -13,6 +13,9 @@ test_that("AcqFunctionEI works", {
expect_learner(acqf$surrogate$learner)
expect_true(acqf$requires_predict_type_se)
+ expect_r6(acqf$constants, "ParamSet")
+ expect_equal(acqf$constants$ids(), "epsilon")
+
design = MAKE_DESIGN(inst)
inst$eval_batch(design)
diff --git a/tests/testthat/test_AcqFunctionSmsEgo.R b/tests/testthat/test_AcqFunctionSmsEgo.R
index 020a946f..b6cdece9 100644
--- a/tests/testthat/test_AcqFunctionSmsEgo.R
+++ b/tests/testthat/test_AcqFunctionSmsEgo.R
@@ -12,6 +12,9 @@ test_that("AcqFunctionSmsEgo works", {
expect_list(acqf$surrogate$learner, types = "Learner")
expect_true(acqf$requires_predict_type_se)
+ expect_r6(acqf$constants, "ParamSet")
+ expect_equal(acqf$constants$ids(), c("lambda", "epsilon"))
+
design = MAKE_DESIGN(inst)
inst$eval_batch(design)
diff --git a/tests/testthat/test_AcqOptimizer.R b/tests/testthat/test_AcqOptimizer.R
index 213963fe..69e2ca2f 100644
--- a/tests/testthat/test_AcqOptimizer.R
+++ b/tests/testthat/test_AcqOptimizer.R
@@ -120,3 +120,32 @@ test_that("AcqOptimizer deep clone", {
expect_true(address(acqopt1$terminator) != address(acqopt2$terminator))
})
+test_that("AcqOptimizer callbacks", {
+ domain = ps(x = p_dbl(lower = 10, upper = 20, trafo = function(x) x - 15))
+ objective = ObjectiveRFunDt$new(
+ fun = function(xdt) data.table(y = xdt$x ^ 2),
+ domain = domain,
+ codomain = ps(y = p_dbl(tags = "minimize")),
+ check_values = FALSE
+ )
+ instance = MAKE_INST(objective = objective, search_space = domain, terminator = trm("evals", n_evals = 5L))
+ design = MAKE_DESIGN(instance)
+ instance$eval_batch(design)
+ callback = callback_batch("mlr3mbo.acqopt_time",
+ on_optimization_begin = function(callback, context) {
+ callback$state$begin = Sys.time()
+ },
+ on_optimization_end = function(callback, context) {
+ callback$state$end = Sys.time()
+ attr(callback$state$outer_instance, "acq_opt_runtime") = as.numeric(callback$state$end - callback$state$begin)
+ }
+ )
+ callback$state$outer_instance = instance
+ acqfun = AcqFunctionEI$new(SurrogateLearner$new(REGR_FEATURELESS, archive = instance$archive))
+ acqopt = AcqOptimizer$new(opt("random_search", batch_size = 10L), trm("evals", n_evals = 10L), acq_function = acqfun, callbacks = callback)
+ acqfun$surrogate$update()
+ acqfun$update()
+ res = acqopt$optimize()
+ expect_number(attr(instance, "acq_opt_runtime"))
+})
+