Scipy找不到最佳值(简单的余弦函数)

时间:2018-07-06 06:30:45

标签: python optimization scipy

我正在尝试使用scipy优化器估算余弦函数的参数(是的,我知道可以使用arc cos,但我不想这样做)。

代码+演示:

man

结果不好,当cos函数的自变量达到大于2的值时,估计将“跳过”这些值并返回最大自变量值。

import numpy
import scipy

def solver(data):
    Z=numpy.zeros(len(data))
    a=0.003
    for i in range(len(data)):
        def minimizer(b):
            return numpy.abs(data[i]-numpy.cos(b))
        Z[i]=scipy.optimize.minimize(minimizer,a,bounds=[(0,numpy.pi)],method="L-BFGS-B").x[0]
    return Z

Y=numpy.zeros(100)
for i in range(100):
   Y[i]=numpy.cos(i/25)

solver(Y)

是什么原因导致这种现象?还有其他一些优化程序/设置可以帮助解决该问题吗?

2 个答案:

答案 0 :(得分:3)

原因是,对于函数f = abs(cos(0.75*pi) - cos(z)),梯度f'恰好在z = pi消失,如以下图所示:

enter image description here

如果您检查结果以优化程序,那么您将看到:

      fun: array([0.29289322])
 hess_inv: <1x1 LbfgsInvHessProduct with dtype=float64>
      jac: array([0.])
  message: b'CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL'
     nfev: 16
      nit: 2
   status: 0
  success: True
        x: array([3.14159265])

因此,优化过程达到了其收敛标准之一。有关标准的更多详细信息,请参见L-BFGS-B documentation。它说

  

gtol:浮动

     

当max {| proj g_i | i = 1,...,n} <= gtol,其中pg_i是投影梯度的第i个分量。

因此它最终到达点z >= pi,由于约束,该点然后投影回到z = pi,此时函数的梯度为零,因此停止。您可以通过注册显示当前参数向量的回调来观察到这一点:

def new_callback():
    step = 1

    def callback(xk):
        nonlocal step
        print('Step #{}: xk = {}'.format(step, xk))
        step += 1

    return callback

scipy.optimize.minimize(..., callback=new_callback())

哪个输出:

Step #1: xk = [0.006]
Step #2: xk = [3.14159265]

因此在第二步中,它击中了z >= pi,该投影又投射回z = pi

您可以通过将范围减小到bounds=[(0, 0.99*np.pi)]来避免此问题。这将为您提供预期的结果,但是该方法将无法收敛。您将看到类似的内容:

      fun: array([1.32930966e-09])
 hess_inv: <1x1 LbfgsInvHessProduct with dtype=float64>
      jac: array([0.44124484])
  message: b'ABNORMAL_TERMINATION_IN_LNSRCH'
     nfev: 160
      nit: 6
   status: 2
  success: False
        x: array([2.35619449])

请注意消息ABNORMAL_TERMINATION_IN_LNSRCH。这是由于abs(x)的性质以及它的导数在x = 0处不连续的事实(您可以了解有关该here的更多信息)。

替代方法(寻找根源)

对于上面的所有行,我们试图找到一个值z(或cos(z) == cos(0.75*pi))的值abs(cos(z) - cos(0.75*pi)) < eps。这个问题实际上是在找到函数f = cos(z) - cos(0.75*pi)的根,在这里我们可以利用cos是连续函数的事实。我们需要将边界a, b设置为f(a)*f(b) < 0(即它们具有相反的符号)。例如,使用bisect method

res = scipy.optimize.bisect(f, 0, np.pi)

答案 1 :(得分:3)

除了一般的minimize方法外,SciPy还具有minimize_scalar专门用于解决一维问题,例如此处,而least_squares则用于最小化测量两种量之差的特定种类的函数(例如cos(b)diff[i]之间的差异)。即使没有微调,后者在这里的表现也很好。

for i in range(len(data)):
    Z[i] = scipy.optimize.least_squares(lambda b: data[i] - numpy.cos(b), a, bounds=(0, numpy.pi)).x[0]

传递给least_squares的函数是我们希望接近0且没有绝对值的东西。我要补充一点,a = 0.003似乎是起点的次佳选择,因为它离边界太近了。尽管如此,它仍然有效。

此外,正如已经发布的a_guest一样,考虑到我们已经有了一个不错的包围间隔[0,pi],标量根查找方法应该在做相同的事情的同时减少引发的惊喜。二等分可靠,但速度慢; Brent's method是我可能会使用的。

for i in range(len(data)):
    Z[i] = scipy.optimize.brentq(lambda b: data[i] - numpy.cos(b), 0, numpy.pi)