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

Rya*_*yan 264 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])
Run Code Online (Sandbox Code Playgroud)

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

我们可以做得更好吗?

Nic*_*mer 226

我测试过的所有建议的方法,加上np.array(map(f, x))perfplot(我的一个小项目).

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

如果你想已经矢量化功能矢量(如x**2在原岗位的例子),使用的是比什么都更快(注意对数标度):

在此输入图像描述

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

在此输入图像描述


重现图的代码:

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)',
    )
Run Code Online (Sandbox Code Playgroud)

  • 你似乎离开了你的情节中的`f(x)`.它可能不适用于每个`f`,但它适用于此处,并且它是适用时最快的解决方案. (6认同)
  • 普通的 for 循环怎么样? (6认同)
  • 另外,您的图不支持您的主张,即vf = np.vectorize(f); y = vf(x)`赢得简短输入。 (2认同)
  • 这些函数的内存使用情况有什么显着差异吗?我有使用直接函数方法快速运行的代码,但对于大型数组,它会耗尽内存(由于 numpy.sqrt 的临时 float64 表示)。 (2认同)

sat*_*oto 123

如何使用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)
# Output : array([ 1,  4,  9, 16, 25])
Run Code Online (Sandbox Code Playgroud)

http://docs.scipy.org/doc/numpy-1.10.1/reference/generated/numpy.vectorize.html

  • 从那个doc:`vectorize函数主要是为了方便而不是为了提高性能.实现本质上是for循环.在其他问题中,我发现`vectorize`可能会使用户迭代速度加倍.但真正的加速是真正的'numpy`阵列操作. (71认同)
  • 这不是更有效率. (31认同)
  • 请注意,向量化至少可以使非1d数组正常工作 (2认同)
  • 但是 `squarer(x)` 已经适用于非一维数组。“vectorize”实际上仅比列表理解(如问题中的列表理解)有任何优势,而不是比“squarer(x)”有任何优势。 (2认同)
  • 过去,“np.vectorize”比等效的列表理解慢。现在它的扩展性更好,因此对于大参数来说速度更快。它仍然不如使用编译后的“numpy”方法和运算符而不使用任何类型的 python 级别循环那么快。 (2认同)

Mik*_*e T 64

TL; DR

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

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

通常避免np.vectorize,因为它表现不佳,并且(或有)一些问题.如果您正在处理其他数据类型,您可能需要调查下面显示的其他方法.

比较方法

下面是一些简单的测试来比较三种方法来映射函数,这个例子使用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))
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

有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
Run Code Online (Sandbox Code Playgroud)

并且有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
Run Code Online (Sandbox Code Playgroud)

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

  • 我很困惑,当OP询问如何跨数组“映射”函数时,“f(x)”版本(“直接”)实际上如何被认为具有可比性?在 f(x) = x ** 2 的情况下,numpy 对整个数组而不是每个元素执行 **。例如,如果 f(x) 是“lambda x: x + x”,那么答案就非常不同,因为 numpy 连接数组而不是对每个元素进行加法。这真的是预期的比较吗?请解释一下。 (5认同)
  • 你没有测试`f(x)`的直接解决方案,[其他一切超过一个数量级](https://ideone.com/ov8EkZ). (4认同)
  • 因此,例如,使用`'np.fromiter((f(xi)表示x中的xi),x.dtype,count = len(x))'` (3认同)
  • “直接”版本不是逐元素的,而是逐行的。要查看这一点,请以“lambda x: str(x)”为例 (3认同)
  • 如果你使用`count`参数和一个生成器表达式,那么`np.fromiter`会明显加快. (2认同)
  • 如果`f`有2个变量并且数组是2D怎么办? (2认同)

ead*_*ead 31

由于这个问题得到了很多回答 - 有数字,numbacython.这个答案的目标是考虑这些可能性.

但首先让我们说明一点:无论你如何将Python函数映射到numpy-array,它都保留了Python函数,这意味着每次评估:

  • 必须将numpy-array元素转换为Python对象(例如a 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"
Run Code Online (Sandbox Code Playgroud)

np.vectorize被选为纯python函数类方法的代表.使用perfplot(参见本答复附录中的代码),我们得到以下运行时间:

在此输入图像描述

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

人们常常听到,numpy-performance和它一样好,因为它是引擎盖下的纯粹C.然而,还有很大的改进空间!

vectorized numpy-version使用了大量额外的内存和内存访问.Numexp-library尝试平铺numpy-arrays,从而获得更好的缓存利用率:

# 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")
Run Code Online (Sandbox Code Playgroud)

导致以下比较:

在此输入图像描述

我无法解释上图中的所有内容:我们可以在开始时看到更大的数字开销,但是因为它更好地利用了缓存,所以对于更大的数组,它大约快10倍!


另一种方法是jit-compile函数,从而得到一个真正的纯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
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述


但是,任务是令人尴尬的可并行化,因此我们也可以使用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
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述


虽然numba专注于使用numpy-arrays优化操作,但Cython是一种更通用的工具.提取与numba相同的性能更复杂 - 通常它低于llvm(numba)vs本地编译器(gcc/MSVC):

%%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
Run Code Online (Sandbox Code Playgroud)

Cython导致功能稍慢:

在此输入图像描述


结论

显然,只测试一个函数并不能证明什么.另外应该记住,对于选择功能 - 例如,内存的带宽是大于10 ^ 5个元素的瓶颈 - 因此我们在该区域中具有相同的numba,numexpr和cython性能.

然而,从这次调查和我迄今为止的经验来看,我认为,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)'
    )
Run Code Online (Sandbox Code Playgroud)


use*_*ica 24

squares = squarer(x)
Run Code Online (Sandbox Code Playgroud)

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

您希望以元素方式应用于NumPy数组的大多数函数都可以正常工作,但有些可能需要更改.例如,if元素不起作用.你想要转换那些使用如下构造numpy.where:

def using_if(x):
    if x < 5:
        return x
    else:
        return x**2
Run Code Online (Sandbox Code Playgroud)

def using_where(x):
    return numpy.where(x < 5, x, x**2)
Run Code Online (Sandbox Code Playgroud)


Lyt*_*eFM 15

编辑: 原始答案具有误导性, np.sqrt 直接应用于数组,只是开销很小

在多维情况下,您希望应用对一数组进行操作的内置函数,numpy.apply_along_axis是一个不错的选择,也适用于来自 numpy 和 scipy 的更复杂的函数组合。

之前的误导性陈述:

添加方法:

def along_axis(x):
    return np.apply_along_axis(f, 0, x)
Run Code Online (Sandbox Code Playgroud)

perfplot 代码给出的性能结果接近np.sqrt.

  • 这是误导性的。您实际上并没有以这种方式矢量化“f”。例如,尝试在 Nico 的性能代码中将“np.sqrt”替换为“math.sqrt”,您将收到错误。这里实际发生的是使用数组参数调用“f”,因为 x 是一维的,并且您告诉它沿包含所有元素的第一个轴应用它。为了使这个答案有效,“apply_along_axis”的参数应该替换为“x[None,:]”。然后你会发现沿轴是其中最慢的。 (5认同)

小智 14

似乎没有人提到ufunc在 numpy package:中生产的内置工厂方法np.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 vf(arr, arr) # 450ms
Run Code Online (Sandbox Code Playgroud)

我也测试了更大的样本,改进是成正比的。另请参阅此处的文档


Pei*_* Li 9

我相信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])
Run Code Online (Sandbox Code Playgroud)

  • 它是`**'运算符,它将计算应用于`t`的每个元素t.这是普通的numpy.将它包装在`lambda`中并没有做任何额外的事情. (7认同)
  • 这不是一个新的 - 它一直是这种情况 - 它是numpy的核心功能之一. (3认同)

小智 4

正如这篇文章中提到的,只需使用生成器表达式,如下所示:

numpy.fromiter((<some_func>(x) for x in <something>),<dtype>,<size of something>)
Run Code Online (Sandbox Code Playgroud)