【问题标题】:Speed up NumPy loop on 2D arrays - removes rows that are similar加快二维数组上的 NumPy 循环 - 删除相似的行
【发布时间】:2017-08-09 20:16:32
【问题描述】:

我正在尝试显着加快以下代码的速度,但无济于事。该代码采用二维数组并删除与数组中的其他行相比过于相似的数组行。请参阅下面的代码和 cmets。

as0 = a.shape[0]
for i in range(as0):
    a2s0 = a.shape[0] # shape may change after each iteration
    if i > (a2s0 - 1):
        break
    # takes the difference between all rows in array by iterating over each
    # row. Then sums the absolutes. The condition finally gives a boolean
    # array output - similarity condition of 0.01
    t = np.sum(np.absolute(a[i,:] - a), axis=1)<0.01
    # Retains the indices that are too similar and then deletes the
    # necessary row
    inddel = np.where(t)[0]
    inddel = [k for k in inddel if k != i]
    a = np.delete(a, inddel, 0)

我想知道矢量化是否可行,但我不太熟悉。任何帮助将不胜感激。

编辑:

    if i >= (a2s0 - 1): # Added equality sign
        break
    # Now this only calculates over rows that have not been compared.
    t = np.sum(np.absolute(a[i,:] - a[np.arange(i+1,a2s0),:]), axis=1)>0.01
    t = np.concatenate((np.ones(i+1,dtype=bool), t))
    a = a[t, :]

【问题讨论】:

  • 假设我们有行号:6,10,12“相似”彼此。那么,是否可以删除前两行并保留最后一行,即12
  • 是的。删除哪些索引并不重要。只要唯一的行仍然存在。

标签: python performance numpy vectorization


【解决方案1】:

方法 #1:广播

这是一种使用broadcasting 的矢量化方法,将a 扩展到3D,然后以矢量化方式在所有迭代中执行这些计算 -

mask = (np.absolute(a[:,None] - a)).sum(2) < 0.01
a_out = a[~np.triu(mask,1).any(0)]

方法 #2:使用 pdist('cityblock')

对于大型数组,我们会在以前的方法中遇到内存问题。因此,作为另一种方法,我们可以利用 pdist 的 'cityblock' 计算曼哈顿距离,然后以平方距离矩阵形式 ID 对应的 row/col,而无需实际计算该形式,而是使用 searchsorted 代替有效的解决方案它。

这是实现 -

from scipy.spatial.distance import pdist

n = a.shape[0]
dists = pdist(a, 'cityblock')
idx = np.flatnonzero(dists < thresh)
sep_idx = np.arange(n-1,0,-1).cumsum()
rm_idx = np.unique(np.searchsorted(sep_idx,idx,'right'))
a_out = np.delete(a,rm_idx,axis=0)

基准测试

方法-

# Approach#2 from this post
def remove_similar_rows(a, thresh=0.01):
    n = a.shape[0]
    dists = pdist(a, 'cityblock')
    idx = np.flatnonzero(dists < thresh)
    sep_idx = np.arange(n-1,0,-1).cumsum()
    rm_idx = np.unique(np.searchsorted(sep_idx,idx,'right'))
    return np.delete(a,rm_idx,axis=0)

# @John Zwinck's soln
def pairwise_manhattan_distances(a, thresh=0.01):
    d = manhattan_distances(a)
    return a[~np.any(np.tril(d < thresh), axis=0)]

时间安排 -

In [209]: a = np.random.randint(0,9,(4000,30))

# Let's set 100 rows randomly as dups    
In [210]: idx0 = np.random.choice(4000,size=100, replace=0)

In [211]: idx1 = np.random.choice(4000,size=100, replace=0)

In [217]: a[idx0] = a[idx1]

In [238]: %timeit pairwise_manhattan_distances(a, thresh=0.01)
1 loops, best of 3: 225 ms per loop

In [239]: %timeit remove_similar_rows(a, thresh=0.01)
10 loops, best of 3: 100 ms per loop

