【问题标题】:Efficient way to check high dimensional arrays are overlapped in two ndarray in Python检查高维数组的有效方法在Python中的两个ndarray中重叠
【发布时间】:2016-01-24 19:41:27
【问题描述】:

比如我有两个ndarray,train_dataset的形状是(10000, 28, 28)val_dateset的形状是(2000, 28, 28)

除了使用迭代之外,有没有什么有效的方法可以使用numpy数组函数找到两个ndarrays之间的重叠?

【问题讨论】:

  • 你能解释一下你所说的“重叠”是什么意思吗?您是否正在寻找在 train_datasetval_dataset 中找到的行的索引?
  • 对,我想找出两个数据集中出现的元素(28*28)。
  • 如果您有任何机会尝试创建训练和验证数据集,最好使用 scikit-learn 的cross_validation module

标签: python arrays performance numpy set-operations


【解决方案1】:

我从Jaime's excellent answer here 学到的一个技巧是使用np.void dtype 以便将输入数组中的每一行视为单个元素。这允许您将它们视为一维数组,然后可以将其传递给np.in1d 或其他set routines 之一。

import numpy as np

def find_overlap(A, B):

    if not A.dtype == B.dtype:
        raise TypeError("A and B must have the same dtype")
    if not A.shape[1:] == B.shape[1:]:
        raise ValueError("the shapes of A and B must be identical apart from "
                         "the row dimension")

    # reshape A and B to 2D arrays. force a copy if neccessary in order to
    # ensure that they are C-contiguous.
    A = np.ascontiguousarray(A.reshape(A.shape[0], -1))
    B = np.ascontiguousarray(B.reshape(B.shape[0], -1))

    # void type that views each row in A and B as a single item
    t = np.dtype((np.void, A.dtype.itemsize * A.shape[1]))

    # use in1d to find rows in A that are also in B
    return np.in1d(A.view(t), B.view(t))

例如:

gen = np.random.RandomState(0)

A = gen.randn(1000, 28, 28)
dupe_idx = gen.choice(A.shape[0], size=200, replace=False)
B = A[dupe_idx]

A_in_B = find_overlap(A, B)

print(np.all(np.where(A_in_B)[0] == np.sort(dupe_idx)))
# True

这种方法比 Divakar 的内存效率更高,因为它不需要广播到 (m, n, ...) 布尔数组。事实上,如果 AB 是行优先的,则根本不需要复制。


为了比较,我稍微调整了 Divakar 和 B. M. 的解决方案。

def divakar(A, B):
    A.shape = A.shape[0], -1
    B.shape = B.shape[0], -1
    return (B[:,None] == A).all(axis=(2)).any(0)

