2010-10-14 12 views
16

¿Alguien sabe un módulo scipy/numpy que permitirá ajustar la caída exponencial a los datos?adaptación decaimiento exponencial sin adivinación inicial

búsqueda de Google volvió a los pocos blogs, por ejemplo - http://exnumerus.blogspot.com/2010/04/how-to-fit-exponential-decay-example-in.html, pero esa solución requiere desfase y ser pre-especificada, lo cual no siempre es posible

EDIT:

obras curve_fit, pero puede fallar bastante miserablemente sin una conjetura inicial para los parámetros, y eso a veces es necesario. El código que estoy trabajando es

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

que funciona, pero si quitamos "P0 = adivinar", falla estrepitosamente.

+1

falla estrepitosamente debido a que el valor supuesto por omisión para p0 es [1,1,1]. El problema es que la segunda variable debe ser negativa. Si cambias tu función exp_decay para reflejar esto (usa np.exp (-x * t)) o usas p0 = [1, -1,1], supongo que funcionará. Estos métodos pueden tener problemas con los cambios de signo en las variables. –

Respuesta

37

Usted tiene dos opciones:

  1. linealizar el sistema, y ​​en forma de una línea en el registro de los datos.
  2. Usar un solucionador no lineal (por ejemplo scipy.optimize.curve_fit

La primera opción es, con mucho, el más rápido y más robusto. Sin embargo, se requiere que se conozca la ordenada en el desplazamiento a priori, de lo contrario es imposible linealizar la ecuación. (es decir y = A * exp(K * t) puede ser linealizado mediante el ajuste de y = log(A * exp(K * t)) = K * t + log(A), pero y = A*exp(K*t) + C sólo puede ser linealizado mediante el ajuste de y - C = K*t + log(A), y como y es la variable independiente, C debe ser conocida de antemano para que esto sea un sistema lineal.

Si usa un método no lineal, es a) no guara nteed para converger y producir una solución, b) será mucho más lento, c) da una estimación mucho más pobre de la incertidumbre en sus parámetros, yd) a menudo es mucho menos precisa. Sin embargo, un método no lineal tiene una gran ventaja sobre una inversión lineal: puede resolver un sistema no lineal de ecuaciones. En su caso, esto significa que no tiene que saber C de antemano.

Sólo para dar un ejemplo, vamos a resolver para y = A * exp (K * t) con algunos datos ruidosos utilizando tanto lineal y métodos no lineales:

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

Nota que el lineal la solución proporciona un resultado mucho más cercano a los valores reales. Sin embargo, tenemos que proporcionar el valor de desplazamiento y para usar una solución lineal. La solución no lineal no requiere este conocimiento a priori.

+0

¡Gracias! Desafortunadamente, el problema con curve_fit es que puede fallar miserablemente si no se proporciona una conjetura inicial para los parámetros –

+0

@cheshire: ese es un hecho matemático de la vida cuando se usa cualquier solución no lineal. No hay una "mejor" manera de evitarlo, aunque algunos métodos no lineales funcionarán mejor que otros para su problema en particular.En tu caso, puedes especificar el jacobiano, que te ayudará enormemente en esta situación. También puede usar los datos de entrada para hacer una estimación inteligente de los parámetros de inicio. –

+0

He usado QtiPlot anteriormente, y encaja muy bien sin ninguna adivinación inicial –

7

Usaría la función scipy.optimize.curve_fit. La cadena de documentación para que incluso tiene un ejemplo de instalación de un decaimiento exponencial en él, que voy a copiar aquí:

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

Los parámetros ajustados pueden variar debido al ruido aleatorio añadido, pero tengo 2.47990495, 1.40709306, 0.53753635 como a, b, y c, así que no está tan mal con el ruido que hay. Si ajusto a y en lugar de yn obtengo los valores exactos a, byc.

+0

¡Me ganaste! ¡Eso es lo que obtengo por tomarme tanto tiempo para escribir un ejemplo! :) También dejaré el mío, ya que explica un poco sobre los pros y los contras ... –

1

La forma correcta de hacerlo es hacer una estimación de Prony y usar el resultado como la conjetura inicial para el ajuste de mínimos cuadrados (o alguna otra rutina de ajuste más robusta). La estimación de prisiones no necesita una estimación inicial, pero sí necesita muchos puntos para arrojar una buena estimación.

He aquí un resumen

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

En esta octava se implementa como expfit, para que pueda escribir su propia rutina basada en la función de la biblioteca de octava.

La estimación de prisiones necesita que se conozca el desplazamiento, pero si avanza "lo suficiente" en su desintegración, tiene una estimación razonable del desplazamiento, por lo que puede cambiar los datos para colocar el desplazamiento en 0. cualquier tasa, la estimación de Prony es solo una forma de obtener una estimación inicial razonable para otras rutinas de adaptación.

+0

