Come si adatta la curva esponenziale e logaritmica in Python? Ho trovato solo adattamento polinomiale


157

Ho un set di dati e voglio confrontare quale riga lo descrive meglio (polinomi di diversi ordini, esponenziali o logaritmici).

Uso Python e Numpy e per l'adattamento polinomiale c'è una funzione polyfit(). Ma non ho trovato tali funzioni per adattamento esponenziale e logaritmico.

Ci sono? O come risolverlo altrimenti?

Risposte:


222

Per inserire y = A + B log x , basta inserire y contro (log x ).

>>> x = numpy.array([1, 7, 20, 50, 79])
>>> y = numpy.array([10, 19, 30, 35, 51])
>>> numpy.polyfit(numpy.log(x), y, 1)
array([ 8.46295607,  6.61867463])
# y ≈ 8.46 log(x) + 6.62

Per il montaggio di y = Ae Bx , prendi il logaritmo di entrambi i lati per dare il log y = log A + Bx . Quindi adatta (registra y ) contro x .

Si noti che l'adattamento (log y ) come se fosse lineare enfatizzerà i valori piccoli di y , causando una grande deviazione per y grande . Questo perché polyfit(regressione lineare) funziona minimizzando ∑ iY ) 2 = ∑ i ( Y i - Ŷ i ) 2 . Quando Y i = log y i , i residui Δ Y i = Δ (log y i ) ≈ Δ y i / | y i |. Quindi anche sepolyfitprende una pessima decisione per il grande y , il "divide-by- | y |" fattore compenserà, causando polyfitfavori piccoli valori.

Ciò potrebbe essere alleviato dando ad ogni voce un "peso" proporzionale a y . polyfitsupporta i minimi quadrati ponderati tramite l' wargomento della parola chiave.

>>> x = numpy.array([10, 19, 30, 35, 51])
>>> y = numpy.array([1, 7, 20, 50, 79])
>>> numpy.polyfit(x, numpy.log(y), 1)
array([ 0.10502711, -0.40116352])
#    y ≈ exp(-0.401) * exp(0.105 * x) = 0.670 * exp(0.105 * x)
# (^ biased towards small values)
>>> numpy.polyfit(x, numpy.log(y), 1, w=numpy.sqrt(y))
array([ 0.06009446,  1.41648096])
#    y ≈ exp(1.42) * exp(0.0601 * x) = 4.12 * exp(0.0601 * x)
# (^ not so biased)

Si noti che Excel, LibreOffice e la maggior parte dei calcolatori scientifici utilizzano in genere la formula non ponderata (distorta) per le linee di regressione / tendenza esponenziali. Se vuoi che i tuoi risultati siano compatibili con queste piattaforme, non includere i pesi anche se fornisce risultati migliori.


Ora, se puoi usare scipy, puoi scipy.optimize.curve_fitadattarlo a qualsiasi modello senza trasformazioni.

Per y = A + B log x il risultato è lo stesso del metodo di trasformazione:

>>> x = numpy.array([1, 7, 20, 50, 79])
>>> y = numpy.array([10, 19, 30, 35, 51])
>>> scipy.optimize.curve_fit(lambda t,a,b: a+b*numpy.log(t),  x,  y)
(array([ 6.61867467,  8.46295606]), 
 array([[ 28.15948002,  -7.89609542],
        [ -7.89609542,   2.9857172 ]]))
# y ≈ 6.62 + 8.46 log(x)

Per y = Ae Bx , tuttavia, possiamo ottenere un adattamento migliore poiché calcola direttamente Δ (log y ). Ma dobbiamo fornire un'ipotesi di inizializzazione in modo da curve_fitpoter raggiungere il minimo locale desiderato.

>>> x = numpy.array([10, 19, 30, 35, 51])
>>> y = numpy.array([1, 7, 20, 50, 79])
>>> scipy.optimize.curve_fit(lambda t,a,b: a*numpy.exp(b*t),  x,  y)
(array([  5.60728326e-21,   9.99993501e-01]),
 array([[  4.14809412e-27,  -1.45078961e-08],
        [ -1.45078961e-08,   5.07411462e+10]]))
# oops, definitely wrong.
>>> scipy.optimize.curve_fit(lambda t,a,b: a*numpy.exp(b*t),  x,  y,  p0=(4, 0.1))
(array([ 4.88003249,  0.05531256]),
 array([[  1.01261314e+01,  -4.31940132e-02],
        [ -4.31940132e-02,   1.91188656e-04]]))
# y ≈ 4.88 exp(0.0553 x). much better.

confronto di regressione esponenziale


2
@Tomas: giusto. La modifica della base del registro moltiplica semplicemente una costante per registrare x o registrare y, il che non influisce su r ^ 2.
kennytm,

