如何设置curve_fit的初始值以找到最佳优化,而不仅仅是局部优化?

时间:2018-09-16 16:26:21

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

我正在尝试拟合幂律函数,以便找到最佳拟合参数。但是,我发现如果参数的初始猜测不同,则“最佳匹配”输出将不同。除非找到正确的初始猜测,否则我将获得最佳优化,而不是局部优化。有什么方法可以找到合适的初始猜测吗?我的代码在下面列出。请随时输入任何内容。谢谢!

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()

拟合度如下所示,这不是最佳拟合度。 enter image description here

如果我将初始猜测更改为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)

我可以得到最合适的 enter image description here

--------------------- 已于2018年7月10日更新 -------------- -------------------------------------------------- -------------------------------------------------- -------

由于我需要运行成千上万甚至数百万的幂律功能,因此使用@James Phillips的方法过于昂贵。那么,除了curve_fit之外,什么方法还合适?例如sklearn,np.linalg.lstsq等。

3 个答案:

答案 0 :(得分:3)

没有简单的答案:如果有,它将在curve_fit中实现,然后就不必问您起点了。一种合理的方法是首先拟合齐次模型y = a*x**b。假设正y(通常在使用幂定律时会发生这种情况),可以通过粗略而快速的方式完成:在对数-对数刻度上,log(y) = log(a) + b*log(x)是线性回归,可以使用{ {1}}。这为np.linalg.lstsqlog(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] 成为NumPy数组。否则,您首先要切片test_X,它会被NumPy分解为 integers 的数组,然后使用负指数抛出错误。 (而且我想X[1:]的目的是使其成为一个浮点数组?这就是1.0参数应使用的参数。)

答案 1 :(得分:2)

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

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)

答案 2 :(得分:2)

除了“欢迎来到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的符号一样,我认为您可以从简单的数据图中看到该边界。当我尝试解决您的问题时,如果初始值为cb=1的边界无济于事,但对于b=0b=-5来说,边界却无济于事。

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

允许我推荐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

这更好一些。

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