如何在numpy行上应用泛型函数?

时间:2017-05-29 09:52:49

标签: python arrays numpy

在您将此标记为重复之前,请允许我向您解释我已阅读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操作实现相同的效果。我该怎么做?

2 个答案:

答案 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行ab。也就是说,它通过列表来迭代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

矢量化/ frompyfunc

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

我不知道您是如何尝试使用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),且NM相比较小,则此迭代费用不高。也就是说,一个大任务的几个循环是可以的。如果它们简化大型阵列内存管理,它们甚至可能更快。

最好的

理想的解决方案是重写泛型函数,以便使用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])