4
Ciò conferirà maggior peso ai valori con una piccola y. Quindi è meglio ponderare i contributi ai valori chi-quadrati di y_i
Rupert Nash

17
Questa soluzione è errata nel tradizionale senso di adattamento della curva. Non minimizzerà il quadrato sommato dei residui nello spazio lineare, ma nello spazio del registro. Come accennato in precedenza, ciò modifica efficacemente la ponderazione dei punti: le osservazioni in cui yè piccolo saranno sovrappesate artificialmente. È meglio definire la funzione (lineare, non la trasformazione del registro) e utilizzare un adattatore di curve o un minimizzatore.
santon,

3
@santon Risolto il pregiudizio nella regressione esponenziale.
kennytm,

2
Grazie per aver aggiunto il peso! Molte / la maggior parte delle persone non sa che puoi ottenere risultati comicamente negativi se provi a prendere semplicemente il registro (dati) e ad attraversarlo (come Excel). Come facevo da anni. Quando il mio insegnante bayesiano mi ha mostrato questo, ero tipo "Ma non insegnano nel modo [sbagliato] in fisica?" - "Sì, lo chiamiamo 'fisica del bambino', è una semplificazione. Questo è il modo corretto di farlo".
DeusXMachina,

102

È inoltre possibile montare un set di un dato a qualsiasi funzione ti piace usare curve_fitda scipy.optimize. Ad esempio, se si desidera adattare una funzione esponenziale (dalla documentazione ):

import numpy as np
import matplotlib.pyplot as plt
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)

E poi se vuoi tracciare, potresti fare:

plt.figure()
plt.plot(x, yn, 'ko', label="Original Noised Data")
plt.plot(x, func(x, *popt), 'r-', label="Fitted Curve")
plt.legend()
plt.show()

(Nota: il *davanti poptquando si traccia si espanderà i termini in a, be cche func. Si aspetta)


2
Bello. C'è un modo per verificare quanto ci siamo adattati? Valore R-quadrato? Esistono diversi parametri dell'algoritmo di ottimizzazione che puoi provare a ottenere una soluzione migliore (o più veloce)?
user391339

Per la bontà di adattamento, è possibile inserire i parametri ottimizzati adattati nella funzione di ottimizzazione scipy chisquare; restituisce 2 valori, il secondo dei quali è il valore p.

Qualche idea su come selezionare i parametri a, be c?
I_told_you_so

47

Ho avuto qualche problema con questo, quindi lasciatemi essere molto esplicito in modo che nessuno come me possa capire.

Diciamo che abbiamo un file di dati o qualcosa del genere

# -*- coding: utf-8 -*-

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

"""
Generate some data, let's imagine that you already have this. 
"""
x = np.linspace(0, 3, 50)
y = np.exp(x)

"""
Plot your data
"""
plt.plot(x, y, 'ro',label="Original Data")

"""
brutal force to avoid errors
"""    
x = np.array(x, dtype=float) #transform your data in a numpy array of floats 
y = np.array(y, dtype=float) #so the curve_fit can work

"""
create a function to fit with your data. a, b, c and d are the coefficients
that curve_fit will calculate for you. 
In this part you need to guess and/or use mathematical knowledge to find
a function that resembles your data
"""
def func(x, a, b, c, d):
    return a*x**3 + b*x**2 +c*x + d

"""
make the curve_fit
"""
popt, pcov = curve_fit(func, x, y)

"""
The result is:
popt[0] = a , popt[1] = b, popt[2] = c and popt[3] = d of the function,
so f(x) = popt[0]*x**3 + popt[1]*x**2 + popt[2]*x + popt[3].
"""
print "a = %s , b = %s, c = %s, d = %s" % (popt[0], popt[1], popt[2], popt[3])

"""
Use sympy to generate the latex sintax of the function
"""
xs = sym.Symbol('\lambda')    
tex = sym.latex(func(xs,*popt)).replace('$', '')
plt.title(r'$f(\lambda)= %s$' %(tex),fontsize=16)

"""
Print the coefficients and plot the funcion.
"""

plt.plot(x, func(x, *popt), label="Fitted Curve") #same as line above \/
#plt.plot(x, popt[0]*x**3 + popt[1]*x**2 + popt[2]*x + popt[3], label="Fitted Curve") 

plt.legend(loc='upper left')
plt.show()

il risultato è: a = 0.849195983017, b = -1.18101681765, c = 2.24061176543, d = 0.816643894816

Dati grezzi e funzione adattata


8
y = [np.exp(i) for i in x]è molto lento; uno dei motivi per cui Numpy è stato creato è che hai potuto scrivere y=np.exp(x). Inoltre, con quella sostituzione, puoi sbarazzarti della tua sezione di forza brutale. In ipython, c'è la %timeitmagia dalla quale In [27]: %timeit ylist=[exp(i) for i in x] 10000 loops, best of 3: 172 us per loop In [28]: %timeit yarr=exp(x) 100000 loops, best of 3: 2.85 us per loop
esci il

