Skip to content

Commit 947b859

Browse files
committed
refactoring; use TimerOutputs; repeated function eval option
1 parent ab7221c commit 947b859

File tree

10 files changed

+454
-302
lines changed

10 files changed

+454
-302
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ modeloptimizer = MLGPOptimizer(every = 50, noisebounds = [-4, 3], # bounds
3030
maxeval = 40)
3131
opt = BOpt(f,
3232
model,
33-
ExpectedImprovement(), # type of acquisition
33+
UpperConfidenceBound(), # type of acquisition
3434
modeloptimizer,
3535
[-5., -5.], [5., 5.], # lowerbounds, upperbounds
36-
maxiterations = 500,
37-
sense = Min,
38-
gradientfree = false, # use gradient information
36+
repetitions = 5, # evaluate the function for each input 5 times
37+
maxiterations = 100, # evaluate at 100 input positions
38+
sense = Min, # minimize the function
3939
verbosity = Progress)
4040

4141
result = boptimize!(opt)

REQUIRE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ julia 1.0
22
GaussianProcesses 0.9.0
33
NLopt
44
SpecialFunctions
5+
TimerOutputs
56
ElasticPDMats 0.2.1
67
ForwardDiff
78
DiffResults

examples/1dplot.jl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using BayesianOptimization, GaussianProcesses, PGFPlotsX, Random
2+
push!(PGFPlotsX.CUSTOM_PREAMBLE, "\\usepgfplotslibrary{fillbetween}")
3+
4+
Random.seed!(13)
5+
f(x, noisevariance = 1) = .1*sum((x .- 2).^2) + cos(sum(π/2 * x)) + noisevariance * randn()
6+
model = ElasticGPE(1, mean = MeanConst(0.),
7+
kernel = SEArd([0.], 5.), logNoise = 0.)
8+
modeloptimizer = MLGPOptimizer(every = 50, noisebounds = [-2., 3],
9+
kernbounds = [[-1, 0], [4, 10]], maxeval = 40)
10+
opt = BOpt(f, model, ExpectedImprovement(),
11+
modeloptimizer, [-5.], [5.],
12+
maxiterations = 5, sense = Min, repetitions = 5,
13+
acquisitionoptions = (maxeval = 4000, restarts = 50),
14+
verbosity = Progress)
15+
result = boptimize!(opt)
16+
17+
acqfunc = BayesianOptimization.acquisitionfunction(opt.acquisition, model)
18+
xs = -5:.02:5
19+
ms, var = predict_f(model, xs)
20+
sig = sqrt.(var)
21+
fmax, xmax = BayesianOptimization.acquire_max(opt.opt,
22+
opt.lowerbounds, opt.upperbounds,
23+
opt.acquisitionoptions.restarts)
24+
@pgf GroupPlot({group_style = {group_size = "1 by 2", vertical_sep = "4mm"},
25+
height = "4cm", width = "8cm", legend_pos = "outer north east",
26+
legend_style = {draw = "none"}},
27+
{legend_columns = 1, xticklabels = ""},
28+
Plot({only_marks}, Coordinates(model.x[:], -model.y[:])),
29+
"\\addlegendentry{observations}",
30+
Plot({no_marks, "red!20", name_path = "A", forget_plot}, Coordinates(xs, -ms .+ sig)),
31+
Plot({no_marks, "red!20", name_path = "B", forget_plot}, Coordinates(xs, -ms .- sig)),
32+
"\\addplot[red!20] fill between [of=A and B];",
33+
"\\addlegendentry{model std}",
34+
Plot({no_marks, blue, very_thick}, Coordinates(xs, -ms)),
35+
"\\addlegendentry{model mean}",
36+
Plot({no_marks, "green!80!black", very_thick}, Coordinates(xs, f.(xs, 0))),
37+
"\\addlegendentry{noisefree target}",
38+
{height = "3cm", ytick = [0, 4e-2, 8e-2]},
39+
Plot({only_marks, red, mark="triangle*",
40+
mark_size = "5pt", mark_options = {rotate = "0"}},
41+
Coordinates(xmax, [fmax])),
42+
Plot({no_marks, very_thick}, Coordinates(xs, (x -> acqfunc([x])).(xs))),
43+
"\\addlegendentry{next acquisition}",
44+
"\\addlegendentry{acquisition function}")

