使用Python中的optimize.leastsq方法获取拟合参数的标准误差。

42
我有一组数据(位移 vs 时间),使用 optimize.leastsq 方法拟合了几个方程。现在我想获得拟合参数的误差值。通过查看文档,输出的矩阵是雅可比矩阵,并且我必须将其乘以残差矩阵以获取我的值。不幸的是,我不是统计学家,所以在术语上有些困惑。
据我所知,我只需要与我的拟合参数相对应的协方差矩阵,这样我就可以平方根对角线元素以获取拟合参数的标准误差。我模糊地记得读到协方差矩阵是从 optimize.leastsq 方法中输出的。这是正确的吗?如果不是,您将如何获取残差矩阵以将输出的雅可比乘以获取我的协方差矩阵?
非常感谢您的帮助。我很新于python,如果问题是基础问题,则表示歉意。
以下是拟合代码:
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)

参数t和disp是时间和位移值的数组(基本上只是2列数据)。我已经在代码顶部导入了所有需要的内容。拟合值和输出提供的矩阵如下:

[  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]]

我怀疑目前的适配可能有些问题。当我解决错误后,这将得到证实。


1
http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.leastsq.html - Sean McCully
不太确定你在问什么,也许一些例子会有所帮助。 - Sean McCully
这是我一直在查看的主要文档。我使用的代码已添加到上面的描述中。 - Phil
1
我觉得我找到了一个解决方法(虽然在重写代码方面有点不方便)。我认为'optimise.curve_fit'输出协方差矩阵,从中可以获得误差,并且它使用与'optimize.leastsq'相同的最小二乘回归方法。有人能确认这个说法是正确的吗? - Phil
是的,curve_fit函数返回参数估计(不确定性)的协方差矩阵。如果你想直接使用leastsq函数,也可以查看curve_fit函数的源代码。 - Josef
3个回答

96

2016年4月6日更新

在大多数情况下,正确获取拟合参数的错误是微妙的。

让我们考虑拟合一个函数y=f(x),其中您有一组数据点(x_i, y_i, yerr_i),其中i是在每个数据点上运行的索引。

在大多数物理测量中,误差yerr_i是测量设备或过程的系统性不确定度,因此可以将其视为不依赖于i的常数。

使用哪种拟合函数以及如何获取参数错误?

optimize.leastsq方法将返回分数协方差矩阵。 将该矩阵的所有元素乘以残差方差(即减少的卡方)并取对角线元素的平方根,将给您提供拟合参数标准偏差的估计。 我已经在下面的一个函数中包含了执行该操作的代码。

另一方面,如果使用optimize.curvefit,上述过程的第一部分(乘以减少的卡方)将在幕后为您完成。 然后,您需要取协方差矩阵的对角线元素的平方根,以获得拟合参数标准偏差的估计。

此外,optimize.curvefit提供了可选参数来处理更一般的情况,其中yerr_i值对每个数据点都不同。 来自文档

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.

如何确定我的误差是正确的?

确定拟合参数的标准误差的适当估计是一个复杂的统计问题。实际上,optimize.curvefitoptimize.leastsq实现的协方差矩阵的结果取决于关于误差的概率分布和参数之间的相互作用的假设; 这些相互作用可能存在于您特定的拟合函数f(x)中。

在我看来,处理复杂的f(x)的最佳方法是使用引导法,该方法在此链接中概述。

让我们看一些例子

首先是一些样板代码。让我们定义一个波浪线函数并生成带有随机误差的数据集。 我们将生成一个带有小随机误差的数据集。

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

现在,让我们使用各种可用的方法来拟合函数:

`optimize.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]

`optimize.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]


观察结果

我们已经开始看到一些有趣的事情了,所有三种方法的参数和误差估计几乎是一致的。这是好的!

现在,假设我们想告诉拟合函数,我们的数据存在一些其他的不确定性,比如系统误差,它会导致额外的误差是err_stdev值的二十倍。那是非常大的误差,实际上,如果我们使用这种误差模拟一些数据,那么看起来就像这样:

enter image description here

显然,我们不可能恢复具有这种噪声级别的拟合参数。

首先,让我们认识到leastsq甚至不允许我们输入这个新的系统误差信息。让我们看看当我们告诉curve_fit有关误差时它会做什么:

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]

咦?这一定是错的!

虽然过去这就是故事的结尾,但最近 curve_fit 添加了一个可选参数 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]

这有点好了一些,但仍然有点可疑。curve_fit认为我们可以从那个嘈杂的信号中得到一个拟合,p1参数的误差水平为10%。让我们看看bootstrap有什么说法:

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 ]

啊,那或许是我们拟合参数误差的更好估计。 bootstrap 认为对于 p1 其不确定性约为 34%。

总结

optimize.leastsqoptimize.curvefit 提供了一种估算拟合参数误差的方式,但我们不能盲目使用这些方法而不加质疑。 bootstrap 是一种使用暴力方法的统计学方法,我认为它在较难解释的情况下更容易发挥作用。

我强烈建议针对特定问题尝试使用 curvefitbootstrap。如果它们相似,那么 curvefit 计算成本更低,因此可能值得使用。 如果它们有显著不同,那么我会选择使用 bootstrap


给定的参数可以通过傅里叶变换(频率和直流参数)精确地提取,因此这些方法“准确地”确定参数并不那么“可疑”。尝试使用简单的多项式或正弦波的一个振荡重新进行,看看它有多可疑。 - user3799584
@user3799584,我认为可疑的不是参数可以被获取这个事实,而是获得的参数误差在10%值上。由于数据更嘈杂,我预计参数的拟合值会有更大的误差。 - Pedro M Duarte
我认为将其与锁相检测进行比较是不公平的。要执行类似于锁相检测的操作,您需要事先知道源的频率。在这个例子中,正弦波的频率是我们正在估计的参数之一。 - Pedro M Duarte
1
默认情况下,sigma值应仅被理解为对各个数据点的权重。这些权重将被curvefit使用,并会影响拟合值,但不会影响协方差矩阵。当您设置absolute_sigma=True时,您告诉curvefit您的sigmas不仅仅是权重,而且还是与$y$相同单位的实际不确定性。在这种情况下,curvefit会返回未归一化的协方差矩阵pcov。这样,您可以取pcov中对角线条目的平方根来直接获得参数误差。 - Pedro M Duarte
1
@PalSzabo 我刚刚编辑了一个文档字符串,解释了如何在示例中修复该问题 :) - roganjosh
显示剩余15条评论

12

在尝试回答自己类似的question问题时,我发现了你的问题。 简短回答。leastsq输出的cov_x应该乘以残差方差。即:

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

就像在curve_fit.py中一样。这是因为leastsq输出了分数协方差矩阵。我的大问题是当我搜索时,残差方差以其他形式出现。

残差方差只是您拟合的减少卡方。


2

在线性回归的情况下,可以精确计算误差。实际上,leastsq函数给出的值是不同的:

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

输出:

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

有趣的是,曲线拟合和自助法将给出哪些结果...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接