在Cython中剪裁一个NumPy数组。

3

我目前有以下的cython函数,用于修改一个填充了零的numpy数组的条目,以求和非零值。在返回数组之前,我想将其修剪并删除所有非零条目。目前,我使用numpy函数 myarray = myarray[~np.all(myarray == 0, axis=1)] 来做到这一点。我想知道是否有(通常情况下)通过使用Cython/C函数而不是依赖于python/numpy来更快地完成此操作的方法。这是我的脚本中仅存的最后几个pythonic交互部分之一(通过使用%%cython -a进行检查)。但我真的不知道如何解决这个问题。通常情况下,我不事先知道最终数组中非零元素的数量。

cdef func():
   np.ndarray[np.float64_t, ndim=2] myarray = np.zeros((lenpropen, 6)) 
   """
   computations
   """

   myarray = myarray[~np.all(myarray == 0, axis=1)]
   return myarray

感谢@Jérôme Richard的评论。基于这个(如果我的理解是正确的),我尝试实现删除-移除惯用法。以下是示例代码。

myarray = np.zeros((5000,6))
myarray[2] = [1,1,1,1,1,1]
@cython.boundscheck(False)  # Deactivate bounds checking                                                                  
@cython.wraparound(False)   # Deactivate negative indexing.                                                               
@cython.cdivision(True)     # Deactivate division by 0 checking.
cdef erase_remove( np.ndarray[np.float64_t, ndim=2] myarray):
    cdef int idx 
    cdef int cursor = 0
    cdef int length_arr = 5000
    for idx in range(5000):
    
        if myarray[idx,0]!=0 and myarray[idx,1]!=0 and myarray[idx,2]!=0 and myarray[idx,3]!=0 and myarray[idx,4]!=0 and  myarray[idx,5]!=0:
            myarray[cursor,0] = myarray[idx,0]
            myarray[cursor,1] = myarray[idx,1]
            myarray[cursor,2] = myarray[idx,2]
            myarray[cursor,3] = myarray[idx,3]
            myarray[cursor,4] = myarray[idx,4]
            myarray[cursor,5] = myarray[idx,5]
            cursor = cursor +1
        else:
            continue
    return  myarray[0:cursor]    
start = timer()
myarray= erase_remove(myarray)
end = timer()
print("final", myarray)
print("time", end-start)

这将产生输出结果。
final [[1. 1. 1. 1. 1. 1.]]
time 1.1235475540161133e-05

与之相比

myarray = np.zeros((5000,6))
print(myarray)

myarray[2] = [1,1,1,1,1,1]
print(myarray)
start = timer()
myarray = myarray[~np.all(myarray == 0, axis=1)]
end = timer()
print(myarray)
print("time", end-start)

生成输出的方法

[[1. 1. 1. 1. 1. 1.]]
time 0.0006445050239562988

我改变了返回值的缩进。否则算法将无法工作(只有一个项目将被过滤)。除此之外,您不需要使用 else+continue。总体而言,看起来很棒 :)。 - Jérôme Richard
1个回答

2
如果最高维度始终只包含像6这样的少量元素,则您的代码不是最佳选择。
首先,myarray == 0np.all~创建临时数组,引入了一些额外的开销,因为它们需要被写入并再次读取。开销取决于临时数组的大小,其中最大的一个是myarray == 0
此外,Numpy调用会执行一些不需要的检查,Cython无法删除这些检查。这些检查引入了恒定的时间开销。因此,对于小输入数组而言,这可能相当大,但对于大输入数组而言不算大。
此外,如果np.all知道最后一个维度的确切大小,那么它的代码可以更快。在这里不是这种情况。实际上,np.all的循环理论上可以展开,因为最后一个维度很小。不幸的是,Cython不会优化Numpy调用,而Numpy是编译为可变输入大小的,因此在编译时大小未知
最后,如果lenpropen很大,计算可以并行化(否则这不会更快,实际上可能会更慢)。但是,请注意,并行实现需要通过两个步骤进行计算:np.all(myarray == 0,axis = 1)需要并行计算,然后您可以通过计算myarray [~result]在并行中创建结果数组并写入它。在顺序执行时,您可以直接在原位过滤行来覆盖myarray,然后生成已过滤行的视图。这种模式被称为删除-搬移习惯用语。请注意,这要求数组是连续的
最后,更快的实现方法是使用2个嵌套循环迭代myarray,内部循环具有恒定的迭代次数。关于lenpropen的大小,您可以使用基于擦除-移除习惯用语的顺序就地实现,或者使用两个步骤的并行就地实现(以及临时数组)。

谢谢您的回复。数组最多只有np.zeros((5000,6))这么大,因此我一直在考虑实现您所描述的顺序执行方法。不幸的是,恕我无知,我并不完全理解“erase-remove惯用法”的讨论。您能否详细说明一下呢? - jcp
@jcp 的想法是使用一个基于 i 的循环来迭代行,并维护一个指向从 0 开始的最后一个过滤行的 cursor 变量。当你找到一行满足类似于 ~np.all 的条件时,你将复制在 array[cursor] 处的该行,然后将 cursor 递增。否则,不需要执行任何操作(舍弃该行)。最后,过滤行将被“移动”到 array 的开头。这是删除步骤。清除步骤只需返回 array[0:cursor] 即可。这样更清楚吗? - Jérôme Richard
是的,谢谢你,那非常有帮助(还有聪明的算法)。如果我的理解是正确的,我已经更新了原始帖子,并实现了erase_remove方法。 - jcp

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