1
Grazie ammetto, hai ragione, ma la parte della forza brutale che devo ancora usare quando ho a che fare con i dati di un CSV, XLS o altri formati che ho affrontato usando questo algoritmo. Penso che il suo utilizzo abbia senso solo quando qualcuno sta cercando di adattare una funzione a partire da dati sperimentali o di simulazione, e nella mia esperienza questi dati sono sempre in formati strani.
Leandro,

3
x = np.array(x, dtype=float)dovrebbe consentirti di sbarazzarti della comprensione lenta dell'elenco.
Ajasja,

8

Beh, immagino che tu possa sempre usare:

np.log   -->  natural log
np.log10 -->  base 10
np.log2  -->  base 2

Modifica leggermente della risposta di IanVS :

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

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

x = np.linspace(1,5,50)   # changed boundary conditions to avoid division by 0
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)

plt.figure()
plt.plot(x, yn, 'ko', label="Original Noised Data")
plt.plot(x, func(x, *popt), 'r-', label="Fitted Curve")
plt.legend()
plt.show()

Ciò si traduce nel seguente grafico:

inserisci qui la descrizione dell'immagine


Esiste un valore di saturazione approssimativo dell'adattamento? In tal caso, come è possibile accedervi?
Ben

7

Ecco un'opzione di linearizzazione su dati semplici che utilizza strumenti di Scikit Learn .

Dato

import numpy as np

import matplotlib.pyplot as plt

from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import FunctionTransformer


np.random.seed(123)

# General Functions
def func_exp(x, a, b, c):
    """Return values from a general exponential function."""
    return a * np.exp(b * x) + c


def func_log(x, a, b, c):
    """Return values from a general log function."""
    return a * np.log(b * x) + c


# Helper
def generate_data(func, *args, jitter=0):
    """Return a tuple of arrays with random data along a general function."""
    xs = np.linspace(1, 5, 50)
    ys = func(xs, *args)
    noise = jitter * np.random.normal(size=len(xs)) + jitter
    xs = xs.reshape(-1, 1)                                  # xs[:, np.newaxis]
    ys = (ys + noise).reshape(-1, 1)
    return xs, ys
transformer = FunctionTransformer(np.log, validate=True)

Codice

Adatta dati esponenziali

# Data
x_samp, y_samp = generate_data(func_exp, 2.5, 1.2, 0.7, jitter=3)
y_trans = transformer.fit_transform(y_samp)             # 1

# Regression
regressor = LinearRegression()
results = regressor.fit(x_samp, y_trans)                # 2
model = results.predict
y_fit = model(x_samp)

# Visualization
plt.scatter(x_samp, y_samp)
plt.plot(x_samp, np.exp(y_fit), "k--", label="Fit")     # 3
plt.title("Exponential Fit")

inserisci qui la descrizione dell'immagine

Adatta i dati del registro

# Data
x_samp, y_samp = generate_data(func_log, 2.5, 1.2, 0.7, jitter=0.15)
x_trans = transformer.fit_transform(x_samp)             # 1

# Regression
regressor = LinearRegression()
results = regressor.fit(x_trans, y_samp)                # 2
model = results.predict
y_fit = model(x_trans)

# Visualization
plt.scatter(x_samp, y_samp)
plt.plot(x_samp, y_fit, "k--", label="Fit")             # 3
plt.title("Logarithmic Fit")

inserisci qui la descrizione dell'immagine


Dettagli

Passaggi generali

  1. Applicare un'operazione di log per i valori dei dati ( x, yo entrambi)
  2. Registra i dati in un modello linearizzato
  3. Traccia "invertendo" qualsiasi operazione di registro (con np.exp()) e adatta ai dati originali

Supponendo che i nostri dati seguano una tendenza esponenziale, un'equazione generale + può essere:

inserisci qui la descrizione dell'immagine

Possiamo linearizzare quest'ultima equazione (es. Y = intercetta + pendenza * x) prendendo il registro :

inserisci qui la descrizione dell'immagine

Data un'equazione linearizzata ++ e i parametri di regressione, abbiamo potuto calcolare:

  • Avia interccept ( ln(A))
  • Bvia pendenza ( B)

Riepilogo delle tecniche di linearizzazione