examples/branin_hartmann.jl

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using BayesianOptimization, GaussianProcesses, Random
2+
3+
# function and regret definitions
4+
branin(x::Vector; kwargs...) = branin(x[1], x[2]; kwargs...)
5+
branin(x1, x2; a = 1, b = 5.1/(4π^2), c = 5/π, r = 6, s = 10, t = 1/(8π),
6+
noiselevel = 0) =
7+
a * (x2 - b*x1^2 + c*x1 - r)^2 + s*(1 - t)*cos(x1) + s + noiselevel * randn()
8+
9+
minima(::typeof(branin)) = [[-π, 12.275], [π, 2.275], [9.42478, 2.475]], 0.397887
10+
11+
hartmann(x; α = [1., 1.2, 3., 3.2], A = [10 3 17 3.5 1.7 8;
12+
.05 10 17 .1 8 14;
13+
3 3.5 1.7 10 17 8;
14+
17 8 .05 10 .1 14],
15+
P = 1e-4 * [1312 1696 5569 124 8283 5886;
16+
2329 4135 8307 3736 1004 9991;
17+
2348 1451 3522 2883 3047 6650;
18+
4047 8828 8732 5743 1091 381]) =
19+
-sum([α[i] * exp(-sum([A[i, j] * (x[j] - P[i, j])^2 for j in 1:6])) for i in 1:4])
20+
21+
minima(::typeof(hartmann)) = [[.20169, .150011, .476874, .275332, .311652, .6573]], -3.32237
22+
23+
euclidean(x, y) = sum((x .- y).^2)
24+
function regret(opt, func)
25+
mins, fmin = minima(func)
26+
(observed_dist = minimum(map(m -> euclidean(m, opt.observed_optimizer), mins)),
27+
observed_regret = abs(opt.observed_optimum - fmin),
28+
model_dist = minimum(map(m -> euclidean(m, opt.model_optimizer), mins)),
29+
model_regret = abs(Int(opt.sense) * opt.model_optimum - fmin))
30+
end
31+
32+
# optimize noise-free branin
33+
34+
opt = BOpt(x -> branin(x, noiselevel = 0),
35+
ElasticGPE(2, mean = MeanConst(-10.), kernel = SEArd([0., 0.], 5.),
36+
logNoise = -2., capacity = 3000),
37+
ExpectedImprovement(),
38+
MLGPOptimizer(every = 50, noisebounds = [-4, 3],
39+
kernbounds = [[-1, -1, 0], [4, 4, 10]],
40+
maxeval = 40),
41+
[-5., 0.], [10., 15.], maxiterations = 200,
42+
sense = Min)
43+
@time boptimize!(opt)
44+
regret(opt, branin)
45+
46+
# optimize hartman
47+
48+
opt = BOpt(hartmann,
49+
ElasticGPE(6, mean = MeanConst(0.), kernel = Mat52Ard(zeros(6), 0.),
50+
logNoise = -2., capacity = 3000),
51+
ExpectedImprovement(),
52+
MLGPOptimizer(every = 20, noisebounds = [-4, 3],
53+
kernbounds = [[-3*ones(6); -3], [4*ones(6); 3]],
54+
maxeval = 100),
55+
zeros(6), ones(6), maxiterations = 300,
56+
sense = Min)
57+
@time boptimize!(opt)
58+
regret(opt, hartmann)
59+
60+
# compare model and observation optimizer on noisy branin
61+
62+
all_obs_regs = []
63+
all_mod_regs = []
64+
for _ in 1:10
65+
opt = BOpt(x -> branin(x, noiselevel = 1),
66+
ElasticGPE(2, mean = MeanConst(-50.), kernel = SEArd(zeros(2), 4.),
67+
logNoise = 2., capacity = 1000),
68+
UpperConfidenceBound(),
69+
MLGPOptimizer(every = 100, noisebounds = [-4, 3],
70+
kernbounds = [[-1, -1, 0], [4, 4, 10]],
71+
f_calls_limit = 40), repetitions = 5,
72+
[-5., 0.], [10., 15.], maxiterations = 20,
73+
sense = Min, verbosity = Silent)
74+
obs_regs = []
75+
mod_regs = []
76+
for i in 1:10
77+
res = boptimize!(opt);
78+
_, obs_reg, _, mod_reg = regret(opt, branin)
79+
push!(obs_regs, obs_reg)
80+
push!(mod_regs, mod_reg)
81+
end
82+
push!(all_obs_regs, obs_regs)
83+
push!(all_mod_regs, mod_regs)
84+
end
85+
using PGFPlotsX
86+
x = (1:length(all_obs_regs[1])) * 20 * 5
87+
@pgf Axis({ymode = "log", legend_entries = ["average observation regret",
88+
"average model regret"],
89+
legend_columns = 1, legend_pos = "north east",
90+
ylabel = "number of observations",
91+
title = "model optimizers become more accurate for noisy objectives"},
92+
Plot({red, very_thick, no_marks}, Coordinates(x, mean(all_obs_regs))),
93+
Plot({blue, very_thick, no_marks}, Coordinates(x, mean(all_mod_regs))),
94+
[Plot({red, thin, forget_plot}, Coordinates(x, y)) for y in all_obs_regs]...,
95+
[Plot({blue, thin, forget_plot}, Coordinates(x, y)) for y in all_mod_regs]...)

