web-dev-qa-db-ja.com

SciPyの指数曲線フィッティング

2つのNumPy配列xyがあります。この単純なコードで指数関数とcurve_fit(SciPy)を使用してデータを近似しようとすると

#!/usr/bin/env python
from pylab import *
from scipy.optimize import curve_fit

x = np.array([399.75, 989.25, 1578.75, 2168.25, 2757.75, 3347.25, 3936.75, 4526.25, 5115.75, 5705.25])
y = np.array([109,62,39,13,10,4,2,0,1,2])

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

popt, pcov = curve_fit(func, x, y)

間違った係数poptを取得します

[a,b,c,d] = [1., 1., 1., 24.19999988]

何が問題ですか?

27
drastega

最初のコメント:a*exp(b - c*x) = (a*exp(b))*exp(-c*x) = A*exp(-c*x)a、またはbは冗長であるため。 bをドロップして使用します:

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

それは主な問題ではありません。問題は、デフォルトの初期推定値(すべて1)を使用すると、_curve_fit_がこの問題の解決に収束しないことです。チェックpcov; infであることがわかります。 cが1の場合、exp(-c*x)のほとんどの値は0にアンダーフローするため、これは驚くべきことではありません。

_In [32]: np.exp(-x)
Out[32]: 
array([  2.45912644e-174,   0.00000000e+000,   0.00000000e+000,
         0.00000000e+000,   0.00000000e+000,   0.00000000e+000,
         0.00000000e+000,   0.00000000e+000,   0.00000000e+000,
         0.00000000e+000])
_

これは、cを小さくする必要があることを示唆しています。より良い初期推測は、たとえば、p0 = (1, 1e-6, 1)です。その後、私は得る:

_In [36]: popt, pcov = curve_fit(func, x, y, p0=(1, 1e-6, 1))

In [37]: popt
Out[37]: array([  1.63561656e+02,   9.71142196e-04,  -1.16854450e+00])
_

これは合理的に見えます:

_In [42]: xx = np.linspace(300, 6000, 1000)

In [43]: yy = func(xx, *popt)

In [44]: plot(x, y, 'ko')
Out[44]: [<matplotlib.lines.Line2D at 0x41c5ad0>]

In [45]: plot(xx, yy)
Out[45]: [<matplotlib.lines.Line2D at 0x41c5c10>]
_

fit plot

39

まず、方程式をa*np.exp(-c*(x-b))+dに変更することをお勧めします。そうしないと、指数関数は常にx=0を中心としますが、常にそうとは限りません。また、妥当な初期条件を指定する必要があります(curve_fitの4番目の引数は、[a,b,c,d]の初期条件を指定します)。

このコードはうまく適合します:

from pylab import *
from scipy.optimize import curve_fit

x = np.array([399.75, 989.25, 1578.75, 2168.25, 2757.75, 3347.25, 3936.75, 4526.25, 5115.75, 5705.25])
y = np.array([109,62,39,13,10,4,2,0,1,2])

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

popt, pcov = curve_fit(func, x, y, [100,400,0.001,0])
print popt

plot(x,y)
x=linspace(400,6000,10000)
plot(x,func(x,*popt))
show()
6