web-dev-qa-db-ja.com

初期推測なしでフィッティング指数減衰

指数減衰をデータに適合させることができるscipy/numpyモジュールを知っている人はいますか?

Google検索でいくつかのブログ投稿が返されました。たとえば、 http://exnumerus.blogspot.com/2010/04/how-to-fit-exponential-decay-example-in.html ですが、ソリューションでは、yオフセットを事前に指定する必要がありますが、これは常に可能とは限りません

編集:

curve_fitは機能しますが、パラメーターの初期推定がないため、非常に惨めに失敗する可能性があり、それが必要になる場合があります。私が取り組んでいるコードは

#!/usr/bin/env python
import numpy as np
import scipy as sp
import pylab as pl
from scipy.optimize.minpack import curve_fit

x = np.array([  50.,  110.,  170.,  230.,  290.,  350.,  410.,  470.,  
530.,  590.])
y = np.array([ 3173.,  2391.,  1726.,  1388.,  1057.,   786.,   598.,   
443.,   339.,   263.])

smoothx = np.linspace(x[0], x[-1], 20)

guess_a, guess_b, guess_c = 4000, -0.005, 100
guess = [guess_a, guess_b, guess_c]

exp_decay = lambda x, A, t, y0: A * np.exp(x * t) + y0

params, cov = curve_fit(exp_decay, x, y, p0=guess)

A, t, y0 = params

print "A = %s\nt = %s\ny0 = %s\n" % (A, t, y0)

pl.clf()
best_fit = lambda x: A * np.exp(t * x) + y0

pl.plot(x, y, 'b.')
pl.plot(smoothx, best_fit(smoothx), 'r-')
pl.show()

これは機能しますが、「p0 = guess」を削除すると、惨めに失敗します。

23

次の2つのオプションがあります。

  1. システムを線形化し、ラインをデータのログに適合させます。
  2. 非線形ソルバーを使用してください(例 _scipy.optimize.curve_fit_

最初のオプションは、断然最速で最も堅牢です。ただし、事前にyオフセットを知っている必要があります。そうでない場合、方程式を線形化することはできません。 (つまり、y = A * exp(K * t)y = log(A * exp(K * t)) = K * t + log(A)をフィッティングすることによって線形化できますが、y = A*exp(K*t) + Cy - C = K*t + log(A)をフィッティングすることによってのみ線形化でき、yは独立変数です。これが線形システムであるためには、Cが事前にわかっている必要があります。

非線形の方法を使用する場合、それはa)収束して解を生成することが保証されていない、b)非常に遅くなる、c)パラメータの不確実性の推定がはるかに悪い、そしてd)多くの場合精度がはるかに低い。ただし、非線形の方法には、線形の反転よりも大きな利点が1つあります。非線形の方程式系を解くことができます。あなたの場合、これはあなたが事前にCを知る必要がないことを意味します。

例を示すために、線形メソッドと非線形メソッドの両方を使用して、いくつかのノイズのあるデータでy = A * exp(K * t)を解いてみましょう。

_import numpy as np
import matplotlib.pyplot as plt
import scipy as sp
import scipy.optimize


def main():
    # Actual parameters
    A0, K0, C0 = 2.5, -4.0, 2.0

    # Generate some data based on these
    tmin, tmax = 0, 0.5
    num = 20
    t = np.linspace(tmin, tmax, num)
    y = model_func(t, A0, K0, C0)

    # Add noise
    noisy_y = y + 0.5 * (np.random.random(num) - 0.5)

    fig = plt.figure()
    ax1 = fig.add_subplot(2,1,1)
    ax2 = fig.add_subplot(2,1,2)

    # Non-linear Fit
    A, K, C = fit_exp_nonlinear(t, noisy_y)
    fit_y = model_func(t, A, K, C)
    plot(ax1, t, y, noisy_y, fit_y, (A0, K0, C0), (A, K, C0))
    ax1.set_title('Non-linear Fit')

    # Linear Fit (Note that we have to provide the y-offset ("C") value!!
    A, K = fit_exp_linear(t, y, C0)
    fit_y = model_func(t, A, K, C0)
    plot(ax2, t, y, noisy_y, fit_y, (A0, K0, C0), (A, K, 0))
    ax2.set_title('Linear Fit')

    plt.show()

def model_func(t, A, K, C):
    return A * np.exp(K * t) + C

def fit_exp_linear(t, y, C=0):
    y = y - C
    y = np.log(y)
    K, A_log = np.polyfit(t, y, 1)
    A = np.exp(A_log)
    return A, K

