如何使用scipy.optimize.least_squares计算标准差误差

7

我比较了 optimize.curve_fit 和 optimize.least_squares 的拟合效果。使用 curve_fit,我可以将协方差矩阵 pcov 作为输出,通过它我可以计算出我的拟合变量的标准偏差误差:

perr = np.sqrt(np.diag(pcov))

如果我使用 least_squares 进行拟合,就不会得到任何协方差矩阵输出,也无法计算出变量的标准差误差。
以下是我的示例:
#import modules
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
from scipy.optimize import least_squares

noise = 0.5
N = 100
t = np.linspace(0, 4*np.pi, N)

# generate data
def generate_data(t, freq, amplitude, phase, offset, noise=0, n_outliers=0, random_state=0):
    #formula for data generation with noise and outliers
    y = np.sin(t * freq + phase) * amplitude + offset
    rnd = np.random.RandomState(random_state)
    error = noise * rnd.randn(t.size)
    outliers = rnd.randint(0, t.size, n_outliers)
    error[outliers] *= 10
    return y + error

#generate data
data = generate_data(t, 1, 3, 0.001, 0.5, noise, n_outliers=10)

#initial guesses
p0=np.ones(4)
x0=np.ones(4)

# create the function we want to fit
def my_sin(x, freq, amplitude, phase, offset):
    return np.sin(x * freq + phase) * amplitude + offset

# create the function we want to fit for least-square
def my_sin_lsq(x, t, y):
    # freq=x[0]
    # phase=x[1]
    # amplitude=x[2]
    # offset=x[3]
    return (np.sin(t*x[0]+x[2])*x[1]+ x[3]) - y

# now do the fit for curve_fit
fit = curve_fit(my_sin, t, data, p0=p0)
print 'Curve fit output:'+str(fit[0])

#now do the fit for least_square
res_lsq = least_squares(my_sin_lsq, x0, args=(t, data))
print 'Least_squares output:'+str(res_lsq.x)


# we'll use this to plot our first estimate. This might already be good enough for you
data_first_guess = my_sin(t, *p0)

#data_first_guess_lsq = x0[2]*np.sin(t*x0[0]+x0[1])+x0[3]
data_first_guess_lsq = my_sin(t, *x0)

# recreate the fitted curve using the optimized parameters
data_fit = my_sin(t, *fit[0])
data_fit_lsq = my_sin(t, *res_lsq.x)

#calculation of residuals
residuals = data - data_fit
residuals_lsq = data - data_fit_lsq
ss_res = np.sum(residuals**2)
ss_tot = np.sum((data-np.mean(data))**2)
ss_res_lsq = np.sum(residuals_lsq**2)
ss_tot_lsq = np.sum((data-np.mean(data))**2)

#R squared
r_squared = 1 - (ss_res/ss_tot)
r_squared_lsq = 1 - (ss_res_lsq/ss_tot_lsq)
print 'R squared curve_fit is:'+str(r_squared)
print 'R squared least_squares is:'+str(r_squared_lsq)

plt.figure()
plt.plot(t, data)
plt.title('curve_fit')
plt.plot(t, data_first_guess)
plt.plot(t, data_fit)
plt.plot(t, residuals)

plt.figure()
plt.plot(t, data)
plt.title('lsq')
plt.plot(t, data_first_guess_lsq)
plt.plot(t, data_fit_lsq)
plt.plot(t, residuals_lsq)

#error
perr = np.sqrt(np.diag(fit[1]))
print 'The standard deviation errors for curve_fit are:' +str(perr)

我很感谢您的任何帮助,祝一切顺利。

PS:我从这个来源得到了很多输入,并使用了部分代码Robust regression


这里没有人能帮助我吗? - strohfelder
2个回答

