Skip to content

Commit

Permalink
Human-in-the-loop MBO (#397)
Browse files Browse the repository at this point in the history
* tryout

* smbo works

* cleanup [ci skip]

* plot, und multiobjective

* fixes

* typo

* roxygen [ci skip]

* add vignette and other stuff

* updateMBO assertions

* cleanup

* vignette: add loop, smbo y arg check

* use makeProposal in stepMBO

* more argchecks

* whoopsie

* fixes checks
jakob-r authored Nov 9, 2017
1 parent b373333 commit 0f6660c
Showing 22 changed files with 636 additions and 43 deletions.
6 changes: 6 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ S3method(initCrit,InfillCritCB)
S3method(initCrit,default)
S3method(plot,MBOMultiObjResult)
S3method(plot,MBOSingleObjResult)
S3method(plot,OptState)
S3method(print,MBOControl)
S3method(print,MBOExampleRun)
S3method(print,MBOExampleRunMultiObj)
@@ -23,6 +24,7 @@ export(crit.mr)
export(crit.se)
export(exampleRun)
export(exampleRunMultiObj)
export(finalizeSMBO)
export(getGlobalOpt)
export(getMBOInfillCritComponents)
export(getMBOInfillCritId)
@@ -33,6 +35,7 @@ export(getSupportedInfillOptFunctions)
export(getSupportedMultipointInfillOptFunctions)
export(hasRequiresInfillCritStandardError)
export(initCrit)
export(initSMBO)
export(makeMBOControl)
export(makeMBOInfillCrit)
export(makeMBOInfillCritAEI)
@@ -48,16 +51,19 @@ export(mbo)
export(mboContinue)
export(mboFinalize)
export(plotExampleRun)
export(proposePoints)
export(renderExampleRunPlot)
export(setMBOControlInfill)
export(setMBOControlMultiObj)
export(setMBOControlMultiPoint)
export(setMBOControlTermination)
export(trafoLog)
export(trafoSqrt)
export(updateSMBO)
import(BBmisc)
import(ParamHelpers)
import(checkmate)
import(data.table)
import(grDevices)
import(mlr)
import(parallelMap)
101 changes: 101 additions & 0 deletions R/SMBO.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#' @title Initialize a manual sequential MBO run.
#' @description When you want to run a human-in-the-loop MBO run you need to initialize it first.
#'
#' @inheritParams mbo
#' @template arg_parset
#' @param minimize [\code{logical}]\cr
#' Should objective values of the target functions be minimized? One value par objective.
#' Default is \code{TRUE} for every objective.
#' @param noisy [\code{logical(1)}]\cr
#' Is the target function noisy?
#' Default is \code{FALSE}.
#'
#' @return [\code{\link{OptState}}]
#' @export
initSMBO = function(par.set, design, learner = NULL, control, minimize = rep(TRUE, control$n.objectives), noisy = FALSE, show.info = getOption("mlrMBO.show.info", TRUE)) {

assertClass(par.set, "ParamSet")
assertDataFrame(design)
assertSetEqual(names(design), c(getParamIds(par.set, repeated = TRUE, with.nr = TRUE), control$y.name))
assertFlag(noisy)
assertLogical(minimize, any.missing = FALSE)

control$minimize = minimize
control$noisy = noisy
if (control$n.objectives == 1) {
dummy.fun = makeSingleObjectiveFunction(name = "human in the loop", fn = function(...) return(NA), par.set = par.set, minimize = control$minimize, noisy = control$noisy)
} else {
dummy.fun = makeMultiObjectiveFunction(name = "human in the loop", fn = function(...) NA, par.set = par.set, n.objectives = control$n.objectives, minimize = control$minimize, noisy = control$noisy)
}

# assertions are done here:
opt.problem = initOptProblem(fun = dummy.fun, design = design, learner = learner, control = control, show.info = show.info, more.args = list())
opt.state = makeOptState(opt.problem)
evalMBODesign.OptState(opt.state)
finalizeMboLoop(opt.state)
return(opt.state)
}

#' @title Updates SMBO with the new observations
#' @description After a function evaluation you want to update the \code{\link{OptState}} to get new proposals.
#'
#' @param opt.state [\code{\link{OptState}}]
#' The optimization state.
#' Generated by \code{\link{initSMBO}}, this function or an \code{\link{mbo}} run.
#' @param x [\code{data.frame}]
#' Named x values.
#' One row per set of x values.
#' @param y [\code{numeric|list}]
#' Outcome of the optimization.
#' For multiple results use a list.
#' For a result of a multi-objective function use a numeric vector.
#'
#' @return [\code{\link{OptState}}]
#' @export
updateSMBO = function(opt.state, x, y) {
opt.problem = getOptStateOptProblem(opt.state)
opt.path = getOptStateOptPath(opt.state)
control = getOptProblemControl(opt.problem)


assertDataFrame(x)
if (nrow(x) > 1 && !is.list(y)) {
stopf("For a multipoint update y has to be a list.")
}
if (!is.list(y)) {
y = list(y)
}
assertList(y, "numeric", len = nrow(x))
assertNumeric(y[[1]], len = control$n.objectives)


infill.values = control$infill.crit$fun(points = x, models = getOptStateModels(opt.state)[[1]], control = control, design = convertOptPathToDf(opt.path, control), attributes = TRUE, iter = getOptStateLoop(opt.state))

prop = makeProposal(
control = control,
prop.points = x,
prop.type = "manual",
crit.vals = matrix(infill.values, ncol = 1L),
crit.components = attr(infill.values, "crit.components"))

extras = getExtras(n = nrow(prop$prop.points), prop = prop, train.time = 0, control = control)
xs = dfRowsToList(prop$prop.points, getOptProblemParSet(getOptStateOptProblem(opt.state)))

for (i in seq_along(xs)) {
addOptPathEl(op = opt.path, x = xs[[i]], y = y[[i]], extra = extras[[i]])
}
finalizeMboLoop(opt.state)
invisible(opt.state)
}

#' @title Finalizes the SMBO Optimization
#' @description Returns the common mlrMBO result object.
#'
#' @param opt.state [\code{\link{OptState}}]
#' The optimization state.
#'
#' @return [\code{\link{MBOSingleObjResult}} | \code{\link{MBOMultiObjResult}}]
#' @export
finalizeSMBO = function(opt.state) {
mboFinalize2(opt.state)
}
2 changes: 1 addition & 1 deletion R/checkLearner.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# check and create default learner
checkLearner = function(learner, par.set, control, fun) {
checkLearner = function(learner, control, fun) {
if (missing(learner) || is.null(learner)) {
learner = makeMBOLearner(control, fun, config = list(show.learner.output = FALSE))
} else {
7 changes: 4 additions & 3 deletions R/checkStuff.R
Original file line number Diff line number Diff line change
@@ -10,14 +10,15 @@
# Learner object.
# @param control [\code{\link{MBOControl}}]
# MBO control object.
checkStuff = function(fun, par.set, design, learner, control) {
checkStuff = function(fun, design, learner, control) {
assertFunction(fun)
assertClass(par.set, "ParamSet")
if (!is.null(design))
assertClass(design, "data.frame")
assertClass(control, "MBOControl")
assertClass(learner, "Learner")

par.set = getParamSet(fun)

if (getNumberOfObjectives(fun) != control$n.objectives) {
stopf("Objective function has %i objectives, but the control object assumes %i.",
getNumberOfObjectives(fun), control$n.objectives)
@@ -81,7 +82,7 @@ checkStuff = function(fun, par.set, design, learner, control) {

if (is.null(control$target.fun.value)) {
# If we minimize, target is -Inf, for maximize it is Inf
control$target.fun.value = if (control$minimize[1L]) -Inf else Inf
control$target.fun.value = ifelse(shouldBeMinimized(fun), -Inf, Inf)
} else {
assertNumber(control$target.fun.value, na.ok = FALSE)
}
2 changes: 1 addition & 1 deletion R/exampleRun.R
Original file line number Diff line number Diff line change
@@ -40,7 +40,7 @@ exampleRun = function(fun, design = NULL, learner = NULL, control,
noisy = isNoisy(fun)
control$noisy = noisy
control$minimize = shouldBeMinimized(fun)
learner = checkLearner(learner, par.set, control, fun)
learner = checkLearner(learner, control, fun)
assertClass(control, "MBOControl")
points.per.dim = asCount(points.per.dim, positive = TRUE)
noisy.evals = asCount(noisy.evals, positive = TRUE)
2 changes: 1 addition & 1 deletion R/exampleRunMultiObj.R
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ exampleRunMultiObj= function(fun, design = NULL, learner, control, points.per.di
if (is.null(design))
design = generateDesign(4 * n.params, par.set)

learner = checkLearner(learner, par.set, control, fun)
learner = checkLearner(learner, control, fun)
assertClass(control, "MBOControl")
minimize = shouldBeMinimized(fun)
control$noisy = isNoisy(fun)
36 changes: 36 additions & 0 deletions R/initOptProblem.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
initOptProblem = function(fun, design, learner, control, show.info, more.args) {
assertClass(fun, "smoof_function")

par.set = getParamSet(fun)

assertDataFrame(design, null.ok = TRUE)
if (!is.null(design)) {
assertSubset(getParamIds(par.set, repeated = TRUE, with.nr = TRUE), names(design))
}


learner = checkLearner(learner, control, fun)

assertClass(control, "MBOControl")

n.params = sum(getParamLengths(par.set))
control$noisy = isNoisy(fun)
control$minimize = shouldBeMinimized(fun)
if (is.null(design))
design = generateDesign(n.params * 4L, par.set, fun = lhs::maximinLHS)
else
assertDataFrame(design, min.rows = 1L, min.cols = 1L)
control = checkStuff(fun, design, learner, control)
control$infill.crit = initCrit(control$infill.crit, fun, design, learner, control)

loadPackages(control)

# generate an OptProblem which gathers all necessary information to define the optimization problem in one environment.
makeOptProblem(
fun = fun,
design = design,
learner = learner,
control = control,
show.info = assertFlag(show.info),
more.args = assertList(more.args, null.ok = TRUE))
}
26 changes: 2 additions & 24 deletions R/mbo.R
Original file line number Diff line number Diff line change
@@ -55,30 +55,8 @@
mbo = function(fun, design = NULL, learner = NULL, control,
show.info = getOption("mlrMBO.show.info", TRUE), more.args = list()) {

assertClass(fun, "smoof_function")
par.set = getParamSet(fun)
n.params = sum(getParamLengths(par.set))
control$noisy = isNoisy(fun)
control$minimize = shouldBeMinimized(fun)
assertFlag(show.info)
if (is.null(design))
design = generateDesign(n.params * 4L, par.set, fun = lhs::maximinLHS)
else
assertDataFrame(design, min.rows = 1L, min.cols = 1L)
learner = checkLearner(learner, par.set, control, fun)
control = checkStuff(fun, par.set, design, learner, control)
control$infill.crit = initCrit(control$infill.crit, fun, design, learner, control)

loadPackages(control)

# generate an OptProblem which gathers all necessary information to define the optimization problem in one environment.
opt.problem = makeOptProblem(
fun = fun,
design = design,
learner = learner,
control = control,
show.info = show.info,
more.args = more.args)
# assertions are done here:
opt.problem = initOptProblem(fun = fun, design = design, learner = learner, control = control, show.info = show.info, more.args = more.args)

# we call the magic mboTemplate where everything happens
final.opt.state = mboTemplate(opt.problem)
2 changes: 1 addition & 1 deletion R/mboTemplate.R
Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@ mboTemplate.OptState = function(obj) {
return(opt.state)
}
repeat {
prop = proposePoints.OptState(opt.state)
prop = proposePoints(opt.state)
evalProposedPoints.OptState(opt.state, prop)
finalizeMboLoop(opt.state)
terminate = getOptStateTermination(opt.state)
89 changes: 89 additions & 0 deletions R/plot_OptState.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#' @title Generate ggplot2 Object
#'
#' @description Plots the values of the infill criterion for a 1- and 2-dimensional numerical search space for a given \code{\link{OptState}}.
#'
#' @param x [\code{OptState}]\cr
#' The OptState.
#' @param scale.panels [\code{logical(1)}]\cr
#' If \code{TRUE} the values in each panel will be scaled to [0,1].
#' @param ... [any] \cr
#' Not used.
#'
#' @export
plot.OptState = function(x, scale.panels = FALSE, ...) {

# all the variables we need
opt.state = x
opt.problem = getOptStateOptProblem(opt.state)
control = getOptProblemControl(opt.problem)
par.set = getOptProblemParSet(opt.problem)
par.dim = getParamNr(par.set, devectorize = TRUE)
if (par.dim > 2) {
stop("Only plotting for 1- and 2-dimensional search spaces is possible.")
}
par.types = getParamTypes(par.set, use.names = TRUE, with.nr = TRUE, df.cols = TRUE, df.discretes.as.factor = TRUE)
par.is.numeric = par.types %in% c("numeric", "integer")
par.count.numeric = sum(par.is.numeric)
par.count.discrete = par.dim - par.count.numeric
opt.path = getOptStateOptPath(opt.state)
design = convertOptPathToDf(opt.path, control)
models = getOptStateModels(opt.state)$models
x.ids = getParamIds(par.set, repeated = TRUE, with.nr = TRUE)
y.ids = control$y.name
infill = control$infill.crit

# the data we need to plot
points = generateGridDesign(par.set, 100, trafo = TRUE)
infill.res = infill$fun(points = points, models = models, control = control, par.set = par.set, design = design, attributes = TRUE, iter = getOptStateLoop(opt.state))

crit.components = attr(infill.res, "crit.components")
if (!is.null(crit.components)) {
plot.data = data.table::data.table(infill = infill.res, crit.components, points)
} else {
plot.data = data.table::data.table(infill = infill.res, points)
}

if (infill$opt.direction == "maximize") {
plot.data$infill = -1 * plot.data$infill
}
colnames(plot.data)[1] = control$infill.crit$id

# add types to points
design$type = ifelse(getOptPathDOB(opt.path) == 0, "init", "seq")

# reduce to usefull infill components
use.only.columns = c(x.ids, control$infill.crit$id, "mean", "se")
use.only.columns = intersect(use.only.columns, colnames(plot.data))
plot.data = plot.data[, use.only.columns, with = FALSE]

# prepare data for ggplot2
mdata = data.table::melt(plot.data, id.vars = x.ids)
mdata$variable = factor(mdata$variable, levels = intersect(use.only.columns, levels(mdata$variable)))
if (scale.panels) {
predict.range = range(mdata[get("variable")=="mean", "value"])
mdata[, ':='("value", normalize(x = get("value"), method = "range")), by = "variable"]
design[[y.ids]] = (design[[y.ids]] + (0 - predict.range[1])) / diff(predict.range)
}
if (par.count.numeric == 2) {
g = ggplot2::ggplot(mdata, ggplot2::aes_string(x = x.ids[1], y = x.ids[2], fill = "value"))
g = g + ggplot2::geom_tile()
g = g + ggplot2::geom_point(data = design, mapping = ggplot2::aes_string(x = x.ids[1], y = x.ids[2], fill = y.ids[1], shape = "type"))
g = g + ggplot2::facet_grid(~variable)
brewer.div = colorRampPalette(RColorBrewer::brewer.pal(11, "Spectral"), interpolate = "spline")
g = g + ggplot2::scale_fill_gradientn(colours = brewer.div(200))
} else if (par.count.numeric == 1) {
g = ggplot2::ggplot(mdata, ggplot2::aes_string(x = x.ids[par.is.numeric], y = "value"))
g = g + ggplot2::geom_line()
g = g + ggplot2::geom_vline(data = design, mapping = ggplot2::aes_string(xintercept = x.ids[par.is.numeric]), alpha = 0.5, size = 0.25)
g = g + ggplot2::geom_point(data = cbind(design, variable = "mean"), mapping = ggplot2::aes_string(x = x.ids[par.is.numeric], y = y.ids[1], shape = "type", color = "type"))
g = g + ggplot2::scale_color_manual(values = c(init = "red", seq = "green"))
if (par.count.discrete == 1) {
formula.txt = paste0(names(par.types[!par.is.numeric]),"~variable")
} else {
formula.txt = "~variable"
}
g = g + ggplot2::facet_grid(as.formula(formula.txt))
}
g = g + ggplot2::scale_shape_manual(values = c(init = 16, seq = 15))
g
}
7 changes: 6 additions & 1 deletion R/proposePoints.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
#' @title Propose candidates for the objective function
#' @description Propose points for the objective function that should be evaluated according to the infill criterion and the recent evaluations.
#'
#' @param opt.state [\code{\link{OptState}}]
#' @export
# Propose infill points - simple dispatcher to real methods
#
# input:
@@ -11,7 +16,7 @@
# errors.models [character] : model errors, resulting in randomly proposed points.
# length is one string PER PROPOSED POINT, not per element of <models>
# NA if the model was Ok, or the (first) error message if some model crashed
proposePoints.OptState = function(opt.state) {
proposePoints = function(opt.state) {
opt.problem = getOptStateOptProblem(opt.state)
control = getOptProblemControl(opt.problem)

1 change: 1 addition & 0 deletions R/zzz.R
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
#' @import smoof
#' @import stats
#' @import utils
#' @import data.table
#' @importFrom lhs randomLHS
#' @useDynLib mlrMBO c_sms_indicator c_eps_indicator
NULL
2 changes: 2 additions & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
@@ -20,6 +20,8 @@ navbar:
href: articles/supplementary/infill_criteria.html
- text: Machine learning with mlrMBO
href: articles/supplementary/machine_learning_with_mlrmbo.html
- text: Human-in-the-loop MBO
href: articles/supplementary/human_in_the_loop_MBO.html
- text: Reference
icon: fa-book
href: reference/index.html
18 changes: 18 additions & 0 deletions man/finalizeSMBO.Rd
47 changes: 47 additions & 0 deletions man/initSMBO.Rd
21 changes: 21 additions & 0 deletions man/plot.OptState.Rd
14 changes: 14 additions & 0 deletions man/proposePoints.Rd
28 changes: 28 additions & 0 deletions man/updateSMBO.Rd
14 changes: 14 additions & 0 deletions tests/testthat/helper_objects.R
Original file line number Diff line number Diff line change
@@ -33,3 +33,17 @@ testfmco2 = makeMultiObjectiveFunction(
par.set = makeNumericParamSet(len = 2L, lower = -2, upper = 1)
)
testdesmco2 = generateTestDesign(10L, getParamSet(testfmco2))

# mixed space test functio
testp.mixed = makeParamSet(
makeDiscreteParam("disc1", values = c("a", "b")),
makeNumericParam("num1", lower = 0, upper = 1)
)
testf.mixed = makeSingleObjectiveFunction(
fn = function(x) {
ifelse(x$disc1 == "a", x$num1 * 2 - 1, 1 - x$num1)
},
par.set = testp.mixed,
has.simple.signature = FALSE
)
testd.mixed = generateTestDesign(10L, testp.mixed)
13 changes: 2 additions & 11 deletions tests/testthat/test_different_learners.R
Original file line number Diff line number Diff line change
@@ -4,17 +4,8 @@ test_that("mbo works with different learners", {

# Test some possible learner on a simple problem with discrete and
# numeric parameters
ps1 = makeParamSet(
makeDiscreteParam("disc1", values = c("a", "b")),
makeNumericParam("num1", lower = 0, upper = 1)
)
f1 = smoof::makeSingleObjectiveFunction(
fn = function(x) {
ifelse(x$disc1 == "a", x$num1 * 2 - 1, 1 - x$num1)
},
par.set = ps1,
has.simple.signature = FALSE
)
ps1 = testp.mixed
f1 = testf.mixed

ps2 = testp.fsphere.2d
f2 = testf.fsphere.2d
71 changes: 71 additions & 0 deletions tests/testthat/test_smbo.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
context("manual smbo")

test_that("human in the middle smbo works", {
fun = testf.fsphere.2d
des = testd.fsphere.2d
des$y = apply(des, 1, fun)
ps = testp.fsphere.2d
ctrl = makeMBOControl()

opt.state = initSMBO(par.set = ps, design = des, control = ctrl)
prop = proposePoints(opt.state)
assertList(prop)
assertDataFrame(prop$prop.points)
assertSetEqual(names(prop$prop.points), getParamIds(ps, TRUE, TRUE))

x = data.frame(x1 = 0, x2 = 0)
y = fun(x = x)
updateSMBO(opt.state, x = x, y = y)

x = data.frame(x1 = -1, x2 = 1)
y = fun(x = x)
updateSMBO(opt.state, x = x, y = y)

plot(opt.state)

or = finalizeSMBO(opt.state)
expect_number(or$y)
expect_equal(getOptPathLength(or$opt.path), 12L)
df = as.data.frame(or$opt.path)
expect_numeric(df$x1)
expect_numeric(df$x2)
expect_set_equal(names(or$x), names(testp.fsphere.2d$pars))
})

test_that("human in the middle smbo works for multi objective", {
par.set = getParamSet(testf.zdt1.2d)
des = testd.zdt1.2d
res = t(apply(des, 1, testf.zdt1.2d))
control = makeMBOControl(n.objectives = 2)
control = setMBOControlInfill(control, crit = crit.dib1)
colnames(res) = control$y.name
des = cbind(des,res)
opt.state = initSMBO(par.set, design = des, control = control, noisy = FALSE, minimize = shouldBeMinimized(testf.zdt1.2d))
plot(opt.state)
proposePoints(opt.state)
x = data.frame(x1 = 0.0002, x2 = 0.1)
y = testf.zdt1.2d(x = x)
updateSMBO(opt.state, x = x, y = y)
or = finalizeSMBO(opt.state)
})

test_that("human in the middle smbo works for mixed spaces", {
par.set = testp.mixed
fun = testf.mixed
design = testd.mixed
design$y = sapply(convertRowsToList(design, name.list = TRUE, name.vector = TRUE), fun)
control = makeMBOControl()
opt.state = initSMBO(par.set = par.set, design = design, control = control, minimize = shouldBeMinimized(fun), noisy = isNoisy(fun))
plot(opt.state)
proposePoints(opt.state)
x = data.frame(disc1 = "a", num1 = 0)
y = fun(x)
updateSMBO(opt.state, x = x, y = y)
or = finalizeSMBO(opt.state)
expect_equal(or$y, y)
expect_equal(getOptPathLength(or$opt.path), 11L)
df = as.data.frame(or$opt.path)
expect_numeric(df$num1)
expect_factor(df$disc1)
expect_set_equal(names(or$x), names(par.set$pars))
})
170 changes: 170 additions & 0 deletions vignettes/supplementary/human_in_the_loop_MBO.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
---
title: "Human-in-the-loop MBO"
output:
html_document:
toc: true
toc_float:
collapsed: true
smooth_scroll: false
dev: svg
vignette: >
%\VignetteIndexEntry{Mixed Space Optimization}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE, cache = FALSE}
library(mlrMBO)
set.seed(123)
knitr::opts_chunk$set(cache = TRUE, collapse = FALSE, dev = "svg", fig.height = 3.5)
knitr::knit_hooks$set(document = function(x){
gsub("```\n*```r*\n*", "", x)
})
hidden.objective = function(x) 1 + sin(x[1]*5) + 0.1 * sum(x^2)
```

## Purpose

This Vignette shows you how to use **mlrMBO** for a guided optimization.
In this setting **mlrMBO** proposes a candidate configuration and you can then decide for yourself whether you want to evaluate it or another value.
You have to evaluate the objective function manually.
The value and the result have to be feed back to **mlrMBO**.
Afterwards you can request the next candidate and so on.

## Introduction

Before we start the optimization you need to define the search space:

```{r parset}
ps = makeParamSet(
makeNumericParam("q", lower = -1, upper = 2),
makeIntegerParam("v", lower = -2, upper = 3)
)
```

Furthermore we need an initial design that includes the results of the evaluated function

```{r init design}
des = generateDesign(n = 7, par.set = ps)
des
```

After evaluating the objective function manually we can add the results

```{r add results}
des$y = c(1.20, 0.97, 0.91, 3.15, 0.58, 1.12, 0.50)
```

Now we define our **mlrMBO**-Control object.
For this example we stick to the defaults except that we set the infill-criterion to the **Expected Improvement**

```{r mbocontrol}
ctrl = makeMBOControl()
ctrl = setMBOControlInfill(ctrl, crit = crit.ei)
```

These information are enough to get us started and initialize the sequential MBO.

```{r initSMBO}
opt.state = initSMBO(par.set = ps, design = des, control = ctrl, minimize = TRUE, noisy = FALSE)
```

At each state the `opt.state` object can be plotted to visualize the predictions of the surrogate model

```{r plotOptState1}
plot(opt.state)
```

The first panel shows the value of the infill criterion.
The higher the value the more this area is desirable to be explored to find the optimum.
In the following panels the mean prediction of the surrogate and the uncertainty estimation is plotted.

Let's see which point MBO suggests we should evaluate in the next step:

```{r proposePoints1}
proposePoints(opt.state)
```

We don't have to stick to the suggestion and evaluate another point:
```{r evaluate}
x = data.frame(q = 1.7, v = 1)
```

After we evaluated the objective function manually we get a return value of `2.19`.
We take both values to update MBO:

```{r update MBO}
updateSMBO(opt.state, x = x, y = 2.19)
```

Now we can plot the state again and ask for a proposal:

```{r plotOptState2}
plot(opt.state)
(prop = proposePoints(opt.state))
```

This time we evaluated the exact proposed points and get a value of `0.13`.

```{r update MBO 2}
updateSMBO(opt.state, x = prop$prop.points, y = 0.13)
```

Let's assume we want to stop here.
To get to the usual MBO result you can call:

```{r}
res = finalizeSMBO(opt.state)
res$x
res$y
```

### Semi Automatic MBO

You can combine the human-in-the-loop MBO with a simple loop to let MBO run for a while and just interfere once in a while.

```{r}
f = function(q, v) 1 + sin(q*5) + 0.1 * (q^2 + v^2)
for (i in 1:10) {
prop = proposePoints(opt.state)
x = dfRowsToList(df = prop$prop.points, par.set = ps)
y = do.call(f, x[[1]])
updateSMBO(opt.state, x = prop$prop.points, y = y)
}
proposePoints(opt.state)
```

## Continue a normal MBO Run

You can also continue a normal call of `mbo()` using this manual interface:

```{r continue mbo}
fun = makeAlpine02Function(1)
res = mbo(fun = fun, control = ctrl)
opt.state = res$final.opt.state
plot(opt.state, scale.panels = TRUE)
(prop = proposePoints(opt.state))
y = fun(prop$prop.points)
updateSMBO(opt.state, x = prop$prop.points, y = y)
# ...
```

## Proposal of multiple points

Using multipoint-MBO you can also obtain multiple suggestions at each call of `proposePoints()`.

```{r multipoint}
ctrl = makeMBOControl(propose.points = 4)
ctrl = setMBOControlInfill(ctrl, crit = makeMBOInfillCritEI())
ctrl = setMBOControlMultiPoint(ctrl, method = "cl")
opt.state = initSMBO(par.set = ps, design = des, control = ctrl, minimize = TRUE, noisy = FALSE)
(prop = proposePoints(opt.state))
```

It's also okay to just evaluate a subset of these points.

```{r update multipoint}
updateSMBO(opt.state, x = prop$prop.points[1:2,], y = list(2.28, 1.67))
# ...
```

0 comments on commit 0f6660c

Please sign in to comment.