Relationship |  Example   |     General Eqn.     |  Altered Var.  |        Linearized Eqn.  
-------------|------------|----------------------|----------------|------------------------------------------
Linear       | x          | y =     B * x    + C | -              |        y =   C    + B * x
Logarithmic  | log(x)     | y = A * log(B*x) + C | log(x)         |        y =   C    + A * (log(B) + log(x))
Exponential  | 2**x, e**x | y = A * exp(B*x) + C | log(y)         | log(y-C) = log(A) + B * x
Power        | x**2       | y =     B * x**N + C | log(x), log(y) | log(y-C) = log(B) + N * log(x)

+ Nota: la linearizzazione delle funzioni esponenziali funziona meglio quando il rumore è piccolo e C = 0. Usare con cautela.

++ Nota: mentre l'alterazione di x data aiuta a linearizzare i dati esponenziali , l'alterazione di y data aiuta a linearizzare i dati di log .


0

Dimostriamo funzionalità lmfitdurante la risoluzione di entrambi i problemi.

Dato

import lmfit

import numpy as np

import matplotlib.pyplot as plt


%matplotlib inline
np.random.seed(123)

# General Functions
def func_log(x, a, b, c):
    """Return values from a general log function."""
    return a * np.log(b * x) + c


# Data
x_samp = np.linspace(1, 5, 50)
_noise = np.random.normal(size=len(x_samp), scale=0.06)
y_samp = 2.5 * np.exp(1.2 * x_samp) + 0.7 + _noise
y_samp2 = 2.5 * np.log(1.2 * x_samp) + 0.7 + _noise

Codice

Approccio 1 - lmfitModello

Adatta dati esponenziali

regressor = lmfit.models.ExponentialModel()                # 1    
initial_guess = dict(amplitude=1, decay=-1)                # 2
results = regressor.fit(y_samp, x=x_samp, **initial_guess)
y_fit = results.best_fit    

plt.plot(x_samp, y_samp, "o", label="Data")
plt.plot(x_samp, y_fit, "k--", label="Fit")
plt.legend()

inserisci qui la descrizione dell'immagine

Approccio 2 - Modello personalizzato

Adatta i dati del registro

regressor = lmfit.Model(func_log)                          # 1
initial_guess = dict(a=1, b=.1, c=.1)                      # 2
results = regressor.fit(y_samp2, x=x_samp, **initial_guess)
y_fit = results.best_fit

plt.plot(x_samp, y_samp2, "o", label="Data")
plt.plot(x_samp, y_fit, "k--", label="Fit")
plt.legend()

inserisci qui la descrizione dell'immagine


Dettagli

  1. Scegli una classe di regressione
  2. Fornitura denominata, ipotesi iniziali che rispettano il dominio della funzione

È possibile determinare i parametri dedotti dall'oggetto regressore. Esempio:

regressor.param_names
# ['decay', 'amplitude']

Nota: ExponentialModel()segue una funzione di decadimento , che accetta due parametri, uno dei quali è negativo.

inserisci qui la descrizione dell'immagine

Vedi anche ExponentialGaussianModel(), che accetta più parametri .

Installa la libreria tramite > pip install lmfit.


0

Wolfram ha una soluzione a forma chiusa per il montaggio di un esponenziale . Hanno anche soluzioni simili per l'adattamento di una legge logaritmica e di potere .

Ho scoperto che funziona meglio di curve_fit di Scipy. Ecco un esempio:

import numpy as np
import matplotlib.pyplot as plt

# Fit the function y = A * exp(B * x) to the data
# returns (A, B)
# From: https://mathworld.wolfram.com/LeastSquaresFittingExponential.html
def fit_exp(xs, ys):
    S_x2_y = 0.0
    S_y_lny = 0.0
    S_x_y = 0.0
    S_x_y_lny = 0.0
    S_y = 0.0
    for (x,y) in zip(xs, ys):
        S_x2_y += x * x * y
        S_y_lny += y * np.log(y)
        S_x_y += x * y
        S_x_y_lny += x * y * np.log(y)
        S_y += y
    #end
    a = (S_x2_y * S_y_lny - S_x_y * S_x_y_lny) / (S_y * S_x2_y - S_x_y * S_x_y)
    b = (S_y * S_x_y_lny - S_x_y * S_y_lny) / (S_y * S_x2_y - S_x_y * S_x_y)
    return (np.exp(a), b)


xs = [33, 34, 35, 36, 37, 38, 39, 40, 41, 42]
ys = [3187, 3545, 4045, 4447, 4872, 5660, 5983, 6254, 6681, 7206]

(A, B) = fit_exp(xs, ys)

plt.figure()
plt.plot(xs, ys, 'o-', label='Raw Data')
plt.plot(xs, [A * np.exp(B *x) for x in xs], 'o-', label='Fit')

plt.title('Exponential Fit Test')
plt.xlabel('X')
plt.ylabel('Y')
plt.legend(loc='best')
plt.tight_layout()
plt.show()

inserisci qui la descrizione dell'immagine

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.