为什么scipy.optimize.root调用了四次初始值的回调函数?

Why does scipy.optimize.root call the callback function with the initial value four times?

我正在研究 scipy.optimize.root,我正在尝试了解它的作用和工作原理。

我的示例代码如下:

import numpy as np
from scipy.optimize import root

def func(x):
     """ test function x + 2 * np.cos(x) = 0 """
     f = x + 2 * np.cos(x)
     print x,f
     return f

def main():
    sol = root(func, 0.3)

if __name__ == "__main__":
    main() 

使用 func 中的 print 语句,我得到以下输出:

[ 0.3] [ 2.21067298]
[ 0.3] [ 2.21067298]
[ 0.3] [ 2.21067298]
[ 0.3] [ 2.21067298]
[-5.10560121] [-4.33928627]
[-1.52444136] [-1.43176461]
[-0.80729233] [ 0.57562174]
[-1.01293614] [ 0.0458079]
[-1.03071618] [-0.0023067]
[-1.02986377] [  7.49624786e-06]
[-1.02986653] [  1.20746968e-09]
[-1.02986653] [ -6.66133815e-16]

到目前为止一切顺利。我现在想知道为什么它用初始值调用四次?非常感谢你。

它们实际上是不一样的。一种潜在的查看方式是更改 print options.

if __name__ == "__main__":
    np.set_printoptions(precision=15)
    main() 

此后输出

[0.3] [2.210672978251212]
[0.3] [2.210672978251212]
[0.3] [2.210672978251212]
[0.300000004470348] [2.210672980079404]
...

在这种情况下,我们很幸运:epsilonic 变化可能仍然低于我们的机器精度,我们什么也看不到。

编辑

答案在 fortran source code 中。 在源码中搜索“call fcn(n,x”,看起来函数是:

  1. 首先在起点评估
  2. 然后在 nprint>0 请求时调用以启用迭代打印。
  3. 最后在终止前调用。

因此一共看到 3 幅画。

如果现在迭代的打印是 "turned on",那么 Jacobian 的数值(和打印)估计就开始了。

您没有提供 jacobian 信息,所以使用 numerical-differentiation。因此 func 被调用的次数高于内部迭代次数。

这也意味着,第一个 x 值不相同,但它们接近 机器精度。将 numpy 的 printoptions 更改为 precision=15 不足以观察到这一点!

默认优化器是 this one 并且文档说:

eps : float

A suitable step length for the forward-difference approximation of the Jacobian (for fprime=None). If eps is less than the machine precision, it is assumed that the relative errors in the functions are of the order of the machine precision.

编辑:看来我错了!

print(x,f)
print('hash x: ', hash(x[0].item()))

输出:

[0.3] [2.21067298]
hash x:  691752902764108160
[0.3] [2.21067298]
hash x:  691752902764108160
[0.3] [2.21067298]
hash x:  691752902764108160
[0.3] [2.21067298]
hash x:  691752913072029696

那些似乎确实是相同的数字(如果hash中没有隐藏魔法)!如果需要一些设置内容而不是缓存(scipy.optimize缓存x参数的某些部分),可能需要查看内部结构。

寻根例程首先会调用一个input sanitizer,它会调用以初始值传入的函数。 (第一次评估)

然后默认Powell root-finder (which is the one you're using) will call its internal MINPACK routine hybrd, which will evaluate once at the beginning (2nd evaluation at the initial value). Then hybrd calls fdjac1在此位置求近似雅可比。这需要两次评估,一次评估值本身(第 3 次!),另一个评估前一步,这是第四次调用,参数略有不同,如 Kanak 的回答中所述。

编辑:当调用函数的成本很高时,重复的回调评估可能是非常不受欢迎的。如果是这种情况,可以 memoize 函数以避免对相同输入进行重复计算,而无需打开数值例程的黑框。记忆化在纯函数(即没有副作用的函数)上工作得很好,当将数值函数传递给求根或最小化例程时,情况通常如此。