我正在尝试使用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)
是什么原因导致这种现象?还有其他一些优化程序/设置可以帮助解决该问题吗?
答案 0 :(得分:3)
原因是,对于函数f = abs(cos(0.75*pi) - cos(z))
,梯度f'
恰好在z = pi
消失,如以下图所示:
如果您检查结果以优化程序,那么您将看到:
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)