如何在Scipy.optimize.minimize中显示结果

时间:2017-10-08 20:47:20

标签: python scipy

如果我们从文档中考虑这个例子:

from scipy.optimize import minimize, rosen, rosen_der
x0 = [1.3, 0.7, 0.8, 1.9, 1.2]
result = minimize(rosen, x0, method='Nelder-Mead', tol=1e-6)
print(result)

我们得到以下结果

final_simplex: (array([[ 1.00000002,  1.00000002,  1.00000007,  1.00000015,  1.00000028],
       [ 0.99999999,  0.99999996,  0.99999994,  0.99999986,  0.99999971],
       [ 1.00000005,  1.00000007,  1.00000017,  1.00000031,  1.00000063],
       [ 1.00000004,  1.00000008,  1.00000013,  1.00000025,  1.00000047],
       [ 0.99999999,  0.99999996,  0.99999994,  0.99999984,  0.99999963],
       [ 1.00000005,  1.00000004,  1.00000003,  1.00000003,  1.00000004]]), array([  1.94206402e-13,   2.44964782e-13,   3.10422870e-13,
         3.37952410e-13,   5.52173609e-13,   7.16586838e-13]))
           fun: 1.9420640199868412e-13
       message: 'Optimization terminated successfully.'
          nfev: 494
           nit: 295
        status: 0
       success: True
             x: array([ 1.00000002,  1.00000002,  1.00000007,  1.00000015,  1.00000028])

如您所见,迭代次数为295.我的问题是如何在每次迭代时获取x的值?

1 个答案:

答案 0 :(得分:1)

一般方法是使用自定义的callback

  

回调:可调用,可选

     

在每次迭代后调用,作为回调(xk),其中xk是当前参数向量。

现在使用它就像以下一样简单:

history = []
def callback(x):
    fobj = rosen(x)
    history.append(fobj)

result = minimize(rosen, x0, method='Nelder-Mead', tol=1e-6, callback=callback)
print(history)

但正如上面评论中这两个链接的第一个评论中所提到的(好的链接!),这种方法正在使用额外的功能评估! (这个来源显然是基于经典的软件开发设计原则问题:使用多少抽象?)

根据您的任务,功能评估可能代价高昂(如果没有,则忽略以下内容)!

在这种情况下,您可以使用memoization来缓存以前计算的值,而不是一直重新计算它们。

现在至少你的优化器'Nelder-Mead'是一个无梯度优化器,你需要缓存多个值才能与无回调解决方案相媲美(因为某种程度上需要较旧的值)。这可能取决于所选择的最小化方法(以及一些内部方法)。

当使用数值微分时,

scipy对渐变使用了一些记忆,因为这非常昂贵。但是这段代码只缓存了最后一个值,这对你的情况不起作用(NM不同)。

因此我们可以构建自己的memoization-cache,仅基于函数(没有使用渐变):

import numpy as np
from scipy.optimize import minimize, rosen, rosen_der


""" Memoization of function-values -> don't recompute """
class Memoize(object):
    """ Modified from https://github.com/scipy/scipy/blob/c96c5294ca73586cadd6f4c10f26b6be5ed35045/scipy/optimize/optimize.py#L53 """
    def __init__(self, fun, n, cache_size=8):
        self.n = n
        self.c_n = cache_size
        self.fun = fun
        self.fobj = np.full((self.c_n), np.inf)
        self.x = np.full((self.c_n, self.n), np.inf)
        self.pos = 0  # circular-like buffer; next to replace = oldest

    def __call__(self, x, *args):
        # Check cache
        cands = np.all(x == self.x, axis=1).nonzero()[0]

        if cands.size:
            return self.fobj[cands]
        else:
            fobj = self.fun(x)
            self.fobj[self.pos] = fobj
            self.x[self.pos] = x
            self.pos = (self.pos + 1) % self.c_n

            return fobj

""" rosen-wrapper to check function-evaluations """
nfev = 0
def rosen_wrapper(x):
    global nfev
    nfev += 1
    return rosen(x)

x0 = [1.3, 0.7, 0.8, 1.9, 1.2]
mem_rosen = Memoize(rosen_wrapper, len(x0))

""" Callback storing history """
history = []
def callback(x):
    fobj = mem_rosen(x)
    history.append(fobj)

result = minimize(mem_rosen, x0, method='Nelder-Mead', tol=1e-6, callback=callback)
print(result)
print('iteration fun(x)')
print(history[::50])  # every 50th
print(nfev)

使用回调和缓存大小8调用:

final_simplex: (array([[ 1.00000002,  1.00000002,  1.00000007,  1.00000015,  1.00000028],
      [ 0.99999999,  0.99999996,  0.99999994,  0.99999986,  0.99999971],
      [ 1.00000005,  1.00000007,  1.00000017,  1.00000031,  1.00000063],
      [ 1.00000004,  1.00000008,  1.00000013,  1.00000025,  1.00000047],
      [ 0.99999999,  0.99999996,  0.99999994,  0.99999984,  0.99999963],
      [ 1.00000005,  1.00000004,  1.00000003,  1.00000003,  1.00000004]]), array([  1.94206402e-13,   2.44964782e-13,   3.10422870e-13,
        3.37952410e-13,   5.52173609e-13,   7.16586838e-13]))
          fun: 1.9420640199868412e-13
      message: 'Optimization terminated successfully.'
         nfev: 494
          nit: 295
       status: 0
      success: True
            x: array([ 1.00000002,  1.00000002,  1.00000007,  1.00000015,  1.00000028])
iteration fun(x)
[array([ 516.14978061]), array([ 1.16866125]), array([ 0.00135733]), array([  6.48182410e-05]), array([  1.03326372e-06]), array([  7.12094933e-10])]
504

缓存24(不推荐;仅用于演示目的):

      nfev: 494
       nit: 295
    status: 0
   success: True
...
494

现在显然需要权衡,因为我们存储了一个大小的缓存:

  • C_SIZE * N#x-vectors
  • C_SIZE * 1#fun-values

我们在每个调用中计算C_SIZE * N上的线性运算。

如果这样做有所回报,以及如何选择缓存大小,则取决于您的功能,最小化器以及可能还有您的参数。

请记住,所选择的方法基于这样的想法:基于numpy的计算的线性数量可能比使用基于纯python的 logn (或类似)算法更快!

(没有广泛检查记忆码!)