在numpy数组上映射函数的最有效方法

时间:2016-02-05 02:08:11

标签: python performance numpy

在numpy数组上映射函数的最有效方法是什么?我在当前项目中一直这样做的方式如下:

import numpy as np 

x = np.array([1, 2, 3, 4, 5])

# Obtain array of square of each element in x
squarer = lambda t: t ** 2
squares = np.array([squarer(xi) for xi in x])

然而,这看起来可能非常低效,因为我使用列表解析将新数组构造为Python列表,然后再将其转换回numpy数组。

我们可以做得更好吗?

12 个答案:

答案 0 :(得分:172)

我已经测试了所有建议的方法以及np.array(map(f, x))perfplot(我的一个小项目)。

  

消息#1:如果您可以使用numpy的本机功能,请执行此操作。

如果您尝试向量化的功能已向量化(如原始帖子中的x**2示例),则使用比其他任何东西都要快(注意对数刻度):

enter image description here

如果您确实需要矢量化,那么您使用哪种变体并不重要。

enter image description here

重现图表的代码:

import numpy as np
import perfplot
import math


def f(x):
    # return math.sqrt(x)
    return np.sqrt(x)


vf = np.vectorize(f)


def array_for(x):
    return np.array([f(xi) for xi in x])


def array_map(x):
    return np.array(list(map(f, x)))


def fromiter(x):
    return np.fromiter((f(xi) for xi in x), x.dtype)


def vectorize(x):
    return np.vectorize(f)(x)


def vectorize_without_init(x):
    return vf(x)


perfplot.show(
    setup=lambda n: np.random.rand(n),
    n_range=[2**k for k in range(20)],
    kernels=[
        f,
        array_for, array_map, fromiter, vectorize, vectorize_without_init
        ],
    logx=True,
    logy=True,
    xlabel='len(x)',
    )

答案 1 :(得分:98)

如何使用numpy.vectorize

>>> import numpy as np
>>> x = np.array([1, 2, 3, 4, 5])
>>> squarer = lambda t: t ** 2
>>> vfunc = np.vectorize(squarer)
>>> vfunc(x)
array([ 1,  4,  9, 16, 25])

https://docs.scipy.org/doc/numpy/reference/generated/numpy.vectorize.html

答案 2 :(得分:50)

TL; DR

@user2357112所述,应用函数的“直接”方法始终是在Numpy数组上映射函数的最快速最简单的方法:

import numpy as np
x = np.array([1, 2, 3, 4, 5])
f = lambda x: x ** 2
squares = f(x)

通常会避免使用np.vectorize,因为它效果不佳,并且已经(或有)issues。如果您正在处理其他数据类型,您可能需要调查下面显示的其他方法。

方法比较

以下是一些比较三种方法来映射函数的简单测试,这个例子使用Python 3.6和NumPy 1.15.4。首先,测试的设置功能:

import timeit
import numpy as np

f = lambda x: x ** 2
vf = np.vectorize(f)

def test_array(x, n):
    t = timeit.timeit(
        'np.array([f(xi) for xi in x])',
        'from __main__ import np, x, f', number=n)
    print('array: {0:.3f}'.format(t))

def test_fromiter(x, n):
    t = timeit.timeit(
        'np.fromiter((f(xi) for xi in x), x.dtype, count=len(x))',
        'from __main__ import np, x, f', number=n)
    print('fromiter: {0:.3f}'.format(t))

def test_direct(x, n):
    t = timeit.timeit(
        'f(x)',
        'from __main__ import x, f', number=n)
    print('direct: {0:.3f}'.format(t))

def test_vectorized(x, n):
    t = timeit.timeit(
        'vf(x)',
        'from __main__ import x, vf', number=n)
    print('vectorized: {0:.3f}'.format(t))

使用五个元素进行测试(从最快到最慢排序):

x = np.array([1, 2, 3, 4, 5])
n = 100000
test_direct(x, n)      # 0.265
test_fromiter(x, n)    # 0.479
test_array(x, n)       # 0.865
test_vectorized(x, n)  # 2.906

有100个元素:

x = np.arange(100)
n = 10000
test_direct(x, n)      # 0.030
test_array(x, n)       # 0.501
test_vectorized(x, n)  # 0.670
test_fromiter(x, n)    # 0.883