def bm(A, B):
    t = 'S' + str(A.size // A.shape[0] * A.dtype.itemsize)
    ma = np.frombuffer(np.ascontiguousarray(A), t)
    mb = np.frombuffer(np.ascontiguousarray(B), t)
    return (mb[:, None] == ma).any(0)

基准测试:

In [1]: na = 1000; nb = 200; rowshape = 28, 28

In [2]: %%timeit A = gen.randn(na, *rowshape); idx = gen.choice(na, size=nb, replace=False); B = A[idx]
divakar(A, B)
   ....: 
1 loops, best of 3: 244 ms per loop

In [3]: %%timeit A = gen.randn(na, *rowshape); idx = gen.choice(na, size=nb, replace=False); B = A[idx]
bm(A, B)
   ....: 
100 loops, best of 3: 2.81 ms per loop

In [4]: %%timeit A = gen.randn(na, *rowshape); idx = gen.choice(na, size=nb, replace=False); B = A[idx]
find_overlap(A, B)
   ....: 
100 loops, best of 3: 15 ms per loop

如您所见,对于小的 n,B.M. 的解决方案比我的解决方案稍快,但 np.in1d 的扩展性优于测试所有元素的相等性 (O(n log n) 而不是 O(n²) 复杂度)。

In [5]: na = 10000; nb = 2000; rowshape = 28, 28

In [6]: %%timeit A = gen.randn(na, *rowshape); idx = gen.choice(na, size=nb, replace=False); B = A[idx]
bm(A, B)
   ....: 
1 loops, best of 3: 271 ms per loop

In [7]: %%timeit A = gen.randn(na, *rowshape); idx = gen.choice(na, size=nb, replace=False); B = A[idx]
find_overlap(A, B)
   ....: 
10 loops, best of 3: 123 ms per loop

对于这种大小的阵列,Divakar 的解决方案在我的笔记本电脑上是难以处理的,因为它需要生成一个 15GB 的中间阵列,而我只有 8GB 的​​ RAM。

【讨论】:

  • 感谢这个解决方案真的很有帮助。我试图更好地理解它。使用np.ascontiguousarray(A.reshape(A.shape[0], -1)) 而不是np.array([x.flatten() for x in A]) 的原因是什么,我见过其他代码用来做类似的事情?是风格问题,还是他们做了不同的事情?
  • @Barker 尝试对这两行进行计时,以获得相当大的输入数组。首先,列表理解几乎肯定会比一次调用reshape 慢,尤其是在A 中有很多行的情况下。同样.flatten() 总是返回一个副本(而.reshape().ravel() 只在必要时返回一个副本),所以你正在为A 中的每一行制作一个临时副本,然后当你调用np.array(...) 时另一个副本在名单上。 np.ascontiguousarray 仅在必要时返回一个副本,因此我的行最多创建一个 A 的副本。
  • 这是唯一适用于较大数据集的解决方案(在此页面上)。它应该有更多的赞成票!
【解决方案2】:

内存允许你可以使用broadcasting,像这样 -

val_dateset[(train_dataset[:,None] == val_dateset).all(axis=(2,3)).any(0)]

示例运行 -

In [55]: train_dataset
Out[55]: 
array([[[1, 1],
        [1, 1]],

       [[1, 0],
        [0, 0]],

       [[0, 0],
        [0, 1]],

       [[0, 1],
        [0, 0]],

       [[1, 1],
        [1, 0]]])

In [56]: val_dateset
Out[56]: 
array([[[0, 1],
        [1, 0]],

       [[1, 1],
        [1, 1]],

       [[0, 0],
        [0, 1]]])

In [57]: val_dateset[(train_dataset[:,None] == val_dateset).all(axis=(2,3)).any(0)]
Out[57]: 
array([[[1, 1],
        [1, 1]],

       [[0, 0],
        [0, 1]]])

如果元素是整数,您可以将输入数组中的每个块 axis=(1,2) 折叠成一个标量,假设它们是可线性索引的数字,然后有效地使用 np.in1dnp.intersect1d 来查找匹配项。

【讨论】:

    【解决方案3】:

    全广播在这里生成一个 10000*2000*28*28 =150 Mo 布尔数组。

    为了效率,你可以:

    • 打包数据,用于 200 ko 数组:

      from pylab import *
      N=10000
      a=rand(N,28,28)
      b=a[[randint(0,N,N//5)]]
      
      packedtype='S'+ str(a.size//a.shape[0]*a.dtype.itemsize) # 'S6272' 
      ma=frombuffer(a,packedtype)  # ma.shape=10000
      mb=frombuffer(b,packedtype)  # mb.shape=2000
      
      %timeit a[:,None]==b   : 102 s
      %timeit ma[:,None]==mb   : 800 ms
      allclose((a[:,None]==b).all((2,3)),(ma[:,None]==mb)) : True
      

      惰性字符串比较有助于减少内存,打破第一个差异:

      In [31]: %timeit a[:100]==b[:100]
      10000 loops, best of 3: 175 µs per loop
      
      In [32]: %timeit a[:100]==a[:100]
      10000 loops, best of 3: 133 µs per loop
      
      In [34]: %timeit ma[:100]==mb[:100]
      100000 loops, best of 3: 7.55 µs per loop
      
      In [35]: %timeit ma[:100]==ma[:100]
      10000 loops, best of 3: 156 µs per loop
      

    这里用(ma[:,None]==mb).nonzero().给出解决方案

    • 使用in1d,获得(Na+Nb) ln(Na+Nb) 的复杂性,反对 Na*Nb 全面比较:

      %timeit in1d(ma,mb).nonzero()  : 590ms 
      

    这里不是很大的收获,但渐近更好。

    【讨论】:

    • 太棒了!从来不知道可以用这样的字符串实现短路。但可能会在其中添加一个ascontiguousarray,因此它适用于像a = rand(28,28,N).T 这样的数组。
    • @morningsun:你是对的。在这种情况下,我只是创建数组,因此它是连续的,但对于外部数据,ma=frombuffer(ascontiguousarray(a),packedtype) 更安全。谢谢。
    【解决方案4】:

    解决方案

    def overlap(a,b):
        """
        returns a boolean index array for input array b representing
        elements in b that are also found in a
        """
        a.repeat(b.shape[0],axis=0)
        b.repeat(a.shape[0],axis=0)
        c = aa == bb
        c = c[::a.shape[0]]
        return c.all(axis=1)[:,0]
    

    您可以使用返回的索引数组对b 进行索引,以提取在a 中也存在的元素

    b[overlap(a,b)]
    

    说明

    为简单起见,我假设您已从 numpy 导入了此示例中的所有内容:

    from numpy import *
    

    因此,例如,给定两个 ndarrays

    a = arange(4*2*2).reshape(4,2,2)
    b = arange(3*2*2).reshape(3,2,2)
    

    我们重复 ab 以使它们具有相同的形状

    aa = a.repeat(b.shape[0],axis=0)
    bb = b.repeat(a.shape[0],axis=0)
    

    然后我们可以简单地比较aabb的元素

    c = aa == bb
    

    最后,通过查看 c 的每 4 个,或者实际上是每个 shape(a)[0]th 元素来获取 b 中的元素的索引,这些元素也在 a 中找到。

    cc == c[::a.shape[0]]
    

    最后,我们提取一个索引数组,其中仅包含子数组中所有元素为True的元素

    c.all(axis=1)[:,0]
    

    在我们的例子中,我们得到

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

    要检查,请更改b的第一个元素

    b[0] = array([[50,60],[70,80]])
    

    我们得到

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

    【讨论】:

    • solution by Divakar其实更清晰简洁,+1。
    • 我建议不要通过 from numpy import * 来污染您的命名空间
    【解决方案5】:

    这个问题来自 Google 的在线深度学习课程? 以下是我的解决方案:

    sum = 0 # number of overlapping rows
    for i in range(val_dataset.shape[0]): # iterate over all rows of val_dataset
        overlap = (train_dataset == val_dataset[i,:,:]).all(axis=1).all(axis=1).sum()
        if overlap:
            sum += 1
    print(sum)
    

    使用自动广播代替迭代。您可以测试性能差异。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-05-06
      • 2021-11-02
      • 1970-01-01
      • 1970-01-01
      • 2015-07-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多