200 votes

Comment faire un ajustement de courbe exponentiel et logarithmique en Python ? Je n'ai trouvé que l'ajustement polynomial

J'ai un ensemble de données et je veux comparer quelle ligne la décrit le mieux (polynômes de différents ordres, exponentielle ou logarithmique).

J'utilise Python et Numpy et pour l'ajustement polynomial il y a une fonction polyfit(). Mais je n'ai trouvé aucune fonction similaire pour l'ajustement exponentiel et logarithmique.

Y a-t-il des fonctions disponibles pour cela? Ou comment résoudre autrement ce problème?

283voto

KennyTM Points 232647

Pour ajuster y = A + B log x, ajustez simplement y par rapport à (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

Pour ajuster y = _Ae_Bx, prendre le logarithme des deux côtés donne log y = log A + Bx. Donc ajustez (log y) par rapport à x.

Remarquez que l'ajustement de (log y) comme s'il était linéaire accentuera les petites valeurs de y, provoquant de fortes déviations pour de grandes valeurs de y. Cela est dû au fait que polyfit (régression linéaire) fonctionne en minimisant ∑<em>i</em>Y)2 = ∑<em>i</em> (YiŶ<em>i</em>)2. Lorsque Y<em>i</em> = log y<em>i</em>, les résidus ΔY<em>i</em> = Δ(log y<em>i</em>) ≈ Δy<em>i</em> / |y<em>i</em>|. Ainsi, même si polyfit prend une très mauvaise décision pour de grandes valeurs de y, le facteur "divisé par |y|" compensera, faisant en sorte que polyfit favorise les petites valeurs.

Cela pourrait être atténué en donnant à chaque entrée un "poids" proportionnel à y. polyfit prend en charge les moindres carrés pondérés via l'argument mot-clé w.

>>> 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)
# (^ biaisé vers les petites valeurs)
>>> 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)
# (^ moins biaisé)

Notez qu'Excel, LibreOffice et la plupart des calculatrices scientifiques utilisent généralement la formule non pondérée (biaisée) pour la régression exponentielle / les lignes de tendance. Si vous voulez que vos résultats soient compatibles avec ces plateformes, n'incluez pas les poids même s'ils offrent de meilleurs résultats.


Maintenant, si vous pouvez utiliser scipy, vous pourriez utiliser scipy.optimize.curve_fit pour ajuster n'importe quel modèle sans transformations.

Pour y = A + B log x, le résultat est le même que la méthode de transformation :

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

Pour y = _Ae_Bx, cependant, nous pouvons obtenir un meilleur ajustement car il calcule directement Δ(log y). Mais nous devons fournir une estimation initiale pour que curve_fit puisse atteindre le minimum local désiré.

>>> 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]]))
# oups, complètement faux.
>>> 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). bien mieux.

comparaison de la régression exponentielle

0 votes

Merci, c'est parfait, mais comment trouver la base du logarithme qui convient le mieux?

1 votes

@Tomas: Généralement le logarithme naturel, mais tout logarithme fonctionne. N'oubliez pas que si vous utilisez la base K, alors l'équation devient y = A*K^(Bx).

0 votes

Alors la qualité de l'ajustement (par exemple R2) ne dépend pas de la base du logarithme? Merci encore une fois, les réponses sont parfaites, très utiles, je vous donnerai un point dès que j'atteindrai une réputation suffisante.

117voto

IanVS Points 268

Vous pouvez également adapter un ensemble de données à n'importe quelle fonction que vous aimez en utilisant curve_fit de scipy.optimize. Par exemple, si vous voulez ajuster une fonction exponentielle (à partir de la documentation):

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)

Ensuite, si vous voulez tracer, vous pourriez faire:

plt.figure()
plt.plot(x, yn, 'ko', label="Données d'origine bruitées")
plt.plot(x, func(x, *popt), 'r-', label="Courbe ajustée")
plt.legend()
plt.show()

(Remarque : le * devant popt lors du tracé va étendre les termes dans les a, b et c attendus par func.)

4 votes

Agréable. Y a-t-il un moyen de vérifier à quel point nous nous sommes bien adaptés ? Valeur R carré ? Y a-t-il différents paramètres d'algorithmes d'optimisation que vous pouvez essayer pour obtenir une solution meilleure (ou plus rapide) ?

0 votes

Pour la qualité d'ajustement, vous pouvez insérer les paramètres optimisés ajustés dans la fonction de l'optimisation scipy chisquare ; elle renvoie 2 valeurs, dont la deuxième est la valeur p.

1 votes

Avez-vous une idée de comment sélectionner les paramètres a, b et c ?

56voto

Leandro Points 121

J'avais des problèmes avec ça alors laisse-moi être très explicite pour que des débutants comme moi puissent comprendre.

Disons que nous avons un fichier de données ou quelque chose comme ça

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

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

"""
Générer des données, imaginons que vous les avez déjà. 
"""
x = np.linspace(0, 3, 50)
y = np.exp(x)

"""
Tracez vos données
"""
plt.plot(x, y, 'ro',label="Données originales")

"""
Force brute pour éviter les erreurs
"""    
x = np.array(x, dtype=float) #transformer vos données en un tableau numpy de floats 
y = np.array(y, dtype=float) #afin que curve_fit puisse fonctionner

"""
créer une fonction pour s'adapter à vos données. a, b, c et d sont les coefficients
que curve_fit calculera pour vous. 
Dans cette partie, vous devez deviner et/ou utiliser des connaissances mathématiques pour trouver
une fonction qui ressemble à vos données
"""
def func(x, a, b, c, d):
    return a*x**3 + b*x**2 +c*x + d

