【问题标题】:Efficient outer product in pythonpython中的高效外积
【发布时间】:2015-01-07 00:16:39
【问题描述】:

当我们必须处理维度为 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

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

【问题讨论】:

  • 向我们展示您现有的代码。
  • 如果调用一个通常高度优化的 numpy 函数太慢,请重新考虑是否可以避免计算完整的外积。你最终想要达到什么目标?
  • 并行计算?这是一个例子(虽然有点旧)atbrox.com/2010/02/08/…

标签: python numpy multiplication


【解决方案1】:

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

numpy.outer

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

numpy.einsum

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

麻木

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

长尾小鹦鹉

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

赛通

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

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

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

结论:

╔═══════════╦═══════════╦═════════╗
║  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

【讨论】:

  • 我喜欢“更少的时间=更快”的解释:)
  • 还有另一种方法 - 广播:b[:,None]*a。但它的时间安排与其他时间相同,介于outereinsum 之间。相对排名会随着 2 个数组的大小而有所不同。
  • 在numba版本中应该使用float64而不是float,否则无法编译。
【解决方案2】:

@elyase 的回答很棒,并且被正确地接受了。这里有一个额外的建议,如果你可以使用它,可以更快地调用np.outer

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

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

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

In [33]: b = np.random.randn(32000)

这是我电脑上 np.outer(a, b) 的基准时间:

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

假设我们将多次重复该操作,数组具有相同的形状。创建一个out 数组来保存结果:

In [35]: out = np.empty((128, 32000))

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

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

因此,如果您可以重用保存外部产品的数组,您将获得不错的性能提升。

如果您使用einsumout 参数,并且在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

【讨论】:

  • 哪个版本的python支持第三个参数。对我来说,它不支持第三个论点。 *TypeError: outer() 正好接受 2 个参数(3 个给定)*
【解决方案3】:

它应该像使用numpy.outer() 一样简单:单个函数调用,将在 C 中实现高性能。

【讨论】:

    猜你喜欢
    • 2014-11-10
    • 2013-11-08
    • 1970-01-01
    • 2015-03-26
    • 2017-06-15
    • 1970-01-01
    • 2012-04-18
    • 2015-01-22
    相关资源
    最近更新 更多