1
votes

I've implemented scipy.optimize.minimize to minimize the mean of delta values of a pandas data frame for a 1D Array with 128 values.

It seems to run and do stuff, but it doesnt stop at max iter or at a callback function taken from another stackoverflow question here.

My code is:

import numpy as np
from scipy.optimize import minimize, rosen
import time
import warnings

class TookTooLong(Warning):
    pass

class MinimizeStopper(object):
    def __init__(self, max_sec=60*60*5):
        self.max_sec = max_sec
        self.start = time.time()
    def __call__(self, xk=None):
        elapsed = time.time() - self.start
        if elapsed > self.max_sec:
            warnings.warn("Terminating optimization: time limit reached",
                          TookTooLong)
        else:
            # you might want to report other stuff here
            print("Elapsed: %.3f sec" % elapsed)

import scipy.optimize
res = scipy.optimize.minimize(minFunct,oned,options= 
            {"disp":True,"maxiter":100},tol=0.01,
                     method ="BFGS",callback=MinimizeStopper(1E-3))

The displayed message after some time tells me that maxiter has been reached and smaller function value than at the start has been reached, but it just doesn't stop. Since its running in jupyter, I have no way to reach res without the cell finishing.

1

1 Answers

1
votes

According to the docs callback should be a callable returning True for termination and having the following format callback(xk). Whereas in your code, you are defining it as an initialization of your class. Instead, you should define an instance of your class then assign its __call__() function to callback like the following:

import time
import warnings
import numpy as np
from scipy.optimize import minimize, rosen

class TookTooLong(Warning):
    pass

class MinimizeStopper(object):
    def __init__(self, max_sec=10):
        self.max_sec = max_sec
        self.start   = time.time()

    def __call__(self, xk):
        # callback to terminate if max_sec exceeded
        elapsed = time.time() - self.start
        if elapsed > self.max_sec:
            warnings.warn("Terminating optimization: time limit reached",
                          TookTooLong)
        else:
            # you might want to report other stuff here
            print("Elapsed: %.3f sec" % elapsed)
# init stopper
minimize_stopper = MinimizeStopper()
# minimze
res = minimize(rosen,
               x0       = np.random.randint(5, size=128),
               method   ="BFGS",
               tol      = 0.01,
               options  = {"maxiter":10, "disp":True},
               callback = minimize_stopper.__call__)

Alternatively you can define a class for your minimizer and in it build a callback function to terminate your minimization after a certain time. This can be done like this:

import time
import warnings
import numpy as np
from scipy.optimize import minimize, rosen


class TookTooLong(Warning):
    pass

class Minimizer:
    def __init__(self, timeout, maxiter):
        self.timeout = timeout
        self.maxiter = maxiter

    def minimize(self):
        self.start_time = time.time()
        # minimize
        res = minimize(rosen,
                       x0       = np.random.randint(5, size=128),
                       method   ="BFGS",
                       tol      = 0.01,
                       options  = {"maxiter":self.maxiter, "disp":True},
                       callback = self.callback)
        return res

    def callback(self, x):
        # callback to terminate if max_sec exceeded
        elapsed = time.time() - self.start_time
        if elapsed > self.timeout:
            warnings.warn("Terminating optimization: time limit reached",
                          TookTooLong)
            return True
        else: 
            print("Elapsed: %.3f sec" % elapsed)

# init minimizer and minimize
minimizer = Minimizer(0.1, 100)
result    = minimizer.minimize()

Test these code with: timeout=0.1 & maxiter=100 then timeout=10 & maxiter=10 to observe both types of termination.