Python3.4 scipy.optimize.minimize 在一次迭代中多次回调 objective 函数

Python3.4 scipy.optimize.minimize callbacks multiple times the objective function in one iteration

我正在尝试优化一个数学模型,我使用 scipy.optimize.minimize 尝试一组不同的 输入值 x 以使我的模型 return 输出值F尽可能接近目标值F_experimental。

在我的程序中,我观察到 scipy.optimize.minimize 回调选项的奇怪行为。可重现的测试代码(部分基于 here)是:

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

Nfeval = 1

def rosen(X): #Rosenbrock function
  global Nfeval
  print('pass rosen',str(Nfeval))
  #Nfeval += 1
  return (1.0 - X[0])**2 + 100.0 * (X[1] - X[0]**2)**2 + \
         (1.0 - X[1])**2 + 100.0 * (X[2] - X[1]**2)**2

def callbackF(Xi):
  global Nfeval
  print('pass callback',str(Nfeval))
  #print(Nfeval, Xi[0], Xi[1], Xi[2], rosen(Xi))
  Nfeval += 1

x0 = np.array([1.1, 1.1, 1.1])
optXvalues=minimize(rosen, x0, method='Nelder-Mead',callback=callbackF)

运行 此代码将 return 在屏幕上显示一个非常奇怪的结果,尽管最小化器收敛了。其中一部分是:

pass rosen 66
pass rosen 66
pass callback 66
pass rosen 67
pass callback 67
pass rosen 68
pass callback 68
pass rosen 69
pass rosen 69
pass callback 69
pass rosen 70
pass rosen 70
pass callback 70
pass rosen 71
pass rosen 71
pass callback 71

问题是为什么最小化器恰好通过objective函数rosen传递了2次? 为什么不一致? 在迭代67和68中只通过了一次。

如果我停用回调选项并在 rosen 函数中添加计数器(只需取消注释),最小化器只会通过一次。

如果我在 callbackF 函数中激活打印函数(只是取消注释)以获取每次迭代的值,程序将再次通过 rosen 函数。

问题是我需要:

  1. 最小化器通过函数只传递一次因为在我的真实问题中,我调用了一个模拟器来获得建议的优化器的结果(F)值 (x).

  2. 每次迭代print/save的方法,迭代次数,x值和F值。

您认为问题出在哪里?可能是回调选项中的错误、我的代码中的错误或我没有正确理解的内容?

提前致谢, 迈克尔

编辑:

以下解决方案实际上是错误的,因为它改变了 Nelder-Mead 算法的工作方式。我把它放在这里以防其他人遵循这个思路但是请忽略解决方案并转到评论.


根据 cel 的评论,显然多次调用 objective 函数是正常的。然而,在像我这样的优化问题中,显然这是不能接受的,因为模拟器和post-处理器嵌入在objective函数中,不能重新运行.

为了避免这个问题,我找到了一个简单但可能不可靠的解决方案,所以请谨慎使用

我添加了一个名为 NfevalPre 的额外全局变量,在 objective 函数中,我添加了一个条件和一个声明,以检查该函数是否已在本次迭代期间被调用,如果没有,则进入它。最终测试人员代码为:

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

Nfeval = 1
NfevalPre=0

def rosen(X): #Rosenbrock function
  global Nfeval, NfevalPre

  if NfevalPre!=Nfeval:
    print('pass rosen',str(Nfeval))
    NfevalPre=Nfeval

  return (1.0 - X[0])**2 + 100.0 * (X[1] - X[0]**2)**2 + \
         (1.0 - X[1])**2 + 100.0 * (X[2] - X[1]**2)**2

def callbackF(Xi):
  global Nfeval
  print('pass callback',str(Nfeval))
  print(Nfeval, Xi[0], Xi[1], Xi[2], rosen(Xi))
  Nfeval += 1

x0 = np.array([1.1, 1.1, 1.1])
optXvalues=minimize(rosen, x0, method='Nelder-Mead',callback=callbackF,options={'disp':True})

包括额外的 print/save 在内的结果是(其中的一部分):

pass rosen 67
pass callback 67
67 0.999945587739 0.999922683215 0.999847949605 1.08857865253e-07
pass rosen 68
pass callback 68
68 0.999945587739 0.999922683215 0.999847949605 1.08857865253e-07
pass rosen 69
pass callback 69
69 1.00005423035 1.00010796114 1.00019867305 4.44156189226e-08
pass rosen 70
pass callback 70
70 1.00002966558 1.00004883027 1.00010442415 1.88645594463e-08
pass rosen 71
pass callback 71
71 1.00002966558 1.00004883027 1.00010442415 1.88645594463e-08

当然,我确信存在更专业的解决方案。 :)