使用1000个以上的数组元素:

x = np.arange(1000)
n = 1000
test_direct(x, n)      # 0.007
test_fromiter(x, n)    # 0.479
test_array(x, n)       # 0.516
test_vectorized(x, n)  # 0.945

不同版本的Python / NumPy和编译器优化会产生不同的结果,因此请对您的环境进行类似的测试。

答案 3 :(得分:17)

squares = squarer(x)

对数组的算术运算是按元素自动应用的,具有高效的C级循环,可避免应用于Python级循环或理解的所有解释器开销。

您希望以元素方式应用于NumPy数组的大多数函数都可以正常工作,但有些可能需要更改。例如,if不能按元素运行。您希望将这些转换为使用numpy.where等构造:

def using_if(x):
    if x < 5:
        return x
    else:
        return x**2

变为

def using_where(x):
    return numpy.where(x < 5, x, x**2)

答案 4 :(得分:17)

自从回答了这个问题以来,发生了很多事情-周围有numexprnumbacython。该答案的目的是考虑这些可能性。

但是首先让我们说明一个显而易见的事实:无论您如何将Python函数映射到numpy数组,它都会保留为Python函数,这意味着每次评估:

  • numpy-array元素必须转换为Python对象(例如Float)。
  • 所有计算都是使用Python对象完成的,这意味着具有解释器,动态调度和不可变对象的开销。

因此,由于上述开销,实际上用于循环遍历数组的机制不会发挥很大作用-它比使用numpy的向量化要慢得多。

让我们看下面的示例:

# numpy-functionality
def f(x):
    return x+2*x*x+4*x*x*x

# python-function as ufunc
import numpy as np
vf=np.vectorize(f)
vf.__name__="vf"
选择

np.vectorize作为方法的纯Python函数类的代表。使用perfplot(请参阅此答案附录中的代码),我们得到以下运行时间:

enter image description here

我们可以看到,numpy方法比纯python版本快10到100倍。对于更大的数组大小,性能下降可能是因为数据不再适合缓存。

经常听到的是,numpy的性能和它的性能一样好,因为它是引擎盖下的纯C语言。但是还有很多改进的空间!

向量化的numpy版本使用大量额外的内存和内存访问。 Numexp库尝试对numpy数组进行平铺,从而获得更好的缓存利用率:

# less cache misses than numpy-functionality
import numexpr as ne
def ne_f(x):
    return ne.evaluate("x+2*x*x+4*x*x*x")

进行以下比较:

enter image description here

我无法解释上面图中的所有内容:一开始我们可以看到numexpr-library的开销更大,但是因为它更好地利用了缓存,所以对于较大的数组,它的速度要快大约十倍!


另一种方法是jit编译函数,从而获得真正的纯C UFunc。这是numba的方法:

# runtime generated C-function as ufunc
import numba as nb
@nb.vectorize(target="cpu")
def nb_vf(x):
    return x+2*x*x+4*x*x*x

它比原始的numpy方法快10倍:

enter image description here


但是,该任务可尴尬地可并行化,因此我们也可以使用prange来并行计算循环:

@nb.njit(parallel=True)
def nb_par_jitf(x):
    y=np.empty(x.shape)
    for i in nb.prange(len(x)):
        y[i]=x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
    return y

正如预期的那样,对于较小的输入,并行功能较慢,但对于较大的输入,并行功能较快(几乎为2倍):

enter image description here


虽然numba专门研究使用numpy数组优化操作,但Cython是更通用的工具。提取与numba相同的性能更加复杂-相对于本地编译器(gcc / MSVC),通常是llvm(numba):

%%cython -c=/openmp -a
import numpy as np
import cython

#single core:
@cython.boundscheck(False) 
@cython.wraparound(False) 
def cy_f(double[::1] x):
    y_out=np.empty(len(x))
    cdef Py_ssize_t i
    cdef double[::1] y=y_out
    for i in range(len(x)):
        y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
    return y_out

#parallel:
from cython.parallel import prange
@cython.boundscheck(False) 
@cython.wraparound(False)  
def cy_par_f(double[::1] x):
    y_out=np.empty(len(x))
    cdef double[::1] y=y_out
    cdef Py_ssize_t i
    cdef Py_ssize_t n = len(x)
    for i in prange(n, nogil=True):
        y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
    return y_out

