实验数据在两个不同区域内的拟合

5

我正在拟合一组实验数据(样本),分别在两个不同的实验区域内,并可以用以下两个数学函数表示:

第一个区域:

y = m*x + c ( the slope can be constrained to zero)

第二个区域:

y = d*exp(-k*x)

以下是实验数据,并且我使用python编写了如下代码:

def func(x, m, c, d, k):
   return m*x+ c + d*np.exp(-k*x) 
popt, pcov = curve_fit(func, t, y)

很抱歉,我的数据未能适当地拟合,拟合(返回的)参数没有意义(请参见下图)。

任何协助将不胜感激。

这里是绘制数据的链接


请问您能否提供数据的链接? - James Phillips
在你的方程式中,你可能需要类似于“如果X < 0.5,则使用equation_1,否则使用equation_2”的逻辑类型。 - James Phillips
这是实验数据的链接:https://docs.google.com/spreadsheets/d/1JOw7bDwWWXSkR_uU2aELrxx33bdqfR1nVci_w1VFUOw/edit?usp=sharing - KJ1
3个回答

4
非常有趣的问题。如a_guest所说,您需要分别适应这两个区域。然而,我认为您可能还希望这两个区域在点t0处平滑连接,即我们从一个模型切换到另一个模型的点。为了做到这一点,我们需要在点t0处添加约束条件y1 == y2。
要使用scipy实现这一点,请查看SLSQP方法的scipy.optimize.minimize。但是,我编写了一个scipy包装器,使这种事情更容易,称为symfit。我将向您展示如何使用symfit完成此操作,因为我认为它更适合此任务,但是使用此示例,您也应该能够使用纯scipy实现它。
from symfit import parameters, variables, Fit, Piecewise, exp, Eq
import numpy as np
import matplotlib.pyplot as plt

t, y = variables('t, y')
m, c, d, k, t0 = parameters('m, c, d, k, t0')

# Help the fit by bounding the switchpoint between the models
t0.min = 0.6
t0.max = 0.9

# Make a piecewise model
y1 = m * t + c
y2 = d * exp(- k * t)
model = {y: Piecewise((y1, t <= t0), (y2, t > t0))}

# As a constraint, we demand equality between the two models at the point t0
# to do this, we substitute t -> t0 and demand equality using `Eq`
constraints = [Eq(y1.subs({t: t0}), y2.subs({t: t0}))]

# Read the data
tdata, ydata = np.genfromtxt('Experimental Data.csv', delimiter=',', skip_header=1).T

fit = Fit(model, t=tdata, y=ydata, constraints=constraints)
fit_result = fit.execute()
print(fit_result)

plt.scatter(tdata, ydata)
plt.plot(tdata, fit.model(t=tdata, **fit_result.params).y)
plt.show()

enter image description here


1
非常好的回答,但您能否更详细地说明一下约束是如何在内部实施的?这两个域是否同时适应于一个复合损失函数?还是分别适应,并且“第二”模型(可能是正确的)的参数受到第一个模型(左侧)的结果的限制(或反之亦然)? - a_guest
1
@a_guest:在内部,我们确实适用不同的损失函数。基本上,我们执行正常的最小二乘拟合,其中等式约束被添加为拉格朗日乘数。这样,拟合问题真正变成了一个保证在最小化的每一步中开关点处的连续性的问题 :)。我希望这回答了你的问题,如果没有,请让我知道。 - tBuLi

3

由于您的数据在不同地区呈现出不同的行为,因此您还需要将数据拟合到这些不同的区域。即,与其对两个模型(函数)求和,您应该使用y = m*x + c在左侧区域单独进行拟合,在右侧区域单独使用y = d*exp(-k*x)进行拟合。如果您难以找到两个区域的边界,可以通过比较拟合优度来评估。

popt_1, pcov_1 = curve_fit(lambda x, m, c: m*x + c, t[t < 0.8], y[t < 0.8], p0=(1, 0))
popt_2, pcov_2 = curve_fit(lambda x, d, k: d*exp(-k*x), t[t >= 0.8], y[t >= 0.8], p0=(400, 1))

编辑

示例代码:

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


df = pd.read_csv('test.csv', index_col=None)
t = df.t.values
y = df.Y.values

