Obtenir des erreurs standard sur les paramètres ajustés en utilisant l'optimisation.la méthode leastsq en python

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

D'après ce que j'ai compris, tout ce dont j'ai besoin c'est la matrice de covariance qui va avec mes paramètres ajustés, donc je peux quadriller les éléments diagonaux pour obtenir mon erreur standard sur les paramètres ajustés. J'ai une vague mémoire de lecture que la matrice de covariance est ce qui est produit de l'optimisation.la méthode lastsq en tout cas. Est-ce correct? Si ce n'est pas le cas, comment vous y prendriez-vous pour obtenir la matrice résiduelle afin de multiplier le Jacobien pour obtenir ma matrice de covariance?

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

le code de raccord 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)

La args t et disp est et tableau de temps et displcement valeurs (fondamentalement juste 2 colonnes de données). J'ai importé tout ce dont j'avais besoin au rythme du code. Les valeurs ajustées et la matrice fournie par la sortie comme suit:

[  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 pense que le fit est un petit suspect de toute façon à moment. Cela sera confirmé quand je peux les erreurs.

29
demandé sur Phil 2013-01-29 15:01:04

3 réponses

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 s'exécute sur 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 de mesure ou procédure, et il peut donc être considéré comme une constante qui ne dépend pas de i .

quelle fonction d'ajustement utiliser, et comment obtenir les erreurs de paramètre?

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

par contre, si vous utilisez optimize.curvefit , la première partie de la procédure ci-dessus (en multipliant par le chi carré réduit) est faite pour vous en coulisse. 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.

en outre, optimize.curvefit fournit des paramètres optionnels 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-type dans les paramètres ajustés est un problème statistique complexe. Les résultats de la matrice de covariance, telle que mise en œuvre 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; interactions qui peuvent exister, en fonction de votre fonction d'ajustement spécifique f(x) .

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

voyons quelques exemples

Tout d'abord, un code boilerplate. Définissons une fonction de ligne et générons des données avec des erreurs aléatoires. Nous allons générer un jeu 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, adaptons 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 presque d'accord. Ce qui est bon!

maintenant, supposons que nous voulions dire aux fonctions appropriées qu'il y a 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 de l'erreur, en fait, si nous simulons certains données avec ce genre d'erreur, il 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 d'entrer cette nouvelle information d'erreur systématique. Voyons ce que fait curve_fit quand on lui parle 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 mauvais!

c'était la fin de l'histoire, mais récemment curve_fit a ajouté le absolute_sigma paramètre optionnel:

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 encore un peu louche. curve_fit pense que nous pouvons obtenir un ajustement de ce signal bruyant, avec un niveau de 10% d'erreur dans le p1 paramètre. 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 ajustement de paramètre. bootstrap pense savoir p1 avec environ 34% d'incertitude.

résumé

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

je recommande fortement d'examiner un problème particulier, et d'essayer curvefit et bootstrap . Si elles sont similaires, alors curvefit est beaucoup moins cher à calculer, donc probablement utile d'utiliser. S'ils diffèrent de manière significative, alors mon argent serait sur le bootstrap .

66
répondu Pedro M Duarte 2018-09-19 19:56:17

trouvé votre question tout en essayant de répondre à mon propre similaire question . Réponse courte. Le cov_x que les extrants de leastsq devraient être multipliés par la variance résiduelle. c'est à dire

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

comme dans curve_fit.py . C'est parce que leastsq produit la matrice de covariance fractionnaire. Mon gros problème était que la variance résiduelle apparaît comme quelque chose d'autre lors de googler.

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

10
répondu HansHarhoff 2017-05-23 12:34:28

il est possible de calculer exactement les erreurs dans le cas d'une régression linéaire. Et en effet la fonction leastsq 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]

c'est intéressant quels résultats curvefit et bootstrap donneront...

2
répondu jambulat 2017-05-24 14:43:40