web-dev-qa-db-fra.com

Obtention d'erreurs standard sur les paramètres ajustés à l'aide de la méthode optimise.leastsq dans python

J'ai un ensemble de données (déplacement en fonction du temps) que j'ai ajusté à quelques équations en utilisant la méthode optimise.leastsq. Je cherche maintenant à obtenir des valeurs d'erreur sur les paramètres ajustés. En parcourant la documentation, la matrice produite est la matrice jacobienne, et je dois la multiplier par la matrice résiduelle pour obtenir mes valeurs. Malheureusement je ne suis pas statisticien donc je me noie un peu dans la terminologie.

D'après ce que je comprends, tout ce dont j'ai besoin est la matrice de covariance qui va avec mes paramètres ajustés, donc je peux enraciner les éléments diagonaux pour obtenir mon erreur standard sur les paramètres ajustés. J'ai un vague souvenir de lecture que la matrice de covariance est de toute façon la sortie de la méthode optimise.leastsq. Est-ce correct? Sinon, comment feriez-vous pour obtenir la matrice résiduelle pour multiplier le jacobien produit pour obtenir ma matrice de covariance?

Toute aide serait grandement appréciée. Je suis très nouveau à python et je m'excuse donc si la question s'avère être une question de base.

le code de montage est le suivant:

fitfunc = lambda p, t: p[0]+p[1]*np.log(t-p[2])+ p[3]*t # Target function'

errfunc = lambda p, t, y: (fitfunc(p, t) - y)# Distance to the target function

p0 = [ 1,1,1,1] # Initial guess for the parameters


  out = optimize.leastsq(errfunc, p0[:], args=(t, disp,), full_output=1)

Les arguments t et disp sont et un tableau de valeurs de temps et de déplacement (essentiellement 2 colonnes de données). J'ai importé tout le nécessaire en haut du code. Les valeurs ajustées et la matrice fournies par la sortie sont les suivantes:

[  7.53847074e-07   1.84931494e-08   3.25102795e+01  -3.28882437e-11]

[[  3.29326356e-01  -7.43957919e-02   8.02246944e+07   2.64522183e-04]
 [ -7.43957919e-02   1.70872763e-02  -1.76477289e+07  -6.35825520e-05]
 [  8.02246944e+07  -1.76477289e+07   2.51023348e+16   5.87705672e+04]
 [  2.64522183e-04  -6.35825520e-05   5.87705672e+04   2.70249488e-07]]

Je soupçonne que l'ajustement est de toute façon un peu suspect pour le moment. Cela sera confirmé lorsque je pourrai éliminer les erreurs.

34
Phil

Mis à jour le 4/6/2016

Obtenir les erreurs correctes dans les paramètres d'ajustement peut être subtil dans la plupart des cas.

Pensons à ajuster une fonction y=f(x) pour laquelle vous avez un ensemble de points de données (x_i, y_i, yerr_i), Où i est un index qui parcourt chacun de vos points de données.

Dans la plupart des mesures physiques, l'erreur yerr_i Est une incertitude systématique de l'appareil ou de la procédure de mesure, et elle peut donc être considérée comme une constante qui ne dépend pas de i.

Quelle fonction d'ajustement utiliser et comment obtenir les erreurs de paramètres?

