【问题标题】:how to set up the initial value for curve_fit to find the best optimizing, not just local optimizing?如何设置curve_fit的初始值以找到最佳优化,而不仅仅是局部优化?
【发布时间】:2018-09-16 16:26:20
【问题描述】:

我正在尝试拟合幂律函数,以找到最佳拟合参数。但是,我发现如果参数的初始猜测不同,“最佳拟合”输出就会不同。除非我找到正确的初始猜测,否则我可以获得最佳优化,而不是局部优化。有没有办法找到**适当的初始猜测** ????。我的代码在下面列出。请随时输入。谢谢!

import numpy as np
import pandas as pd
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
%matplotlib inline

# power law function
def func_powerlaw(x,a,b,c):
    return a*(x**b)+c

test_X = [1.0,2,3,4,5,6,7,8,9,10]
test_Y =[3.0,1.5,1.2222222222222223,1.125,1.08,1.0555555555555556,1.0408163265306123,1.03125, 1.0246913580246915,1.02]

predict_Y = []
for x in test_X:
    predict_Y.append(2*x**-2+1)

如果我与默认初始猜测一致,即 p0 = [1,1,1]

popt, pcov = curve_fit(func_powerlaw, test_X[1:], test_Y[1:], maxfev=2000)


plt.figure(figsize=(10, 5))
plt.plot(test_X, func_powerlaw(test_X, *popt),'r',linewidth=4, label='fit: a=%.4f, b=%.4f, c=%.4f' % tuple(popt))
plt.plot(test_X[1:], test_Y[1:], '--bo')
plt.plot(test_X[1:], predict_Y[1:], '-b')
plt.legend()
plt.show()

合身如下,这不是最合适的。

如果我将初始猜测更改为 p0 = [0.5,0.5,0.5]

popt, pcov = curve_fit(func_powerlaw, test_X[1:], test_Y[1:], p0=np.asarray([0.5,0.5,0.5]), maxfev=2000)

我能找到最合适的

----------于 10.7.2018 更新------------- -------------------------------------------------- -------------------------------------------------- --------

由于我需要运行数千甚至 数百万 的幂律函数,因此使用 @James Phillips 的方法过于昂贵。那么除了curve_fit还有什么合适的方法呢?比如sklearn、np.linalg.lstsq等。

