scipy.optimize.curve_fit对函数形式敏感。规则是什么?

时间:2018-10-22 17:30:10

标签: python scipy curve-fitting

scipy.optimize.curve_fit在一个函数上可以正常工作,但在等效函数上会阻塞。示例:

def func(x, a, b, c, d):
#   return np.exp(a + b * (c -d * x ) ) # works fine
    return a * np.exp( b * (c -d * x ) ) # gives error

误差在协方差矩阵中:

params= [ 1.16507769 13.26573913  5.90351144  6.24181411]
cov=
 [[-2.16168732e+13  2.55685110e+12  2.64410274e+11
-1.20320851e+12]
 [ 6.54220223e+12  7.78321447e+11 -7.70863674e+11 
-3.66264006e+11]
 [-1.50943415e+12 -5.12305287e+11  3.25950385e+11  
2.41081648e+11]
 [-3.07864319e+12 -3.66264061e+11  3.62754606e+11  1.72357248e+11]]

C:\Python34\hsf\pandas\sample.py:119: RuntimeWarning: 
invalid value encountered in sqrt
perr = np.sqrt(np.diag(pcov))
perr= [ nan 882225.28145113 570920.64728287 415159.3038493 ]

协方差矩阵中的第一个数字应该是方差,即平方和。怎么会是负面的?

运行无误的版本给出的图形和

params= [ 4.32069414 39.26093245  1.8885566   2.10902333]
cov=
 [[ 3.82867157e+14 -3.12417211e+14  5.27625493e+12  1.67824840e+13]
 [-2.81245333e+14  2.17844627e+14 -3.31542721e+12 -1.17022169e+13]
 [ 3.77680355e+12 -2.52146086e+12  2.50916226e+10  1.35448290e+11]
 [ 1.51079875e+13 -1.17022170e+13  1.78098719e+11  6.28621803e+11]]
perr= [19566991.51220365 14759560.52776919   158403.35405783   792856.73527642]

1 个答案:

答案 0 :(得分:1)

您有两个冗余参数。您说可以找到的功能的版本为exp(a + b*(c - d*x))。表达式

a + b*(c - d*x)

可以写

a + b*c - b*d*x = A + B*x

其中A = a + b*cB = -b*d。因此,您可以将函数简化为exp(A + B*x)。参数过多的问题在于解决方案不是孤立的-解决方案空间的维数与冗余参数的数量相同。这导致Hessian矩阵是奇异的。由于正常的数值不精确性,冗余情况下的Hessian矩阵不会精确地是奇异的,但条件差且接近奇异。协方差矩阵是从Hessian矩阵的逆得到的,因此,如果Hessian矩阵接近奇异,则协方差的计算在数值上是不稳定的,因此不值得信赖。

以下是演示此问题的完整脚本:

import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt


def func(x, a, b, c, d):
    return np.exp(a + b*(c - d*x))


def func2(x, a, b):
    return np.exp(a - b*x)


np.random.seed(8675309)
x = np.linspace(0, 10, 16)
y = (10*np.exp(-0.5*x)*np.random.lognormal(sigma=0.25, size=len(x))
     + 0.5*np.random.rand(len(x)))

p, pcov = curve_fit(func, x, y)
print("p:", p)
print("pcov:")
print(pcov)
print()

p2, pcov2 = curve_fit(func2, x, y)
print("p2:", p2)
print("pcov2:")
print(pcov2)


plt.plot(x, y, 'bo', label="data")

xx = np.linspace(0, 10, 101)

yy = func(xx, *p)
plt.plot(xx, yy, 'k--', label="4 parameter fit")

yy2 = func2(xx, *p2)
plt.plot(xx, yy2, 'g', linewidth=4, alpha=0.3, label="2 parameter fit")

plt.legend(shadow=True)
plt.grid(True)
plt.xlabel('x')

plt.show()

该脚本生成以下图:

plot

无论我们使用两个还是四个参数,curve_fit都找到了相同的解决方案。

这是脚本的打印输出:

p: [1.15386327 1.18656718 1.09383746 0.47713239]
pcov:
[[-2.43958994e+12 -7.92454940e+12  9.37347302e+12  3.19227931e+12]
 [-3.98689093e+13 -1.05598664e+13  4.33789687e+13  4.25387501e+12]
 [ 3.88631613e+13  1.64330041e+13 -4.79524254e+13 -6.61977557e+12]
 [ 1.60605592e+13  4.25387505e+12 -1.74745310e+13 -1.71360622e+12]]

p2: [2.45177495 0.56614914]
pcov2:
[[0.00143014 0.00077371]
 [0.00077371 0.00130843]]

pcov是四个参数拟合的结果,基本上是垃圾。在此示例中,所有对角元素均为负,矩阵不对称。

pcov2(两个参数拟合的协方差矩阵)很好。

故事的寓意:如果您需要使用curve_fit返回的协方差矩阵,请不要在模型函数中使用冗余参数。


顺便说一句,正如您已经提到的,您可以将exp(A + B*x)重写为exp(A)*exp(B*x),然后定义C = exp(A)来将您的函数表示为C*exp(B*x)。然后,您可以使用参数BC代替A中的Bcurve_fit。请注意,此版本允许C小于或等于0,因此可以使用诸如-2*exp(-3*x)之类的答案。当您使用exp(A + B*x)时,不能使用像这样的负函数。为了确保两个参数化具有相同的可能解决方案,您将必须使用bounds参数来约束C