Scipy优化曲线拟合的限制

5 投票
4 回答
14835 浏览
提问于 2025-04-18 01:36

有没有办法给Scipy的优化曲线拟合设置一些限制呢?

我举个例子:

    def optimized_formula(x, m_1, m_2, y_1, y_2, ratio_2):
        return (log(x[0]) * m_1 + m_2)*((1 - x[1]/max_age)*(1-ratio_2)) + ((log(x[1]) * y_1 + y_2)*(x[1]/max_age)*ratio_2)

    popt, pcov = optimize.curve_fit(optimized_formula, usage_and_age, prices)

x[0]代表年龄,而max_age是一个常量。考虑到这一点,当x[0]接近最大年龄时,x[1]/max_age会接近1。

我想知道是否可以设置一些约束条件,比如要求x[1]/max_age大于0.3并且小于0.7,还有其他的限制,比如m_1小于0,m_2大于0,等等。

4 个回答

2

因为 curve_fit() 是用最小二乘法来处理问题的,所以你可能会想看看 scipy.optimize.fmin_slsqp(),这个方法可以让你进行有约束的优化。你可以查看 这个教程,了解如何使用它。

3

可以试试lmfit这个模块(http://lmfit.github.io/lmfit-py/)。它为scipy.optimize中的许多优化方法增加了一种固定或设置参数范围的方式,包括最小二乘法,并提供了很多工具来简化拟合过程。

6

注意:这是SciPy版本0.17中的新功能

假设你想要把一个模型拟合到这样的数据上:

y=a*t**alpha+b

并且对alpha有一些限制

0<alpha<2

而其他参数a和b则可以自由调整。那么我们应该使用优化函数中的bounds选项,也就是optimize.curve_fit:

import numpy as np
from scipy.optimize import curve_fit
def func(t, a,alpha,b):
     return a*t**alpha+b
param_bounds=([-np.inf,0,-np.inf],[np.inf,2,np.inf])
popt, pcov = optimize.curve_fit(func, xdata,ydata,bounds=param_bounds)

详细信息可以在这里找到

8

正如其他回答中提到的,你可以使用 lmfit 来解决这类问题。因此,我这里加一个例子,方便有兴趣的人参考。

假设你有一个数据集,如下所示:

xdata = np.array([177.,180.,183.,187.,189.,190.,196.,197.,201.,202.,203.,204.,206.,218.,225.,231.,234.,
          252.,262.,266.,267.,268.,277.,286.,303.])

ydata = np.array([0.81,0.74,0.78,0.75,0.77,0.81,0.73,0.76,0.71,0.74,0.81,0.71,0.74,0.71,
      0.72,0.69,0.75,0.59,0.61,0.63,0.64,0.63,0.35,0.27,0.26])

你想要给这些数据拟合一个模型,模型的样子是这样的:

model = n1 + (n2 * x + n3) * 1./ (1. + np.exp(n4 * (n5 - x)))

并且有一些限制条件:

0.2 < n1 < 0.8
-0.3 < n2 < 0

使用 lmfit(版本 0.8.3),你可以得到以下输出结果:

n1:   0.26564921 +/- 0.024765 (9.32%) (init= 0.2)
n2:  -0.00195398 +/- 0.000311 (15.93%) (init=-0.005)
n3:   0.87261892 +/- 0.068601 (7.86%) (init= 1.0766)
n4:  -1.43507072 +/- 1.223086 (85.23%) (init=-0.36379)
n5:   277.684530 +/- 3.768676 (1.36%) (init= 274)

enter image description here

从图中可以看到,拟合效果很好,数据被很好的重现,参数也在你要求的范围内。

下面是完整的代码,可以生成这个图,并附带了一些额外的注释:

from lmfit import minimize, Parameters, Parameter, report_fit
import numpy as np

xdata = np.array([177.,180.,183.,187.,189.,190.,196.,197.,201.,202.,203.,204.,206.,218.,225.,231.,234.,
      252.,262.,266.,267.,268.,277.,286.,303.])

ydata = np.array([0.81,0.74,0.78,0.75,0.77,0.81,0.73,0.76,0.71,0.74,0.81,0.71,0.74,0.71,
      0.72,0.69,0.75,0.59,0.61,0.63,0.64,0.63,0.35,0.27,0.26])

def fit_fc(params, x, data):

    n1 = params['n1'].value
    n2 = params['n2'].value
    n3 = params['n3'].value
    n4 = params['n4'].value
    n5 = params['n5'].value

    model = n1 + (n2 * x + n3) * 1./ (1. + np.exp(n4 * (n5 - x)))

    return model - data #that's what you want to minimize

# create a set of Parameters
# 'value' is the initial condition
# 'min' and 'max' define your boundaries
params = Parameters()
params.add('n1', value= 0.2, min=0.2, max=0.8)
params.add('n2', value= -0.005, min=-0.3, max=10**(-10))
params.add('n3', value= 1.0766, min=-1000., max=1000.)
params.add('n4', value= -0.36379, min=-1000., max=1000.)
params.add('n5', value= 274.0, min=0., max=1000.)

# do fit, here with leastsq model
result = minimize(fit_fc, params, args=(xdata, ydata))

# write error report
report_fit(params)

xplot = np.linspace(min(xdata), max(xdata), 1000)
yplot = result.values['n1'] + (result.values['n2'] * xplot + result.values['n3']) * \
                              1./ (1. + np.exp(result.values['n4'] * (result.values['n5'] - xplot)))
#plot results
try:
    import pylab
    pylab.plot(xdata, ydata, 'k+')
    pylab.plot(xplot, yplot, 'r')
    pylab.show()
except:
    pass

编辑:

如果你使用的是 0.9.x 版本,你需要相应地调整代码;可以查看 这里,了解从 0.8.3 到 0.9.x 的变化。

撰写回答