【问题讨论】:

    标签: python scikit-learn scipy least-squares best-fit-curve


    【解决方案1】:

    这是使用 scipy.optimize.differential_evolution 遗传算法的示例代码,以及您的数据和方程。这个 scipy 模块使用拉丁超立方体算法来确保对参数空间的彻底搜索,因此需要搜索范围 - 在这个例子中,这些范围是基于数据的最大值和最小值。对于其他问题,如果您知道预期的参数值范围,您可能需要提供不同的搜索范围。

    import numpy, scipy, matplotlib
    import matplotlib.pyplot as plt
    from scipy.optimize import curve_fit
    from scipy.optimize import differential_evolution
    import warnings
    
    # power law function
    def func_power_law(x,a,b,c):
        return a*(x**b)+c
    
    test_X = [1.0,2,3,4,5,6,7,8,9,10]
    test_Y =[3.0,1.5,1.2222222222222223,1.125,1.08,1.0555555555555556,1.0408163265306123,1.03125, 1.0246913580246915,1.02]
    
    
    # function for genetic algorithm to minimize (sum of squared error)
    def sumOfSquaredError(parameterTuple):
        warnings.filterwarnings("ignore") # do not print warnings by genetic algorithm
        val = func_power_law(test_X, *parameterTuple)
        return numpy.sum((test_Y - val) ** 2.0)
    
    
    def generate_Initial_Parameters():
        # min and max used for bounds
        maxX = max(test_X)
        minX = min(test_X)
        maxY = max(test_Y)
        minY = min(test_Y)
        maxXY = max(maxX, maxY)
    
        parameterBounds = []
        parameterBounds.append([-maxXY, maxXY]) # seach bounds for a
        parameterBounds.append([-maxXY, maxXY]) # seach bounds for b
        parameterBounds.append([-maxXY, maxXY]) # seach bounds for c
    
        # "seed" the numpy random number generator for repeatable results
        result = differential_evolution(sumOfSquaredError, parameterBounds, seed=3)
        return result.x
    
    # generate initial parameter values
    geneticParameters = generate_Initial_Parameters()
    
    # curve fit the test data
    fittedParameters, pcov = curve_fit(func_power_law, test_X, test_Y, geneticParameters)
    
    print('Parameters', fittedParameters)
    
    modelPredictions = func_power_law(test_X, *fittedParameters) 
    
    absError = modelPredictions - test_Y
    
    SE = numpy.square(absError) # squared errors
    MSE = numpy.mean(SE) # mean squared errors
    RMSE = numpy.sqrt(MSE) # Root Mean Squared Error, RMSE
    Rsquared = 1.0 - (numpy.var(absError) / numpy.var(test_Y))
    print('RMSE:', RMSE)
    print('R-squared:', Rsquared)
    
    print()
    
    
    ##########################################################
    # graphics output section
    def ModelAndScatterPlot(graphWidth, graphHeight):
        f = plt.figure(figsize=(graphWidth/100.0, graphHeight/100.0), dpi=100)
        axes = f.add_subplot(111)
    
        # first the raw data as a scatter plot
        axes.plot(test_X, test_Y,  'D')
    
        # create data for the fitted equation plot
        xModel = numpy.linspace(min(test_X), max(test_X))
        yModel = func_power_law(xModel, *fittedParameters)
    
        # now the model as a line plot
        axes.plot(xModel, yModel)
    
        axes.set_xlabel('X Data') # X axis data label
        axes.set_ylabel('Y Data') # Y axis data label
    
        plt.show()
        plt.close('all') # clean up after using pyplot
    
    graphWidth = 800
    graphHeight = 600
    ModelAndScatterPlot(graphWidth, graphHeight)
    

    【讨论】:

    • 感谢您的回答。为了接受答案,我仍在评估哪种方式更好。
    • scipy.optimize.fminbound 和你上面提到的一样吗?使用模拟退火的 scipy.optimize.anneal 是否能给我们带来更好的结果?
    • 为什么在**generate_Initial_Parameters()**函数中包含minY = min(test_Y)?似乎没有使用...相反,我认为应该是 minY = abs(min(test_Y))。
    • 我根据其他确实使用 minY 的代码改编了这个示例。多年前我测试了模拟退火,当时发现微分演化更好地满足了我为非线性曲线和曲面拟合找到通用“初始参数估计器”的目标。
    • 但是在上面的代码中,minX 和 minY 没有包含在下面的函数中。基于 maxXY = max(maxX, maxY),我认为您的想法是获得最广泛的界限。所以应该是maxXY = max(maxX, maxY, minX, minX),其中minX和minY需要是绝对值。
    【解决方案2】:

    除了 Welcome to Stack Overflow 给出的“没有简单、通用的方法”和 James Phillips 的“差异进化经常 如果比curve_fit() 慢一些,有助于找到好的起点(甚至是好的解决方案!),请允许我单独给出一个您可能会觉得有帮助的答案。

    首先,curve_fit() 默认为任何参数值这一事实是非常糟糕的主意。这种行为没有任何可能的理由,您和其他所有人都应该将存在参数默认值这一事实视为curve_fit() 的实现中的严重错误,并假装此错误不存在。 绝不相信这些默认设置是合理的。

    从一个简单的数据图中,很明显a=1, b=1, c=1 是非常非常糟糕的起始值。函数衰减,所以b < 0。事实上,如果您从 a=1, b=-1, c=1 开始,您就会找到正确的解决方案。

    这也可能有助于为参数设置合理的界限。即使设置 (-100, 100) 的 c 的界限也可能有所帮助。与b 的符号一样,我认为您可以从简单的数据图中看到该边界。当我为您的问题尝试此操作时,如果初始值为 b=1c 的界限将无济于事,但对于 b=0b=-5 则有效。

    更重要的是,尽管您在图中打印了最合适的参数popt,但您没有打印pcov 中保存的变量之间的不确定性或相关性,因此您对结果的解释是不完整的。如果您查看了这些值,您会发现以b=1 开头不仅会导致错误的值,还会导致参数的巨大不确定性和非常非常高的相关性。这是适合告诉您它找到了一个糟糕的解决方案。不幸的是,从curve_fit 返回的pcov 并不是很容易解包。

    请允许我推荐 lmfit (https://lmfit.github.io/lmfit-py/)(免责声明:我是首席开发人员)。除其他功能外,此模块还强制您提供非默认起始值​​,并更轻松地生成更完整的报告。对于您的问题,即使以 a=1, b=1, c=1 开头也会给出更有意义的指示:

    from lmfit import Model
    mod = Model(func_powerlaw)
    params = mod.make_params(a=1, b=1, c=1)
    ret = mod.fit(test_Y[1:], params, x=test_X[1:])
    print(ret.fit_report())
    

    会打印出来:

    [[Model]]
        Model(func_powerlaw)
    [[Fit Statistics]]
        # fitting method   = leastsq
        # function evals   = 1318
        # data points      = 9
        # variables        = 3
        chi-square         = 0.03300395
        reduced chi-square = 0.00550066
        Akaike info crit   = -44.4751740
        Bayesian info crit = -43.8835003
    [[Variables]]
        a: -1319.16780 +/- 6892109.87 (522458.92%) (init = 1)
        b:  2.0034e-04 +/- 1.04592341 (522076.12%) (init = 1)
        c:  1320.73359 +/- 6892110.20 (521839.55%) (init = 1)
    [[Correlations]] (unreported correlations are < 0.100)
        C(a, c) = -1.000
        C(b, c) = -1.000
        C(a, b) =  1.000
    

    那是a = -1.3e3 +/- 6.8e6 -- 定义不是很好!此外,所有参数都是完全相关的。

    b 的初始值更改为-0.5:

    params = mod.make_params(a=1, b=-0.5, c=1) ## Note !
    ret = mod.fit(test_Y[1:], params, x=test_X[1:])
    print(ret.fit_report())
    

    给了

    [[Model]]
        Model(func_powerlaw)
    [[Fit Statistics]]
        # fitting method   = leastsq
        # function evals   = 31
        # data points      = 9
        # variables        = 3
        chi-square         = 4.9304e-32
        reduced chi-square = 8.2173e-33
        Akaike info crit   = -662.560782
        Bayesian info crit = -661.969108
    [[Variables]]
        a:  2.00000000 +/- 1.5579e-15 (0.00%) (init = 1)
        b: -2.00000000 +/- 1.1989e-15 (0.00%) (init = -0.5)
        c:  1.00000000 +/- 8.2926e-17 (0.00%) (init = 1)
    [[Correlations]] (unreported correlations are < 0.100)
        C(a, b) = -0.964
        C(b, c) = -0.880
        C(a, c) =  0.769
    

    哪个更好。

    总之,初始值总是很重要,结果不仅是最佳拟合值,还包括不确定性和相关性。

    【讨论】:

    • 感谢您的回复!这是一个很好的呼吁。我以前没有真正看过pcov。我有一个与此一致的快速问题。我尝试了最初的猜测 [0.5, 0.5, 0.5] 和 [1.0, -1.0, 1.0] 都给了我优化点。不过pcov结果有点不一样~~你知道为什么吗?
    【解决方案3】:

    没有简单的答案:如果有,它将在curve_fit 中实现,然后它就不必询问您的起点。一种合理的方法是首先拟合齐次模型y = a*x**b。假设 y 为正(当你使用幂律时通常是这种情况),这可以通过一种粗略而快速的方式完成:在对数尺度上,log(y) = log(a) + b*log(x) 这是线性回归,可以用np.linalg.lstsq 解决.这为log(a)b 提供了候选人;使用这种方法的c 的候选者是0

    test_X = np.array([1.0,2,3,4,5,6,7,8,9,10])
    test_Y = np.array([3.0,1.5,1.2222222222222223,1.125,1.08,1.0555555555555556,1.0408163265306123,1.03125, 1.0246913580246915,1.02])
    
    rough_fit = np.linalg.lstsq(np.stack((np.ones_like(test_X), np.log(test_X)), axis=1), np.log(test_Y))[0]
    p0 = [np.exp(rough_fit[0]), rough_fit[1], 0]
    

    结果是您在第二张图片中看到的非常合适的。

    顺便说一句,最好一次性将test_X 变成一个NumPy 数组。否则,您首先对 X[1:] 进行切片,这将得到 NumPy 字段作为 整数 的数组,然后抛出带有负指数的错误。 (我想1.0 的目的是使它成为一个浮点数组?这就是dtype=np.float 参数应该用于的用途。)

    【讨论】:

    • 感谢您的回复。在这种情况下,我需要拟合超过数千甚至数百万的幂律函数。我认为首先应用对数刻度是一个很好的解决方案,我也在其他地方找到了这个解决方案。我只是想知道在使用 log-log 之后,您认为使用 sklearn package linear_model 可以解决这个问题吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-29
    • 1970-01-01
    • 2023-03-06
    相关资源
    最近更新 更多