"""
effectuer le curve_fit
"""
popt, pcov = curve_fit(func, x, y)

"""
Le résultat est :
popt[0] = a , popt[1] = b, popt[2] = c et popt[3] = d de la fonction,
donc 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])

"""
Utilisez sympy pour générer la syntaxe latex de la fonction
"""
xs = sym.Symbol('\lambda')    
tex = sym.latex(func(xs,*popt)).replace('$', '')
plt.title(r'$f(\lambda)= %s$' %(tex),fontsize=16)

"""
Imprimez les coefficients et tracez la fonction.
"""

plt.plot(x, func(x, *popt), label="Courbe ajustée") #pareil que la ligne ci-dessus \/
#plt.plot(x, popt[0]*x**3 + popt[1]*x**2 + popt[2]*x + popt[3], label="Courbe ajustée") 

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

le résultat est : a = 0.849195983017 , b = -1.18101681765, c = 2.24061176543, d = 0.816643894816

Données brutes et fonction ajustée

9 votes

y = [np.exp(i) for i in x] est très lent; une raison pour laquelle numpy a été créé était pour que vous puissiez écrire y=np.exp(x). De plus, avec ce remplacement, vous pouvez vous débarrasser de votre section de force brute. Dans ipython, il y a la magie %timeit à partir de laquelle In [27]: %timeit ylist=[exp(i) for i in x] 10000 boucles, meilleur de 3: 172 us par boucle In [28]: %timeit yarr=exp(x) 100000 boucles, meilleur de 3: 2.85 us par boucle

1 votes

Merci esmit, tu as raison, mais la partie de la force brute que j'ai encore besoin d'utiliser, c'est lorsque je travaille avec des données issues d'un fichier csv, xls ou d'autres formats auxquels j'ai été confronté en utilisant cet algorithme. Je pense que son utilisation n'a de sens que lorsque quelqu'un essaie d'ajuster une fonction à partir de données expérimentales ou de simulation, et dans mon expérience, ces données sont toujours présentées dans des formats étranges.

3 votes

x = np.array(x, dtype=float) devrait vous permettre de vous débarrasser de la lente compréhension de liste.

12voto

pylang Points 12013

Voici une option de linéarisation sur des données simples qui utilise des outils de scikit learn.

Données

import numpy as np

import matplotlib.pyplot as plt

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

np.random.seed(123)

# Fonctions Générales
def func_exp(x, a, b, c):
    """Retourne des valeurs d'une fonction exponentielle générale."""
    return a * np.exp(b * x) + c

def func_log(x, a, b, c):
    """Retourne des valeurs d'une fonction logarithmique générale."""
    return a * np.log(b * x) + c

# Assistant
def generate_data(func, *args, jitter=0):
    """Retourne un tuple de tableaux avec des données aléatoires le long d'une fonction générale."""
    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)

Code

Ajuster les données exponentielles

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

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

# Visualisation
plt.scatter(x_samp, y_samp)
plt.plot(x_samp, np.exp(y_fit), "k--", label="Ajustement")     # 3
plt.title("Ajustement Exponentiel")

description de l'image ici

Ajuster les données logarithmiques

# Données
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

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

# Visualisation
plt.scatter(x_samp, y_samp)
plt.plot(x_samp, y_fit, "k--", label="Ajustement")             # 3
plt.title("Ajustement Logarithmique")

description de l'image ici


Détails

Étapes Générales

  1. Appliquer une opération logarithmique aux valeurs des données (x, y ou les deux)
  2. Régresser les données pour un modèle linéarisé
  3. Tracer en "inversant" les opérations logarithmiques (avec np.exp()) et s'adapter aux données initiales

En supposant que nos données suivent une tendance exponentielle, une équation générale+ pourrait être :

description de l'image ici

Nous pouvons linéariser cette dernière équation (par exemple y = intercept + pente * x) en prenant le log :

description de l'image ici

Étant donné une équation linéarisée++ et les paramètres de régression, nous pourrions calculer :

  • A via l'intercept (ln(A))
  • B via la pente (B)

Résumé des Techniques de Linéarisation

Relation |  Exemple   |     Eqn. Générale     |  Variable Modifiée |        Eqn. Linéarisée  
-------------|------------|----------------------|----------------|------------------------------------------
Linéaire       | x          | y =     B * x    + C | -              |        y =   C    + B * x
Logarithmique  | log(x)     | y = A * log(B*x) + C | log(x)         |        y =   C    + A * (log(B) + log(x))
Exponentielle  | 2**x, e**x | y = A * exp(B*x) + C | log(y)         | log(y-C) = log(A) + B * x
Puissance        | x**2       | y =     B * x**N + C | log(x), log(y) | log(y-C) = log(B) + N * log(x)

<sup>+</sup>Remarque : linéariser les fonctions exponentielles fonctionne mieux lorsque le bruit est faible et C=0. Utiliser avec précaution.

<sup>++</sup>Remarque : bien que la modification des données x aide à linéariser les données <em>exponentielles</em>, la modification des données y aide à linéariser les données <em>logarithmiques</em>.

10voto

murphy1310 Points 346

Eh bien, je suppose que vous pouvez toujours utiliser :

np.log   -->  logarithme naturel
np.log10 -->  base 10
np.log2  -->  base 2

En modifiant légèrement la réponse de 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)   # conditions aux limites modifiées pour éviter la division par 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="Données bruitées originales")
plt.plot(x, func(x, *popt), 'r-', label="Courbe ajustée")
plt.legend()
plt.show()

Cela donne le graphique suivant :

description de l'image

0 votes

Y a-t-il une valeur de saturation que l'ajustement approxime ? Si oui, comment y accéder ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X