协方差矩阵是 lmfit Python3-6 中的 NoneType

Covariance matrix is NoneType in lmfit Python3-6

我想使用 lmfit 拟合 f(x,y) 函数。数据集小,拟合参数多(x轴6个点,y轴11个点,16个无约束拟合参数)。使用 Model.fit 中的所有默认值,我无法获得协方差矩阵,并且在拟合过程中,自由参数的值根本没有改变。

我试图更改参数的初始值。然而,当我在 OriginPro 曲面拟合功能中设置相同类型的问题时,Levenberg-Marquardt 算法设法拟合数据并估计误差(尽管某些参数的值相当大)。这意味着我的代码一定有问题。我找不到问题所在。我不是Python大师

MWE如下。

import numpy as np
from lmfit import Model, Parameters
import numdifftools # not calling this doesn't change anything

x, y = np.array([226.5, 361.05, 404.41, 589, 632.8, 1013.98]), np.linspace(0,100,11)
X, Y = np.meshgrid(x, y)

Z = np.array([[1.3945, 1.34896, 1.34415, 1.33432, 1.33306, 1.32612],\
[1.39422, 1.3487, 1.34389, 1.33408, 1.33282, 1.32591],\
[1.39336, 1.34795, 1.34315, 1.33336, 1.33211, 1.32524],\
[1.39208, 1.34682, 1.34205, 1.3323, 1.33105, 1.32424],\
[1.39046, 1.3454, 1.34065, 1.33095, 1.32972, 1.32296],\
[1.38854, 1.34373, 1.33901, 1.32937, 1.32814, 1.32145],\
[1.38636, 1.34184, 1.33714, 1.32757, 1.32636, 1.31974],\
[1.38395, 1.33974, 1.33508, 1.32559, 1.32438, 1.31784],\
[1.38132, 1.33746, 1.33284, 1.32342, 1.32223, 1.31576],\
[1.37849, 1.33501, 1.33042, 1.32109, 1.31991, 1.31353],\
[1.37547, 1.33239, 1.32784, 1.31861, 1.31744, 1.31114]])

#This has to be defined beforehand (otherwise parameters names are not defined error)
a1,a2,a3,a4 = 1.3208, -1.2325E-5, -1.8674E-6, 5.0233E-9
b1,b2,b3,b4 = 5208.2413, -0.5179, -2.284E-2, 6.9608E-5
c1,c2,c3,c4 = -2.5551E8, -18341.336, -920, 2.7729
d1,d2,d3,d4 = 9.3495, 2E-3, 3.6733E-5, -1.2932E-7

# Function to fit
def model(x, y, *args):
    return a1+a2*y+a3*np.power(y,2)+a4*np.power(y,3)+\
    (b1+b2*y+b3*np.power(y,2)+b4*np.power(y,3))/np.power(x,2)+\
    (c1+c2*y+c3*np.power(y,2)+c4*np.power(y,3))/np.power(x,4)+\
    (d1+d2*y+d3*np.power(y,2)+d4*np.power(y,3))/np.power(x,6)

# This is the callable that is passed to Model.fit. M is a (2,N) array
# where N is the total number of data points in Z, which will be ravelled
# to one dimension.
def _model(M, **args):
    x, y = M
    arr = model(x, y, params)
    return arr

# We need to ravel the meshgrids of X, Y points to a pair of 1-D arrays.
xdata = np.vstack((X.ravel(), Y.ravel()))