En realidad, para la estimación de Prony y los métodos relacionados (ESPRIT, MUSIC) no es necesario conocer la compensación. Siempre que tenga suficientes muestras, el algoritmo puede inferir el desplazamiento. –

0

Nunca obtuve curve_fit para que funcione correctamente, como dices, no quiero adivinar nada. Estaba tratando de simplificar el ejemplo de Joe Kington y esto es lo que obtuve trabajando. La idea es traducir los datos 'ruidoso' en registro y luego transalte de nuevo y utilizar polyfit y polyval de averiguar los parámetros:

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

donde xVals y yVals son sólo listas.

0

No conozco python, pero conozco una manera simple de estimar de forma no iterativa los coeficientes de decaimiento exponencial con un desplazamiento, dados tres puntos de datos con una diferencia fija en su coordenada independiente. Sus puntos de datos tienen una diferencia fija en su coordenada independiente (sus valores de x están espaciados en un intervalo de 60), por lo que mi método puede aplicarse a ellos. Seguramente podrás traducir las matemáticas a python.

Supongamos

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

donde C = exp(-c)

Dada y_0, y_1, y_2, para x = 0, 1, 2, resolvemos

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

para encontrar A, B, C de la siguiente manera:

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) 

La exponencial correspondiente pasa exactamente por los tres puntos (0, y_0), (1, y_1) y (2, y_2). Si los puntos de datos no están en coordenadas x 0, 1, 2, sino más bien en la k, k + s, y k + 2 * s, luego

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

para que pueda utilizar las fórmulas anteriores para encontrar A, B , C y luego se calculan

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

los coeficientes resultantes son muy sensibles a los errores en las coordenadas y, lo que puede llevar a grandes errores si se extrapola más allá del rango definido por los tres puntos de datos utilizados, por lo que es mejor calcule A, B, C a partir de tres puntos de datos que estén lo más separados posible (aunque tengan una distancia fija entre ellos).

Su conjunto de datos tiene 10 puntos de datos equidistantes. Vamos a elegir los tres puntos de datos (110, 2391), (350, 786), (590, 263) para su uso: estos tienen la mayor distancia posible fija (240) en la coordenada independiente. Entonces, y_0 = 2391, y_1 = 786, y_2 = 263, k = 110, s = 240. Entonces A = 10.20055, B = 2380.799, C = 0.3258567, A '= 10.20055, B' = 3980.329, C '= 0.9953388.La exponencial es

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

Puede usar esta exponencial como la conjetura inicial en un algoritmo de ajuste no lineal.

La fórmula para calcular A es la misma que la utilizada por la transformación Shanks (http://en.wikipedia.org/wiki/Shanks_transformation).

4

Procedimiento para ajuste exponencial sin inicial adivinar proceso no iterativo:

enter image description here

Esto viene de la de papel (pp.16-17): https://fr.scribd.com/doc/14674814/Regressions-et-equations-integrales

Si es necesario, esto se puede utilizar para inicializar un cálculo de regresión no lineal para elegir un criterio específico de optimización.

Ejemplo:

El ejemplo dado por Joe Kington es interesante. Lamentablemente, no se muestran los datos, solo el gráfico. Entonces, los datos (x, y) a continuación provienen de un escaneo gráfico del gráfico y como consecuencia los valores numéricos probablemente no sean exactamente los utilizados por Joe Kington. Sin embargo, las respectivas ecuaciones de las curvas "ajustadas" son muy próximas entre sí, considerando la amplia dispersión de los puntos.

enter image description here

La figura superior es la copia de la gráfica de la Kington.

La figura inferior muestra los resultados obtenidos con el procedimiento presentado anteriormente.

+0

¡impresionante! ¿esa es tu investigación? –

+0

@ George Karpenkov: No realmente. De hecho, necesitaba una herramienta simple y confiable para ajustar algunas funciones a datos experimentales. Este método fue desarrollado para eso. Esto fue hace mucho tiempo en este campo de investigación: https://fr.scribd.com/doc/23155389/Theoretical-Impedance-of-Capacitive-Electrodes – JJacquelin

+0

Para los interesados, he implementado este método en R: https: //gist.github.com/johanvdw/443a820a7f4ffa7e9f8997481d7ca8b3 – johanvdw

0

Si su descomposición no se inicia desde 0 uso:

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

donde x0 el inicio de la decadencia (en la que desea iniciar el ajuste). Y luego otra vez utilizar x0 para el trazado:

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

donde la función es:

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

implementación de Python de la solución de @ JJacquelin. Necesitaba una solución aproximada sin solución, sin conjeturas iniciales, por lo que la respuesta de @JJacquelin fue realmente útil. La pregunta original se planteó como una petición de python numpy/scipy. Tomé el código R limpio de @ johanvdw y lo refactoricé como python/numpy. Espero sea de utilidad a alguien: 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] 
Cuestiones relacionadas