How to display progress of scipy.optimize function?

后端 未结 7 675
醉酒成梦
醉酒成梦 2020-12-01 05:02

I use scipy.optimize to minimize a function of 12 arguments.

I started the optimization a while ago and still waiting for results.

Is there a wa

7条回答
  •  甜味超标
    2020-12-01 05:26

    Many of the optimizers in scipy indeed lack verbose output (the 'trust-constr' method of scipy.optimize.minimize being an exception). I faced a similar issue and solved it by creating a wrapper around the objective function and using the callback function. No additional function evaluations are performed here, so this should be an efficient solution.

    import numpy as np
    
    class Simulator:
    def __init__(self, function):
        self.f = function # actual objective function
        self.num_calls = 0 # how many times f has been called
        self.callback_count = 0 # number of times callback has been called, also measures iteration count
        self.list_calls_inp = [] # input of all calls
        self.list_calls_res = [] # result of all calls
        self.decreasing_list_calls_inp = [] # input of calls that resulted in decrease
        self.decreasing_list_calls_res = [] # result of calls that resulted in decrease
        self.list_callback_inp = [] # only appends inputs on callback, as such they correspond to the iterations
        self.list_callback_res = [] # only appends results on callback, as such they correspond to the iterations
    
    def simulate(self, x):
        """Executes the actual simulation and returns the result, while
        updating the lists too. Pass to optimizer without arguments or
        parentheses."""
        result = self.f(x) # the actual evaluation of the function
        if not self.num_calls: # first call is stored in all lists
            self.decreasing_list_calls_inp.append(x)
            self.decreasing_list_calls_res.append(result)
            self.list_callback_inp.append(x)
            self.list_callback_res.append(result)
        elif result < self.decreasing_list_calls_res[-1]:
            self.decreasing_list_calls_inp.append(x)
            self.decreasing_list_calls_res.append(result)
        self.list_calls_inp.append(x)
        self.list_calls_res.append(result)
        self.num_calls += 1
        return result
    
    def callback(self, xk, *_):
        """Callback function that can be used by optimizers of scipy.optimize.
        The third argument "*_" makes sure that it still works when the
        optimizer calls the callback function with more than one argument. Pass
        to optimizer without arguments or parentheses."""
        s1 = ""
        xk = np.atleast_1d(xk)
        # search backwards in input list for input corresponding to xk
        for i, x in reversed(list(enumerate(self.list_calls_inp))):
            x = np.atleast_1d(x)
            if np.allclose(x, xk):
                break
    
        for comp in xk:
            s1 += f"{comp:10.5e}\t"
        s1 += f"{self.list_calls_res[i]:10.5e}"
    
        self.list_callback_inp.append(xk)
        self.list_callback_res.append(self.list_calls_res[i])
    
        if not self.callback_count:
            s0 = ""
            for j, _ in enumerate(xk):
                tmp = f"Comp-{j+1}"
                s0 += f"{tmp:10s}\t"
            s0 += "Objective"
            print(s0)
        print(s1)
        self.callback_count += 1
    

    A simple test can be defined

    from scipy.optimize import minimize, rosen
    ros_sim = Simulator(rosen)
    minimize(ros_sim.simulate, [0, 0], method='BFGS', callback=ros_sim.callback, options={"disp": True})
    
    print(f"Number of calls to Simulator instance {ros_sim.num_calls}")
    

    resulting in:

    Comp-1          Comp-2          Objective
    1.76348e-01     -1.31390e-07    7.75116e-01
    2.85778e-01     4.49433e-02     6.44992e-01
    3.14130e-01     9.14198e-02     4.75685e-01
    4.26061e-01     1.66413e-01     3.52251e-01
    5.47657e-01     2.69948e-01     2.94496e-01
    5.59299e-01     3.00400e-01     2.09631e-01
    6.49988e-01     4.12880e-01     1.31733e-01
    7.29661e-01     5.21348e-01     8.53096e-02
    7.97441e-01     6.39950e-01     4.26607e-02
    8.43948e-01     7.08872e-01     2.54921e-02
    8.73649e-01     7.56823e-01     2.01121e-02
    9.05079e-01     8.12892e-01     1.29502e-02
    9.38085e-01     8.78276e-01     4.13206e-03
    9.73116e-01     9.44072e-01     1.55308e-03
    9.86552e-01     9.73498e-01     1.85366e-04
    9.99529e-01     9.98598e-01     2.14298e-05
    9.99114e-01     9.98178e-01     1.04837e-06
    9.99913e-01     9.99825e-01     7.61051e-09
    9.99995e-01     9.99989e-01     2.83979e-11
    Optimization terminated successfully.
             Current function value: 0.000000
             Iterations: 19
             Function evaluations: 96
             Gradient evaluations: 24
    Number of calls to Simulator instance 96
    

    Of course this is just a template, it can be adjusted to your needs. It does not provide all information about the status of the optimizer (like e.g. in the Optimization Toolbox of MATLAB), but at least you have some idea of the progress of the optimization.

    A similar approach can be found here, without using the callback function. In my approach the callback function is used to print output exactly when the optimizer has finished an iteration, and not every single function call.

提交回复
热议问题