src/BayesianOptimization.jl

Lines changed: 51 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -2,65 +2,34 @@ module BayesianOptimization
22
import NLopt, GaussianProcesses
33
import GaussianProcesses: GPBase, GPE
44
import ElasticPDMats: ElasticPDMat
5-
using ForwardDiff, DiffResults, Random, Dates, SpecialFunctions
5+
using ForwardDiff, DiffResults, Random, Dates, SpecialFunctions, TimerOutputs
66
export BOpt, ExpectedImprovement, ProbabilityOfImprovement, UpperConfidenceBound,
77
ThompsonSamplingSimple, MutualInformation, boptimize!, MLGPOptimizer, NoOptimizer,
88
Min, Max, BrochuBetaScaling, NoBetaScaling, Silent, Timings, Progress
99

10+
ENABLE_TIMINGS = true
1011

11-
mutable struct IterationCounter
12-
c::Int
13-
i::Int
14-
N::Int
15-
end
16-
isdone(s::IterationCounter) = s.c == s.N
17-
step!(s::IterationCounter) = (s.c += 1; s.i += 1)
18-
init!(s::IterationCounter) = s.c = 0
19-
mutable struct DurationCounter
20-
starttime::Float64
21-
duration::Float64
22-
now::Float64
23-
endtime::Float64
24-
end
25-
function init!(s::DurationCounter)
26-
s.starttime = time()
27-
s.endtime = s.starttime + s.duration
28-
end
29-
isdone(s::DurationCounter) = (s.now = time()) >= s.endtime
3012
abstract type ModelOptimizer end
31-
mutable struct MLGPOptimizer{NT} <: ModelOptimizer
32-
i::Int
33-
every::Int
34-
options::NT
35-
end
36-
"""
37-
MLGPOptimizer(; every = 10, kwargs...)
38-
39-
Set the GP hyperparameters to the maximum likelihood estimate `every` number of steps.
40-
"""
41-
MLGPOptimizer(; every = 10, kwargs...) = MLGPOptimizer(0, every, kwargs.data)
42-
function optimizemodel!(o::MLGPOptimizer, model::GPBase)
43-
if o.i % o.every == 0
44-
optimizemodel!(model; o.options...)
45-
end
46-
o.i += 1
47-
end
48-
4913
"""
5014
Don't optimize the model ever.
5115
"""
5216
struct NoModelOptimizer <: ModelOptimizer end
5317
optimizemodel!(o::NoModelOptimizer, model) = Nothing
5418

19+
include("utils.jl")
20+
include("acquisitionfunctions.jl")
21+
include("acquisition.jl")
22+
include("models/gp.jl")
23+
5524
@enum Sense Min=-1 Max=1
5625
@enum Verbosity Silent=0 Timings=1 Progress=2
5726

