使用两个numpy向量中的元素对的函数填充矩阵的最快方法?

时间:2014-10-31 10:01:46

标签: python performance numpy pandas

我有两个1维numpy向量vavb,用于通过将所有对组合传递给函数来填充矩阵。

na = len(va)
nb = len(vb)
D = np.zeros((na, nb))
for i in range(na):
    for j in range(nb):
        D[i, j] = foo(va[i], vb[j])

目前,由于va和vb相对较大(4626和737),这段代码需要很长时间才能运行。但是我希望这可以改进,因为使用scipy中的cdist方法执行了类似的程序并且具有非常好的性能。

D = cdist(va, vb, metric)

我显然知道scipy有利于在C中而不是在python中运行这段代码 - 但是我希望有一些不知道的numpy函数可以快速执行。

3 个答案:

答案 0 :(得分:3)

cdist很快,因为它是用高度优化的C代码编写的(正如您已经指出的那样),它只支持一小组预定义的metric s

由于您希望将操作一般地应用于任何给定的foo函数,因此您别无选择,只能调用该函数na - 次 - nb次。那部分不太可能进一步优化。

要优化的是循环和索引。尝试一些建议:

  1. 使用xrange代替range(如果在python2.x中,在python3中,范围已经是类似生成器)
  2. 使用enumerate,而不是范围+显式索引
  3. 使用python speed“magic”,例如cythonnumba,以加快循环过程。
  4. 如果您可以对foo做出进一步的假设,则可以进一步加快速度。

答案 1 :(得分:3)

就像@ shx2所说,这完全取决于foo。如果你可以用numpy ufunc表达它,那么使用outer方法:

In [11]: N = 400

In [12]: B = np.empty((N, N))

In [13]: x = np.random.random(N)

In [14]: y = np.random.random(N)

In [15]: %%timeit
for i in range(N):
   for j in range(N):
     B[i, j] = x[i] - y[j]
   ....: 
10 loops, best of 3: 87.2 ms per loop

In [16]: %timeit A = np.subtract.outer(x, y)   # <--- np.subtract is a ufunc
1000 loops, best of 3: 294 µs per loop

否则你可以将循环推向cython级别。上面继续一个简单的例子:

In [45]: %%cython
cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)
def foo(double[::1] x, double[::1] y, double[:, ::1] out):
    cdef int i, j
    for i in xrange(x.shape[0]):
        for j in xrange(y.shape[0]):
            out[i, j] = x[i] - y[j]
   ....: 

In [46]: foo(x, y, B)

In [47]: np.allclose(B, np.subtract.outer(x, y))
Out[47]: True

In [48]: %timeit foo(x, y, B)
10000 loops, best of 3: 149 µs per loop

cython示例故意过于简单化:实际上你可能想要添加一些形状/步幅检查,在你的函数中分配内存等。

答案 2 :(得分:2)

文档调用functional programming routines的最不为人知的numpy函数之一是np.frompyfunc。这会从Python函数创建一个numpy ufunc。不是一些其他对象可以模拟一个numpy ufunc,而是一个带有所有铃声和口哨的正确ufunc。虽然行为在很多方面与np.vectorize非常相似,但它有一些明显的优势,希望以下代码可以强调:

In [2]: def f(a, b):
   ...:     return a + b
   ...:

In [3]: f_vec = np.vectorize(f)

In [4]: f_ufunc = np.frompyfunc(f, 2, 1)  # 2 inputs, 1 output

In [5]: a = np.random.rand(1000)

In [6]: b = np.random.rand(2000)

In [7]: %timeit np.add.outer(a, b)  # a baseline for comparison
100 loops, best of 3: 9.89 ms per loop

In [8]: %timeit f_vec(a[:, None], b)  # 50x slower than np.add
1 loops, best of 3: 488 ms per loop

In [9]: %timeit f_ufunc(a[:, None], b)  # ~20% faster than np.vectorize...
1 loops, best of 3: 425 ms per loop

In [10]: %timeit f_ufunc.outer(a, b)  # ...and you get to use ufunc methods
1 loops, best of 3: 427 ms per loop

因此虽然它仍然明显不如正确的矢量化实现,但它更快一点(循环在C中,但你仍然有Python函数调用开销)。