diff --git a/NAMESPACE b/NAMESPACE index e5a32f857..6ac7d63d4 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -19,6 +19,7 @@ export(crit.cb1) export(crit.cb2) export(crit.dib1) export(crit.ei) +export(crit.eis) export(crit.eqi) export(crit.mr) export(crit.se) @@ -34,6 +35,7 @@ export(getMBOInfillCritParams) export(getSupportedInfillOptFunctions) export(getSupportedMultipointInfillOptFunctions) export(hasRequiresInfillCritStandardError) +export(hasRequiresInfillCritTime) export(initCrit) export(initSMBO) export(makeMBOControl) @@ -42,6 +44,7 @@ export(makeMBOInfillCritAEI) export(makeMBOInfillCritCB) export(makeMBOInfillCritDIB) export(makeMBOInfillCritEI) +export(makeMBOInfillCritEIs) export(makeMBOInfillCritEQI) export(makeMBOInfillCritMeanResponse) export(makeMBOInfillCritStandardError) diff --git a/R/OptState_getter.R b/R/OptState_getter.R index 98b2df398..ece0cf7b9 100644 --- a/R/OptState_getter.R +++ b/R/OptState_getter.R @@ -41,7 +41,7 @@ getOptStateTimeModel = function(opt.state) { if (is.null(time.model) || getTaskSize(time.model) != length(na.omit(exec.times))) { opt.problem = getOptStateOptProblem(opt.state) opt.path = getOptStateOptPath(opt.state) - time.task = cbind(getOptPathX(opt.path), exec.time = getOptPathExecTimes(opt.path)) + time.task = cbind(getOptPathX(opt.path), exec.time = log(getOptPathExecTimes(opt.path))) time.task = time.task[!is.na(time.task$exec.time), ] time.task = makeRegrTask(id = "time.task", data = time.task, target = "exec.time") time.model = train(learner = getOptProblemLearner(opt.problem), task = time.task) diff --git a/R/getMBOInfillCrit.R b/R/getMBOInfillCrit.R index d68146fc8..cea926555 100644 --- a/R/getMBOInfillCrit.R +++ b/R/getMBOInfillCrit.R @@ -47,6 +47,14 @@ hasRequiresInfillCritStandardError = function(x) { return(x$requires.se) } +#' @export +#' @rdname getMBOInfillCrit +hasRequiresInfillCritTime = function(x) { + assertClass(x, "MBOInfillCrit") + return(x$requires.time) +} + + #' @export #' @rdname getMBOInfillCrit getMBOInfillCritComponents = function(x) { diff --git a/R/infill_crits.R b/R/infill_crits.R index eca1dab1d..6efd7b135 100644 --- a/R/infill_crits.R +++ b/R/infill_crits.R @@ -240,6 +240,44 @@ makeMBOInfillCritEQI = function(eqi.beta = 0.75, se.threshold = 1e-6) { ) } +#' @export +#' @rdname infillcrits +makeMBOInfillCritEIs = function(se.threshold = 1e-6) { + assertNumber(se.threshold, lower = 1e-20) + force(se.threshold) + makeMBOInfillCrit( + fun = function(points, models, control, par.set, design, iter, attributes = FALSE) { + model = models[[1L]] + time.model = models$time.model + maximize.mult = ifelse(control$minimize, 1, -1) + y = maximize.mult * design[, control$y.name] + p = predict(model, newdata = points)$data + p.time = exp(predict(time.model, newdata = points)$data$response) #time is modeled on log-scale + p.mu = maximize.mult * p$response + p.se = p$se + y.min = min(y) + d = y.min - p.mu + xcr = d / p.se + xcr.prob = pnorm(xcr) + xcr.dens = dnorm(xcr) + ei = (d * xcr.prob + p.se * xcr.dens) / p.time + res = ifelse(p.se < se.threshold, 0, -ei) + if (attributes) { + res = setAttribute(res, "crit.components", data.frame(se = p$se, mean = p$response)) + } + return(res) + }, + name = "Expected improvement per second", + id = "eis", + components = c("se", "mean"), + params = list(se.threshold = se.threshold), + opt.direction = "maximize", + requires.se = TRUE, + requires.time = TRUE + ) +} + + # ==================== # MULTI-CRITERIA STUFF # ==================== diff --git a/R/makeMBOInfillCrit.R b/R/makeMBOInfillCrit.R index 6cece664b..7e058a0d4 100644 --- a/R/makeMBOInfillCrit.R +++ b/R/makeMBOInfillCrit.R @@ -42,13 +42,16 @@ #' Does the infill criterion require the regression learner to provide a standard #' error estimation? #' Default is \code{FALSE}. +#' @param requires.time [\code{logical(1)}]\cr +#' Does the infill criterion require estimated runtime of the target function based on the \code{time.model}? +#' Default is \code{FALSE}. #' @return [\code{\link{MBOInfillCrit}}] #' @rdname MBOInfillCrit #' @aliases MBOInfillCrit #' @export makeMBOInfillCrit = function(fun, name, id, opt.direction = "minimize", components = character(0L), params = list(), - requires.se = FALSE) { + requires.se = FALSE, requires.time = FALSE) { assertFunction( fun, args = c("points", "models", "control", @@ -61,6 +64,7 @@ makeMBOInfillCrit = function(fun, name, id, assertCharacter(components, unique = TRUE) assertList(params) assertFlag(requires.se) + assertFlag(requires.time) ic = makeS3Obj(c(paste0("InfillCrit", toupper(id)), "MBOInfillCrit"), fun = fun, @@ -69,7 +73,8 @@ makeMBOInfillCrit = function(fun, name, id, opt.direction = opt.direction, components = components, params = params, - requires.se = requires.se + requires.se = requires.se, + requires.time = requires.time ) return(ic) } @@ -78,11 +83,16 @@ makeMBOInfillCrit = function(fun, name, id, print.MBOInfillCrit = function(x, ...) { components = getMBOInfillCritComponents(x) params = getMBOInfillCritParams(x) + requirements = c() + if (hasRequiresInfillCritStandardError(x)) + requirements = c(requirements, "SE estimation") + if (hasRequiresInfillCritTime(x)) + requirements = c(requirements, "runtime estimation") catf("Infill criterion : %s (%s)", getMBOInfillCritName(x), getMBOInfillCritId(x)) catf(" Direction of optimization : %s", x$opt.direction) - if (hasRequiresInfillCritStandardError(x)) - catf(" Requirement : SE estimation") + if (length(requirements) > 0) + catf(" Requirements : %s", collapse(requirements, sep = ", ")) if (length(components) > 0) catf(" Components : %s", collapse(components, sep = ", ")) if (length(params) > 0) diff --git a/R/proposePointsByInfillOptimization.R b/R/proposePointsByInfillOptimization.R index 055860837..2497c6975 100644 --- a/R/proposePointsByInfillOptimization.R +++ b/R/proposePointsByInfillOptimization.R @@ -34,6 +34,8 @@ proposePointsByInfillOptimization = function(opt.state, par.set = NULL, control if (!ch$ok) return(ch$prop) design = convertOptPathToDf(opt.path, control) + if (hasRequiresInfillCritTime(control$infill.crit)) + models$time.model = getOptStateTimeModel(opt.state) infill.crit.fun = control$infill.crit$fun infill.opt.fun = getInfillOptFunction(control$infill.opt) # store time to propose single point diff --git a/R/zzz.R b/R/zzz.R index 117fe1039..0632ade85 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -23,6 +23,7 @@ NULL #' @section Predefined standard infill criteria: #' \describe{ #' \item{crit.ei}{Expected Improvement} +#' \item{crit.eis}{Expected Improvement per second} #' \item{crit.mr}{Mean response} #' \item{crit.se}{Standard error} #' \item{crit.cb}{Confidence bound with lambda automatically chosen, see \code{\link{infillcrits}}} @@ -46,6 +47,13 @@ crit.ei = makeMBOInfillCritEI() #' @docType NULL #' @format NULL #' @keywords NULL +crit.eis = makeMBOInfillCritEIs() +#' @rdname MBOInfillCrit +#' @export +#' @usage NULL +#' @docType NULL +#' @format NULL +#' @keywords NULL crit.mr = makeMBOInfillCritMeanResponse() #' @rdname MBOInfillCrit #' @export diff --git a/man/MBOInfillCrit.Rd b/man/MBOInfillCrit.Rd index 6e3d13bbc..6bcd84e16 100644 --- a/man/MBOInfillCrit.Rd +++ b/man/MBOInfillCrit.Rd @@ -4,6 +4,7 @@ \alias{makeMBOInfillCrit} \alias{MBOInfillCrit} \alias{crit.ei} +\alias{crit.eis} \alias{crit.mr} \alias{crit.se} \alias{crit.cb} @@ -15,7 +16,8 @@ \title{Create an infill criterion.} \usage{ makeMBOInfillCrit(fun, name, id, opt.direction = "minimize", - components = character(0L), params = list(), requires.se = FALSE) + components = character(0L), params = list(), requires.se = FALSE, + requires.time = FALSE) } \arguments{ \item{fun}{[\code{function(points, models, control, par.set, design, iter)}]\cr @@ -61,6 +63,10 @@ Default is the empty list.} Does the infill criterion require the regression learner to provide a standard error estimation? Default is \code{FALSE}.} + +\item{requires.time}{[\code{logical(1)}]\cr +Does the infill criterion require estimated runtime of the target function based on the \code{time.model}? +Default is \code{FALSE}.} } \value{ [\code{\link{MBOInfillCrit}}] @@ -75,6 +81,7 @@ the package allows for the creation of custom infill criteria. \describe{ \item{crit.ei}{Expected Improvement} +\item{crit.eis}{Expected Improvement per second} \item{crit.mr}{Mean response} \item{crit.se}{Standard error} \item{crit.cb}{Confidence bound with lambda automatically chosen, see \code{\link{infillcrits}}} diff --git a/man/getMBOInfillCrit.Rd b/man/getMBOInfillCrit.Rd index 07b04ba90..a22ddcee2 100644 --- a/man/getMBOInfillCrit.Rd +++ b/man/getMBOInfillCrit.Rd @@ -7,6 +7,7 @@ \alias{getMBOInfillCritName} \alias{getMBOInfillCritId} \alias{hasRequiresInfillCritStandardError} +\alias{hasRequiresInfillCritTime} \alias{getMBOInfillCritComponents} \title{Get properties of MBO infill criterion.} \usage{ @@ -20,6 +21,8 @@ getMBOInfillCritId(x) hasRequiresInfillCritStandardError(x) +hasRequiresInfillCritTime(x) + getMBOInfillCritComponents(x) } \arguments{ diff --git a/man/infillcrits.Rd b/man/infillcrits.Rd index 2f1906f51..eb141c296 100644 --- a/man/infillcrits.Rd +++ b/man/infillcrits.Rd @@ -8,6 +8,7 @@ \alias{makeMBOInfillCritCB} \alias{makeMBOInfillCritAEI} \alias{makeMBOInfillCritEQI} +\alias{makeMBOInfillCritEIs} \alias{makeMBOInfillCritDIB} \title{Infill criteria.} \usage{ @@ -23,6 +24,8 @@ makeMBOInfillCritAEI(aei.use.nugget = FALSE, se.threshold = 1e-06) makeMBOInfillCritEQI(eqi.beta = 0.75, se.threshold = 1e-06) +makeMBOInfillCritEIs(se.threshold = 1e-06) + makeMBOInfillCritDIB(cb.lambda = 1, sms.eps = NULL) } \arguments{ diff --git a/tests/testthat/test_infillcrits.R b/tests/testthat/test_infillcrits.R index 873e521f7..13d1eefad 100644 --- a/tests/testthat/test_infillcrits.R +++ b/tests/testthat/test_infillcrits.R @@ -44,16 +44,18 @@ test_that("infill crits", { opdf = as.data.frame(or$opt.path) opdf = split(opdf, opdf$prop.type) - if (!is.null(opdf$infill_ei)) + if (!is.null(opdf[["infill_ei"]])) expect_true(!anyMissing(opdf$infill_ei[, c("ei","se","mean")])) - if (!is.null(opdf$infill_cb)) { - expect_true(!anyMissing(opdf$infill_cb[, c("se","mean","lambda")])) + if (!is.null(opdf[["infill_eis"]])) + expect_true(!anyMissing(opdf$infill_ei[, c("eis","se","mean")])) + if (!is.null(opdf[["infill_cb"]])) { + expect_true(!anyMissing(opdf[["infill_cb"]][, c("se","mean","lambda")])) expect_true(all(opdf$infill_cb$lambda == 2)) } if (!is.null(opdf$infill_aei)) - expect_true(!anyMissing(opdf$infill_aei[, c("se","mean","tau")])) + expect_true(!anyMissing(opdf[["infill_aei"]][, c("se","mean","tau")])) if (!is.null(opdf$infill_eqi)) - expect_true(!anyMissing(opdf$infill_eqi[, c("se","mean","tau")])) + expect_true(!anyMissing(opdf[["infill_eqi"]][, c("se","mean","tau")])) expect_true(nrow(opdf$final_eval) == 10L) } @@ -94,6 +96,20 @@ test_that("infill crits", { des = generateTestDesign(ninit, getParamSet(funs[[1]]$f1)) or = mbo(funs[[1]]$f1, des, learner = makeLearner("regr.km", predict.type = "se", nugget.estim = TRUE), control = ctrl) expect_lt(or$y, 50) + + # special function for EIs + f3 = makeSingleObjectiveFunction("test", + fn = function(x) { + Sys.sleep(runif(1, 0.01, 0.05)) + sin(x) + }, + par.set = makeParamSet(makeNumericParam("x", lower = -5, upper = 5)) + ) + des = generateTestDesign(ninit, getParamSet(f3)) + for (lrn in learners) { + or = mbo(f3, des, learner = lrn, control = mycontrol(crit.eis)) + mycheck(or, TRUE) + } })