Scikit-optimize, or skopt
, is a pretty neat module that lets you "minimize (very) expensive and noisy black-box functions". Here are some methods you can call to do that:
dummy_minimize()
forest_minimize()
gbrt_minimize()
gp_minimize()
When they are done, these methods return a special object that contains information about the performed optimization — parameters tried, values gotten, the predicted surrogate model, etc. You can then save()
and load()
this object to/from a file.
There is a problem, though — there isn't a method you can call to resume the optimization; you can only start new optimizations from scratch.
Fortunately, the returned object contains enough information to reload the optimization:
The optimization functions have input parameters that let you specify already known points — x0
and y0
. These can be used to pass the results of the previous optimization and thus continue from its end state.
Since there is no longer a need to query points randomly, n_random_starts
is set to 0.
The previous result's parameters are deepcopy()
-ed to avoid changing it throughout the reloaded optimization.
There is a problem with this simple approach — its behavior is not idential to uninterrupted optimizations. In other words, if you optimize a funciton for 25 iterations and then use this method to reload that optimization for another 25, it won't query for the same points as the initial optimization if it had been set to run for 50 iterations. Here are the sources of error:
- The
x0
-y0
points are loaded at the beginning of the optimization. This is treated as a typical iteration, and exhausts a step of the internal random state. Here's what we can do about it: - Advance the random state a step back. As far as I'm aware, this is impossible.
- Don't treat the loading operation as an iteration — break into the
base_minimize
method and explicitly pass the points to theOptimizer
object without usingoptimizer.tell()
. The problem with this approach is that the first iteration won't query for the right point. This point is determined at the end of the call tooptimizer.tell()
(this is what advances the random state in the first place), and since we avoided it, the first iteration'soptimizer.ask()
call will use a random point instead. - Use the optimizations'
callback
option to save a copy of the random state that's lagging a step behind. Loading the points will exhaust this extra step and bring the random state to its proper place. This works, but requires you to modify code that runs optimizations. Thus, you won't be able to reload old optimizations that were created before the modification. - The internally-used
Optimizer
object contains some internal values that are lost. Even if you fix the first issue, your reloads will still occasionally break and query for different points. - For example,
gp_minimize
uses three different functions to predict the next point, and picks one. The probabilities used to to pick each are derived fromself.gains_
, an internally-stored property. This roperty is set to zeros upon creation and modified every iteration with the-=
operator. Since reloaded optimizations skip the iterations of already queried points, this property will be different. Even if there was a convenient way to modify this property, we would have no idea what to set it to unless we keep track of it while running the initial optimization.
The simplest way to achieve consistency is to query points in exactly the same manner as we would in an uninterrupted optimization. This is identical to just running the initial optimization with the same parameters, but for a larger number of iterations. To cut out the time required to run the initial portion of the optimization, we can avoid evaluating the function and instead pass the already known values.
We can do this by making a wrapper to the evaluated function, func_new()
. For the first part of the reloaded optimization, this wrapper will return the previously gotten values. Then, it will switch to evaluating the function as usual.
- This approach will only save the time taken by function evaluations. It will not help if your function performs relatively fast to
Optimizer
'sask()
andtell()
. - The initial random state is saved as an object and returned with the optimization result. The problem is that only
copy()
is used (as opposed todeepcopy()
), and the random state's internal values get modified throughout the optimization. Thus, the object behaves differently and becomes unusable. It's possible to recreate this object, but you'll need to knowinit_seed
, the parameter passed asrandom_state
to the initial optimization.