def fit_exp_nonlinear(t, y):
    opt_parms, parm_cov = sp.optimize.curve_fit(model_func, t, y, maxfev=1000)
    A, K, C = opt_parms
    return A, K, C

def plot(ax, t, y, noisy_y, fit_y, orig_parms, fit_parms):
    A0, K0, C0 = orig_parms
    A, K, C = fit_parms

    ax.plot(t, y, 'k--', 
      label='Actual Function:\n $y = %0.2f e^{%0.2f t} + %0.2f$' % (A0, K0, C0))
    ax.plot(t, fit_y, 'b-',
      label='Fitted Function:\n $y = %0.2f e^{%0.2f t} + %0.2f$' % (A, K, C))
    ax.plot(t, noisy_y, 'ro')
    ax.legend(bbox_to_anchor=(1.05, 1.1), fancybox=True, shadow=True)

if __name__ == '__main__':
    main()
_

Fitting exp

線形解では、実際の値により近い結果が得られることに注意してください。ただし、線形ソリューションを使用するには、yオフセット値を指定する必要があります。非線形ソリューションは、この事前の知識を必要としません。

45
Joe Kington

scipy.optimize.curve_fit 関数。そのためのドキュメント文字列には、ここにコピーする指数減衰の例も含まれています。

>>> import numpy as np
>>> from scipy.optimize import curve_fit
>>> def func(x, a, b, c):
...     return a*np.exp(-b*x) + c

>>> x = np.linspace(0,4,50)
>>> y = func(x, 2.5, 1.3, 0.5)
>>> yn = y + 0.2*np.random.normal(size=len(x))

>>> popt, pcov = curve_fit(func, x, yn)

ランダムノイズが追加されたため、適合したパラメーターは異なりますが、aにはノイズが含まれているため、2.47990495、1.40709306、0.53753635をa、b、cとして取得しました。 ynではなくyに適合させると、正確なa、b、cの値が得られます。

7
Justin Peel

反復プロセスではなく初期推測なしで指数関数を近似する手順:

enter image description here

これは論文(pp.16-17)に由来します: https://fr.scribd.com/doc/14674814/Regressions-et-equations-integrales

必要に応じて、これを使用して非線形回帰計算を初期化し、最適化の特定の基準を選択できます。

例:

Joe Kingtonの例は興味深いものです。残念ながら、データは表示されず、グラフのみが表示されます。したがって、以下のデータ(x、y)はグラフのグラフィックスキャンから得られたものであり、その結果、数値はおそらくJoe Kingtonによって使用されたものと正確には一致しません。それにもかかわらず、「適合された」曲線のそれぞれの方程式は、点の広いばらつきを考慮して、互いに非常に近いです。

enter image description here

上の図は、Kingtonのグラフのコピーです。

下の図は、上記の手順で得られた結果を示しています。

6
JJacquelin

それを行う正しい方法は、Prony推定を行い、その結果を最小二乗フィッティング(またはその他のよりロバストなフィッティングルーチン)の初期推定として使用することです。 Prony推定では初期推定は必要ありませんが、適切な推定を行うには多くのポイントが必要です。

ここに概要があります

http://www.statsci.org/other/prony.html

Octaveでは、これはexpfitとして実装されているため、Octaveライブラリー関数に基づいて独自のルーチンを作成できます。

Prony推定ではオフセットを知る必要がありますが、減衰に「十分に」進んだ場合、オフセットの妥当な推定値があるため、データをシフトしてオフセットを0に配置できます。いずれにせよ、Prony推定は、他のフィッティングルーチンの妥当な初期推定値を取得する方法にすぎません。

3
Marcus P S

私はpythonを知りませんが、独立した座標に一定の差がある3つのデータポイントが与えられた場合、オフセットを使用して指数関数的減衰の係数を非反復的に推定する簡単な方法を知っています。データポイントの独立した座標には一定の差があるため(x値は60の間隔で配置されます)、私の方法をそれらに適用できます。あなたはきっと数学をPythonに翻訳することができます。

想定する

_y = A + B*exp(-c*x) = A + B*C^x
_

ここで、C = exp(-c)

Y_0、y_1、y_2が与えられた場合、x = 0、1、2に対して、

_y_0 = A + B
y_1 = A + B*C
y_2 = A + B*C^2
_

a、B、Cを次のように検索します。

_A = (y_0*y_2 - y_1^2)/(y_0 + y_2 - 2*y_1)
B = (y_1 - y_0)^2/(y_0 + y_2 - 2*y_1)
C = (y_2 - y_1)/(y_1 - y_0)
_

