当卡方接近于零时,lmfit 的参数不确定性不准确

Inaccurate parameter uncertainties from lmfit when chi-square is close to zero

当卡方非常接近于零时(即当模型几乎完美地拟合数据时),我的 lmfit(当 scale_covar = False 时)报告的最佳拟合参数似乎不准确.下面我执行了 Y = [0,1,2]X = [0,1,2] 的直线回归,并向数据中添加了越来越多的噪声 (f)。只有当噪声足够大时,报告的不确定性才等于普通最小二乘法预期的不确定性。

这是 lmfit 如何估计参数不确定性的 bug/limitation,还是我对 lmfit 的预期的 bug/limitation?

# python 3.7.6
# lmfit 1.0.2

import numpy as np
from lmfit import minimize, Parameters

def residual(p, x, y):
    return y - (p['a'] + p['b'] * x)

def report(out):
    print(f'{f:8.0e} {out.chisqr:8.0e} {out.params["a"].value:8.2f} {out.covar[0,0]**.5:8.2f} {out.params["b"].value:8.2f} {out.covar[1,1]**.5:8.2f} {out.covar[0,1]/(out.covar[0,0]*out.covar[1,1])**.5:8.2f}')

print(f'{"noise":>8} {"chisq":>8} {"a":>8} {"SE(a)":>8} {"b":>8} {"SE(b)":>8} {"cor(a,b)":>8}')
print(f'{"-"*8} {"-"*8} {"-"*8} {"-"*8} {"-"*8} {"-"*8} {"-"*8}')

params = Parameters()
params.add('a', value=0.5)
params.add('b', value=0.5)

f = 0
X = np.array([0.,1.,2.])
Y = np.array([0.,1.,2.])
out = minimize(residual, params, args=(X, Y), scale_covar = False)
report(out)

for e in np.linspace(16,2,15):
    f = 10**-e
    Y[1] = 1 + f
    out = minimize(residual, params, args=(X, Y), scale_covar = False)
    report(out)

# OUTPUT:
# 
# 
#    noise    chisq        a    SE(a)        b    SE(b) cor(a,b)
# -------- -------- -------- -------- -------- -------- --------
#    0e+00    2e-24     0.00     1.00     1.00     0.45    -0.00
#    1e-16    2e-24     0.00     1.00     1.00     0.45    -0.00
#    1e-15    2e-24     0.00     1.00     1.00     0.45    -0.00
#    1e-14    1e-24    -0.00     0.00     1.00     0.50    -0.45
#    1e-13    2e-24    -0.00     0.00     1.00     1.00    -0.89
#    1e-12    2e-24    -0.00     0.00     1.00     0.50    -0.45
#    1e-11    7e-23     0.00     1.00     1.00     0.45    -0.00
#    1e-10    7e-21     0.00     1.00     1.00     0.45    -0.00
#    1e-09    7e-19     0.00     1.00     1.00     0.45    -0.00
#    1e-08    7e-17     0.00     0.24     1.00     0.50    -0.44
#    1e-07    7e-15     0.00     0.93     1.00     0.67    -0.74
#    1e-06    7e-13     0.00     0.92     1.00     0.70    -0.77
#    1e-05    7e-11     0.00     0.91     1.00     0.71    -0.77
#    1e-04    7e-09     0.00     0.91     1.00     0.71    -0.77
#    1e-03    7e-07     0.00     0.91     1.00     0.71    -0.77
#    1e-02    7e-05     0.00     0.91     1.00     0.71    -0.77

郑重声明,当使用 least_squares(信任区域反射)方法而不是默认的 leastsq (Levenberg-Marquardt) 方法时,上述行为会发生变化,而是返回准确的估计值(请参阅下面的代码)。

在许多(大多数?)情况下,前一种选择在收敛方面等同于或优于后者,并且在(边缘)情况(例如所描述的情况)中更稳健地估计方差-协方差的额外好处这里。我不确定使用 leastsqleast_squares 有什么明显的好处。

import numpy as np
from lmfit import minimize, Parameters, __version__

def residual(p, x, y):
    return y - (p['a'] + p['b'] * x)

