【问题标题】:Can fit curve with scipy minimize but not with scipy curve_fit可以用 scipy 最小化曲线拟合曲线,但不能用 scipy curve_fit
【发布时间】:2020-07-12 05:13:12
【问题描述】:

我正在尝试使用scipy curve_fit 将函数y= 1-a(1-bx)**n 拟合到一些实验数据中。该模型仅在 y>0 时存在,因此我剪裁计算值以强制执行此操作。 代码如下所示

import numpy as np
import scipy.optimize
import matplotlib.pyplot as plt

# Driver function for scipy.minimize

def driver_func(x, xobs, yobs):

    # Evaluate the fit function with the current parameter estimates

    ynew = myfunc(xobs, *x)
    yerr = np.sum((ynew - yobs) ** 2)

    return yerr

# Define function

def myfunc(x, a, b, n):

    y = 1.0 - a * np.power(1.0 - b * x, n) 
    y = np.clip(y, 0.00, None )

    return y

if __name__ == "__main__":

    # Initialise data

    yobs = np.array([0.005, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.004, 
                    0.048, 0.119, 0.199, 0.277, 0.346, 0.395, 0.444, 0.469, 
                    0.502, 0.527, 0.553, 0.582, 0.595, 0.603, 0.612, 0.599])
    xobs = np.array([0.013, 0.088, 0.159, 0.230, 0.292, 0.362, 0.419, 0.471,
                    0.528, 0.585, 0.639, 0.687, 0.726, 0.772, 0.814, 0.854,
                    0.889, 0.924, 0.958, 0.989, 1.015, 1.045, 1.076, 1.078])

    # Initial guess

    p0 = [2.0, 0.5, 2.0]

    # Check fit pre-regression

    yold = myfunc(xobs, *p0)
    plt.plot(xobs, yobs, 'ko', label='data', fillstyle='none')
    plt.plot(xobs, yold, 'g-', label='pre-fit: a=%4.2f, b=%4.2f, n=%4.2f' % tuple(p0))

    # Fit curve using SCIPY CURVE_FIT

    try:
        popt, pcov = scipy.optimize.curve_fit(myfunc, xobs, yobs, p0=p0)
    except:
        print("Could not fit data using SCIPY curve_fit")
    else:
        ynew = myfunc(xobs, *popt)
        plt.plot(xobs, ynew, 'r-', label='post-curve_fit: a=%4.2f, b=%4.2f, n=%4.2f' % tuple(popt))

    # Fit curve using SCIPY MINIMIZE

    res = scipy.optimize.minimize(driver_func, p0, args=(xobs, yobs), method='Nelder-Mead')
    ynw2 = myfunc(xobs, *res.x)
    plt.plot(xobs, ynw2, 'y-', label='post-minimize: a=%4.2f, b=%4.2f, n=%4.2f' % tuple(res.x))

    plt.legend()
    plt.show()

我也使用 SCIPY MINIMIZE 来实现同样的目的。如下图所示,MINIMIZE 有效,但 CURVE_FIT 基本上用完评估并放弃,即使开始的猜测与 MINIMIZE 解决方案相差不远(至少在视觉上)。希望有任何关于为什么 curve_fit 似乎在这里不起作用的想法。

谢谢!

更新: 在 mikuszefski 的 cmets 之后,我做了以下调整 1. 去掉fit函数中的裁剪,如下:

def myfunc_noclip(x, a, b, n):
    y = 1.0 - a * np.power(1.0 - b * x, n) 
    return y
  1. 通过删除低于阈值的数据来引入裁剪数组

    ymin = 0.01
    xclp = xobs[np.where(yobs >= ymin)]
    yclp = yobs[np.where(yobs >= ymin)]
    
  2. 改进了最初的猜测(再次直观地)

    p0 = [1.75, 0.5, 2.0]
    
  3. 更新了对 curve_fit 的调用

    popt, pcov = scipy.optimize.curve_fit(myfunc_noclip, xclp, yclp, p0=p0)
    

但这似乎没有帮助,如下图所示:

stackoverflow 上的其他帖子似乎表明 scipy curve_fit 在拟合参数之一是指数的情况下拟合曲线有问题,例如 SciPy curve_fit not working when one of the parameters to fit is a power 所以我猜我有同样的问题。虽然不知道如何解决它......

