假设我们有一个特别简单的功能,如
import scipy as sp
def func(x, y):
return x + y
这个函数显然适用于x
和y
的几个内置python数据类型,如string,list,int,float,array等。由于我们对数组特别感兴趣,我们考虑两个数组:
x = sp.array([-2, -1, 0, 1, 2])
y = sp.array([-2, -1, 0, 1, 2])
xx = x[:, sp.newaxis]
yy = y[sp.newaxis, :]
>>> func(xx, yy)
返回
array([[-4, -3, -2, -1, 0],
[-3, -2, -1, 0, 1],
[-2, -1, 0, 1, 2],
[-1, 0, 1, 2, 3],
[ 0, 1, 2, 3, 4]])
正如我们所期望的那样。
现在如果想要将数组作为以下函数的输入引入呢?
def func2(x, y):
if x > y:
return x + y
else:
return x - y
执行>>>func(xx, yy)
会引发错误。
人们想出的第一个明显的方法是scipy / numpy中的sp.vectorize
函数。尽管如此,这种方法已被证明效率不高。任何人都可以想到一种更强大的方式来广播任何函数到numpy数组吗?
如果以阵列友好的方式重写代码是唯一的方法,那么如果您也可以在这里提及它会有所帮助。
答案 0 :(得分:11)
np.vectorize
是将操作数字的Python函数转换为在ndarrays上运行的numpy函数的一般方法。
然而,正如你所指出的那样,它并不是很快,因为它正在“引擎盖下”使用Python循环。
为了获得更好的速度,你必须手工制作一个将numpy数组作为输入的函数,并利用这个numpy-ness:
import numpy as np
def func2(x, y):
return np.where(x>y,x+y,x-y)
x = np.array([-2, -1, 0, 1, 2])
y = np.array([-2, -1, 0, 1, 2])
xx = x[:, np.newaxis]
yy = y[np.newaxis, :]
print(func2(xx, yy))
# [[ 0 -1 -2 -3 -4]
# [-3 0 -1 -2 -3]
# [-2 -1 0 -1 -2]
# [-1 0 1 0 -1]
# [ 0 1 2 3 0]]
关于表现:
<强> test.py 强>:
import numpy as np
def func2a(x, y):
return np.where(x>y,x+y,x-y)
def func2b(x, y):
ind=x>y
z=np.empty(ind.shape,dtype=x.dtype)
z[ind]=(x+y)[ind]
z[~ind]=(x-y)[~ind]
return z
def func2c(x, y):
# x, y= x[:, None], y[None, :]
A, L= x+ y, x<= y
A[L]= (x- y)[L]
return A
N=40
x = np.random.random(N)
y = np.random.random(N)
xx = x[:, np.newaxis]
yy = y[np.newaxis, :]
跑步:
N = 30:
% python -mtimeit -s'import test' 'test.func2a(test.xx,test.yy)'
1000 loops, best of 3: 219 usec per loop
% python -mtimeit -s'import test' 'test.func2b(test.xx,test.yy)'
1000 loops, best of 3: 488 usec per loop
% python -mtimeit -s'import test' 'test.func2c(test.xx,test.yy)'
1000 loops, best of 3: 248 usec per loop
N = 1000:
% python -mtimeit -s'import test' 'test.func2a(test.xx,test.yy)'
10 loops, best of 3: 93.7 msec per loop
% python -mtimeit -s'import test' 'test.func2b(test.xx,test.yy)'
10 loops, best of 3: 367 msec per loop
% python -mtimeit -s'import test' 'test.func2c(test.xx,test.yy)'
10 loops, best of 3: 186 msec per loop
这似乎表明func2a
略快于func2c
(func2b
速度非常慢。)
答案 1 :(得分:10)
对于这种特殊情况,您还可以编写一个在NumPy数组和普通Python浮点数上运行的函数:
def func2d(x, y):
z = 2.0 * (x > y) - 1.0
z *= y
return x + z
此版本的速度也是unutbu's func2a()
的四倍(使用N = 100
测试)。
答案 2 :(得分:1)
为了得到基本的想法,你可以修改你的功能,例如这种方式:
def func2(x, y):
x, y= x[:, None], y[None, :]
A= x+ y
A[x<= y]= (x- y)[x<= y]
return A
因此,根据您的情况,这样的事情应该是一个非常合理的起点:
In []: def func(x, y):
..: x, y= x[:, None], y[None, :]
..: return x+ y
..:
In []: def func2(x, y):
..: x, y= x[:, None], y[None, :]
..: A, L= x+ y, x<= y
..: A[L]= (x- y)[L]
..: return A
..:
In []: x, y= arange(-2, 3), arange(-2, 3)
In []: func(x, y)
Out[]:
array([[-4, -3, -2, -1, 0],
[-3, -2, -1, 0, 1],
[-2, -1, 0, 1, 2],
[-1, 0, 1, 2, 3],
[ 0, 1, 2, 3, 4]])
In []: func2(x, y)
Out[]:
array([[ 0, -1, -2, -3, -4],
[-3, 0, -1, -2, -3],
[-2, -1, 0, -1, -2],
[-1, 0, 1, 0, -1],
[ 0, 1, 2, 3, 0]])
虽然这种处理似乎浪费了资源,但情况并非如此。始终测量您的程序的实际性能,然后(不是更早)进行必要的更改。
恕我直言还有一个额外的好处:这种“矢量化”使你的代码最终真的一致和可读。