対応する指数は、3つの点(0、y_0)、(1、y_1)、および(2、y_2)を正確に通過します。データポイントがx座標0、1、2ではなく、k、k + s、およびk + 2 * sにある場合、

_y = A′ + B′*C′^(k + s*x) = A′ + B′*C′^k*(C′^s)^x = A + B*C^x
_

したがって、上記の式を使用してA、B、Cを検索し、計算することができます

_A′ = A
C′ = C^(1/s)
B′ = B/(C′^k)
_

結果の係数は、y座標のエラーに非常に敏感です。使用される3つのデータポイントで定義された範囲を超えて外挿すると、大きなエラーが発生する可能性があるため、可能な限り離れている(ただし、両者の距離は固定されている)。

データセットには10​​の等距離データポイントがあります。 3つのデータポイント(110、2391)、(350、786)、(590、263)を選択して使用します。これらは、独立座標で最大の固定距離(240)を持っています。したがって、y_0 = 2391、y_1 = 786、y_2 = 263、k = 110、s =240。次に、A = 10.20055、B = 2380.799、C = 0.3258567、A '= 10.20055、B' = 3980.329、C '= 0.9953388。指数は

_y = 10.20055 + 3980.329*0.9953388^x = 10.20055 + 3980.329*exp(-0.004672073*x)
_

この指数は、非線形近似アルゴリズムの初期推定として使用できます。

Aの計算式は、シャンクス変換で使用されるものと同じです( http://en.wikipedia.org/wiki/Shanks_transformation )。

2
Louis Strous

@JJacquelinのソリューションのPython実装。私は初期の推測がなく、おおよその非ソルバーベースのソリューションが必要だったので、@ JJacquelinの答えは本当に役に立ちました。元の質問はpython numpy/scipyリクエストとして提起されました。@ johanvdwのNice clean Rコードを受け取り、それをpython/numpyとしてリファクタリングしました。誰かに役立つと思います: https:/ /Gist.github.com/friendtogeoff/00b89fa8d9acc1b2bdf3bdb675178a29

import numpy as np

"""
compute an exponential decay fit to two vectors of x and y data
result is in form y = a + b * exp(c*x).
ref. https://Gist.github.com/johanvdw/443a820a7f4ffa7e9f8997481d7ca8b3
"""
def exp_est(x,y):
    n = np.size(x)
    # sort the data into ascending x order
    y = y[np.argsort(x)]
    x = x[np.argsort(x)]

    Sk = np.zeros(n)

    for n in range(1,n):
        Sk[n] = Sk[n-1] + (y[n] + y[n-1])*(x[n]-x[n-1])/2
    dx = x - x[0]
    dy = y - y[0]

    m1 = np.matrix([[np.sum(dx**2), np.sum(dx*Sk)],
                    [np.sum(dx*Sk), np.sum(Sk**2)]])
    m2 = np.matrix([np.sum(dx*dy), np.sum(dy*Sk)])

    [d, c] = (m1.I * m2.T).flat

    m3 = np.matrix([[n,                  np.sum(np.exp(  c*x))],
                    [np.sum(np.exp(c*x)),np.sum(np.exp(2*c*x))]])

    m4 = np.matrix([np.sum(y), np.sum(y*np.exp(c*x).T)])

    [a, b] = (m3.I * m4.T).flat

    return [a,b,c]
1
FriendToGeoff

何も推測したくないので、curve_fitが正しく機能しませんでした。私はジョー・キントンの例を単純化しようとしていました、そしてこれは私が働いていたものです。アイデアは、「ノイズの多い」データをログに変換してから変換し直し、polyfitとpolyvalを使用してパラメーターを把握することです。

model = np.polyfit(xVals, np.log(yVals) , 1);   
splineYs = np.exp(np.polyval(model,xVals[0]));
pyplot.plot(xVals,yVals,','); #show scatter plot of original data
pyplot.plot(xVals,splineYs('b-'); #show fitted line
pyplot.show()

ここで、xValsとyValsは単なるリストです。

1
Elendurwen

減衰が0から開始しない場合:

popt, pcov = curve_fit(self.func, x-x0, y)

ここで、x0は減衰の開始(フィットを開始する場所)。そして、もう一度x0を使ってプロットします:

plt.plot(x, self.func(x-x0, *popt),'--r', label='Fit')

関数は次のとおりです。

    def func(self, x, a, tau, c):
        return a * np.exp(-x/tau) + c
0
Max