Cython会使功能变慢:

enter image description here


结论

很明显,仅测试一个功能并不能证明任何事情。还应该记住,对于所选的功能示例,内存的带宽是大于10 ^ 5个元素的瓶颈-因此,在该区域中,numba,numexpr和cython的性能相同。 / p>

从这次调查和到目前为止的经验来看,我要指出的是,numba似乎是性能最佳的最简单工具。


使用perfplot-package绘制运行时间:

import perfplot
perfplot.show(
    setup=lambda n: np.random.rand(n),
    n_range=[2**k for k in range(0,24)],
    kernels=[
        f, 
        vf,
        ne_f, 
        nb_vf, nb_par_jitf,
        cy_f, cy_par_f,
        ],
    logx=True,
    logy=True,
    xlabel='len(x)'
    )

答案 5 :(得分:10)

我相信numpy的更新版本(我使用1.13)你可以简单地通过将numpy数组传递给你为标量类型编写的函数来调用函数,它会自动将函数调用应用于numpy数组上的每个元素并返回另一个numpy数组

>>> import numpy as np
>>> squarer = lambda t: t ** 2
>>> x = np.array([1, 2, 3, 4, 5])
>>> squarer(x)
array([ 1,  4,  9, 16, 25])

答案 6 :(得分:2)

以上所有答案都比较好,但是如果您需要使用自定义函数进行映射,并且您有numpy.ndarray,则需要保留数组的形状。

我只比较了两个,但是它将保留ndarray的形状。我已将具有100万个条目的数组用于比较。在这里,我使用平方函数,该函数也是内置在numpy中的,并且具有很大的性能提升,因为有需要,您可以使用自己选择的函数。

import numpy, time
def timeit():
    y = numpy.arange(1000000)
    now = time.time()
    numpy.array([x * x for x in y.reshape(-1)]).reshape(y.shape)        
    print(time.time() - now)
    now = time.time()
    numpy.fromiter((x * x for x in y.reshape(-1)), y.dtype).reshape(y.shape)
    print(time.time() - now)
    now = time.time()
    numpy.square(y)  
    print(time.time() - now)

输出

>>> timeit()
1.162431240081787    # list comprehension and then building numpy array
1.0775556564331055   # from numpy.fromiter
0.002948284149169922 # using inbuilt function

在这里您可以清楚地看到numpy.fromiter在考虑简单方法的情况下非常有效,如果有内置功能,请使用它。

答案 7 :(得分:1)

在许多情况下,numpy.apply_along_axis将是最佳选择。与其他方法相比,它的性能提高了大约100倍-不仅对于微不足道的测试功能,而且对于numpy和scipy的更复杂的功能组成。

当我添加方法时:

def along_axis(x):
    return np.apply_along_axis(f, 0, x)

对于perfplot代码,我得到以下结果: enter image description here

答案 8 :(得分:0)

this post中所述,只需使用如下的生成器表达式:

require 'date'
prod_date = Products.where(event_id: event).last.created_at
prod_date = prod_date.to_time.strftime("%Y-%m-%d %H:%M:%S")

答案 9 :(得分:0)

似乎没有人提到过一种内置的工厂方法,以numpy包的形式生产ufuncnp.frompyfunc,我再次测试过np.vectorize,并使其性能提高了20到30倍左右%。当然,它可以在规定的C代码或numba(我尚未测试)的情况下很好地执行,但比np.vectorize

更好。
f = lambda x, y: x * y
f_arr = np.frompyfunc(f, 2, 1)
vf = np.vectorize(f)
arr = np.linspace(0, 1, 10000)

%timeit f_arr(arr, arr) # 307ms
%timeit f_arr(arr, arr) # 450ms

我还测试了较大的样本,并且改进成比例。另请参见文档here

答案 10 :(得分:0)

使用numpy.fromfunction(function, shape, **kwargs)

请参见“ enter image description here

答案 11 :(得分:-3)

也许使用vectorize更好

def square(x):
   return x**2

vfunc=vectorize(square)

vfunc([1,2,3,4,5])

output:array([ 1,  4,  9, 16, 25])
相关问题