58-
mutable struct BOpt{F,M,A,MO}
27+
mutable struct BOpt{F,M,A,AO,MO}
5928
func::F
6029
sense::Sense
6130
model::M
6231
acquisition::A
63-
acquisitionoptions::NamedTuple
32+
acquisitionoptions::AO
6433
modeloptimizer::MO
6534
lowerbounds::Array{Float64, 1}
6635
upperbounds::Array{Float64, 1}
@@ -73,34 +42,35 @@ mutable struct BOpt{F,M,A,MO}
7342
opt::NLopt.Opt
7443
verbosity::Verbosity
7544
lhs_iterations::Int
45+
repetitions::Int
46+
timeroutput::TimerOutput
7647
end
77-
isdone(o::BOpt) = isdone(o.iterations) || isdone(o.duration)
7848
"""
7949
BOpt(func, model, acquisition, modeloptimizer, lowerbounds, upperbounds;
8050
sense = Max, maxiterations = 10^4, maxduration = Inf,
81-
acquisitionoptions = NamedTuple(), gradientfree = false,
51+
acquisitionoptions = NamedTuple(), repetitions = 1,
8252
verbosity = Progress, lhs_iterations = 5*length(lowerbounds))
8353
"""
8454
function BOpt(func, model, acquisition, modeloptimizer, lowerbounds, upperbounds;
8555
sense = Max, maxiterations = 10^4, maxduration = Inf,
86-
acquisitionoptions = NamedTuple(), gradientfree = false,
87-
verbosity = Progress, lhs_iterations = 5*length(lowerbounds))
88-
if gradientfree
89-
default_acquisitionoptions = (method = :GN_DIRECT_L, restarts = 1, maxeval = 500)
90-
else
91-
default_acquisitionoptions = (method = :LD_LBFGS, restarts = 10, maxeval = 500)
92-
end
93-
acquisitionoptions = merge(default_acquisitionoptions, acquisitionoptions)
56+
acquisitionoptions = NamedTuple(),
57+
repetitions = 1, verbosity = Progress,
58+
lhs_iterations = 5*length(lowerbounds))
9459
now = time()
95-
BOpt(func, sense, model, acquisition, acquisitionoptions,
60+
acquisitionoptions = merge(defaultoptions(typeof(model), typeof(acquisition)),
61+
acquisitionoptions)
62+
maxiterations < lhs_iterations && @error("maxiterations = $maxiterations < lhs_iterations = $lhs_iterations")
63+
BOpt(func, sense, model, acquisition,
64+
acquisitionoptions,
9665
modeloptimizer, lowerbounds, upperbounds,
9766
-Inf64*Int(sense), Array{Float64}(undef, length(lowerbounds)),
9867
-Inf64*Int(sense), Array{Float64}(undef, length(lowerbounds)),
9968
IterationCounter(0, 0, maxiterations),
10069
DurationCounter(now, maxduration, now, now + maxduration),
10170
NLopt.Opt(acquisitionoptions.method, length(lowerbounds)),
102-
verbosity, lhs_iterations)
71+
verbosity, lhs_iterations, repetitions, TimerOutput())
10372
end
73+
isdone(o::BOpt) = isdone(o.iterations) || isdone(o.duration)
10474
import Base: show
10575
function show(io::IO, mime::MIME"text/plain", o::BOpt)
10676
println(io, "Bayesian Optimization object\n\nmodel:")
@@ -119,54 +89,52 @@ function show(io::IO, mime::MIME"text/plain", o::BOpt)
11989
end
12090
end
12191

