web-dev-qa-db-fra.com

ajustement de la décroissance exponentielle sans deviner

Est-ce que quelqu'un connaît un module scipy/numpy qui permettra d'adapter la décroissance exponentielle aux données? 

La recherche Google a renvoyé quelques articles, par exemple - http://exnumerus.blogspot.com/2010/04/how-to-fit-exponential-decay-example-in.html , mais cette solution exige que le décalage y soit prédéfini, ce qui n'est pas toujours possible 

MODIFIER:

curve_fit fonctionne, mais il peut très mal échouer sans deviner les paramètres, ce qui est parfois nécessaire. Le code avec lequel je travaille est

#!/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()

ce qui fonctionne, mais si on enlève "p0 = devine", ça échoue lamentablement.

20
George Karpenkov

Vous avez deux options:

  1. Linéarisez le système et insérez une ligne dans le journal des données.
  2. Utilisez un solveur non linéaire (par exemple, scipy.optimize.curve_fit

La première option est de loin la plus rapide et la plus robuste. Cependant, vous devez connaître le décalage y a priori, sinon il est impossible de linéariser l'équation. (i.e. y = A * exp(K * t) peut être linéarisé en ajustant y = log(A * exp(K * t)) = K * t + log(A), mais y = A*exp(K*t) + C ne peut être linéarisé qu’en ajustant y - C = K*t + log(A), et comme y est votre variable indépendante, C doit être connu auparavant pour que ce soit un système linéaire.

Si vous utilisez une méthode non linéaire, il est a) non garanti de converger et d'obtenir une solution, b) sera beaucoup plus lent, c) donne une estimation beaucoup plus pauvre de l'incertitude de vos paramètres, et d) est souvent beaucoup moins précise . Cependant, une méthode non linéaire présente un énorme avantage par rapport à une inversion linéaire: elle peut résoudre un système d'équations non linéaire. Dans votre cas, cela signifie que vous n'avez pas besoin de connaître C à l'avance.

Juste pour donner un exemple, résolvons pour y = A * exp (K * t) avec des données bruitées utilisant des méthodes linéaires et non linéaires:

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 __== '__main__':
    main()

Fitting exp

Notez que la solution linéaire fournit un résultat beaucoup plus proche des valeurs réelles. Cependant, nous devons fournir la valeur y-offset pour utiliser une solution linéaire. La solution non linéaire ne nécessite pas cette connaissance a priori. 

42
Joe Kington

Je voudrais utiliser la fonction scipy.optimize.curve_fit. La doc string for it a même un exemple d'adaptation d'une décroissance exponentielle que je vais copier ici:

>>> 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)

Les paramètres ajustés varieront en raison du bruit aléatoire ajouté, mais j'ai obtenu 2,47990495, 1,40709306, 0,53753635 comme a, b et c, donc ce n'est pas si mal avec le bruit. Si je correspond à y au lieu de yn, j'obtiens les valeurs exactes a, b et c.

7
Justin Peel

Procédure à mettre en place de manière exponentielle sans deviner, pas de processus itératif:

 enter image description here

Cela provient de l'article (pp.16-17): https://fr.scribd.com/doc/14674814/Regressions-et-equations-integrales

Si nécessaire, cela peut être utilisé pour initialiser un calcul de régression non linéaire afin de choisir un critère d'optimisation spécifique.

EXEMPLE :

L'exemple donné par Joe Kington est intéressant. Malheureusement, les données ne sont pas affichées, mais uniquement le graphique. Ainsi, les données (x, y) ci-dessous proviennent d’un balayage graphique du graphique. Par conséquent, les valeurs numériques ne sont probablement pas exactement celles utilisées par Joe Kington. Néanmoins, les équations respectives des courbes "ajustées" sont très proches les unes des autres, compte tenu de la large dispersion des points.

 enter image description here

La figure du haut est la copie du graphique de Kington.

La figure inférieure montre les résultats obtenus avec la procédure présentée ci-dessus.

6
JJacquelin