【讨论】:

  • 你的(np.absolute(a[:,None] - a)).sum(2) 在数学上很优雅,但如果输入甚至是中等大,它就会爆炸。我尝试使用 (4000,30) 的输入形状,它花了很多秒,大量的内存,然后我杀死了它。我的答案中的manhattan_distances(a) 在 150 毫秒内做同样的事情。如果输入很小,你的方法会更快。
  • @JohnZwinck 你能在你的帖子中分享你的基准测试设置吗?
  • 很简单:a = np.random.random((4000,30)) 然后在 IPython 中 %timeit (np.absolute(a[:,None] - a)).sum(2)
  • @JohnZwinck 添加了另一种有效的方法,以及时序测试。
  • @Jean-MichelLaurenceNairac:只需分几块重复运行即可。例如,在前 10k 行上运行算法,然后在接下来的 10k 行上运行,边走边删除。希望您删除很多行,最后您可以组装剩余的行以进行最后一次传递。
【解决方案2】:

让我们创建一些假数据:

np.random.seed(0)
a = np.random.random((4,3))

现在我们有了:

array([[ 0.5488135 ,  0.71518937,  0.60276338],
       [ 0.54488318,  0.4236548 ,  0.64589411],
       [ 0.43758721,  0.891773  ,  0.96366276],
       [ 0.38344152,  0.79172504,  0.52889492]])

接下来,我们需要所有行对的元素差异之和。我们可以使用Manhattan Distance:

d = sklearn.metrics.pairwise.manhattan_distances(a)

这给出了:

array([[ 0.        ,  0.33859562,  0.64870931,  0.31577611],
       [ 0.33859562,  0.        ,  0.89318282,  0.6465111 ],
       [ 0.64870931,  0.89318282,  0.        ,  0.5889615 ],
       [ 0.31577611,  0.6465111 ,  0.5889615 ,  0.        ]])

现在您可以应用一个阈值,只保留一个三角形:

m = np.tril(d < 0.4, -1) # large threshold just for this example

并获得一个布尔掩码:

array([[False, False, False, False],
       [ True, False, False, False],
       [False, False, False, False],
       [ True, False, False, False]], dtype=bool)

这告诉您第 0 行与第 1 行和第 3 行“太相似”。现在您可以从原始矩阵中删除掩码的任何元素为 True 的行:

a[~np.any(m, axis=0)] # axis can be either 0 or 1 - design choice

这给了你:

array([[ 0.54488318,  0.4236548 ,  0.64589411],
       [ 0.43758721,  0.891773  ,  0.96366276],
       [ 0.38344152,  0.79172504,  0.52889492]])

把它们放在一起:

d = sklearn.metrics.pairwise.manhattan_distances(a)
a = a[~np.any(np.tril(d < 0.4, -1), axis=0)]

【讨论】:

  • 为了复制你的,我必须做一个小改动:m = np.tril(d &lt; 0.4, k = -1)
  • @Jean-MichelLaurenceNairac:你说得对,我忽略了在我发布的代码中设置k=-1。现已修复。
【解决方案3】:

第一行:

t = np.sum(np.absolute(a[i,:] - a), axis=1)<0.01

每次取单行与整个数组之差的绝对值之和。这可能不是您需要的,而是尝试获取当前行与数组中后面的行之间的差异。 您已经将前面的所有行与当前行进行了比较,为什么还要再做一次?

此外,从数组中删除行是一项昂贵且缓慢的操作,因此您可能会发现检查所有行然后删除附近的重复项会更快。您也不能检查任何已被删除的行,因为您知道它们将被删除。

【讨论】:

  • 谢谢史蒂夫。到目前为止,我添加了一个删除删除的编辑
  • 我现在已经包含了只计算未见过的行的代码。再次感谢
猜你喜欢
  • 1970-01-01
  • 2012-06-26
  • 2021-09-03
  • 1970-01-01
  • 2021-08-10
  • 2021-03-03
  • 2019-01-05
  • 2015-04-08
  • 2015-06-08
相关资源
最近更新 更多