# Fitting parameters.
fmodel = Model(_model)
params = Parameters()
params.add_many(('a1',1.3208,True,1,np.inf,None,None),\
                ('a2',-1.2325E-5,True,-np.inf,np.inf,None,None),\
                ('a3',-1.8674E-6,True,-np.inf,np.inf,None,None),\
                ('a4',5.0233E-9,True,-np.inf,np.inf,None,None),\
                ('b1',5208.2413,True,-np.inf,np.inf,None,None),\
                ('b2',-0.5179,True,-np.inf,np.inf,None,None),\
                ('b3',-2.284E-2,True,-np.inf,np.inf,None,None),\
                ('b4',6.9608E-5,True,-np.inf,np.inf,None,None),\
                ('c1',-2.5551E8,True,-np.inf,np.inf,None,None),\
                ('c2',-18341.336,True,-np.inf,np.inf,None,None),\
                ('c3',-920,True,-np.inf,np.inf,None,None),\
                ('c4',2.7729,True,-np.inf,np.inf,None,None),\
                ('d1',9.3495,True,-np.inf,np.inf,None,None),\
                ('d2',2E-3,True,-np.inf,np.inf,None,None),\
                ('d3',3.6733E-5,True,-np.inf,np.inf,None,None),\
                ('d4',-1.2932E-7,True,-np.inf,np.inf,None,None))

result = fmodel.fit(Z.ravel(), params, M=xdata)
fit = model(X, Y, result.params)

print(result.covar)

此代码导致协方差为 NoneType。我希望它最终会被计算出来,因为 Origin 可以以某种方式管理。如果需要,我可以提供 Origin Surface Fitting Parameters 中的所有参数。

绘制 Z 拟合差异时,低 x 值存在相当大的差异(在 Origin 中没有发生)。

您没有以 lmfit 可以合理使用的方式定义您的模型函数。你有:

def _model(M, **args):
    x, y = M
    arr = model(x, y, params)
    return arr


def model(x, y, *args):
    return a1+a2*y+a3*np.power(y,2)+a4*np.power(y,3)+\
    (b1+b2*y+b3*np.power(y,2)+b4*np.power(y,3))/np.power(x,2)+\
    (c1+c2*y+c3*np.power(y,2)+c4*np.power(y,3))/np.power(x,4)+\
    (d1+d2*y+d3*np.power(y,2)+d4*np.power(y,3))/np.power(x,6)


model = Model(_model)

有几个问题:

  1. args 未在 _model 中使用,并且 params 未在函数中定义,因此将是模块级的。
  2. model 类似,args 未使用,a1a2 等将从模块级(编程)变量和(重要的是!!) 这些将不会更新。

简而言之,您的模型函数永远不会看到参数的不同值。

lmfit.Model 采用 命名函数参数 并将其转换为参数名称。它不会将 **kws*position_args 转换为参数名称。所以我认为你想要做的是编写一个这样的模型函数:

def model(x, y, a1, a2, a2, a4, b1, b2, b3 ,b3, c1, c2, c3, c4,
          d1, d2, d3, d4):
    return a1+a2*y+a3*np.power(y,2)+a4*np.power(y,3)+\
    (b1+b2*y+b3*np.power(y,2)+b4*np.power(y,3))/np.power(x,2)+\
    (c1+c2*y+c3*np.power(y,2)+c4*np.power(y,3))/np.power(x,4)+\
    (d1+d2*y+d3*np.power(y,2)+d4*np.power(y,3))/np.power(x,6)

然后创建一个模型:

# Note: don't give a function and Model instance the same name!!
my_model = Model(model, independent_vars=('x', 'y'))  

定义该模型后,您可以 运行 进行拟合,而无需解开数据(lmfit 中的独立数据几乎可以是任何数据类型,数据数组可以是多个维):

result = my_model.fit(Z, params, x=X, y=Y)

就其价值而言,从适合 运行 完成的意义上来说,进行此类更改对我有用。拟合仍然停留在某些参数未从其初始值更新的情况下,但这与设置和 运行 拟合的机制是一个独立的问题,可能是由于多项式非常不稳定或初步估计不佳。

顺便说一句:np.power(y,n) 可以拼写为 y**n 并且可读性很重要。此外,有时会通过替换

来提高数值稳定性
a + b*x + c*x**2 + d*x**3

a + x*(b + x*(c + x*d)) 

虽然我不知道这对你的情况是否有帮助。