python中的高效外部产品

the*_*tna 9 python numpy multiplication

当我们必须处理10k阶维度的向量时,python中的外部产品似乎相当慢.请问有人可以告诉我如何在python中加快这个操作?

代码如下:

 In [8]: a.shape
 Out[8]: (128,)

 In [9]: b.shape
 Out[9]: (32000,)

 In [10]: %timeit np.outer(b,a)
 100 loops, best of 3: 15.4 ms per loop
Run Code Online (Sandbox Code Playgroud)

由于我必须多次执行此操作,因此我的代码变得越来越慢.

ely*_*ase 30

它真的没有比这更快,这些是你的选择:

numpy.outer

>>> %timeit np.outer(a,b)
100 loops, best of 3: 9.79 ms per loop
Run Code Online (Sandbox Code Playgroud)

numpy.einsum

>>> %timeit np.einsum('i,j->ij', a, b)
100 loops, best of 3: 16.6 ms per loop
Run Code Online (Sandbox Code Playgroud)

numba

from numba.decorators import autojit

@autojit
def outer_numba(a, b):
    m = a.shape[0]
    n = b.shape[0]
    result = np.empty((m, n), dtype=np.float)
    for i in range(m):
        for j in range(n):
            result[i, j] = a[i]*b[j]
    return result

>>> %timeit outer_numba(a,b)
100 loops, best of 3: 9.77 ms per loop
Run Code Online (Sandbox Code Playgroud)

多嘴的人

from parakeet import jit

@jit
def outer_parakeet(a, b):
   ... same as numba

>>> %timeit outer_parakeet(a, b)
100 loops, best of 3: 11.6 ms per loop
Run Code Online (Sandbox Code Playgroud)

用Cython

cimport numpy as np
import numpy as np
cimport cython
ctypedef np.float64_t DTYPE_t

@cython.boundscheck(False)
@cython.wraparound(False)
def outer_cython(np.ndarray[DTYPE_t, ndim=1] a, np.ndarray[DTYPE_t, ndim=1] b):
    cdef int m = a.shape[0]
    cdef int n = b.shape[0]
    cdef np.ndarray[DTYPE_t, ndim=2] result = np.empty((m, n), dtype=np.float64)
    for i in range(m):
        for j in range(n):
            result[i, j] = a[i]*b[j]
    return result

>>> %timeit outer_cython(a, b)
100 loops, best of 3: 10.1 ms per loop
Run Code Online (Sandbox Code Playgroud)

theano

from theano import tensor as T
from theano import function

x = T.vector()
y = T.vector()

outer_theano = function([x, y], T.outer(x, y))

>>> %timeit outer_theano(a, b)
100 loops, best of 3: 17.4 ms per loop
Run Code Online (Sandbox Code Playgroud)

pypy

# Same code as the `outer_numba` function
>>> timeit.timeit("outer_pypy(a,b)", number=100, setup="import numpy as np;a = np.random.rand(128,);b = np.random.rand(32000,);from test import outer_pypy;outer_pypy(a,b)")*1000 / 100.0
16.36 # ms
Run Code Online (Sandbox Code Playgroud)

结论:

???????????????????????????????????
?  method   ? time(ms)* ? version ?
???????????????????????????????????
? numba     ? 9.77      ? 0.16.0  ?
? np.outer  ? 9.79      ? 1.9.1   ?
? cython    ? 10.1      ? 0.21.2  ?
? parakeet  ? 11.6      ? 0.23.2  ?
? pypy      ? 16.36     ? 2.4.0   ?
? np.einsum ? 16.6      ? 1.9.1   ?
? theano    ? 17.4      ? 0.6.0   ?
???????????????????????????????????
* less time = faster
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢"更少时间=更快"的解释:) (10认同)

War*_*ser 5

@ elyase的答案很棒,并且得到了正确的接受.这是一个额外的建议,如果你可以使用它,可能会使呼叫np.outer更快.

您说"我必须多次执行此操作",因此您可以重复使用包含外部产品的阵列,而不是每次都分配新的数组.这可以提高性能.

首先,使用一些随机数据:

In [32]: a = np.random.randn(128)

In [33]: b = np.random.randn(32000)
Run Code Online (Sandbox Code Playgroud)

这是我的计算机上np.outer(a,b)的基线时序:

In [34]: %timeit np.outer(a, b)
100 loops, best of 3: 5.52 ms per loop
Run Code Online (Sandbox Code Playgroud)

假设我们将使用相同形状的数组重复该操作几次.创建一个out数组来保存结果:

In [35]: out = np.empty((128, 32000))
Run Code Online (Sandbox Code Playgroud)

现在使用out作为第三个参数np.outer:

In [36]: %timeit np.outer(a, b, out)
100 loops, best of 3: 2.38 ms per loop
Run Code Online (Sandbox Code Playgroud)

因此,如果您可以重用包含外部产品的阵列,那么您将获得良好的性能提升.

如果你使用out参数,你会获得类似的好处,如果你einsum为输出添加第三个参数而不是在函数中分配它,则在cython函数中np.empty.(@ elyase的答案中的其他编译/ jitted代码也可能从中受益,但我只尝试了cython版本.)

Nota bene!上面显示的好处可能在实践中没有实现.该out阵列适合我的CPU的L3缓存,当它在timeit命令执行的循环中使用时,它可能保留在缓存中.实际上,在调用之间可能会将数组移出缓存np.outer.在这种情况下,改进并不是那么引人注目,但至少应该是调用的成本np.empty(),即

In [53]: %timeit np.empty((128, 32000))
1000 loops, best of 3: 1.29 ms per loop
Run Code Online (Sandbox Code Playgroud)