我正在尝试将scipy.optimize.leastsq
与fit函数一起使用,该函数使用预分配的内存来存储残差。我有数百万的非线性拟合,时间至关重要。我在C
编码了fit函数,我注意到当fit函数简单地返回对作为输入获得的缓冲区内存的引用时,scipy.optimize.leastsq
无法正常工作。当我注意到问题可以在纯Py_INCREF
中重现时,我以为我已经搞砸了Python
s。这里有一些模拟代码来说明问题(实际问题有不同的拟合函数,而且要复杂得多):
import numpy as np
import scipy.optimize as opt
# the mock-up data to be fitted
x = np.arange(5.)
noise = np.array([0.0, -0.02, 0.01, -0.03, 0.01])
y = np.exp(-x) + noise
# preallocate the buffer memory
buf = np.empty_like(x)
# fit function writing residuals to preallocated memory and returning a reference
def dy(p, x, buf, y):
buf[:] = p[0]*np.exp(-p[1]*x) - y
return buf
# starting values (true values are [1., 1.])
p0 = np.array([1.2, 0.8])
# leastsq with dy(): DOESN'T WORK CORRECTLY
opt.leastsq(dy, p0, args=(x, buf, y))
# -> (array([ 1.2, 0.8]), 4)
为了使其正常工作,我必须将fit函数包装成一个复制函数:
# wrapper that calls dy() and returns a copy of the array
def dy2(*args):
return dy(*args).copy()
# leastsq with dy2(): WORKS CORRECTLY
opt.leastsq(dy2, p0, args=(x, buf, y))
# -> (array([ 0.99917134, 1.03603201]), 1)
...但是制作副本显然首先使用缓冲内存!作为旁注,opt.fmin
也适用于缓冲存储器(但实际上对我的应用来说太慢了):
def sum2(p, x, buf, y):
dy(p, x, buf, y)
return buf.dot(buf)
opt.fmin(sum2, p0, args=(x, buf, y))
# Optimization terminated successfully.
# Current function value: 0.001200
# Iterations: 32
# Function evaluations: 63
# -> array([ 0.99915812, 1.03600273])
上述示例中scipy.optimize.leastsq
使用dy2()
而不是dy()
的任何想法?
答案 0 :(得分:2)
好的,我认为这就是这里发生的事情:基础FORTRAN
例程LMDIF
在*fvec
中显示用户定义的带内存的函数,其中存储结果。这个指针可能指向临时内存,因为LMDIF
需要缓存几个函数求值的结果来估计雅可比行列式。
由于从Python
调用了用户定义的函数,*fvec
中的内存无法直接使用,因此包装器raw_multipack_lm_function()
首先通过评估Python
函数来工作然后然后将结果复制到*fvec
。在输入LMDIF
之前,调用用户定义的函数一次以找出输出数组的形状。
问题出现是因为第一次功能评估的内存随后作为原始LMDIF
传递给*fvec
,好像它是新的一次性内存。 LMDIF
继续使用它来存储第一个函数求值,然后使用不同的*fvec
再次调用用户函数。但是在dy()
的示例中,用户函数会在将结果复制到LMDIF
想要的内存之前覆盖前一个函数调用的内存。这使得LMDIF
认为结果永远不会改变,因此适用参数对拟合质量没有影响的情况下退出。
我认为这是来自minpack_lmdif()
的{{1}}中的错误,因为它假设用户定义的函数总是返回新的一次性内存,而scipy/optimize/__minpack.h
则不是这样(似乎成为编码拟合函数的完全合法的方法)。以下dy()
说明了一个简单的解决方法:
git diff
因此,我们为diff --git a/scipy/optimize/__minpack.h b/scipy/optimize/__minpack.h
index 2c0ea33..465724b 100644
--- a/scipy/optimize/__minpack.h
+++ b/scipy/optimize/__minpack.h
@@ -483,7 +483,7 @@ static PyObject *minpack_lmdif(PyObject *dummy, PyObject *args) {
qtf = (double *) ap_qtf->data;
fjac = (double *) ap_fjac->data;
ldfjac = dims[1];
- wa = (double *)malloc((3*n + m)* sizeof(double));
+ wa = (double *)malloc((3*n + 2*m)* sizeof(double));
if (wa == NULL) {
PyErr_NoMemory();
goto fail;
@@ -492,12 +492,15 @@ static PyObject *minpack_lmdif(PyObject *dummy, PyObject *args) {
/* Call the underlying FORTRAN routines. */
n_int = n; /* to provide int*-pointed storage for int argument of LMDIF */
- LMDIF(raw_multipack_lm_function, &m, &n_int, x, fvec, &ftol, &xtol, >ol, &maxfev, &epsfcn, diag,
-
+ LMDIF(raw_multipack_lm_function, &m, &n_int, x, wa+3*n+m, &ftol, &xtol, >ol, &maxfev, &epsfcn, d
+
RESTORE_FUNC();
if (info < 0) goto fail; /* Python error */
+ /* Copy final residuals back to initial array */
+ memcpy(fvec, wa+3*n+m, m*sizeof(double));
+
free(wa);
Py_DECREF(extra_args);
Py_DECREF(ap_diag);
分配m
个临时空间元素,并使用额外的内存作为初始LMDIF
。这可以防止在调用用户函数时发生内存冲突。要返回正确的最终残差,需要额外的*fvec
将最终结果存储在原始数组中。
与dy()的原始示例一样,这允许编写fit函数以使其不受内存分配的影响。由于memcpy()
中的整个内部循环没有内存分配,因此可以预期性能得到改善。
<强>更新强>
以下是一些时间结果。显然,测试问题非常小且快速收敛,因此它可能无法代表真实世界的应用程序。这是修补版LMDIF
:
scipy.optimize.leastsq
因此,通过写入预分配的内存无法获得任何结果:In [1]: def dy0(p, x, y): return p[0]*np.exp(-p[1]*x) - y
In [2]: %timeit p = opt.leastsq(dy2, p0, args=(x, buf, y))
1000 loops, best of 3: 399 us per loop
In [3]: %timeit p = opt.leastsq(dy, p0, args=(x, buf, y))
1000 loops, best of 3: 363 us per loop
In [4]: %timeit p = opt.leastsq(dy0, p0, args=(x, y))
1000 loops, best of 3: 341 us per loop
的直接实现是最快的。但是如果我们为dy0()
编写一个更有效的包装器来更好地利用预分配的内存呢?这是我写的一个:
LMDIF
那是件好事。 In [5]: %timeit p = mp.leastsq(dy, (p0.copy(), x, buf, y))
1000 loops, best of 3: 279 us per loop
仍然评估一般的mp.leastsq
函数,其限制是第一个参数将被结果覆盖,第三个参数是缓冲区内存。但是,让我们看看如果我们在Python
中编码dy()
会发生什么:
C
哎哟! In [6]: %timeit p = opt.leastsq(fitfun.e2_diff, p0, args=(x, buf, y))
10000 loops, best of 3: 48.2 us per loop
完美矢量化代码(至少对于短数组)的性能如此之多。让我们使用改进的包装器:
numpy
In [7]: %timeit p = mp.leastsq(fitfun.e2_diff, (p0.copy(), x, buf, y))
100000 loops, best of 3: 6.94 us per loop
和opt.leastsq
之间的额外加速来自摆脱元组构建代码和mp.leastsq
。最后,memcpy
的原始效果不会回调到LMDIF
:
Python
没有太大的不同。所以回调Python并不会花费太多,但不要让In [8]: %timeit p = fitfun.e2_fit(p0.copy(), x, buf, y)
100000 loops, best of 3: 6.13 us per loop
为你做计算!许多后续拟合(我的应用程序)的进一步加速可以来自重用numpy
的临时存储器wa
。
最重要的是,所有计算现在都会返回正确的结果!