高效使用Numpy将函数值赋给数组

6

我希望能够以最快的方式使用Numpy在Python3.6中执行简单操作。我希望创建一个函数,将一个给定的数组转换为函数值的数组。以下是使用map实现此目的的简化代码:

import numpy as np
def func(x):
    return x**2
xRange = np.arange(0,1,0.01)
arr_func = np.array(list(map(func, xRange)))

然而,由于我正在使用复杂的函数和大型数组,因此运行时速度对我来说非常重要。是否有更快的方法?

编辑 我的问题与这个不同,因为我询问的是从函数分配,而不是从生成器分配。


2
实际的实现将涉及特定的优化。因此,在没有看到它的情况下,对于通用情况没有神奇的方法。 - Divakar
1
为什么要使用xRange和pRange?在这种特殊情况下,**2操作已经向量化,因此通过执行映射而不是只执行“arr_func = func(xRange)”会产生惩罚。在一般情况下,您必须尽可能地利用向量化操作。 - Ignacio Vergara Kausel
谢谢@IgnacioVergaraKausel,pRange是粘贴时的错误。我已经将其删除。 - splinter
1
只是补充一下,如果您只是使用 func(xRange) 的话,我会得到44.8微秒,而您的map转换成列表再转换为数组需要33.4毫秒(对于100000个随机元素的数组)。 - Ignacio Vergara Kausel
可能是如何从生成器构建numpy数组?的重复问题。 - orip
显示剩余2条评论
2个回答

2

请查看相关的如何从生成器构建numpy数组?问题,其中最有说服力的选项似乎是预先分配numpy数组并设置值,而不是创建一个一次性的中间列表。

arr_func = np.empty(len(xRange))
for i in range(len(xRange)):
  arr_func[i] = func(xRange[i])

1

对于无法使用编译的numpy函数重写的复杂函数,我们无法在速度上取得大的改进。

定义一个需要标量的math方法的函数,例如:

def func(x):
    return math.sin(x)**2 + math.cos(x)**2

In [868]: x = np.linspace(0,np.pi,10000)

作为参考,请进行直接的列表推导:

In [869]: np.array([func(i) for i in x])
Out[869]: array([ 1.,  1.,  1., ...,  1.,  1.,  1.])

In [870]: timeit np.array([func(i) for i in x])
13.4 ms ± 211 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

您的列表映射稍微快一些:

In [871]: timeit np.array(list(map(func, x)))
12.6 ms ± 12.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

对于像这样的一维数组,可以用np.fromiter替换np.array。它也适用于生成器,包括Py3中的map
In [875]: timeit np.fromiter(map(func, x),float)
13.1 ms ± 176 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

这样可以避免先创建整个列表可能带来的时间惩罚。但在这种情况下,它并没有起到帮助作用。
另一个迭代器是np.frompyfunc。 它被np.vectorize使用,但通常具有更少的开销和更快的速度。 它返回一个dtype对象数组:
In [876]: f = np.frompyfunc(func, 1, 1)
In [877]: f(x)
Out[877]: array([1.0, 1.0, 1.0, ..., 1.0, 1.0, 1.0], dtype=object)
In [878]: timeit f(x)
11.1 ms ± 298 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [879]: timeit f(x).astype(float)
11.2 ms ± 85.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

略微提高了速度。我注意到在1000个项目x中有更大的改进。如果您的问题需要针对彼此进行广播的几个数组,则这将更好。
将值分配给预先分配的out数组可能会节省内存,并且通常建议作为列表追加迭代的替代方法。但是在这里,它并没有提高速度:
In [882]: %%timeit 
     ...: out = np.empty_like(x)
     ...: for i,j in enumerate(x): out[i]=func(j)
16.1 ms ± 308 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

使用 enumeraterange 迭代略快。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接