-
-
Notifications
You must be signed in to change notification settings - Fork 47
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
Noisy Optimization: OCBA and Incumbent #430
Open
ja-thomas
wants to merge
33
commits into
main
Choose a base branch
from
feature_noisy_incumbent_ocba
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 18 commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
6a6db9b
handle multiple noisy instances
jakob-r 6f0a537
fix tests
jakob-r d8dc631
error handling
jakob-r b1a79a3
initial test
ja-thomas 06c7a7d
resolved merge conflicts master
juliambr ab6fa36
implementation of ocba and incumbent
juliambr 2dd3c91
modified noisy control object
juliambr ab9cb52
added aggregation functionality
juliambr 2190a10
corrected typos
juliambr 2772d12
added ocba and incumbent replication plus tests
juliambr 1e973fd
printing control object in noisy case
juliambr 3cc45eb
make noisy function to make smoof function noisy
juliambr 02dd45d
remove makenoisy function
juliambr cbfaffe
fixed test for incumbent
juliambr 3067bef
more documentation
juliambr b9be9ba
fixed test for incumbent
juliambr 773c4ef
bug usage data.table
juliambr 7ac3f6c
fixed tests
juliambr 677d211
Merge branch 'master' into feature_noisy
jakob-r 34b9fdc
error handling
juliambr 6e35606
smbo noisy fix
jakob-r 17d33d0
Merge branch 'master' of https://github.com/mlr-org/mlrMBO into featu…
juliambr e820fe0
Merge branch 'feature_noisy' of https://github.com/mlr-org/mlrMBO int…
juliambr 3755cd4
fixed OCBA divide by 0 bug
juliambr ae2fd31
correct for numerical error in OCBA
juliambr 4009dbc
adding second stage identification for mlrMBO
juliambr f5e6a40
identification phase bugs
juliambr af13fc3
tests for identification
juliambr 507431f
fixing bug in identification
juliambr 1fcc40d
documentation of identification method
juliambr da0a514
adapt identification strategy
juliambr 901e4c4
identification termination by max evals
juliambr 0337c31
merge master
juliambr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
intensifyOptState = function(opt.state) { | ||
opt.problem = getOptStateOptProblem(opt.state) | ||
control = getOptProblemControl(opt.problem) | ||
|
||
switch(control$noisy.method, | ||
"incumbent" = intensifyIncumbent(opt.state), | ||
"ocba" = intensifyOCBA(opt.state) | ||
) | ||
} | ||
|
||
intensifyIncumbent = function(opt.state) { | ||
|
||
opt.problem = getOptStateOptProblem(opt.state) | ||
control = getOptProblemControl(opt.problem) | ||
op = as.data.table(getOptStateOptPath(opt.state)) | ||
par.names = colnames(op)[1:(which(colnames(op) == "y") - 1)] #FIXME: This sucks | ||
|
||
# get a summary of the design | ||
ds = getOptPathSummary(opt.state, par.names) | ||
nds = nrow(ds) | ||
|
||
# incumbent: current best point w. r. t. mean over all function evaluations | ||
# the newest point cannot be the incumbent, it is always a challenger | ||
# DOES NOT WORK FOR MULTIPOINT PROPOSAL YET | ||
inc = which.min(ds[- nds, ]$y) | ||
# incumbent is replicated once in each iteration | ||
replicatePoint(opt.state, x = ds[inc, ..par.names], type = "incumbent", reps = 1L) | ||
|
||
# determine a set of challengers | ||
if (control$noisy.incumbent.nchallengers == 0L) { | ||
cls = c(nds) | ||
} else { | ||
# determine set of p points to be challenged against incumbent | ||
# incumbent is excluded (cannot be challenged against itself) | ||
# and new point is always set as a challenger | ||
# points are drawn randomly without replacement with probability prop. to their function value | ||
cls = setdiff(seq_len(nds), c(inc, nds)) | ||
p = min(control$noisy.incumbent.nchallengers, nds - 2) | ||
probs = exp(- ds[cls, ]$y) / sum(exp(- ds[cls, ]$y)) | ||
cls = sample(cls, size = p, prob = probs, replace = FALSE) | ||
cls = c(cls, nds) | ||
} | ||
|
||
# start the race | ||
for (cl in cls) { | ||
|
||
r = 1L | ||
replicatePoint(opt.state, x = ds[cl, ..par.names], type = paste("challenger"), reps = r) | ||
ds = getOptPathSummary(opt.state, par.names) | ||
|
||
# proceed as long as challenger has less runs than incumbent and is better than incumbent | ||
while((ds[cl, "runs"] < ds[inc, "runs"]) && (ds[cl, "y"] < ds[inc, "y"])) { | ||
r = 2L * r | ||
replicatePoint(opt.state, x = ds[cl, ..par.names], type = paste("challenger"), reps = r) | ||
ds = getOptPathSummary(opt.state, par.names) | ||
} | ||
|
||
} | ||
return(opt.state) | ||
} | ||
|
||
replicatePoint = function(opt.state, x, type, reps = 1L) { | ||
|
||
# replicate rows according to the number of desired replicates | ||
xs = seq_len(nrow(x)) | ||
xrep = x[rep(xs, reps), ] | ||
|
||
opt.problem = getOptStateOptProblem(opt.state) | ||
control = getOptProblemControl(opt.problem) | ||
|
||
prop = makeProposal(control, xrep, prop.type = rep(type, nrow(xrep))) | ||
evalProposedPoints.OptState(opt.state, prop) | ||
|
||
return(opt.state) | ||
} | ||
|
||
getOptPathSummary = function(opt.state, par.names) { | ||
op = as.data.table(getOptStateOptPath(opt.state)) | ||
ds = op[, .(y = mean(y), ysd = sd(y), runs = .N), by = par.names] | ||
return(ds) | ||
} | ||
|
||
|
||
intensifyOCBA = function(opt.state) { | ||
|
||
# some intialization | ||
opt.problem = getOptStateOptProblem(opt.state) | ||
control = getOptProblemControl(opt.problem) | ||
par.set = getOptProblemParSet(opt.problem) | ||
|
||
op = as.data.table(getOptStateOptPath(opt.state)) | ||
par.names = colnames(op)[1:(which(colnames(op) == "y") - 1)] #FIXME: This sucks | ||
|
||
# minimum number of replicates at each point | ||
minrep = max(control$noisy.ocba.initial, 2L) | ||
|
||
# calculate summary of the dsign | ||
ds = getOptPathSummary(opt.state, par.names) | ||
nds = nrow(ds) | ||
|
||
# make sure that initially, each point is evaluated at least minrep times | ||
xinit = rep(seq_len(nds), pmax(minrep - ds$runs, 0)) | ||
opt.state = replicatePoint(opt.state, x = ds[xinit, ..par.names], type = paste("initeval")) | ||
|
||
ds = getOptPathSummary(opt.state, par.names) | ||
add = distributeOCBA(ds, budget = control$noisy.ocba.budget) | ||
reps = rep(seq_len(nds), add) | ||
|
||
replicatePoint(opt.state, x = ds[reps, ..par.names], type = paste("OCBA")) | ||
|
||
return(opt.state) | ||
} | ||
|
||
|
||
distributeOCBA = function(ds, budget) { | ||
|
||
nds = nrow(ds) | ||
|
||
# TODO: until now only minimization possible | ||
tbudget = budget + sum(ds$runs) | ||
|
||
# search for the best and second-best dsign | ||
b = order(ds$y)[1] | ||
s = order(ds$y)[2] | ||
|
||
# vector of ratios | ||
ratio = rep(0, nds) | ||
ratio[s] = 1 | ||
|
||
# calculate ratios | ||
tmp = (ds[b, ]$y - ds[s, ]$y) / (ds[b, ]$y - ds[- c(s, b), ]$y) | ||
ratio[- c(s, b)] = tmp^2 * ds[- c(s, b), ]$ysd^2 / ds[s, ]$ysd^2 | ||
ratio[b] = ds[b, ]$ysd * sqrt(sum(ratio^2 / ds$ysd^2)) | ||
|
||
# additional replications | ||
add = rep(0, nds) | ||
|
||
# do not disable any dsign | ||
disabled = rep(FALSE, nds) | ||
|
||
more_alloc = TRUE | ||
|
||
while (more_alloc) { | ||
|
||
add[!disabled] = roundPreserveSum(tbudget / sum(ratio[!disabled]) * ratio[!disabled]) | ||
|
||
# disable designs that have been run too much | ||
disabled = disabled | (ds$runs > add) | ||
more_alloc = any(ds$runs > add) | ||
|
||
# set additional replications s.t. already run replications are set | ||
add[disabled] = ds[disabled, ]$runs | ||
|
||
# decrease total budget correspondingly | ||
tbudget = budget + sum(ds$runs) - sum(add[disabled]) | ||
} | ||
|
||
add = add - ds$runs | ||
|
||
return(add) | ||
} | ||
|
||
roundPreserveSum = function(x, digits = 0) { | ||
up = 10 ^ digits | ||
x = x * up | ||
y = floor(x) | ||
indices = tail(order(x-y), round(sum(x)) - sum(y)) | ||
y[indices] = y[indices] + 1 | ||
y / up | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
#' @title Set options for handling noisy functions. | ||
#' @description | ||
#' Extends an MBO control object with options for handling noisy functions. | ||
#' @template arg_control | ||
#' @param method [\code{character(1)}]\cr | ||
#' Which of the replication strategies should be used? Possible values are: | ||
#' \dQuote{fixed}: Every point is evaluated \code{instances} times. \cr | ||
#' \dQuote{incumbent}: Use an incumbent strategy as intensification strategy. | ||
#' The size of the set of additional challengers (apart from the incumbent) can be specified in \code{incumbent.nchallengers}. \cr | ||
#' \dQuote{ocba}: Distribution replication budget according to OCBA. | ||
#' The replication budget per iteration is specified in \code{ocba.budget}, | ||
#' the initial number of iterations per parameter is specified in \code{ocba.initial}. \cr | ||
#' @param instances [\code{integer(1)}]\cr | ||
#' How many instances of one parameter will be calculated? | ||
#' @param instance.param [\code{character(1)}]\cr | ||
#' What is the name of the function param that defines the instance? | ||
#' @param self.replicating [\code{logical(1)}]\cr | ||
#' TRUE if the function returns a vector of noisy results for one input. Then \code{instances} specifies the length of the result we expect. | ||
#' @param incumbent.nchallengers [\code{integer(1)}]\cr | ||
#' The size of the set of additional challengers (apart from the incumbent), defaults to \code{0}. | ||
#' @param ocba.budget [\code{integer(1)}]\cr | ||
#' The budget that is allocated in each iteration per Optimal Computing Budget Allocation (OCBA) rule, defaults to 10. | ||
#' @param ocba.initial [\code{integer(1)}]\cr | ||
#' The number of initial replications at each new point, defaults to \code{3}. | ||
#' This needs to be larger than 1, since OCBA requires an initial variance estimate at each point. | ||
#' @param instance.aggregation [\code{function}]\cr | ||
#' Should data be aggregated per instance? If yes, a function (e. g. mean) needs to be specified. | ||
#' @return [\code{\link{MBOControl}}]. | ||
#' @family MBOControl | ||
#' @export | ||
setMBOControlNoisy = function(control, | ||
method = NULL, | ||
instances = NULL, | ||
instance.param = NULL, | ||
instance.aggregation = NULL, | ||
self.replicating = NULL, | ||
incumbent.nchallengers = NULL, | ||
ocba.budget = NULL, | ||
ocba.initial = NULL) { | ||
|
||
assertClass(control, "MBOControl") | ||
control$noisy.method = coalesce(method, control$noisy.method, "fixed") | ||
assertChoice(control$noisy.method, choices = c("fixed", "incumbent", "ocba")) | ||
control$noisy.instances = assertInt(instances, lower = 1L, null.ok = TRUE, na.ok = FALSE) %??% control$noisy.instances %??% 1L | ||
control$noisy.self.replicating = assertFlag(self.replicating, null.ok = TRUE, na.ok = FALSE) %??% control$noisy.self.replicating %??% FALSE | ||
control$noisy.instance.param = assertString(instance.param, null.ok = TRUE, na.ok = TRUE) %??% control$noisy.instance.param %??% ifelse(control$noisy.self.replicating, "noisy.repl", NA_character_) | ||
control$noisy.instance.aggregation = assertClass(instance.aggregation, "function", null.ok = TRUE) %??% control$noisy.instance.aggregation | ||
control$noisy.ocba.budget = assertInt(ocba.budget, lower = 1L, null.ok = TRUE, na.ok = FALSE) %??% control$noisy.ocba.budget %??% 10L | ||
control$noisy.ocba.initial = assertInt(ocba.initial, lower = 2L, null.ok = TRUE, na.ok = FALSE) %??% control$noisy.ocba.initial %??% 3L | ||
control$noisy.incumbent.nchallengers = assertInt(incumbent.nchallengers, lower = 0L, null.ok = TRUE, na.ok = FALSE) %??% control$noisy.incumbent.nchallengers %??% 0L | ||
|
||
if (control$noisy.self.replicating && control$noisy.instance.param != "noisy.repl") { | ||
stop("You can not change the instance.param for self replicating functions.") | ||
} | ||
|
||
return(control) | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes look like an error @juliambr
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops, that shouldn't be there - removed it. Thanks!