Surrogate-assisted continuous and discrete, constrained optimization
Description
Surrogate-assisted optimization replaces expensive functions in the objecitve and/or constraints by a surrogate. In Nonconvex, a Gaussian process (GP) from AbstractGPs.jl is used. A certain amount of "benefit of the doubt" is given to solutions by minimizing:
μ(x) - η * σ(x)
where μ(x)
and σ(x)
are the mean and standard deviation of the posterior GP's prediction of the function's value at point x
. η
is a positive number that resembles how much benefit of the doubt we want to give the solution. A high η
means more exploration and a low η
means more exploitation.
Similarly, expensive inequality constraints are replaced by:
μ(x) - η * σ(x) <= 0
giving the solution the benefit of the doubt. And each equality constraint is replaced by 2 inequality constraints as such:
μ(x) - η * σ(x) <= 0 <= μ(x) + η * σ(x)
Once the surrogates are formed, they are solved using a sub-optimizer to get the next query point to update the surrogate model. Prior to the optimization loop, initialization is done using a number of points using a Sobol sequence of points.
Quick start
using Nonconvex
Nonconvex.@load BayesOpt
f(x) = sqrt(x[2])
g(x, a, b) = (a*x[1] + b)^3 - x[2]
model = Model()
set_objective!(model, f, flags = [:expensive])
addvar!(model, [1e-4, 1e-4], [10.0, 10.0])
add_ineq_constraint!(model, x -> g(x, 2, 0), flags = [:expensive])
add_ineq_constraint!(model, x -> g(x, -1, 1))
alg = BayesOptAlg(IpoptAlg())
options = BayesOptOptions(
sub_options = IpoptOptions(),
maxiter = 50, ftol = 1e-4, ctol = 1e-5,
)
r = optimize(model, alg, [1.234, 2.345], options = options)
Note that the flags
keyword argument was used when defining the objective and constraints and set to [:expensive]
. This is a hint to Nonconvex to use a surrogate in place of these constraint functions.
Construct an instance
To construct an instance of the surrogate-assisted optimization algorithm, use:
alg = BayesOptAlg(subsolver)
where subsolver
is any Nonconvex optimizer to be used to solve the surrogate model.
Options
The options keyword argument to the optimize
function shown above must be an instance of the BayesOptOptions
struct when the algorihm is a BayesOptAlg
. The following options can be set using keyword arguments when constructing BayesOptOptions
.
sub_options
: options for the sub-optimizermaxiter
: the maximum number of iterations in the Bayesian optimization routineinitialize
:true
by default. Iftrue
, the GP will be initialized using a Sobol sequence of query pointsninit
: number of initialization pointsctol
: feasibility tolerance when accepting a solutionftol
: relative tolerance in the function valuepostoptimize
:true
by default. Iftrue
, a local optimization procedure will be used after the Bayesian optimization is completed.kernel
: the GP kernel used. All the kernels from KernelFunctions.jl are available.noise
: GP observation noise parameterstd_multiple
:η
in the description of the algorithm above.
Advanced: manually constructing surrogate functions
Sometimes a function used in the model may need to be replaced by a surrogate but not the entire objective or constraint function. In this case, the surrogate function can be defined explicitly and passed in to the optimize
function using the keyword argument surrogates
. A surrogate for the function f
can be constructed using:
s1 = Nonconvex.surrogate(f, x0)
where x0
is the initial query point. The output of s1(x)
will be an interval from [IntervalArithmetic.jl])(https://github.com/JuliaIntervals/IntervalArithmetic.jl) with lo
and hi
fields, where lo = μ(x) - η * σ(x)
and hi = μ(x) + η σ(x)
. This interval will propagate through the objective function and/or contraint functions outputting an interval or an array of intervals at the end.
To define the objective or constraint functions using the manually contructed surrogates, one needs to return the lo
field of the output manually at the end of the objective function or inequality constraint function definitions. Equality constraints should also be transformed to a 2-block inequality constraint manually as described above. When manually passing surrogates to the optimize
function, the :expensive
flag is redundant and will be ignored.
Example:
x0 = [1.234, 2.345]
s1 = Nonconvex.surrogate(f, x0)
s2 = Nonconvex.surrogate(x -> [g(x, 2, 0), g(x, -1, 1)], x0)
model = Model()
set_objective!(model, x -> s1(x).lo)
addvar!(model, [1e-4, 1e-4], [10.0, 10.0])
add_ineq_constraint!(model, x -> getproperty.(s2(x), :lo))
alg = BayesOptAlg(IpoptAlg())
options = BayesOptOptions(
sub_options = IpoptOptions(print_level = 0), maxiter = 50, ctol = 1e-4,
ninit = 2, initialize = true, postoptimize = false,
)
r = optimize(model, alg, x0, options = options, surrogates = [s1, s2])