La méthode optimize.leastsq Renverra la matrice de covariance fractionnaire. En multipliant tous les éléments de cette matrice par la variance résiduelle (c'est-à-dire le chi carré réduit) et en prenant la racine carrée des éléments diagonaux, vous obtiendrez une estimation de l'écart-type des paramètres d'ajustement. J'ai inclus le code pour le faire dans l'une des fonctions ci-dessous.

D'un autre côté, si vous utilisez optimize.curvefit, La première partie de la procédure ci-dessus (multipliant par le chi réduit au carré) est faite pour vous en arrière-plan. Vous devez ensuite prendre la racine carrée des éléments diagonaux de la matrice de covariance pour obtenir une estimation de l'écart-type des paramètres d'ajustement.

De plus, optimize.curvefit Fournit des paramètres facultatifs pour traiter des cas plus généraux, où la valeur yerr_i Est différente pour chaque point de données. De la documentation :

sigma : None or M-length sequence, optional
    If not None, the uncertainties in the ydata array. These are used as
    weights in the least-squares problem
    i.e. minimising ``np.sum( ((f(xdata, *popt) - ydata) / sigma)**2 )``
    If None, the uncertainties are assumed to be 1.

absolute_sigma : bool, optional
    If False, `sigma` denotes relative weights of the data points.
    The returned covariance matrix `pcov` is based on *estimated*
    errors in the data, and is not affected by the overall
    magnitude of the values in `sigma`. Only the relative
    magnitudes of the `sigma` values matter.

Comment puis-je être sûr que mes erreurs sont correctes?

La détermination d'une estimation correcte de l'erreur standard dans les paramètres ajustés est un problème statistique compliqué. Les résultats de la matrice de covariance, telle qu'implémentée par optimize.curvefit Et optimize.leastsq Reposent en fait sur des hypothèses concernant la distribution de probabilité des erreurs et les interactions entre les paramètres; les interactions qui peuvent exister, selon votre fonction d'ajustement spécifique f(x).

À mon avis, la meilleure façon de traiter une f(x) compliquée est d'utiliser la méthode bootstrap, décrite dans ce lien .

Voyons quelques exemples

Tout d'abord, un code passe-partout. Définissons une fonction de ligne ondulée et générons des données avec des erreurs aléatoires. Nous allons générer un ensemble de données avec une petite erreur aléatoire.

import numpy as np
from scipy import optimize
import random

def f( x, p0, p1, p2):
    return p0*x + 0.4*np.sin(p1*x) + p2

def ff(x, p):
    return f(x, *p)

# These are the true parameters
p0 = 1.0
p1 = 40
p2 = 2.0

# These are initial guesses for fits:
pstart = [
    p0 + random.random(),
    p1 + 5.*random.random(), 
    p2 + random.random()
]

%matplotlib inline
import matplotlib.pyplot as plt
xvals = np.linspace(0., 1, 120)
yvals = f(xvals, p0, p1, p2)

# Generate data with a bit of randomness
# (the noise-less function that underlies the data is shown as a blue line)

xdata = np.array(xvals)
np.random.seed(42)
err_stdev = 0.2
yvals_err =  np.random.normal(0., err_stdev, len(xdata))
ydata = f(xdata, p0, p1, p2) + yvals_err

plt.plot(xvals, yvals)
plt.plot(xdata, ydata, 'o', mfc='None')

fig01

Maintenant, ajustons la fonction en utilisant les différentes méthodes disponibles:

`optimiser.leastsq`

def fit_leastsq(p0, datax, datay, function):

    errfunc = lambda p, x, y: function(x,p) - y

    pfit, pcov, infodict, errmsg, success = \
        optimize.leastsq(errfunc, p0, args=(datax, datay), \
                          full_output=1, epsfcn=0.0001)

    if (len(datay) > len(p0)) and pcov is not None:
        s_sq = (errfunc(pfit, datax, datay)**2).sum()/(len(datay)-len(p0))
        pcov = pcov * s_sq
    else:
        pcov = np.inf

    error = [] 
    for i in range(len(pfit)):
        try:
          error.append(np.absolute(pcov[i][i])**0.5)
        except:
          error.append( 0.00 )
    pfit_leastsq = pfit
    perr_leastsq = np.array(error) 
    return pfit_leastsq, perr_leastsq 

pfit, perr = fit_leastsq(pstart, xdata, ydata, ff)

print("\n# Fit parameters and parameter errors from lestsq method :")
print("pfit = ", pfit)
print("perr = ", perr)


# Fit parameters and parameter errors from lestsq method :
pfit =  [  1.04951642  39.98832634   1.95947613]
perr =  [ 0.0584024   0.10597135  0.03376631]


`optimiser.curve_fit`

def fit_curvefit(p0, datax, datay, function, yerr=err_stdev, **kwargs):
    """
    Note: As per the current documentation (Scipy V1.1.0), sigma (yerr) must be:
        None or M-length sequence or MxM array, optional
    Therefore, replace:
        err_stdev = 0.2
    With:
        err_stdev = [0.2 for item in xdata]
    Or similar, to create an M-length sequence for this example.
    """
    pfit, pcov = \
         optimize.curve_fit(f,datax,datay,p0=p0,\
                            sigma=yerr, epsfcn=0.0001, **kwargs)
    error = [] 
    for i in range(len(pfit)):
        try:
          error.append(np.absolute(pcov[i][i])**0.5)
        except:
          error.append( 0.00 )
    pfit_curvefit = pfit
    perr_curvefit = np.array(error)
    return pfit_curvefit, perr_curvefit 

pfit, perr = fit_curvefit(pstart, xdata, ydata, ff)

print("\n# Fit parameters and parameter errors from curve_fit method :")
print("pfit = ", pfit)
print("perr = ", perr)


# Fit parameters and parameter errors from curve_fit method :
pfit =  [  1.04951642  39.98832634   1.95947613]
perr =  [ 0.0584024   0.10597135  0.03376631]


`bootstrap`

def fit_bootstrap(p0, datax, datay, function, yerr_systematic=0.0):

    errfunc = lambda p, x, y: function(x,p) - y

    # Fit first time
    pfit, perr = optimize.leastsq(errfunc, p0, args=(datax, datay), full_output=0)


    # Get the stdev of the residuals
    residuals = errfunc(pfit, datax, datay)
    sigma_res = np.std(residuals)

    sigma_err_total = np.sqrt(sigma_res**2 + yerr_systematic**2)

    # 100 random data sets are generated and fitted
    ps = []
    for i in range(100):

        randomDelta = np.random.normal(0., sigma_err_total, len(datay))
        randomdataY = datay + randomDelta

        randomfit, randomcov = \
            optimize.leastsq(errfunc, p0, args=(datax, randomdataY),\
                             full_output=0)

        ps.append(randomfit) 

    ps = np.array(ps)
    mean_pfit = np.mean(ps,0)

    # You can choose the confidence interval that you want for your
    # parameter estimates: 
    Nsigma = 1. # 1sigma gets approximately the same as methods above
                # 1sigma corresponds to 68.3% confidence interval
                # 2sigma corresponds to 95.44% confidence interval
    err_pfit = Nsigma * np.std(ps,0) 

    pfit_bootstrap = mean_pfit
    perr_bootstrap = err_pfit
    return pfit_bootstrap, perr_bootstrap 

pfit, perr = fit_bootstrap(pstart, xdata, ydata, ff)

print("\n# Fit parameters and parameter errors from bootstrap method :")
print("pfit = ", pfit)
print("perr = ", perr)


# Fit parameters and parameter errors from bootstrap method :
pfit =  [  1.05058465  39.96530055   1.96074046]
perr =  [ 0.06462981  0.1118803   0.03544364]


Observations

Nous commençons déjà à voir quelque chose d'intéressant, les paramètres et les estimations d'erreur pour les trois méthodes sont presque d'accord. Ça c'est bon!

Supposons maintenant que nous voulions dire aux fonctions d'ajustement qu'il existe une autre incertitude dans nos données, peut-être une incertitude systématique qui contribuerait à une erreur supplémentaire de vingt fois la valeur de err_stdev. C'est beaucoup d'erreur, en fait, si nous simulons certaines données avec ce type d'erreur, cela ressemblerait à ceci:

enter image description here

Il n'y a certainement aucun espoir que nous pourrions récupérer les paramètres d'ajustement avec ce niveau de bruit.

Pour commencer, réalisons que leastsq ne nous permet même pas de saisir cette nouvelle information d'erreur systématique. Voyons ce que fait curve_fit Lorsque nous lui parlons de l'erreur:

pfit, perr = fit_curvefit(pstart, xdata, ydata, ff, yerr=20*err_stdev)

print("\nFit parameters and parameter errors from curve_fit method (20x error) :")
print("pfit = ", pfit)
print("perr = ", perr)


Fit parameters and parameter errors from curve_fit method (20x error) :
pfit =  [  1.04951642  39.98832633   1.95947613]
perr =  [ 0.0584024   0.10597135  0.03376631]

Quoi? Cela doit certainement être faux!

C'était la fin de l'histoire, mais récemment curve_fit A ajouté le paramètre facultatif absolute_sigma:

pfit, perr = fit_curvefit(pstart, xdata, ydata, ff, yerr=20*err_stdev, absolute_sigma=True)

print("\n# Fit parameters and parameter errors from curve_fit method (20x error, absolute_sigma) :")
print("pfit = ", pfit)
print("perr = ", perr)


# Fit parameters and parameter errors from curve_fit method (20x error, absolute_sigma) :
pfit =  [  1.04951642  39.98832633   1.95947613]
perr =  [ 1.25570187  2.27847504  0.72600466]

C'est un peu mieux, mais toujours un peu louche. curve_fit Pense que nous pouvons obtenir un ajustement de ce signal bruyant, avec un niveau d'erreur de 10% dans le paramètre p1. Voyons ce que bootstrap a à dire:

pfit, perr = fit_bootstrap(pstart, xdata, ydata, ff, yerr_systematic=20.0)

print("\nFit parameters and parameter errors from bootstrap method (20x error):")
print("pfit = ", pfit)
print("perr = ", perr)


Fit parameters and parameter errors from bootstrap method (20x error):
pfit =  [  2.54029171e-02   3.84313695e+01   2.55729825e+00]
perr =  [  6.41602813  13.22283345   3.6629705 ]

Ah, c'est peut-être une meilleure estimation de l'erreur dans notre paramètre d'ajustement. bootstrap pense qu'il connaît p1 avec une incertitude d'environ 34%.

Sommaire

optimize.leastsq Et optimize.curvefit Nous fournissent un moyen d'estimer les erreurs dans les paramètres ajustés, mais nous ne pouvons pas simplement utiliser ces méthodes sans les remettre en question un peu. Le bootstrap est une méthode statistique qui utilise la force brute, et à mon avis, elle a tendance à mieux fonctionner dans des situations qui peuvent être plus difficiles à interpréter.

Je recommande fortement de regarder un problème particulier et d'essayer curvefit et bootstrap. S'ils sont similaires, alors curvefit est beaucoup moins cher à calculer, donc vaut probablement la peine d'être utilisé. S'ils diffèrent considérablement, mon argent serait sur le bootstrap.

74
Pedro M Duarte

J'ai trouvé votre question en essayant de répondre à ma propre question question . Réponse courte. Le cov_x que les sorties des moindres carrés devraient être multipliées par la variance résiduelle. c'est à dire.

s_sq = (func(popt, args)**2).sum()/(len(ydata)-len(p0))
pcov = pcov * s_sq

un péché curve_fit.py. En effet, lesssq génère la matrice de covariance fractionnaire. Mon gros problème était que la variance résiduelle apparaît comme autre chose lors de la recherche sur Google.

La variance résiduelle est simplement réduite du chi carré de votre ajustement.

10
HansHarhoff

Il est possible de calculer exactement les erreurs en cas de régression linéaire. Et en effet, la fonction lesssq donne des valeurs différentes:

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

A = 1.353
B = 2.145

yerr = 0.25

xs = np.linspace( 2, 8, 1448 )
ys = A * xs + B + np.random.normal( 0, yerr, len( xs ) )

def linearRegression( _xs, _ys ):
    if _xs.shape[0] != _ys.shape[0]:
        raise ValueError( 'xs and ys must be of the same length' )
    xSum = ySum = xxSum = yySum = xySum = 0.0
    numPts = 0

    for i in range( len( _xs ) ):
        xSum += _xs[i]
        ySum += _ys[i]
        xxSum += _xs[i] * _xs[i]
        yySum += _ys[i] * _ys[i]
        xySum += _xs[i] * _ys[i]
        numPts += 1

    k = ( xySum - xSum * ySum / numPts ) / ( xxSum - xSum * xSum / numPts )
    b = ( ySum - k * xSum ) / numPts
    sk = np.sqrt( ( ( yySum - ySum * ySum / numPts ) / ( xxSum - xSum * xSum / numPts ) - k**2 ) / numPts )
    sb = np.sqrt( ( yySum - ySum * ySum / numPts ) - k**2 * ( xxSum - xSum * xSum / numPts ) ) / numPts

    return [ k, b, sk, sb ]

def linearRegressionSP( _xs, _ys ):
    defPars = [ 0, 0 ]
    pars, pcov, infodict, errmsg, success = \
    leastsq( lambda _pars, x, y: y - ( _pars[0] * x + _pars[1] ), defPars, args = ( _xs, _ys ), full_output=1 )

    errs = []
    if pcov is not None:
        if( len( _xs ) > len(defPars) ) and pcov is not None:
            s_sq = ( ( ys - ( pars[0] * _xs + pars[1] ) )**2 ).sum() / ( len( _xs ) - len( defPars ) )
            pcov *= s_sq

        for j in range( len( pcov ) ):
            errs.append( pcov[j][j]**0.5 )
    else:
        errs = [ np.inf, np.inf ]
    return np.append( pars, np.array( errs ) )

regr1 = linearRegression( xs, ys )
regr2 = linearRegressionSP( xs, ys )

print( regr1 )
print( 'Calculated sigma = %f' %  ( regr1[3] * np.sqrt( xs.shape[0] ) ) )
print( regr2 )
#print( 'B = %f must be in ( %f,\t%f )' % ( B, regr1[1] - regr1[3], regr1[1] + regr1[3] ) )


plt.plot( xs, ys, 'bo' )
plt.plot( xs, regr1[0] * xs + regr1[1] )
plt.show()

sortie:

[1.3481681543925064, 2.1729338701374137, 0.0036028493647274687, 0.0062446292528624348]
Calculated sigma = 0.237624 # quite close to yerr
[ 1.34816815  2.17293387  0.00360534  0.01907908]

Il est intéressant de savoir quels résultats curvefit et bootstrap donnera ...

2
jambulat