这些天我花了一些时间来解决问题。我有一组数据:
y = f(t),其中y是非常小的浓度(10 ^ -7),t是秒。 t从0到大约12000不等。
测量遵循既定模型:
y = Vs * t - ((Vs - Vi) * (1 - np.exp(-k * t)) / k)
我需要找到Vs,Vi和k。所以我使用了curve_fit,它返回了最佳拟合参数,并绘制了曲线。
然后我使用了类似的模型:
y = (Vs * t/3600 - ((Vs - Vi) * (1 - np.exp(-k * t/3600)) / k)) * 10**7
通过这样做,t是小时数,y是0到大约10之间的数字。返回的参数当然是不同的。但是当我绘制每条曲线时,这就是我得到的:
http://i.imgur.com/XLa4LtL.png
绿色贴合是第一个模型,蓝色贴合是“标准化”模型。红点是实验值。
拟合曲线不同。我认为这不是预期的,我不明白为什么。如果数字是“可合理的”,计算是否更准确?
答案 0 :(得分:4)
docstring for optimize.curve_fit
说,
p0 : None, scalar, or M-length sequence
Initial guess for the parameters. If None, then the initial
values will all be 1 (if the number of parameters for the function
can be determined using introspection, otherwise a ValueError
is raised).
因此,首先,参数的初始猜测默认为1.
此外,曲线拟合算法必须对各种参数值的函数进行采样。最初选择“各种值”时,初始步长大约为1.如果参数值的变化大约为1,则数据会有一定程度的平滑变化,算法会更好。
如果函数变化很大,参数变化大约为1,那么算法可能会错过最佳参数值。
请注意,即使算法在调整参数值时使用自适应步长,如果初始调整距离标记太远以至于产生大残差,并且如果在某个其他方向上进行调整则会产生较小的残差残差,然后算法可能在错误的方向上漂移并错过局部最小值。它可能会找到一些其他(不需要的)局部最小值,或者根本无法收敛。因此,使用具有自适应步长的算法不一定会省去你。
故事的寓意是扩展数据可以提高算法找到所需最小值的机会。
一般来说,数值算法在应用于幅度大约为1的数据时往往更有效。这种偏差以多种方式进入算法。例如,optimize.curve_fit
依赖optimize.leastsq
,而the call signature for optimize.leastsq
依赖:
def leastsq(func, x0, args=(), Dfun=None, full_output=0,
col_deriv=0, ftol=1.49012e-8, xtol=1.49012e-8,
gtol=0.0, maxfev=0, epsfcn=None, factor=100, diag=None):
因此,默认情况下,公差ftol
和xtol
大约为1e-8。如果找到最佳参数值需要更小的容差,那么这些硬编码的默认数字将导致optimize.curve_fit
错过优化参数值。
为了使这更具体,假设您正在尝试最小化f(x) = 1e-100*x**2
。因子1e-100会使y
- 值变得非常大,以至于x
- 范围的广泛值(上面提到的参数值)将在1e-8的容差范围内。因此,如果使用不理想的缩放,leastsq
将无法很好地找到最小值。
使用大小为1的浮点数的另一个原因是因为区间[-1,1]
中有更多(IEEE754)浮点数远远超过1。例如,
import struct
def floats_between(x, y):
"""
http://stackoverflow.com/a/3587987/190597 (jsbueno)
"""
a = struct.pack("<dd", x, y)
b = struct.unpack("<qq", a)
return b[1] - b[0]
In [26]: floats_between(0,1) / float(floats_between(1e6,1e7))
Out[26]: 311.4397707054894
这表明,在区间[1e6,1e7]中,表示0到1之间的数字的浮点数超过300倍。 因此,在其他条件相同的情况下,如果使用小数而不是非常大的数字,通常会得到更准确的答案。
答案 1 :(得分:2)
我认为它更多地与您传递给曲线拟合的初始参数估计有关。如果您没有通过任何我认为它们都默认为1.规范化您的数据会使这些初始估计更接近事实。如果您不想使用标准化数据,请自行传递初始估算并给出合理的值。
答案 2 :(得分:2)
其他人已经提到你可能需要一个良好的开始猜测你的健康。在这种情况下,我通常会尝试找到一些快速而肮脏的技巧,以至少获得参数的大概估计。在您的情况下,对于较大的t
,指数会很快衰减到零,因此对于大t
,您有
y == Vs * t - (Vs - Vi) / k
按照
进行一阶线性拟合[slope1, offset1] = polyfit(t[t > 2000], y[t > 2000], 1)
您将获得slope1 == Vs
和offset1 == (Vi - Vs) / k
。
从你拥有的所有点中减去这条直线,得到指数
residual == y - slope1 * t - offset1 == (Vs - Vi) * exp(-t * k)
记录双方的日志,你得到
log(residual) == log(Vs - Vi) - t * k
所以再做一次
[slope2, offset2] = polyfit(t, log(y - slope1 * t - offset1), 1)
将为您提供slope2 == -k
和offset2 == log(Vs - Vi)
,因为您已经知道Vi
,因此Vs
应该可以解决这个问题。您可能必须将第二个拟合限制为t
的小值,否则您可能会记录负数的对数。收集您使用这些拟合获得的所有参数,并将它们用作curve_fit
的起点。
最后,你可能想要做一些加权拟合。关于曲线指数部分的信息仅包含在前几个点中,因此您可能应该给予更高的权重。以统计上正确的方式执行此操作并非易事。