在您将此标记为重复之前,请允许我向您解释我已阅读this page以及其他许多内容,但我仍未找到解决问题的方法。
这就是我遇到的问题:给定两个2D数组,我想在两个数组上应用函数F. F将两个1D阵列作为输入。
import numpy as np
a = np.arange(15).reshape([3,5])
b = np.arange(30, step=2).reshape([3,5])
# what is the 'numpy' equivalent of the following?
np.array([np.dot(x,y) for x,y in zip(a,b)])
请注意,np.dot
仅用于演示。这里真正的问题是任何通用函数F,它适用于两组1D数组。
np.apply_along_axis
迭代地应用该函数;例如,使用上面定义的变量,它会F(a[0], b[0])
并将其与F(a[0], b[1])
和F(a[0], b[2])
结合使用。这不是我想要的。理想情况下,我希望它只停留在F(a[0], b[0])
np.dot(a[np.arange(3)], b[np.arange(3)])
的操作,则抛出一个ValueError,表示形状(3,5)和(3,5)没有对齐。我不知道如何解决这个问题。我试图以任何方式解决这个问题,但我提出的唯一解决方案是使用列表理解。但是我担心使用列表理解会导致性能降低。如果可能的话,我想使用numpy操作实现相同的效果。我该怎么做?
答案 0 :(得分:4)
这类问题在SO上已经被打败了,但我会尝试用你的框架来说明问题:
In [1]: a = np.arange(15).reshape([3,5])
...: b = np.arange(30, step=2).reshape([3,5])
...:
In [2]: def f(x,y):
...: return np.dot(x,y)
列表理解方法将f
应用于3行a
和b
。也就是说,它通过列表来迭代2个数组。在每次调用时,您的函数都会获得2个1d数组。 dot
可以接受其他形状,但目前我们假装它只适用于一对1ds
In [3]: np.array([f(x,y) for x,y in zip(a,b)])
Out[3]: array([ 60, 510, 1460])
In [4]: np.dot(a[0],b[0])
Out[4]: 60
np.vectorize
遍历输入(使用广播 - 可以很方便),并给出函数标量值。我将用frompyfunc
来说明返回一个对象dtype数组(由vectorize
使用):
In [5]: vf = np.frompyfunc(f, 2,1)
In [6]: vf(a,b)
Out[6]:
array([[0, 2, 8, 18, 32],
[50, 72, 98, 128, 162],
[200, 242, 288, 338, 392]], dtype=object)
结果是(3,5)数组;偶然地对列进行求和得到了期望的结果
In [9]: vf(a,b).sum(axis=1)
Out[9]: array([60, 510, 1460], dtype=object)
np.vectorize
没有做出任何速度承诺。
我不知道您是如何尝试使用apply_along_axis
的。它只需要一个数组。经过大量的设置后,它最终会做(对于像a
这样的二维数组):
for i in range(3):
idx = (i, slice(None))
outarr[idx] = asanyarray(func1d(arr[idx], *args, **kwargs))
对于3d及更大版本,它会对其他'进行迭代。轴更简单; 2d它是矫枉过正的。在任何情况下,它都不会加快计算速度。它仍然是迭代。
(apply_along_axis
需要arr
和*args
。它会在arr
上进行迭代,但使用*args
整数。)。
np.dot(a[np.arange(3)], b[np.arange(3)])
与
相同np.dot(a, b)
dot
是矩阵乘积,(3,5)与(5,3)一起产生(3,3)。它处理1d作为特殊情况(参见docs),(3,)with(3,)产生(3,)。
对于真正通用的f(x,y)
,压缩列表推导的唯一替代方法是这样的索引循环:
In [18]: c = np.zeros((a.shape[0]))
In [19]: for i in range(a.shape[0]):
...: c[i] = f(a[i,:], b[i,:])
In [20]: c
Out[20]: array([ 60., 510., 1460.])
速度将类似。 (该行动可以通过cython
转移到已编译的代码,但我不认为你已经准备好深入了解。)
如评论中所述,如果数组为(N,M)
,且N
与M
相比较小,则此迭代费用不高。也就是说,一个大任务的几个循环是可以的。如果它们简化大型阵列内存管理,它们甚至可能更快。
理想的解决方案是重写泛型函数,以便使用numpy compilied函数处理2d数组。
在矩阵乘法的情况下,einsum
已经实现了产品总和的一般形式'在编译的代码中:
In [22]: np.einsum('ij,ij->i',a,b)
Out[22]: array([ 60, 510, 1460])
matmul
也会推广该产品,但最适合使用3d数组:
In [25]: a[:,None,:]@b[:,:,None] # needs reshape
Out[25]:
array([[[ 60]],
[[ 510]],
[[1460]]])
答案 1 :(得分:2)
如果您想使用NumPy快速解决方案,请远离通用功能。尽管NumPy具有隐藏 python循环的一些功能,但循环仍然存在(在函数内)并且这些解决方案并不快(至少与普通的NumPy函数相比)。
你应该做的是:在NumPy,SciPy中找到一个能够满足你需要的功能。这些功能很快,但有时需要进行一些搜索和/或实验才能找到匹配项。
例如,矢量点乘积只是沿元素乘法行的总和:
np.sum(a * b, axis=1) # array([ 60, 510, 1460])
np.einsum('ij,ij->i', a, b) # array([ 60, 510, 1460])