有没有办法在scipy.optimize.curve_fit

时间:2018-05-21 15:19:33

标签: python scipy curve-fitting

我试图描述70年代在海洋模型中由核试验引起的碳同位素的扩散。 大气信号是一个强烈的尖峰,随着洋流的深入传播(更深的电流要慢得多)。

我的目标是检测浓度上升的开始和不同深度水平的增加率。

我假设碳同位素的海洋浓度表现为具有3个区段的分段线性函数:

  1. 一直到时间(b
  2. 的常量初始值(t_0
  3. 从时间(t_0)到(t_1)的浓度线性增加,速率为m1
  4. 时间(t_1)后的浓度线性下降,速率为m2
  5. 我在python中使用此代码表示函数:

    import numpy as np
    import matplotlib.pyplot as plt
    import scipy.optimize as sio
    
    def piecewise_linear( t, t0, t1, b, m1, m2 ):
        condlist = [ t < t0,
                    (t >= t0 ) & ( t < t1 ),
                    t >= t1
                   ]
        funclist = [lambda t: b,
                    lambda t: b + m1 * ( t - t0 ),
                    lambda t: b + m1 * ( t - t0 ) + m2 * ( t - t1 )
                   ]
        return np.piecewise( t, condlist, funclist )
    

    对于给定的时间数组t,我希望能够拟合此函数的两个“类型”:

    1. 一条完整的3段线,代表上层海洋,信号快速传播,穗被完全捕获。
    2. 一个特殊情况,在时间序列结束时,浓度没有达到峰值(这将代表深海中的信号,传播信号需要很长时间)
    3. 例如

      t = np.arange( 0, 15, 0.1 )
      y_full = piecewise_linear( t, 5, 10, 2, 2, -4 )
      y_cut = piecewise_linear( t, 5, 15, 2, 2, -4 )
      plt.plot( t, y_full )
      plt.plot( t, y_cut )
      plt.legend( [ 'surface', 'deep ocean' ] )
      

      enter image description here

      对于第一种情况,当我在添加一些随机噪声后尝试将函数拟合到信号时,我得到了很好的结果:

      noise = np.random.normal( 0, 1, len( y_full ) ) * 1
      y = y_full
      yy = y_full + noise
      bounds = ( [ 0, 0, 0, 0, -np.inf ], [ np.inf, np.inf, np.inf, np.inf, 0 ] )
      fit,_ = sio.curve_fit( piecewise_linear, t, yy, bounds=bounds )
      print( fit )
      y_fit = piecewise_linear( t, *tuple( fit ) )
      plt.plot( t, yy, color='0.5' )
      plt.plot( t, y_fit, linewidth=3 )
      plt.plot( t, y, linestyle='--', linewidth=3 )
      

      结果是

      >>[  5.00001407  10.01945313   2.13055863   1.95208167  -3.95199719]
      

      enter image description here

      然而,当我尝试评估第二种情况(深海)时,我常常得到如下结果:

      noise = np.random.normal( 0, 1, len(y_full ) ) * 1#
      y = y_cut
      yy = y_cut+noise
      bounds = ( [ 0, 0, 0, 0, -np.inf], [ np.inf, np.inf, np.inf, np.inf, 0 ] )
      fit,_ = sio.curve_fit( piecewise_linear, t, yy, bounds=bounds )
      print( fit )
      y_fit = piecewise_linear( t, *tuple( fit ) )
      plt.plot( t, yy, color='0.5' )
      plt.plot( t, y_fit, linewidth=3 )
      plt.plot( t, y, linestyle='--', linewidth=3 )
      plt.legend( [ 'noisy data', 'fit', 'original' ] )
      

      我得到了

      >>[ 1.83838997  0.40000014  1.51810839  2.56982348 -1.0622842 ]
      

      enter image description here

      优化确定t_0大于t_1,这在此上下文中是无意义的。

      有没有办法在曲线拟合中建立条件t_0 < t_1?或者我是否必须测试,给出了哪种类型的曲线然后适合两个不同的函数(3段或2段分段线性函数)?

      非常感谢任何帮助

3 个答案:

答案 0 :(得分:1)

您可以考虑使用lmfit(https://lmfit.github.io/lmfit-py)。 Lmfit为曲线拟合提供了更高级别的接口,并使拟合参数成为一流的python对象。除此之外,这很容易修复一些参数,并以比scipy.optimize.curve_fit使用的更加pythonic的方式设置参数的界限。特别是对于您的问题,lmfit参数还支持使用数学表达式作为所有参数的约束表达式。

要将模型函数piecewise_linear()转换为使用lmfit进行曲线拟合的模型,您可以执行类似

的操作
from lmfit import Model

# make a model
mymodel = Model(piecewise_linear)

# create parameters and set initial values
# note that parameters are *named* from the 
# names of arguments of your model function
params = mymodel.make_params(t0=0, t1=1, b=3, m1=2, m2=2)

# now, you can place bounds on parameters, maybe like
params['b'].min = 0
params['m1'].min = 0

# but what you want is an inequality constraint, so
#   1. add a new parameter 'tdiff'
#   2. constrain t1 = t0 + tdiff
#   3. set a minimum value of 0 for tdiff
params.add('tdiff', value=1, min=0)
params['t1'].expr = 't0 + tdiff'

# now perform the fit
result = mymodel.fit(yy, params, t=t)

# print out results
print(result.fit_report())

您可以阅读lmfit文档或其他SO问题,了解如何从拟合结果中提取其他信息。

答案 1 :(得分:1)

在这种情况下,curve_fit有几个缺点,因此需要考虑MNewille的解决方案。此外,curve_fit没有参数args(与例如leastsq相反),这可能允许关闭第二个斜率。在这里,没有m2的第二个拟合函数可能是一个解决方案。但是,如果curve_fit是必须的,并且需要在两种情况下都能使用通用拟合函数,那么解决方案可能看起来像(注意从数据中提取的起始参数):

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

"""
 we know t0 > 0, t1 > t0, b>0, m1 > 0, m2 < 0
"""
def piecewise_linear( t, t0, a , b, m1, m2 ):
    t0 = abs( t0 )
    t1 = abs( a ) * t0
    b = abs( b )
    m1 = abs( m1 )
    m2 = - abs( m2 )
    condlist = [ t < t0,
                 ( t >= t0 ) & ( t < t1 ),
                 t >= t1
               ]
    funclist = [ lambda t: b,
                 lambda t: b + m1 * ( t - t0 ),
                 lambda t: b + m1 * ( t - t0 ) + m2 * ( t - t1 )
               ]
    return np.piecewise( t, condlist, funclist )

t = np.arange( 0, 15, 0.1 )
y_full = piecewise_linear( t, 5, 2, 2, 2, -4 )
y_cut = piecewise_linear( t, 5, 3, 2, 2, -4 )

####################

#~ plt.plot( t, y_full )
#~ plt.plot( t, y_cut )
#~ plt.legend( [ 'surface', 'deep ocean'] )

####################

#~ noise = np.random.normal( 0, 1, len( y_full ) ) * 1
#~ y = y_full
#~ yy = y_full + noise

#~ bounds = ( [ 0, 0, 0, 0, -np.inf ], [ np.inf, np.inf, np.inf, np.inf, 0 ] )
#~ fit,_ = sio.curve_fit( piecewise_linear, t, yy, bounds=bounds )
#~ print( fit )
#~ y_fit = piecewise_linear( t, *tuple( fit ) )
#~ plt.plot( t, yy, color='0.5' )
#~ plt.plot( t, y_fit, linewidth=3 )
#~ plt.plot( t, y, linestyle='--', linewidth=3 )

####################

noise = np.random.normal( 0, 1, len( y_full ) ) * 1
y = y_cut
yy = y_cut + noise
tPos = np.argmax( yy )
t1Start = t[ tPos ]
t0Start = t[ tPos // 2 ]
bStart = yy[ 0 ]
aStart = 2
m1Start = ( yy[ tPos ] - yy[ tPos // 2 ] ) / ( t1Start - t0Start )

p0 = [ t0Start, aStart, bStart, m1Start, 0 ])
fit,_ = sio.curve_fit( piecewise_linear, t, yy, p0=p0 )
print( fit )
y_fit = piecewise_linear( t, *tuple( fit ) )

plt.plot( t, yy, color='0.5' )
plt.plot( t, y_fit, linewidth=3 )
plt.plot( t, y, linestyle='--', linewidth=3 )
plt.legend( [ 'noisy data', 'fit', 'original' ] )
plt.show()

它适用于测试数据。必须记住,返回的拟合参数可能是负面的。由于函数采用模数,因此也需要对返回的参数进行处理。另请注意t1不再是直接拟合,而是t0的倍数。因此,错误需要相应地传播。新结构不需要bounds

另请注意,选择起始参数p0也适用于案例1。

答案 2 :(得分:0)

由于我不知道Python中使用的回归算法究竟是什么,我无法真正回答你的问题。该算法可能像往常一样是一个迭代过程。

作为附加信息,我会展示一种非常简单的方法,可以在没有迭代过程的情况下给出近似答案,也不会进行初步猜测。基于积分方程拟合的理论可以在https://fr.scribd.com/doc/14674814/Regressions-et-equations-integrales中找到,并且在分段函数的情况下使用的一些例子显示在:https://fr.scribd.com/document/380941024/Regression-par-morceaux-Piecewise-Regression-pdf

在由三个线性段组成的分段函数的情况下,对于上述第二篇论文的第30页给出了微积分的方法。用任何计算机语言编写代码都非常容易。我想也可以使用Python。

从扫描原始图表获得的数据:

enter image description here

使用积分方程的回归方法导致下一个结果:

enter image description here

拟合的等式是: enter image description here

H是Heaviside函数。

参数a1,a2,p1,q1,p2,q2,p3,q3的值如上图所示。

可以看到第一个段并不像预期的那样完全水平。但斜率非常小:0.166

由于算法第二部分略有变化,可以指定斜率= 0(即p1 = 0)。修改后的算法如下所示:

enter image description here

现在,结果是:

enter image description here