boundary = t[y.argmax()]
t1 = t[t < boundary]
y1 = y[t < boundary]
t2 = t[t >= boundary]
y2 = y[t >= boundary]

f1 = lambda x, m, c: m*x + c
f2 = lambda x, d, k: d*np.exp(-k*x)
popt_1 ,pcov_1 = curve_fit(f1, t1, y1, p0=((y1[-1] - y1[0]) / (t1[-1] - t1[0]), y1[0]))
popt_2 ,pcov_2 = curve_fit(f2, t2, y2, p0=(y2[0], 1))

plt.title('Fitted data on two different domains')
plt.xlabel('t [a.u.]')
plt.ylabel('y [a.u.]')
plt.plot(t, y, '-o', label='Data')
plt.plot(t1, f1(t1, *popt_1), '--', color='#ff7f0e', lw=3, label='Fit')
plt.plot(t2, f2(t2, *popt_2), '--', color='#ff7f0e', lw=3, label='_nolegend_')
plt.grid()
plt.legend()
plt.show()

这将产生以下图表:

数据图表

请注意,生成的“复合”函数在边界处不连续。如果不希望出现这种情况,您可以在拟合另一个域之前修复一个拟合参数(例如k)。或者,您可以分别拟合两个区域,然后确定边界处的值为两个单独函数的平均值(即y_b = (f1(t1[-1], *popt_1) + f2(t2[0], *popt_2)) / 2),然后通过约束参数来重复拟合,以满足此边界条件。

例如,首先拟合线性函数,然后固定指数中的d参数,以使边界处具有连续性(请注意,线性函数f1会在t2[0]处进行外推,以确保连续性):

f1 = lambda x, m, c: m*x + c
popt_1, pcov_1 = curve_fit(f1, t1, y1, p0=((y1[-1] - y1[0]) / (t1[-1] - t1[0]), y1[0]))

d = f1(t2[0], *popt_1)
f2 = lambda x, k: d*np.exp(-k*(x - boundary))
popt_2, pcov_2 = curve_fit(f2, t2, y2, p0=(1,))

这将生成以下图表:

连续拟合


从图像中可以看出,边界位于最大的“Y”值处 - 如果这总是成立的话,那么自动找到边界就非常简单了。在我看来,这个值左侧的数据似乎不是一条直线。 - James Phillips
@KJ1 我不确定你是怎么得出这个结论的,但我编辑了我的答案并添加了一个代码示例来实现这种方法。确实,在y.argmax()中找到边界似乎已经足够了,但是如果没有关于问题的详细知识,仍然很难判断(例如,人们可以期望在域[0,1]上有线性行为)。尽管在左侧域中的数据不是完全线性的,但它确实涉及线性趋势,而且这个事实也并不妨碍你尝试拟合线性模型。 - a_guest
1
@KJ1 你应该具体说明为什么“不起作用”。对于你的示例数据,a_guest的方法在我的环境中运行良好。 - Mr. T
感谢@a-guest的贡献。根据您的方法,模型适合得很好。然而,参数d似乎不正确。它报告约为2300。我预期它应该与"c"的值大致相同。我的模型假设dexp(-kt)是"y"的初始值,这更像是数据的第一段拟合的平均值,其中y = mx + c。如果将m限制为零,它应该为您提供c,c应该在与d相同的区域内。请参见我在附加链接中绘制的图表。https://drive.google.com/file/d/10xFdGyjt_eANCsoE6M0wpn2ih1RE_ykI/view - KJ1
@KJ1 d的大值是由于您选择了d*exp(-k*t)作为拟合函数,但该函数(或者更好地说是相应的定义域)“开始”于大于零的边界(因此d反映了该函数在t = 0处取值,而这是其定义域之外的)。如果您想让d反映定义域起始值,则需要确保exp(-k*x) == 1,即您需要拟合函数d*exp(-k*(t - boundary))(其中boundary == t2[0])。 - a_guest
显示剩余3条评论

2
如果您更喜欢使用单个方程式,我发现Hocket-Sherby方程式“y = b - (b-a) * exp(-c * (x**d))”似乎对您的数据拟合得不错,其R平方为0.99,RMSE为11.2,参数为a = 1.1262189756312683E+01,b = 3.2040596733114870E+02,c = 3.9385197507261771E-01,d = -4.7723382040098095E + 00。

modelplot


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接