import numpy as np
from math import inf
from spotpython.fun.objectivefunctions import Analytical
from spotpython.spot import spot
from scipy.optimize import shgo
from scipy.optimize import direct
from scipy.optimize import differential_evolution
import matplotlib.pyplot as plt
Appendix D — Documentation of the Sequential Parameter Optimization
This document describes the Spot
features. The official spotpython
documentation can be found here: https://sequential-parameter-optimization.github.io/spotpython/.
D.1 An Initial Example
The spotpython
package provides several classes of objective functions. We will use an analytical objective function, i.e., a function that can be described by a (closed) formula: \[
f(x) = x^2.
\]
= Analytical().fun_sphere fun
= np.linspace(-1,1,100).reshape(-1,1)
x = fun(x)
y
plt.figure()"k")
plt.plot(x,y, plt.show()
from spotpython.utils.init import fun_control_init, design_control_init, surrogate_control_init, optimizer_control_init
= spot.Spot(fun=fun,
spot_1 =fun_control_init(
fun_control= np.array([-10]),
lower = np.array([100]),
upper = 7,
fun_evals = 1,
fun_repeats = inf,
max_time = False,
noise = np.sqrt(np.spacing(1)),
tolerance_x =["num"],
var_type= "y",
infill_criterion = 1,
n_points =123,
seed= 50),
log_level =design_control_init(
design_control=5,
init_size=1),
repeats=surrogate_control_init(
surrogate_control=False,
noise=-4,
min_theta=3,
max_theta=1,
n_theta=differential_evolution,
model_optimizer=10000))
model_fun_evals spot_1.run()
spotpython tuning: 2.0168953451588423 [#########-] 85.71%
spotpython tuning: 0.010160677392696235 [##########] 100.00% Done...
<spotpython.spot.spot.Spot at 0x1044bbbc0>
D.2 Organization
Spot
organizes the surrogate based optimization process in four steps:
- Selection of the objective function:
fun
. - Selection of the initial design:
design
. - Selection of the optimization algorithm:
optimizer
. - Selection of the surrogate model:
surrogate
.
For each of these steps, the user can specify an object:
from spotpython.fun.objectivefunctions import Analytical
= Analytical().fun_sphere
fun from spotpython.design.spacefilling import SpaceFilling
= SpaceFilling(2)
design from scipy.optimize import differential_evolution
= differential_evolution
optimizer from spotpython.build.kriging import Kriging
= Kriging() surrogate
For each of these steps, the user can specify a dictionary of control parameters.
fun_control
design_control
optimizer_control
surrogate_control
Each of these dictionaries has an initialzaion method, e.g., fun_control_init()
. The initialization methods set the default values for the control parameters.
- The specification of an lower bound in
fun_control
is mandatory.
from spotpython.utils.init import fun_control_init, design_control_init, optimizer_control_init, surrogate_control_init
=fun_control_init(lower=np.array([-1, -1]),
fun_control=np.array([1, 1]))
upper=design_control_init()
design_control=optimizer_control_init()
optimizer_control=surrogate_control_init() surrogate_control
D.3 The Spot Object
Based on the definition of the fun
, design
, optimizer
, and surrogate
objects, and their corresponding control parameter dictionaries, fun_control
, design_control
, optimizer_control
, and surrogate_control
, the spot
object can be build as follows:
from spotpython.spot import spot
= spot.Spot(fun=fun,
spot_tuner =fun_control,
fun_control=design_control,
design_control=optimizer_control,
optimizer_control=surrogate_control) surrogate_control
D.4 Run
spot_tuner.run()
spotpython tuning: 1.5904060546935205e-05 [#######---] 73.33%
spotpython tuning: 1.5904060546935205e-05 [########--] 80.00%
spotpython tuning: 1.5904060546935205e-05 [#########-] 86.67%
spotpython tuning: 1.5904060546935205e-05 [#########-] 93.33%
spotpython tuning: 1.2084513018724136e-05 [##########] 100.00% Done...
<spotpython.spot.spot.Spot at 0x1071afc80>
D.5 Print the Results
spot_tuner.print_results()
min y: 1.2084513018724136e-05
x0: -0.003294464459178357
x1: -0.0011095120305498227
[['x0', np.float64(-0.003294464459178357)],
['x1', np.float64(-0.0011095120305498227)]]
D.6 Show the Progress
spot_tuner.plot_progress()
D.7 Visualize the Surrogate
- The plot method of the
kriging
surrogate is used. - Note: the plot uses the interval defined by the ranges of the natural variables.
spot_tuner.surrogate.plot()
D.8 Run With a Specific Start Design
To pass a specific start design, use the X_start
argument of the run
method.
= spot.Spot(fun=fun,
spot_x0 =fun_control_init(
fun_control= np.array([-10]),
lower = np.array([100]),
upper = 7,
fun_evals = 1,
fun_repeats = inf,
max_time = False,
noise = np.sqrt(np.spacing(1)),
tolerance_x =["num"],
var_type= "y",
infill_criterion = 1,
n_points =123,
seed= 50),
log_level =design_control_init(
design_control=5,
init_size=1),
repeats=surrogate_control_init(
surrogate_control=False,
noise=-4,
min_theta=3,
max_theta=1,
n_theta=differential_evolution,
model_optimizer=10000))
model_fun_evals=np.array([0.5, -0.5]))
spot_x0.run(X_start spot_x0.plot_progress()
spotpython tuning: 2.0168953451588423 [#########-] 85.71%
spotpython tuning: 0.010160677392696235 [##########] 100.00% Done...
D.9 Init: Build Initial Design
from spotpython.design.spacefilling import SpaceFilling
from spotpython.build.kriging import Kriging
from spotpython.fun.objectivefunctions import Analytical
from spotpython.utils.init import fun_control_init
= SpaceFilling(2)
gen = np.random.RandomState(1)
rng = np.array([-5,-0])
lower = np.array([10,15])
upper = Analytical().fun_branin
fun
= fun_control_init(sigma=0)
fun_control
= gen.scipy_lhd(10, lower=lower, upper = upper)
X print(X)
= fun(X, fun_control=fun_control)
y print(y)
[[ 8.97647221 13.41926847]
[ 0.66946019 1.22344228]
[ 5.23614115 13.78185824]
[ 5.6149825 11.5851384 ]
[-1.72963184 1.66516096]
[-4.26945568 7.1325531 ]
[ 1.26363761 10.17935555]
[ 2.88779942 8.05508969]
[-3.39111089 4.15213772]
[ 7.30131231 5.22275244]]
[128.95676449 31.73474356 172.89678121 126.71295908 64.34349975
70.16178611 48.71407916 31.77322887 76.91788181 30.69410529]
D.10 Replicability
Seed
= SpaceFilling(2, seed=123)
gen = gen.scipy_lhd(3)
X0 = SpaceFilling(2, seed=345)
gen = gen.scipy_lhd(3)
X1 = gen.scipy_lhd(3)
X2 = SpaceFilling(2, seed=123)
gen = gen.scipy_lhd(3)
X3 X0, X1, X2, X3
(array([[0.77254938, 0.31539299],
[0.59321338, 0.93854273],
[0.27469803, 0.3959685 ]]),
array([[0.78373509, 0.86811887],
[0.06692621, 0.6058029 ],
[0.41374778, 0.00525456]]),
array([[0.121357 , 0.69043832],
[0.41906219, 0.32838498],
[0.86742658, 0.52910374]]),
array([[0.77254938, 0.31539299],
[0.59321338, 0.93854273],
[0.27469803, 0.3959685 ]]))
D.11 Surrogates
D.11.1 A Simple Predictor
The code below shows how to use a simple model for prediction. Assume that only two (very costly) measurements are available:
- f(0) = 0.5
- f(2) = 2.5
We are interested in the value at \(x_0 = 1\), i.e., \(f(x_0 = 1)\), but cannot run an additional, third experiment.
from sklearn import linear_model
= np.array([[0], [2]])
X = np.array([0.5, 2.5])
y = linear_model.LinearRegression()
S_lm = S_lm.fit(X, y)
S_lm = np.array([[1]])
X0 = S_lm.predict(X0)
y0 print(y0)
[1.5]
Central Idea: Evaluation of the surrogate model S_lm
is much cheaper (or / and much faster) than running the real-world experiment \(f\).
D.12 Tensorboard Setup
D.12.1 Tensorboard Configuration
The TENSORBOARD_CLEAN
argument can be set to True
in the fun_control
dictionary to archive the TensorBoard folder if it already exists. This is useful if you want to start a hyperparameter tuning process from scratch. If you want to continue a hyperparameter tuning process, set TENSORBOARD_CLEAN
to False
. Then the TensorBoard folder will not be archived and the old and new TensorBoard files will shown in the TensorBoard dashboard.
D.12.2 Starting TensorBoard
TensorBoard
can be started as a background process with the following command, where ./runs
is the default directory for the TensorBoard log files:
tensorboard --logdir="./runs"
The TensorBoard path can be printed with the following command (after a fun_control
object has been created):
from spotpython.utils.init import get_tensorboard_path
get_tensorboard_path(fun_control)
D.13 Demo/Test: Objective Function Fails
SPOT expects np.nan
values from failed objective function values. These are handled. Note: SPOT’s counter considers only successful executions of the objective function.
import numpy as np
from spotpython.fun.objectivefunctions import Analytical
from spotpython.spot import spot
import numpy as np
from math import inf
# number of initial points:
= 20
ni # number of points
= 30
n
= Analytical().fun_random_error
fun =fun_control_init(
fun_control= np.array([-1]),
lower = np.array([1]),
upper= n,
fun_evals =False)
show_progress=design_control_init(init_size=ni)
design_control
= spot.Spot(fun=fun,
spot_1 =fun_control,
fun_control=design_control)
design_control
# assert value error from the run method
try:
spot_1.run()except ValueError as e:
print(e)
D.14 Handling Results: Printing, Saving, and Loading
The results can be printed with the following command:
=False) spot_tuner.print_results(print_screen
The tuned hyperparameters can be obtained as a dictionary with the following command:
from spotpython.hyperparameters.values import get_tuned_hyperparameters
get_tuned_hyperparameters(spot_tuner, fun_control)
The results can be saved and reloaded with the following commands:
from spotpython.utils.file import save_pickle, load_pickle
from spotpython.utils.init import get_experiment_name
= get_experiment_name("024")
experiment_name = False
SAVE_AND_LOAD if SAVE_AND_LOAD == True:
save_pickle(spot_tuner, experiment_name)= load_pickle(experiment_name) spot_tuner
D.15 spotpython
as a Hyperparameter Tuner
D.15.1 Modifying Hyperparameter Levels
spotpython
distinguishes between different types of hyperparameters. The following types are supported:
int
(integer)float
(floating point number)boolean
(boolean)factor
(categorical)
D.15.1.1 Integer Hyperparameters
Integer hyperparameters can be modified with the set_int_hyperparameter_values()
[SOURCE] function. The following code snippet shows how to modify the n_estimators
hyperparameter of a random forest model:
from spotriver.hyperdict.river_hyper_dict import RiverHyperDict
from spotpython.utils.init import fun_control_init
from spotpython.hyperparameters.values import set_int_hyperparameter_values
from spotpython.utils.eda import gen_design_table
= fun_control_init(
fun_control ="forest.AMFRegressor",
core_model_name=RiverHyperDict,
hyperdict
)print("Before modification:")
print(gen_design_table(fun_control))
"n_estimators", 2, 5)
set_int_hyperparameter_values(fun_control, print("After modification:")
print(gen_design_table(fun_control))
Before modification:
| name | type | default | lower | upper | transform |
|-----------------|--------|-----------|---------|---------|-----------------------|
| n_estimators | int | 3 | 2 | 5 | transform_power_2_int |
| step | float | 1 | 0.1 | 10 | None |
| use_aggregation | factor | 1 | 0 | 1 | None |
Setting hyperparameter n_estimators to value [2, 5].
Variable type is int.
Core type is None.
Calling modify_hyper_parameter_bounds().
After modification:
| name | type | default | lower | upper | transform |
|-----------------|--------|-----------|---------|---------|-----------------------|
| n_estimators | int | 3 | 2 | 5 | transform_power_2_int |
| step | float | 1 | 0.1 | 10 | None |
| use_aggregation | factor | 1 | 0 | 1 | None |
D.15.1.2 Float Hyperparameters
Float hyperparameters can be modified with the set_float_hyperparameter_values()
[SOURCE] function. The following code snippet shows how to modify the step
hyperparameter of a hyperparameter of a Mondrian Regression Tree model:
from spotriver.hyperdict.river_hyper_dict import RiverHyperDict
from spotpython.utils.init import fun_control_init
from spotpython.hyperparameters.values import set_float_hyperparameter_values
from spotpython.utils.eda import gen_design_table
= fun_control_init(
fun_control ="forest.AMFRegressor",
core_model_name=RiverHyperDict,
hyperdict
)print("Before modification:")
print(gen_design_table(fun_control))
"step", 0.2, 5)
set_float_hyperparameter_values(fun_control, print("After modification:")
print(gen_design_table(fun_control))
Before modification:
| name | type | default | lower | upper | transform |
|-----------------|--------|-----------|---------|---------|-----------------------|
| n_estimators | int | 3 | 2 | 5 | transform_power_2_int |
| step | float | 1 | 0.1 | 10 | None |
| use_aggregation | factor | 1 | 0 | 1 | None |
Setting hyperparameter step to value [0.2, 5].
Variable type is float.
Core type is None.
Calling modify_hyper_parameter_bounds().
After modification:
| name | type | default | lower | upper | transform |
|-----------------|--------|-----------|---------|---------|-----------------------|
| n_estimators | int | 3 | 2 | 5 | transform_power_2_int |
| step | float | 1 | 0.2 | 5 | None |
| use_aggregation | factor | 1 | 0 | 1 | None |
D.15.1.3 Boolean Hyperparameters
Boolean hyperparameters can be modified with the set_boolean_hyperparameter_values()
[SOURCE] function. The following code snippet shows how to modify the use_aggregation
hyperparameter of a Mondrian Regression Tree model:
from spotriver.hyperdict.river_hyper_dict import RiverHyperDict
from spotpython.utils.init import fun_control_init
from spotpython.hyperparameters.values import set_boolean_hyperparameter_values
from spotpython.utils.eda import gen_design_table
= fun_control_init(
fun_control ="forest.AMFRegressor",
core_model_name=RiverHyperDict,
hyperdict
)print("Before modification:")
print(gen_design_table(fun_control))
"use_aggregation", 0, 0)
set_boolean_hyperparameter_values(fun_control, print("After modification:")
print(gen_design_table(fun_control))
Before modification:
| name | type | default | lower | upper | transform |
|-----------------|--------|-----------|---------|---------|-----------------------|
| n_estimators | int | 3 | 2 | 5 | transform_power_2_int |
| step | float | 1 | 0.1 | 10 | None |
| use_aggregation | factor | 1 | 0 | 1 | None |
Setting hyperparameter use_aggregation to value [0, 0].
Variable type is factor.
Core type is bool.
Calling modify_boolean_hyper_parameter_levels().
After modification:
| name | type | default | lower | upper | transform |
|-----------------|--------|-----------|---------|---------|-----------------------|
| n_estimators | int | 3 | 2 | 5 | transform_power_2_int |
| step | float | 1 | 0.1 | 10 | None |
| use_aggregation | factor | 1 | 0 | 0 | None |
D.15.1.4 Factor Hyperparameters
Factor hyperparameters can be modified with the set_factor_hyperparameter_values()
[SOURCE] function. The following code snippet shows how to modify the leaf_model
hyperparameter of a Hoeffding Tree Regressor model:
from spotriver.hyperdict.river_hyper_dict import RiverHyperDict
from spotpython.utils.init import fun_control_init
from spotpython.hyperparameters.values import set_factor_hyperparameter_values
from spotpython.utils.eda import gen_design_table
= fun_control_init(
fun_control ="tree.HoeffdingTreeRegressor",
core_model_name=RiverHyperDict,
hyperdict
)print("Before modification:")
print(gen_design_table(fun_control))
"leaf_model", ['LinearRegression',
set_factor_hyperparameter_values(fun_control, 'Perceptron'])
print("After modification:")
Before modification:
| name | type | default | lower | upper | transform |
|------------------------|--------|------------------|---------|----------|------------------------|
| grace_period | int | 200 | 10 | 1000 | None |
| max_depth | int | 20 | 2 | 20 | transform_power_2_int |
| delta | float | 1e-07 | 1e-08 | 1e-06 | None |
| tau | float | 0.05 | 0.01 | 0.1 | None |
| leaf_prediction | factor | mean | 0 | 2 | None |
| leaf_model | factor | LinearRegression | 0 | 2 | None |
| model_selector_decay | float | 0.95 | 0.9 | 0.99 | None |
| splitter | factor | EBSTSplitter | 0 | 2 | None |
| min_samples_split | int | 5 | 2 | 10 | None |
| binary_split | factor | 0 | 0 | 1 | None |
| max_size | float | 500.0 | 100 | 1000 | None |
| memory_estimate_period | int | 6 | 3 | 8 | transform_power_10_int |
| stop_mem_management | factor | 0 | 0 | 1 | None |
| remove_poor_attrs | factor | 0 | 0 | 1 | None |
| merit_preprune | factor | 1 | 0 | 1 | None |
After modification: