在NumPy数组的每个单元格处有效地计算函数

zzlelutf  于 2023-10-19  发布在  其他
关注(0)|答案(6)|浏览(84)

给定NumPy数组A,将 * 相同 * 函数f()应用于 * 每个 * 单元格的最快/最有效的方法是什么?
我通过f(A(i,j))赋值给A(i,j)。函数f()没有二进制输出,因此掩码操作没有帮助。通过每个单元的双循环迭代是最优解吗?

x0fgdtte

x0fgdtte1#

你可以只vectorize这个函数,然后在每次需要它的时候直接将它应用到Numpy数组中:

import numpy as np

def f(x):
    return x * x + 3 * x - 2 if x > 0 else x * 5 + 8

f = np.vectorize(f)  # or use a different name if you want to keep the original f

result_array = f(A)  # if A is your Numpy array

在向量化时直接指定显式输出类型可能更好:

f = np.vectorize(f, otypes=[np.float])
up9lanfz

up9lanfz2#

类似的问题是:Mapping a NumPy array in place .如果你能为f()找到一个ufunc,那么你应该使用out参数。

odopli94

odopli943#

如果你正在处理数字和f(A(i,j)) = f(A(j,i)),你可以使用scipy.spatial.distance.cdist将f定义为A(i)A(j)之间的距离。

hyrbngr7

hyrbngr74#

我想我找到了一个更好的解决办法。将函数改为python通用函数的想法(参见documentation),它可以在后台执行并行计算。
可以用C编写自己的定制ufunc,这肯定更有效,或者通过调用np.frompyfunc,这是内置的工厂方法。经过测试,这比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

我也测试了更大的样本,改进是成比例的。其他方法的性能比较见this post

643ylb08

643ylb085#

当2d数组(或nd-array)是C-或F-连续的,那么将函数Map到2d数组上的任务实际上与将函数Map到1d数组上的任务相同-我们只需要这样看待它,例如。通过np.ravel(A,'K')
一维阵列的可能解决方案已经讨论过,例如here
然而,当2d数组的内存不连续时,情况就有点复杂了,因为如果轴以错误的顺序处理,人们希望避免可能的缓存未命中。
Numpy已经有了一台机器,可以以最佳顺序加工轴。使用这种机器的一种可能性是np.vectorize。然而,numpy在np.vectorize上的文档指出,它“主要是为了方便而提供的,而不是为了性能”-一个慢的python函数仍然是一个慢的python函数,具有整个相关的开销!另一个问题是它巨大的内存消耗-例如,参见SO-post
当一个人想要有一个C函数的性能,但使用numpy的机器,一个很好的解决方案是使用numba创建ufuncs,例如:

# 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

它很容易击败np.vectorize,而且当相同的功能将被执行为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"

时间测量代码见本答复的附录:

Numba的版本(绿色)比python函数(即np.vectorize),这并不奇怪。但它也比numpy-function快10倍左右,因为numbas版本不需要中间数组,因此更有效地使用缓存。
虽然numba的ufunc方法是可用性和性能之间的一个很好的折衷,但它仍然不是我们能做的最好的。然而,对于任何任务,都没有最佳的银或方法--人们必须了解限制是什么以及如何减轻限制。
例如,对于超越函数(例如,expsincos)numba与numpy的np.exp相比没有任何优势(没有创建临时数组-这是加速的主要来源)。然而,我的Anaconda安装使用Intel的VML来处理大于8192的向量-如果内存不连续,它就不能这样做。因此,最好将元素复制到连续内存中,以便能够使用英特尔的VML:

import numba as nb
@nb.vectorize(target="cpu")
def nb_vexp(x):
    return np.exp(x)

def np_copy_exp(x):
    copy = np.ravel(x, 'K')
    return np.exp(copy).reshape(x.shape)

为了比较的公平性,我关闭了VML的并行化(参见附录中的代码):

可以看到,一旦VML开始工作,复制的开销就得到了补偿。然而,一旦数据对于L3缓存来说变得太大,那么优势就很小,因为任务再次成为内存带宽限制。
另一方面,numba也可以使用Intel的SVML,如this post中所解释的:

from llvmlite import binding
# set before import
binding.set_option('SVML', '-vector-library=SVML')

import numba as nb

@nb.vectorize(target="cpu")
def nb_vexp_svml(x):
    return np.exp(x)

使用具有并行化的VML可以得到:

numba的版本开销更小,但对于某些大小,VML甚至优于SVML,尽管有额外的复制开销-这并不奇怪,因为numba的ufuncs不是并行的。
列表:
A.多项式函数的比较:

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

B. exp的比较:

import perfplot
import numexpr as ne # using ne is the easiest way to set vml_num_threads
ne.set_vml_num_threads(1)
perfplot.show(
    setup=lambda n: np.random.rand(n,n)[::2,::2],
    n_range=[2**k for k in range(0,12)],
    kernels=[
        nb_vexp, 
        np.exp,
        np_copy_exp,
        ],
    logx=True,
    logy=True,
    xlabel='len(x)',
    )
bzzcjhmw

bzzcjhmw6#

上面的答案都比较好,但是如果你需要使用自定义函数进行Map,并且你有numpy.ndarray,你需要保持数组的形状。
我只比较了两个,但它将保留ndarray的形状。我使用了具有100万个条目的数组进行比较。这里我使用平方函数。我将介绍n维数组的一般情况。对于二维,只需将iter用于2D。

import numpy, time

def A(e):
    return e * e

def timeit():
    y = numpy.arange(1000000)
    now = time.time()
    numpy.array([A(x) for x in y.reshape(-1)]).reshape(y.shape)        
    print(time.time() - now)
    now = time.time()
    numpy.fromiter((A(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用户广场功能,使用任何你的选择。如果你的函数依赖于i, j,这是数组的索引,那么就像for ind in range(arr.size)这样的数组的大小,使用numpy.unravel_index根据你的一维索引和数组numpy的形状来获得i, j, ..。unravel_index
这个答案的灵感来自于我对另一个问题here的回答

相关问题