【问题讨论】:

    标签: python-3.x curve-fitting scipy-optimize


    【解决方案1】:

    这个问题是由函数定义中的裁剪引起的。这两种最小化方法的工作方式根本不同,因此对这种剪裁的反应也大不相同。这里minimizeNelder-Mead 一起使用,这是一种无梯度的方法。因此,该算法不计算数值梯度,也不估计任何雅可比行列式。相比之下,最终由curve_fit 调用的least-squares 正是这样做的。然而,如果函数不是连续的,那么逼近一个梯度并由此得出任何雅可比行列都会有些问题。如前所述,np.clip 引入了这种不连续性。删除后,可以很容易地看到,P0 的猜测不如包含剪辑的情况下看起来那么好。 curve_fit 确实与增加的 maxfev=5000 收敛,而 minimize 在将方法更改为 method='CG' 时立即失败。要查看算法困难,可以尝试手动提供jac

    一些注意事项: 1)关于剪裁,删除被剪裁的数据可能是个好主意,这样可以避免相应的问题。 2)查看协方差矩阵,n 的误差以及与其他值的相关性非常高。

    这就是我的收获

    import numpy as np
    import scipy.optimize
    import matplotlib.pyplot as plt
    
    # Driver function for scipy.minimize
    def driver_func( x, xobs, yobs ):
        # Evaluate the fit function with the current parameter estimates
        ynew = myfunc( xobs, *x)
        yerr = np.sum( ( ynew - yobs ) ** 2 )
        return yerr
    
    # Define functions
    def myfunc( x, a, b, n ):
        y = 1.0 - a * np.power( 1.0 - b * x, n ) 
        y = np.clip( y, 0.00, None )
        return y
    
    def myfunc_noclip( x, a, b, n ):
        y = 1.0 - a * np.power( 1.0 - b * x, n ) 
        return y
    
    if __name__ == "__main__":
    
        # Initialise data
        yobs = np.array([
            0.005, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.004, 
            0.048, 0.119, 0.199, 0.277, 0.346, 0.395, 0.444, 0.469, 
            0.502, 0.527, 0.553, 0.582, 0.595, 0.603, 0.612, 0.599
        ])
        xobs = np.array([
            0.013, 0.088, 0.159, 0.230, 0.292, 0.362, 0.419, 0.471,
            0.528, 0.585, 0.639, 0.687, 0.726, 0.772, 0.814, 0.854,
            0.889, 0.924, 0.958, 0.989, 1.015, 1.045, 1.076, 1.078
        ])
    
        # Clipped data
        ymin = 0.01
        xclp = xobs[ np.where( yobs >= ymin ) ]
        yclp = yobs[ np.where( yobs >= ymin ) ]
    
        # Initial guess
        p0 = [ 2.0, 0.5, 2.0 ]
    
        # Check fit pre-regression
        yold = myfunc( xobs, *p0 )
        plt.plot( xobs, yobs, 'ko', label='data', fillstyle='none' )
        plt.plot( xobs, yold, 'g-', label='pre-fit: a=%4.2f, b=%4.2f, n=%4.2f' % tuple( p0 ) )
    
        # Fit curve using SCIPY CURVE_FIT
        try:
            popt, pcov = scipy.optimize.curve_fit( myfunc, xobs, yobs, p0=p0, maxfev=5000 )
        except:
            print("Could not fit data using SCIPY curve_fit")
        else:
            ynew = myfunc( xobs, *popt )
            plt.plot( xobs, ynew, 'r-', label="curve-fit: a=%4.2f, b=%4.2e, n=%4.2f" % tuple( popt ) )
    
        # Fit curve using SCIPY CURVE_FIT on clipped data
        p0 = [ 1.75, 1e-4, 1e3 ]
        try:
            popt, pcov = scipy.optimize.curve_fit( myfunc_noclip, xclp, yclp, p0=p0 )
        except:
            print("Could not fit data using SCIPY curve_fit")
        else:
            ynew = myfunc_noclip( xobs, *popt )
            plt.plot( xobs, ynew, 'k-', label="curve-fit clipped data: a=%4.2f, b=%4.2e, n=%4.2f" % tuple( popt ) )
    
        # Fit curve using SCIPY MINIMIZE
        p0 = [ 2.0, 0.5, 2.0 ]
        res = scipy.optimize.minimize( driver_func, p0, args=( xobs, yobs ), method='Nelder-Mead' )
        # ~res = scipy.optimize.minimize(driver_func, p0, args=(xobs, yobs), method='CG')
        ynw2 = myfunc( xobs, *res.x )
        plt.plot( xobs, ynw2, 'y--', label='Nelder-Mead 1: a=%4.2f, b=%4.2f, n=%4.2f' % tuple( res.x ) )
        p0 = [ 2.4, 3.6e-4, 5.6e3 ]
        res = scipy.optimize.minimize( driver_func, p0, args=( xobs, yobs ), method='Nelder-Mead' )
        # ~res = scipy.optimize.minimize(driver_func, p0, args=(xobs, yobs), method='CG')
        ynw2 = myfunc( xobs, *res.x )
        plt.plot( xobs, ynw2, 'b:', label='Nelder-Mead 2: a=%4.2f, b=%4.2e, n=%4.2e' % tuple( res.x ) )
    
        plt.legend( loc=2 )
        plt.ylim( -0.05, 0.7 )
        plt.grid()
        plt.show()
    

    所以我会说它工作正常。不过,我收到了溢出警告。

    【讨论】:

    • 嗨 mikuszefski 感谢您的 cmets。我已更新帖子以包含您在 (1) 中建议的更改结果,但不幸的是它们没有帮助。不知道如何解决 (2) - 该等式是通用的,并且似乎在其他数据集上工作正常,所以我想我只是对示例中的数据不走运。我对其他 (x,y) 数据集也有同样的问题
    • @PetGriffin 制作并根据编辑。在这里工作正常。
    • 嗨@mikuszefski - 感谢您抽出时间进一步调查。我运行了你的代码版本,它工作正常,但如果我注释掉语句 p0 = [ 1.75, 1e-4, 1e3 ] 那么它不会。可以使用数据的其他属性来估计初始参数,并且这些起始值(不幸的是)是非物理的。所以问题似乎是我的数据和模型而不是曲线拟合
    • @PetGriffin 我认为问题在于该模型中参数的极高相关性。 chi^2 最小值可能在参数空间中以某种对角线方式非常非常拉伸。因此,数据中的一些微小变化可能会导致拟合结果发生重大变化。从这个意义上说,这是一个模型问题,是的。您会看到初始斜率与b * n 相关,因此您可以轻松地在它们之间切换。根据问题背后的物理原理,您可以尝试不同的饱和度模型。
    猜你喜欢
    • 2021-11-01
    • 2016-11-29
    • 2021-09-25
    • 2020-05-05
    • 1970-01-01
    • 2011-07-20
    • 2017-04-21
    • 2018-11-20
    • 1970-01-01
    相关资源
    最近更新 更多