我最近花了些时间试图解决一个问题。我有一组数据: 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之间的数字。显然,返回的参数也不同。但是,当我绘制每条曲线时,我得到了以下结果: 图片链接 绿色曲线是第一个模型拟合的,蓝色曲线是“归一化”模型拟合的。红色点是实验值。 拟合曲线是不同的。我认为这是意料之外的,我不明白为什么。如果数字是“合理的”,计算结果会更准确吗?
2、解决方案 优化.curve_fit的文档说明说, p0 :无、标量或M长度序列 参数的初始猜测值。如果为无,则初始值都为1(如果可以使用自省确定函数的参数数量,否则会引发ValueError)。
因此,首先,参数的初始猜测值默认是1。此外,曲线拟合算法必须对各种参数值对函数进行采样。“各种值”最初选择一个初始步长,该步长约为1。如果数据随着参数值以约1的顺序相对平滑地变化,则算法将效果更好。 如果函数随着参数以大约1的顺序变化很剧烈,那么算法可能会错过最优参数值。 请注意,即使算法在调整参数值时使用自适应步长,如果初始调整相差太大以至于产生较大的残差,而向其他方向调整恰好产生较小的残差,那么算法可能会朝错误的方向漫游,而错过局部最小值。它可能会找到其他(不受欢迎的)局部最小值,或者根本无法收敛。因此,使用具有自适应步长的算法不一定能拯救你。 归根结底,对数据进行缩放可以提高算法找到所需最小值的机会。
一般来说,当应用于数值为1的数量级的数据时,数值算法往往都能更好地工作。这种偏差以多种方式进入算法。例如,optimize.curve_fit依赖optimize.leastsq,而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值压缩得更多,以至于在[-1,1]间隔中很宽的x值范围(前面提到的参数值)将符合1e-8的公差。因此,在非理想的缩放情况下,leastsq对于寻找最小值不会起到良好的作用。
使用约1数量级浮点值的另一个原因是,[-1,1]区间中的IEEE754浮点数比远离1的浮点数要多得多。例如,
import struct def floats_between(x, y): """ stackoverflow.com/a/3587987/1… (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 这表明在0到1之间的数字的浮点数比在[1e6,1e7]区间中的浮点数多300倍以上。 因此,在其他条件相同的情况下,如果你用较小的数字来工作,通常会比用非常大的数字得到更准确的答案。