def report(out):
    print(f'{f:8.0e} {out.chisqr:8.0e} {out.params["a"].value:8.2f} {out.covar[0,0]**.5:8.2f} {out.params["b"].value:8.2f} {out.covar[1,1]**.5:8.2f} {out.covar[0,1]/(out.covar[0,0]*out.covar[1,1])**.5:8.2f}')

params = Parameters()
params.add('a', value=0.5)
params.add('b', value=0.5)

for method in ['least_squares', 'leastsq']:

    print(f'\nMETHOD = {method}\n')
    print(f'{"noise":>8} {"chisq":>8} {"a":>8} {"SE(a)":>8} {"b":>8} {"SE(b)":>8} {"cor(a,b)":>8}')
    print(f'{"-"*8} {"-"*8} {"-"*8} {"-"*8} {"-"*8} {"-"*8} {"-"*8}')

    f = 0
    X = np.array([0.,1.,2.])
    Y = np.array([0.,1.,2.])
    out = minimize(residual, params, args=(X, Y), scale_covar = False, method = method)
    report(out)

    for e in np.linspace(16,2,15):
        f = 10**-e
        Y[1] = 1 + f
        out = minimize(residual, params, args=(X, Y), scale_covar = False, method = method)
        report(out)

# OUTPUT:
# 
# METHOD = least_squares
# 
#    noise    chisq        a    SE(a)        b    SE(b) cor(a,b)
# -------- -------- -------- -------- -------- -------- --------
#    0e+00    2e-32    -0.00     0.91     1.00     0.71    -0.77
#    1e-16    2e-32    -0.00     0.91     1.00     0.71    -0.77
#    1e-15    8e-31     0.00     0.91     1.00     0.71    -0.77
#    1e-14    7e-29     0.00     0.91     1.00     0.71    -0.77
#    1e-13    7e-27     0.00     0.91     1.00     0.71    -0.77
#    1e-12    7e-25     0.00     0.91     1.00     0.71    -0.77
#    1e-11    7e-23     0.00     0.91     1.00     0.71    -0.77
#    1e-10    7e-21     0.00     0.91     1.00     0.71    -0.77
#    1e-09    7e-19     0.00     0.91     1.00     0.71    -0.77
#    1e-08    7e-17     0.00     0.91     1.00     0.71    -0.77
#    1e-07    7e-15     0.00     0.91     1.00     0.71    -0.77
#    1e-06    7e-13     0.00     0.91     1.00     0.71    -0.77
#    1e-05    7e-11     0.00     0.91     1.00     0.71    -0.77
#    1e-04    7e-09     0.00     0.91     1.00     0.71    -0.77
#    1e-03    7e-07     0.00     0.91     1.00     0.71    -0.77
#    1e-02    7e-05     0.00     0.91     1.00     0.71    -0.77
# 
# METHOD = leastsq
# 
#    noise    chisq        a    SE(a)        b    SE(b) cor(a,b)
# -------- -------- -------- -------- -------- -------- --------
#    0e+00    2e-24     0.00     1.00     1.00     0.45    -0.00
#    1e-16    2e-24     0.00     1.00     1.00     0.45    -0.00
#    1e-15    2e-24     0.00     1.00     1.00     0.45    -0.00
#    1e-14    1e-24    -0.00     0.00     1.00     0.50    -0.45
#    1e-13    2e-24    -0.00     0.00     1.00     1.00    -0.89
#    1e-12    2e-24    -0.00     0.00     1.00     0.50    -0.45
#    1e-11    7e-23     0.00     1.00     1.00     0.45    -0.00
#    1e-10    7e-21     0.00     1.00     1.00     0.45    -0.00
#    1e-09    7e-19     0.00     1.00     1.00     0.45    -0.00
#    1e-08    7e-17     0.00     0.24     1.00     0.50    -0.44
#    1e-07    7e-15     0.00     0.93     1.00     0.67    -0.74
#    1e-06    7e-13     0.00     0.92     1.00     0.70    -0.77
#    1e-05    7e-11     0.00     0.91     1.00     0.71    -0.77
#    1e-04    7e-09     0.00     0.91     1.00     0.71    -0.77
#    1e-03    7e-07     0.00     0.91     1.00     0.71    -0.77
#    1e-02    7e-05     0.00     0.91     1.00     0.71    -0.77