La bonne façon de le faire est d’effectuer une estimation de Prony et d’utiliser le résultat comme estimation initiale pour l’ajustement des moindres carrés (ou toute autre routine d’ajustement plus robuste). L'estimation de Prony n'a pas besoin de deviner, mais elle nécessite beaucoup de points pour obtenir une bonne estimation.

Voici un aperçu

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

Dans Octave, ceci est implémenté sous la forme expfit. Vous pouvez donc écrire votre propre routine basée sur la fonction de bibliothèque Octave.

L’estimation Prony a besoin du décalage pour être connue, mais si vous allez «assez loin» dans votre désintégration, vous avez une estimation raisonnable du décalage, vous pouvez donc simplement décaler les données pour placer le décalage à 0. En tout cas, Prony L'estimation est juste un moyen d'obtenir une estimation initiale raisonnable pour d'autres routines d'ajustement. 

3
Marcus P S

Je n'ai jamais eu Courbe_fit pour fonctionner correctement, comme vous dites que je ne veux rien deviner. J'essayais de simplifier l'exemple de Joe Kington et c'est ce que j'ai réussi à faire. L'idée est de traduire les données "bruyantes" en log, puis de les retralger et d'utiliser polyfit et polyval pour comprendre les paramètres:

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()

où xVals et yVals ne sont que des listes. 

1
Elendurwen

Je ne connais pas python, mais je connais un moyen simple d'estimer de manière non itérative les coefficients de décroissance exponentielle avec un décalage, à partir de trois points de données avec une différence fixe dans leurs coordonnées indépendantes. Vos points de données ont une différence fixe dans leur coordonnée indépendante (vos valeurs x sont espacées d'un intervalle de 60), donc ma méthode peut leur être appliquée. Vous pouvez sûrement traduire les maths en python.

Assumer

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

C = exp(-c)

Étant donné y_0, y_1, y_2, pour x = 0, 1, 2, nous résolvons 

y_0 = A + B
y_1 = A + B*C
y_2 = A + B*C^2

pour trouver A, B, C comme suit:

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)

L'exponentielle correspondante passe exactement par les trois points (0, y_0), (1, y_1) et (2, y_2). Si vos points de données ne sont pas aux coordonnées x 0, 1, 2 mais plutôt à k, k + s et k + 2 * s, alors 

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

afin que vous puissiez utiliser les formules ci-dessus pour trouver A, B, C, puis calculer

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

Les coefficients résultants sont très sensibles aux erreurs dans les coordonnées y, ce qui peut entraîner de grandes erreurs si vous extrapolez au-delà de la plage définie par les trois points de données utilisés. Il est donc préférable de calculer A, B, C à partir de trois points de données qui sont: aussi éloignés que possible (tout en maintenant une distance fixe entre eux).

Votre ensemble de données a 10 points de données équidistants. Choisissons les trois points de données (110, 2391), (350, 786), (590, 263) à utiliser - ils ont la plus grande distance fixe possible (240) dans la coordonnée indépendante. Donc, y_0 = 2391, y_1 = 786, y_2 = 263, k = 110, s = 240. Alors A = 10.20055, B = 2380,799, C = 0,3258567, A '= 10.20055, B' = 3980,329, C '= 0,9953388. L'exponentielle est

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

Vous pouvez utiliser cette exponentielle comme estimation initiale dans un algorithme d'ajustement non linéaire.

La formule pour calculer A est la même que celle utilisée par la transformation de Shanks ( http://en.wikipedia.org/wiki/Shanks_transformation ).

1
Louis Strous

Implémentation en python de la solution @ JJacquelin. J'avais besoin d'une solution approximative non résolue, sans suppositions initiales, donc la réponse de @JJacquelin a été vraiment utile. La question initiale était une requête numypy/scipy en python. J'ai pris le code Nice clean R de @ johanvdw et je l'ai remanié en python/numpy. J'espère utile à quelqu'un: 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]
0
FriendToGeoff

Si votre carie ne commence pas à 0, utilisez:

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

où x0 le début de la décroissance (où vous voulez commencer l'ajustement). Et utilisez à nouveau x0 pour tracer:

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

où la fonction est:

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