为什么scipy.optimize.minimize(默认)在不使用Skyfield的情况下报告成功?

uho*_*hoh 4 python minimize scipy skyfield

scipy.optimize.minimize使用默认方法将返回初始值作为结果,而不会出现任何错误或警告消息。在使用此答案建议的Nelder-Mead方法解决问题时,我想了解:

为什么默认方法会在不警告起点的情况下返回错误答案作为答案-并且 在这种情况下,有什么方法可以防止“没有警告就错误回答”避免这种情况?

请注意,该函数separation使用python软件包Skyfield生成无法保证平滑的最小化值,这可能就是为什么Simplex在此处更好的原因。

结果:

测试结果:[ 2.14159739 ]'正确': 2.14159265359初始值:0.0

默认结果:[ 10000。 ]“正确”:13054初始值: 10000

Nelder-Mead结果:[ 13053.81011963 ]'正确': 13054初始值:10000

FULL OUTPUT using DEFAULT METHOD:
   status: 0
  success: True
     njev: 1
     nfev: 3
 hess_inv: array([[1]])
      fun: 1694.98753895812
        x: array([ 10000.])
  message: 'Optimization terminated successfully.'
      jac: array([ 0.])
      nit: 0

FULL OUTPUT using Nelder-Mead METHOD:
  status: 0
    nfev: 63
 success: True
     fun: 3.2179306044608054
       x: array([ 13053.81011963])
 message: 'Optimization terminated successfully.'
     nit: 28
Run Code Online (Sandbox Code Playgroud)

这是完整的脚本:

def g(x, a, b):
    return np.cos(a*x + b)

def separation(seconds, lat, lon):
    lat, lon, seconds = float(lat), float(lon), float(seconds) # necessary it seems
    place = earth.topos(lat, lon)
    jd = JulianDate(utc=(2016, 3, 9, 0, 0, seconds))
    mpos = place.at(jd).observe(moon).apparent().position.km
    spos = place.at(jd).observe(sun).apparent().position.km
    mlen = np.sqrt((mpos**2).sum())
    slen = np.sqrt((spos**2).sum())
    sepa = ((3600.*180./np.pi) *
            np.arccos(np.dot(mpos, spos)/(mlen*slen)))
    return sepa

from skyfield.api import load, now, JulianDate
import numpy as np
from scipy.optimize import minimize

data = load('de421.bsp')

sun   = data['sun']
earth = data['earth']
moon  = data['moon']

x_init = 0.0
out_g = minimize(g, x_init, args=(1, 1))
print "test result: ", out_g.x, "'correct': ", np.pi-1, "initial: ", x_init    # gives right answer

sec_init = 10000
out_s_def = minimize(separation, sec_init, args=(32.5, 215.1))
print "default result: ", out_s_def.x, "'correct': ", 13054, "initial: ", sec_init

sec_init = 10000
out_s_NM = minimize(separation, sec_init, args=(32.5, 215.1),
                 method = "Nelder-Mead")
print "Nelder-Mead result: ", out_s_NM.x, "'correct': ", 13054, "initial: ", sec_init

print ""
print "FULL OUTPUT using DEFAULT METHOD:"
print out_s_def
print ""
print "FULL OUTPUT using Nelder-Mead METHOD:"
print out_s_NM
Run Code Online (Sandbox Code Playgroud)

pv.*_*pv. 5

1)

您的函数是分段常量(具有小规模的“楼梯”模式)。并非到处都是可区分的。

初始猜测时函数的梯度为零。

默认的BFGS优化器会看到零梯度,并根据其标准将其确定为局部最小值(基于关于输入函数的假设,这种假设在这种情况下是不正确的,例如微分)。

基本上,完全平坦的区域会轰炸优化器。优化器在初始点附近的一个很小的平坦区域中探测该函数,在该区域中一切看上去都像一个常数,因此认为您给了它一个常数。因为您的功能在任何地方都无法微分,所以几乎所有的初始点都可能在这样平坦的区域内,因此在选择初始点时不会出现运气不好的情况。

还要注意,Nelder-Mead 不能幸免于此---恰好它的初始单纯形大于阶梯的大小,因此它会在更大的位置附近探测功能。如果初始单纯形小于阶梯尺寸,则优化器的行为与BFGS类似。

2)

通用答案:局部优化器返回局部最优值。它们是否与真正的最优值一致取决于函数的属性。

通常,要查看您是否陷入局部最优状态,请尝试不同的初始猜测。

同样,对不可微函数使用基于导数的优化器也不是一个好主意。如果该函数可以“大”微分,则可以调整数值微分的步长。

因为没有便宜/通用的方法可以数字地检查函数是否到处都是可微的,所以不会进行这种检查---相反,这是优化方法中的一种假设,必须由输入目标函数并选择优化方法的人来确保。