Python 在numpy数组上映射函数的最有效方法
在numpy数组上映射函数最有效的方法是什么?我在当前项目中的做法如下:Python 在numpy数组上映射函数的最有效方法,python,performance,numpy,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列表,然后再
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数组
我们能做得更好吗?如中所述,只需使用生成器表达式,如下所示:
numpy.fromiter((<some_func>(x) for x in <something>),<dtype>,<size of something>)
numpy.fromiter(((x)表示x英寸),)
如何使用
数组上的算术运算是按元素自动应用的,高效的C级循环避免了应用于Python级循环或理解的所有解释器开销
您希望应用于NumPy数组elementwise的大多数函数都可以正常工作,尽管有些函数可能需要更改。例如,如果
在元素方面不起作用。您可能希望将其转换为使用以下结构:
def使用_if(x):
如果x<5:
返回x
其他:
返回x**2
变成
def using_where(x):
return numpy.where(x < 5, x, x**2)
def使用_,其中(x):
返回数值,其中(x<5,x,x**2)
TL;博士
如所述,应用函数的“直接”方法始终是将函数映射到Numpy数组上的最快、最简单的方法:
import numpy as np
x = np.array([1, 2, 3, 4, 5])
f = lambda x: x ** 2
squares = f(x)
通常避免使用np.vectorize
,因为它的性能不好,并且有(或曾经有)许多问题。如果您正在处理其他数据类型,则可能需要研究下面所示的其他方法
方法比较
下面是一些比较映射函数的三种方法的简单测试,本例使用Python3.6和Numpy1.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))
使用五个元素进行测试(从最快到最慢排序):
有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和编译器优化会有不同的结果,因此请针对您的环境进行类似的测试。我相信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])
我已经用(我的一个小项目)测试了所有建议的方法以及
np.array(map(f,x))
信息#1:如果您可以使用numpy的本机函数,请这样做
如果您尝试矢量化的函数已经矢量化(如原始帖子中的x**2
示例),则使用该函数比使用其他函数快得多(注意日志比例):
如果您真的需要矢量化,那么使用哪种变体其实并不重要
复制绘图的代码:
将numpy导入为np
导入性能图
输入数学
def f(x):
#返回math.sqrt(x)
返回np.sqrt(x)
vf=np.矢量化(f)
用于(x)的def阵列_:
返回NP.数组([X]席席的F(Xi))
def阵列_映射(x):
返回np.array(list(map(f,x)))
来自ITER的def(x):
返回NP.FrutMER((席席X(f)(x)),x.dType)
def矢量化(x):
返回np.矢量化(f)(x)
def矢量化_,不带_init(x):
返回vf(x)
perfplot.show(
设置=np.random.rand,
n_范围=[2**k表示范围(20)中的k],
内核=[f,数组,数组映射,fromiter,
向量化,向量化\u而不使用\u init],
xlabel=“len(x)”,
)
这个答案的目的是将这些可能性考虑在内
但首先让我们陈述一个显而易见的事实:无论您如何将Python函数映射到numpy数组,它始终是一个Python函数,这意味着对于每个计算:
- numpy数组元素必须转换为Python对象(例如
)Float
- 所有的计算都是用Python对象完成的,这意味着要有解释器、动态分派和不可变对象的开销
# 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
(参见本答案附录中的代码),我们得到以下运行时间:
我们可以看到,numpy方法比纯python版本快10-100倍。较大阵列大小的性能下降可能是因为数据不再适合缓存
还值得一提的是,vectorize
也使用了大量内存,因此内存使用往往是瓶颈(参见相关内容)。还要注意的是,numpy的文档中指出它“主要是为了方便,而不是为了性能”
当需要性能时,应使用其他工具,除了从头开始编写C扩展外,还有以下几种可能性:
人们经常听说,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")
导致以下比较:
我无法解释上图中的所有内容:一开始我们可以看到numexpr库的开销更大,但因为它更好地利用了缓存,所以对于更大的阵列,它的速度大约快了10倍
另一种方法是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倍:
# 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
然而,该任务的并行性令人尴尬,因此我们也可以使用
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
正如预期的那样,paral
# 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")
# 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
@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
%%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
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)'
)
def along_axis(x):
return np.apply_along_axis(f, 0, x)
>>> timeit()
1.162431240081787 # list comprehension and then building numpy array
1.0775556564331055 # from numpy.fromiter
0.002948284149169922 # using inbuilt function