122-
sample(lowerbounds, upperbounds) =
123-
rand(length(lowerbounds)) .* (upperbounds .- lowerbounds) .+ lowerbounds
124-
12592
function initialise_model!(o)
126-
dac = @elapsed x = latin_hypercube_sampling(o.lowerbounds, o.upperbounds,
127-
o.lhs_iterations)
128-
dfunc = @elapsed y = Int(o.sense) .* o.func.([x[:, i] for i in 1:size(x, 2)])
129-
o.iterations.i = o.iterations.c = length(y)
130-
dmu = @elapsed update!(o.model, x, y)
131-
dom = @elapsed optimizemodel!(o.modeloptimizer, o.model)
132-
o.opt = nlopt_setup(o.acquisition, o.model, o.lowerbounds, o.upperbounds;
133-
o.acquisitionoptions...)
134-
dac, dfunc, dmu, dom
93+
@mytimeit o.timeroutput "acquisition" x = latin_hypercube_sampling(o.lowerbounds, o.upperbounds, o.lhs_iterations)
94+
y = Float64[]
95+
for i in 1:size(x, 2)
96+
for j in 1:o.repetitions
97+
@mytimeit o.timeroutput "function evaluation" push!(y, Int(o.sense) * o.func(x[:, i]))
98+
end
99+
end
100+
o.iterations.i = o.iterations.c = length(y)/o.repetitions
101+
@mytimeit o.timeroutput "model update" update!(o.model,
102+
hcat(hcat([fill(x[:, i], o.repetitions) for i in 1:size(x, 2)]...)...),
103+
y)
104+
@mytimeit o.timeroutput "model hyperparameter optimization" optimizemodel!(o.modeloptimizer, o.model)
105+
o.opt = nlopt_setup(o.acquisition, o.model, o.lowerbounds, o.upperbounds,
106+
o.acquisitionoptions)
135107
end
136108
"""
137109
boptimize!(o::BOpt)
138110
"""
139111
function boptimize!(o::BOpt)
140112
init!(o.duration)
141113
init!(o.iterations)
142-
dfunc = dom = dac = dmu = 0.
143-
if o.iterations.i == 0 dac, dfunc, dmu, dom = initialise_model!(o) end
114+
reset_timer!(o.timeroutput)
115+
o.iterations.i == 0 && initialise_model!(o)
144116
while !isdone(o)
145117
o.verbosity >= Progress && @info("$(now())\titeration: $(o.iterations.i)\tcurrent optimum: $(o.observed_optimum)")
146118
setparams!(o.acquisition, o.model)
147-
dac += @elapsed f, x = acquire_max(o.opt, o.lowerbounds, o.upperbounds,
148-
o.acquisitionoptions.restarts)
149-
dfunc += @elapsed y = Int(o.sense) * o.func(x)
119+
@mytimeit o.timeroutput "acquisition" f, x = acquire_max(o.opt, o.lowerbounds, o.upperbounds, o.acquisitionoptions.restarts)
120+
ys = Float64[]
150121
step!(o.iterations)
151-
if y > Int(o.sense) * o.observed_optimum
152-
o.observed_optimum = Int(o.sense) * y
153-
o.observed_optimizer = x
122+
for _ in 1:o.repetitions
123+
@mytimeit o.timeroutput "function evaluation" y = Int(o.sense) * o.func(x)
124+
push!(ys, y)
125+
if y > Int(o.sense) * o.observed_optimum
126+
o.observed_optimum = Int(o.sense) * y
127+
o.observed_optimizer = x
128+
end
154129
end
155-
dmu += @elapsed update!(o.model, x, y)
156-
dom += @elapsed optimizemodel!(o.modeloptimizer, o.model)
130+
@mytimeit o.timeroutput "model update" update!(o.model, hcat(fill(x, o.repetitions)...), ys)
131+
@mytimeit o.timeroutput "model hyperparameter optimization" optimizemodel!(o.modeloptimizer, o.model)
157132
end
133+
@mytimeit o.timeroutput "acquisition" o.model_optimum, o.model_optimizer = acquire_model_max(o)
158134
o.duration.now = time()
159-
o.verbosity >= Timings && @info("time spent for:
160-
function evaluation \t $dfunc s
161-
model update \t\t $dmu s
162-
model optimization \t $dom s
163-
acquisition \t\t $dac s")
164-
dom += @elapsed o.model_optimum, o.model_optimizer = acquire_model_max(o, restarts = 10, maxeval = 2000)
135+
o.verbosity >= Timings && @info(o.timeroutput)
165136
(observerd_optimum = o.observed_optimum, observed_optimizer = o.observed_optimizer,
166137
model_optimum = Int(o.sense) * o.model_optimum, model_optimizer = o.model_optimizer)
167138
end
168139

169-
include("acquisition.jl")
170-
include("models/gp.jl")
171-
172140
end # module

0 commit comments

Comments
 (0)