Python 如何提高这个numpy循环的效率

Python 如何提高这个numpy循环的效率,python,optimization,numpy,Python,Optimization,Numpy,我有一个包含标签的numpy数组。我想根据每个标签的大小和边界框计算一个数字。如何更有效地编写此文件,以便在大型阵列(约15000个标签)上使用它 我无法使用一些NumPy矢量化函数有效地实现这一点,所以也许一个聪明的Python实现会更快 def first_row(a, labels): d = {} d_setdefault = d.setdefault len_ = len num_labels = len_(labels) for i, row

我有一个包含标签的numpy数组。我想根据每个标签的大小和边界框计算一个数字。如何更有效地编写此文件,以便在大型阵列(约15000个标签)上使用它


我无法使用一些NumPy矢量化函数有效地实现这一点,所以也许一个聪明的Python实现会更快

def first_row(a, labels):
    d = {}
    d_setdefault = d.setdefault
    len_ = len
    num_labels = len_(labels)
    for i, row in enumerate(a):
        for label in row:
            d_setdefault(label, i)
        if len_(d) == num_labels:
            break
    return d
此函数返回一个字典,将每个标签映射到它出现的第一行的索引。将该函数应用于
A
A.T
A[:-1]
A.T[:-1]
还将提供第一列以及最后一行和列

如果您想要列表而不是字典,可以使用
map(d.get,labels)
将字典转换为列表。或者,您可以从一开始就使用NumPy数组而不是字典,但一旦找到所有标签,您将无法尽早离开循环


我很想知道这是否(以及在多大程度上)确实加快了代码的速度,但我相信它比原始解决方案快。

性能瓶颈似乎确实是调用
argmax
。可以通过如下更改循环来避免(仅计算y0,y1,但很容易推广到x0,x1):

我不确定性能差异的原因,但一个原因可能是像
=
argmax
max
这样的所有操作都可以直接从输入数组的形状预先分配其输出数组,这对于
argwhere
算法是不可能的:

  • 将数组更改为一维
  • 通过argsort()获取排序索引
  • 将维度数组上的已排序版本获取为已排序的
  • 使用where()和diff()查找已排序的标签中的标签更改位置
  • 使用更改位置和排序索引获取标签在一维中的原始位置
  • 从on标注位置计算二维位置
  • 对于大型阵列,如(7000、9000),可以在30秒内完成计算

    代码如下:

    import numpy as np
    
    A = np.array([[ 1, 1, 0, 3, 3],
               [ 1, 1, 0, 0, 0],
               [ 1, 0, 0, 2, 2],
               [ 1, 0, 2, 2, 2]] )
    
    def label_range(A):
        from itertools import izip_longest
        h, w = A.shape
        tmp = A.reshape(-1)
    
        index = np.argsort(tmp)
        sorted_A = tmp[index]
        pos = np.where(np.diff(sorted_A))[0]+1
        for p1,p2 in izip_longest(pos,pos[1:]):
            label_index = index[p1:p2]
            y = label_index // w
            x = label_index % w
    
            x0 = np.min(x)
            x1 = np.max(x)+1
            y0 = np.min(y)
            y1 = np.max(y)+1
            label = tmp[label_index[0]]
    
            yield label,x0,y0,x1,y1
    
    for label,x0,y0,x1,y1 in label_range(A):
        print "%d:(%d,%d)-(%d,%d)" % (label, x0,y0,x1,y1)
    
    #B = np.random.randint(0, 100, (7000, 9000))
    #list(label_range(B))
    
    另一种方法:

    使用bincount()获取每行和每列中的标签计数,并将信息保存在rows和cols数组中

    对于每个标签,您只需要在行和列中搜索范围。它比排序更快,在我的电脑上,它可以在几秒钟内完成计算

    def label_range2(A):
        maxlabel = np.max(A)+1
        h, w = A.shape
        rows = np.zeros((h, maxlabel), np.bool)
        for row in xrange(h):
            rows[row,:] = np.bincount(A[row,:], minlength=maxlabel) > 0
    
        cols = np.zeros((w, maxlabel), np.bool)
        for col in xrange(w):
            cols[col,:] =np.bincount(A[:,col], minlength=maxlabel) > 0
    
        for label in xrange(1, maxlabel):
            row = rows[:, label]
            col = cols[:, label]
            y = np.where(row)[0]
            x = np.where(col)[0]
            x0 = np.min(x)
            x1 = np.max(x)+1
            y0 = np.min(y)
            y1 = np.max(y)+1        
            yield label, x0,y0,x1,y1
    

    使用PyPy,您只需运行循环,而不用担心矢量化。它应该很快。

    在实际用例中,
    A
    有多大?您是否进行了一些分析,以查看哪些语句会让您慢下来?可能是函数
    myfunc
    可以通过将y0、x0、y1、x1保存在单独的数组中来并行化,从而退出循环,只调用函数一次。否则,如果速度真的很重要,您可能需要研究是否值得编写一些C代码。我发现cython在使用numpy阵列时非常舒适。我认为杀手是
    argwhere
    调用每个标签。这不是最漂亮的方式,但它确实有效。我原来的方式跑了很长时间,我甚至从未让它结束(20分钟后放弃)。我刚刚运行了你的方法,并在6m30秒内得到它。@ajwood:谢谢你的反馈。我知道这并不漂亮,但这是我能想到的最简单的解决办法。如果你想做得更快,我建议用Cython实现。我不小心否决了你的帖子,因为我认为算法是错误的。我不得不做一个虚拟编辑来解锁投票——改为向上投票。:)这看起来很有希望,我会尽快试一试。
    import numpy as np
    
    A = np.array([[ 1, 1, 0, 3, 3],
               [ 1, 1, 0, 0, 0],
               [ 1, 0, 0, 2, 2],
               [ 1, 0, 2, 2, 2]] )
    
    def label_range(A):
        from itertools import izip_longest
        h, w = A.shape
        tmp = A.reshape(-1)
    
        index = np.argsort(tmp)
        sorted_A = tmp[index]
        pos = np.where(np.diff(sorted_A))[0]+1
        for p1,p2 in izip_longest(pos,pos[1:]):
            label_index = index[p1:p2]
            y = label_index // w
            x = label_index % w
    
            x0 = np.min(x)
            x1 = np.max(x)+1
            y0 = np.min(y)
            y1 = np.max(y)+1
            label = tmp[label_index[0]]
    
            yield label,x0,y0,x1,y1
    
    for label,x0,y0,x1,y1 in label_range(A):
        print "%d:(%d,%d)-(%d,%d)" % (label, x0,y0,x1,y1)
    
    #B = np.random.randint(0, 100, (7000, 9000))
    #list(label_range(B))
    
    def label_range2(A):
        maxlabel = np.max(A)+1
        h, w = A.shape
        rows = np.zeros((h, maxlabel), np.bool)
        for row in xrange(h):
            rows[row,:] = np.bincount(A[row,:], minlength=maxlabel) > 0
    
        cols = np.zeros((w, maxlabel), np.bool)
        for col in xrange(w):
            cols[col,:] =np.bincount(A[:,col], minlength=maxlabel) > 0
    
        for label in xrange(1, maxlabel):
            row = rows[:, label]
            col = cols[:, label]
            y = np.where(row)[0]
            x = np.where(col)[0]
            x0 = np.min(x)
            x1 = np.max(x)+1
            y0 = np.min(y)
            y1 = np.max(y)+1        
            yield label, x0,y0,x1,y1