【问题标题】:Python3.4 scipy.optimize.minimize callbacks multiple times the objective function in one iterationPython3.4 scipy.optimize.minimize 在一次迭代中多次回调目标函数
【发布时间】:2015-12-05 07:03:20
【问题描述】:

我正在尝试优化一个数学模型,我使用 scipy.optimize.minimize 来尝试一组不同的输入值 x,以便我的模型返回 输出值 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)

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

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

问题是为什么最小化器恰好通过了 2 次目标函数罗森? 为什么不一致? 在迭代 67 和 68 中只通过了一次.

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

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

问题是我需要:

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

  2. 一种打印/保存每次迭代、迭代次数、x 值和 F 值的方法。

你认为问题是什么?可能是回调选项中的错误、我的代码中的错误或我不正确理解的东西吗?

提前致谢, 迈克尔

【问题讨论】:

  • 您不能期望每个步骤中只有一个函数评估。每个迭代步骤中的函数评估次数取决于线搜索策略。这对我来说似乎是预期的行为。
  • 但是考虑到我不能在每次传递中运行相同的模拟,你知道是否有正确的方法来打印每次迭代的结果吗?或者,我可以添加一个触发变量和一个条件来检查每次是否是第一次通过。
  • 我不明白在这种情况下simulation 是什么。
  • 而不是将分析表达式作为目标函数,例如Rosenbrock 函数,我有一个分子动力学模拟,它基于作为输入的 x 值,返回不同的结果 F 作为输出。它类似于航空优化行业中典型的设计问题。问题是每次模拟至少持续几分钟,所以我不能在每次通过时重新运行它。

标签: numpy optimization scipy python-3.4 minimize


【解决方案1】:

编辑:

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


基于cel的cmets,显然多次调用目标函数是正常的。然而,在像我这样的优化问题中,很明显这是不能接受的,因为模拟器和后处理器嵌入在目标函数中,不得重新运行

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

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

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})

包括额外打印/保存的结果是(其中的一部分):

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

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

【讨论】:

  • 查看/usr/lib/python3/dist-packages/scipy/optimize/optimize.py 中的代码我看到func 在一次迭代中被不同的X 值调用。首先是xr = (1 + rho) * xbar - rho * sim[-1],然后是xc = (1 + psi * rho) * xbar - psi * rho * sim[-1] 或其他一些表达式。不应该为这两个值运行模拟吗?
  • 你是绝对正确的。实际上,您使我摆脱了许多以后难以追查的问题,因此非常感谢。 Nelder-Mead 在每次迭代中有 4 种不同的凸变换,这就是多次访问该函数的原因。也许这个算法不适合我的问题。无论如何,这是正确的,非常感谢!
猜你喜欢
  • 1970-01-01
  • 2020-07-23
  • 2014-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-10
  • 2013-09-25
  • 1970-01-01
相关资源
最近更新 更多