Running Experiments with COCO¶
COCO provides an interface for running experiments. This interface fgeneric
has been
ported in different languages:
- in Python, Matlab/GNU Octave, C/C++, R and Java,
exampleexperiment
and exampletiming
¶
In each language, two example scripts are provided. Below are the example scripts in Matlab/GNU Octave:
exampleexperiment
runs an experiment on one testbed,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | % runs an entire experiment for benchmarking MY_OPTIMIZER
% on the noise-free testbed. fgeneric.m and benchmarks.m
% must be in the path of Matlab/Octave
% CAPITALIZATION indicates code adaptations to be made
addpath('PUT_PATH_TO_BBOB/matlab'); % should point to fgeneric.m etc.
datapath = 'PUT_MY_BBOB_DATA_PATH'; % different folder for each experiment
% opt.inputFormat = 'row';
opt.algName = 'PUT ALGORITHM NAME';
opt.comments = 'PUT MORE DETAILED INFORMATION, PARAMETER SETTINGS ETC';
maxfunevals = '10 * dim'; % 10*dim is a short test-experiment taking a few minutes
% INCREMENT maxfunevals successively to larger value(s)
minfunevals = 'dim + 2'; % PUT MINIMAL SENSIBLE NUMBER OF EVALUATIONS for a restart
maxrestarts = 1e4; % SET to zero for an entirely deterministic algorithm
dimensions = [2, 3, 5, 10, 20, 40]; % small dimensions first, for CPU reasons
functions = benchmarks('FunctionIndices'); % or benchmarksnoisy(...)
instances = [1:5, 41:50]; % 15 function instances
more off; % in octave pagination is on by default
t0 = clock;
rand('state', sum(100 * t0));
for dim = dimensions
for ifun = functions
for iinstance = instances
fgeneric('initialize', ifun, iinstance, datapath, opt);
% independent restarts until maxfunevals or ftarget is reached
for restarts = 0:maxrestarts
if restarts > 0 % write additional restarted info
fgeneric('restart', 'independent restart')
end
MY_OPTIMIZER('fgeneric', dim, fgeneric('ftarget'), ...
eval(maxfunevals) - fgeneric('evaluations'));
if fgeneric('fbest') < fgeneric('ftarget') || ...
fgeneric('evaluations') + eval(minfunevals) > eval(maxfunevals)
break;
end
end
disp(sprintf([' f%d in %d-D, instance %d: FEs=%d with %d restarts,' ...
' fbest-ftarget=%.4e, elapsed time [h]: %.2f'], ...
ifun, dim, iinstance, ...
fgeneric('evaluations'), ...
restarts, ...
fgeneric('fbest') - fgeneric('ftarget'), ...
etime(clock, t0)/60/60));
fgeneric('finalize');
end
disp([' date and time: ' num2str(clock, ' %.0f')]);
end
disp(sprintf('---- dimension %d-D done ----', dim));
end
|
exampletiming
runs the CPU-timing experiment.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | % runs the timing experiment for MY_OPTIMIZER. fgeneric.m
% and benchmarks.m must be in the path of MATLAB/Octave
addpath('PUT_PATH_TO_BBOB/matlab'); % should point to fgeneric.m etc.
more off; % in octave pagination is on by default
timings = [];
runs = [];
dims = [];
for dim = [2,3,5,10,20,40]
nbrun = 0;
ftarget = fgeneric('initialize', 8, 1, 'tmp');
tic;
while toc < 30 % at least 30 seconds
MY_OPTIMIZER(@fgeneric, dim, ftarget, 1e5); % adjust maxfunevals
nbrun = nbrun + 1;
end % while
timings(end+1) = toc / fgeneric('evaluations');
dims(end+1) = dim; % not really needed
runs(end+1) = nbrun; % not really needed
fgeneric('finalize');
disp([['Dimensions:' sprintf(' %11d ', dims)]; ...
[' runs:' sprintf(' %11d ', runs)]; ...
[' times [s]:' sprintf(' %11.1e ', timings)]]);
end
|
Matlab/GNU Octave¶
The above example scripts run a complete experiment. The entire interface
for running experiments is defined via the function fgeneric.m
.
fgeneric
is initialized with a function number, instance number and
output data path. fgeneric
can then be called to evaluate the test function.
The end of a run or trial is signaled by calling fgeneric
with the ‘finalize’
keyword.
C/C++¶
The interface for running experiments relies on functions such as
fgeneric_initialize
, fgeneric_finalize
, fgeneric_ftarget
.
The evaluation function is fgeneric_evaluate
for a
single vector or fgeneric_evaluate_vector
for an array of vectors as input.
A specific folder structure is needed for running an experiment. This folder
structure can be obtained by un-tarring the archive createfolders.tar.gz
and renaming the output folder or alternatively by executing the Python script
createfolders.py
before executing any experiment program. Make sure
createfolders.py
is in your current working directory and from the
command-line simply execute:
$ python createfolders.py FOLDERNAME
FOLDERNAME was created.
The code provided can be compiled in C or C++.
R¶
Quick Installation¶
Run the following commands in your R session:
install.packages(c("BBmisc", "stringr"),
repos="http://cran.at.r-project.org")
fn <- file.path(tempdir(), "bbob_current.tar.gz")
download.file("http://coco.lri.fr/downloads/download15.03/bbobr.tar.gz",
destfile=fn)
install.packages(fn, repos=NULL)
file.remove(fn)
You should now be able to load the package by running
library("bbob")
If all went well, you can skip down to the next section. Otherwise consulte the detailed instructions which follow.
Detailed Installation¶
Before you start, install the required dependencies. At the R prompt enter
install.packages(c("BBmisc", "stringr"))
This should download and install the two packages. Now download the current BBOB R source package and save it somewhere on your hard drive. The most up-to-date version is always available here, but there should also be a stable version available on the COCO website. Now it’s time to install the package. In R, run the following command:
install.packages("/path/to/bbob_current.tar.gz", repos=NULL)
Note that you will have to adjust the path and possibly the filename to match the location where you stored the downloaded package on your hard drive. On Windows you will also need to have the Rtools installed for this to work since the package contains C code. If you have any problems with this step, do not hesitate to contact me for assistance.
After completing the above steps, you should be able to load the package in R
library("bbob")
The help page for the bbo_benchmark function should get you started if you do not want to continue reading this introduction.
?bbo_benchmark
Simple experiment¶
If you already have an optimizer ready to go in R and all you want to do is produce a BBOB dataset for post-processing, then this section will walk you through the required steps. We will use the high-level interface bbo_benchmark in this example. If you want to parallelize your optimization runs, need to perform complex initializations or just want full control, skip down to the next section for a brief tour of the low-level interface.
In this example we will use the L-BFGS-B optimizer included in base R. You will need to adapt parts of the code to suit your optimizer. The first step is to wrap your optimization algorithm in a function with the following signature
function(par, fun, lower, upper, max_eval)
where par will be a numeric vector with the starting point for the optimization, fun is the function to be minimized, lower and upper are the bounds of the box constraints and max_eval is the number of function evaluations left in the allocated budget. What these five parameters mean is best conveyed by an example. Here we wrap the optim function included in base R
my_optimizer <- function(par, fun, lower, upper, max_eval) {
optim(par, fun, method="L-BFGS-B",
lower=lower, upper=upper, control=list(maxit=max_eval))
}
If your algorithm does not have the notion of an initial parameter setting, you can safely ignore the par parameter in your implementation. You might also notice, that we do not strictly adhere to the max_eval limit because the number of iterations is generally not equal to the number of function evaluations for L-BFGS-B. This is OK. max_eval is only a hint to the optimizer how much effort it should put into optimizing the function.
Should your algorithm perform restarts internally it is possible to log these using the bbob_log_restart function. The function takes exactly one argument, a string describing the reason for the restart.
We are now ready to run our experiment. This is done by calling the bbo_benchmark function
bbo_benchmark(my_optimizer, "l-bfgs-b", "optim_l-bfgs-b")
which will perform the BBOB experiments (caution, may take many hours). The first argument passed to bbo_benchmark is our optimization wrapper from the previous step. Make sure that it has the correct function signature! Next we supply the so called algorithm id. This is a short descriptive name for our optimization algorithm. Ideally it should include the package name and version which contains the algorithm. So for for genoud from the rgenoud package, we might use rgenoud::genoud (5.7-3) as the algorithm id. The last required argument is the name of the base directory where the result files will be stored. Again, it is a good idea to include the algorithm name in the directory name. If no other arguments are given, this runs a complete BBO benchmark on the noiseless test functions. This includes all instances (1-5 and 21-30), all dimensions (2, 3, 5, 10, 20, 40). If you do not want to include runs in 40 dimensions or want to use different instances you can change the defaults using the dimensions and instances arguments to bbo_benchmark. For details, see the manual page for bbo_benchmark.
If no other budget is specified, bbo_benchmark will perform random independent restarts of your algorithm until the desired target precision (1e-8) is reached or the default budget of 10000000 function evaluations is exhausted. If you want to reduce the budget, you can by specifiying it as the budget argument to bbo_benchmark.
To run the required timing experiment, execute the following code:
bbo_timing(my_optimizer)
It will return a data frame with the relevant timing information.
Low-level interface¶
Will follow soon.
Python¶
The interface for running an experiment is fgeneric
which is used
within exampleexperiment.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | #!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Runs an entire experiment for benchmarking PURE_RANDOM_SEARCH on a testbed.
CAPITALIZATION indicates code adaptations to be made.
This script as well as files bbobbenchmarks.py and fgeneric.py need to be
in the current working directory.
Under unix-like systems:
nohup nice python exampleexperiment.py [data_path [dimensions [functions [instances]]]] > output.txt &
"""
import sys # in case we want to control what to run via command line args
import time
import numpy as np
import fgeneric
import bbobbenchmarks
argv = sys.argv[1:] # shortcut for input arguments
datapath = 'PUT_MY_BBOB_DATA_PATH' if len(argv) < 1 else argv[0]
dimensions = (2, 3, 5, 10, 20, 40) if len(argv) < 2 else eval(argv[1])
function_ids = bbobbenchmarks.nfreeIDs if len(argv) < 3 else eval(argv[2])
# function_ids = bbobbenchmarks.noisyIDs if len(argv) < 3 else eval(argv[2])
instances = range(1, 6) + range(41, 51) if len(argv) < 4 else eval(argv[3])
opts = dict(algid='PUT ALGORITHM NAME',
comments='PUT MORE DETAILED INFORMATION, PARAMETER SETTINGS ETC')
maxfunevals = '10 * dim' # 10*dim is a short test-experiment taking a few minutes
# INCREMENT maxfunevals SUCCESSIVELY to larger value(s)
minfunevals = 'dim + 2' # PUT MINIMAL sensible number of EVALUATIONS before to restart
maxrestarts = 10000 # SET to zero if algorithm is entirely deterministic
def run_optimizer(fun, dim, maxfunevals, ftarget=-np.Inf):
"""start the optimizer, allowing for some preparation.
This implementation is an empty template to be filled
"""
# prepare
x_start = 8. * np.random.rand(dim) - 4
# call, REPLACE with optimizer to be tested
PURE_RANDOM_SEARCH(fun, x_start, maxfunevals, ftarget)
def PURE_RANDOM_SEARCH(fun, x, maxfunevals, ftarget):
"""samples new points uniformly randomly in [-5,5]^dim and evaluates
them on fun until maxfunevals or ftarget is reached, or until
1e8 * dim function evaluations are conducted.
"""
dim = len(x)
maxfunevals = min(1e8 * dim, maxfunevals)
popsize = min(maxfunevals, 200)
fbest = np.inf
for _ in range(0, int(np.ceil(maxfunevals / popsize))):
xpop = 10. * np.random.rand(popsize, dim) - 5.
fvalues = fun(xpop)
idx = np.argsort(fvalues)
if fbest > fvalues[idx[0]]:
fbest = fvalues[idx[0]]
xbest = xpop[idx[0]]
if fbest < ftarget: # task achieved
break
return xbest
t0 = time.time()
np.random.seed(int(t0))
f = fgeneric.LoggingFunction(datapath, **opts)
for dim in dimensions: # small dimensions first, for CPU reasons
for fun_id in function_ids:
for iinstance in instances:
f.setfun(*bbobbenchmarks.instantiate(fun_id, iinstance=iinstance))
# independent restarts until maxfunevals or ftarget is reached
for restarts in xrange(maxrestarts + 1):
if restarts > 0:
f.restart('independent restart') # additional info
run_optimizer(f.evalfun, dim, eval(maxfunevals) - f.evaluations,
f.ftarget)
if (f.fbest < f.ftarget
or f.evaluations + eval(minfunevals) > eval(maxfunevals)):
break
f.finalizerun()
print(' f%d in %d-D, instance %d: FEs=%d with %d restarts, '
'fbest-ftarget=%.4e, elapsed time [h]: %.2f'
% (fun_id, dim, iinstance, f.evaluations, restarts,
f.fbest - f.ftarget, (time.time()-t0)/60./60.))
print ' date and time: %s' % (time.asctime())
print '---- dimension %d-D done ----' % dim
|
Testing New Functions¶
We describe here how to use fgeneric
to record experiments on functions
that are not part of the BBOB testbeds.
Note
This feature is only available in Python for the moment.
Example: log experiment using the Nelder-Mead simplex algorithm
(scipy.optimize.fmin
) on the sphere function.
The following commands from the Python Interpreter does 15 runs of the
Nelder-Mead simplex algorithm on the 2-D sphere functions. The data is recorded
in folder data
in the current working directory.
>>> from pylab import *
>>> import fgeneric as fg
>>> import scipy.optimize as so
>>> f = lambda x: sum(i**2 for i in x) # function definition
>>> e = fg.LoggingFunction(datapath='data', algid='Nelder-Mead simplex',
comments='x0 uniformly sampled in [0, 1]^2, '
'default settings')
>>> for i in range(15): # 15 repetitions
... e.setfun(fun=f, fopt=0., funId='sphere', iinstance='0')
... so.fmin(e.evalfun, x0=rand(2)) # algorithm call
... e.finalizerun()
(<bound method LoggingFunction.evalfun of <fgeneric.LoggingFunction object at [...]>>, 1e-08)
Optimization terminated successfully.
Current function value: 0.000000
Iterations: [...]
Function evaluations: [...]
array([...])
[...]
>>> # Display convergence graphs
>>> import bbob_pproc as bb
>>> ds = bb.load('data')
>>> ds.plot()
(Source code, png, hires.png, pdf)

Testing Functions with Parameter¶
Note
This feature is only available in Python for the moment.
Example: log experiment using the BFGS algorithm
(scipy.optimize.fmin
) on the ellipsoid function with different
condition numbers.
The following commands from the Python Interpreter does 15 runs of the
BFGS algorithm on the 2-D sphere functions. The data is recorded
in folder data
in the current working directory and generate
>>> from pylab import *
>>> import fgeneric as fg
>>> import scipy.optimize as so
>>> import numpy as np
>>> e = fg.LoggingFunction(datapath='ellipsoid', algid='BFGS',
comments='x0 uniformly sampled in [0, 1]^5, default settings')
>>> cond_num = 10**np.arange(0, 7)
>>> for c in cond_num:
... f = lambda x: np.sum(c**np.linspace(0, 1, len(x)) * x**2)
... # function definition: these are term-by-term operations
... for i in range(5): # 5 repetitions
... e.setfun(fun=f, fopt=0., funId='ellipsoid', iinstance=0,
... condnum=c)
... so.fmin_bfgs(e.evalfun, x0=np.random.rand(5)) # algorithm call
... e.finalizerun()
(<bound method LoggingFunction.evalfun of <fgeneric.LoggingFunction object at [...]>>, 1e-08)
Optimization terminated successfully.
Current function value: 0.000000
Iterations: [...]
Function evaluations: [...]
Gradient evaluations: [...]
array([...])
[...]
>>> # plot data
>>> import bbob_pproc as bb
>>> import bbob_pproc.ppfigparam
>>> ds = bb.load('ellipsoid')
>>> bb.ppfigparam.plot(ds, param='condnum')
>>> bb.ppfigparam.beautify()
>>> import matplotlib.pyplot as plt
>>> plt.xlabel('Condition Number')
(Source code, png, hires.png, pdf)