11
optimize.least_squares的结果中有一个称为jac的参数。从文档中可以得知:
jac:ndarray、稀疏矩阵或LinearOperator,形状为(m, n)。
在解决方案处的修改后的雅可比矩阵,即J^T J是成本函数海森矩阵的高斯-牛顿近似。类型与算法使用的类型相同。
这可以用于使用以下公式估计参数的协方差矩阵: Sigma = (J'J)^-1。
J = res_lsq.jac
cov = np.linalg.inv(J.T.dot(J))

要找到参数的方差,可以使用以下方法:

var = np.sqrt(np.diagonal(cov))

我建议使用 res_lsq.fun**2 而不是 residuals_lsq**2,以使答案更通用。 - Asking Questions
6
据我所见,此答案包含两个错误:1.协方差矩阵需要乘以残差的RMS。2.所显示的最终结果是标准差,而不是方差。参见:https://dev59.com/NmUq5IYBdhLWcg3wVvCD#21844726 和 https://dev59.com/kWUq5IYBdhLWcg3wBL7j#14857441。 - Gabriel
@Gabriel 是的,看起来是这样的。正确的写法应该是:std = np.sqrt(np.diagonal(np.linalg.inv(J.T @ J) * (res_lsq.fun.T @ res_lsq.fun / (res_lsq.fun.size - res_lsq.x.size)))) - undefined

6

SciPy程序optimize.least_squares要求用户提供一个输入函数fun(...),该函数返回一组残差向量。通常这样定义:

residuals = (data - model)/sigma

其中datamodel是向量,用于拟合数据和每个数据点对应的模型预测,而sigma是每个data值的1σ不确定度。

在这种情况下,并假设可以信任输入的sigma不确定度,可以使用由least_squares返回的输出雅可比矩阵jac来估计协方差矩阵。此外,假设协方差矩阵是对角矩阵,或者简单地忽略非对角线项,还可以按以下方式获取模型参数的1σ不确定度perr(通常称为“形式误差”)(请参见Numerical Recipes 3rd ed.第15.4.2节):

import numpy as np
from scipy import linalg, optimize

res = optimize.least_squares(...)

U, s, Vh = linalg.svd(res.jac, full_matrices=False)
tol = np.finfo(float).eps*s[0]*max(res.jac.shape)
w = s > tol
cov = (Vh[w].T/s[w]**2) @ Vh[w]  # robust covariance matrix
perr = np.sqrt(np.diag(cov))     # 1sigma uncertainty on fitted parameters

上面的代码用于获取协方差矩阵,形式上与下面这个更简单的代码相同(由Alex建议),但上面的代码具有主要优点,即在雅可比矩阵接近退化时也能正常工作,这在实际最小二乘拟合中经常发生。
cov = linalg.inv(res.jac.T @ res.jac)  # covariance matrix when jac not degenerate

如果一个人不信任输入的不确定性(sigma),仍然可以假设拟合(fit)是良好的,从拟合本身估计数据的不确定性。这相当于假设 chi**2/DOF=1 ,其中 DOF 是自由度的数量。在这种情况下,可以使用以下行来重新缩放协方差矩阵(covariance matrix)以计算不确定性。
chi2dof = np.sum(res.fun**2)/(res.fun.size - res.x.size)
cov *= chi2dof
perr = np.sqrt(np.diag(cov))    # 1sigma uncertainty on fitted parameters

我对尝试将这个答案与网页 http://numerical.recipes/book/book.html 中提供的方程进行调和感到困惑。如果我计算:cov = linalg.inv(res.jac.T @ res.jac) (方程1)或者cov = (Vh[w].T/s[w]**2) @ Vh[w] (方程2)我得到等效的答案。然而,如果我直接计算所引用的方程(参见方程15.4.20):cov(aj, ak) = Sum_i [(Vji Vki) / wi**2] (方程3)我得不到等效的结果。我以为像 Python 一样,C++ 也是行主序?然而从 方程1 中对象形状的检查来看,这必须是正确的表述。 - PetMetz

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