繁体   English   中英

numpy 2d布尔数组索引,沿一个轴减少

[英]numpy 2d boolean array indexing with reduce along one axis

这个问题与类似。

我有一个2d布尔数组“属于”和一个2d浮点数组“角度”。 我想要的是沿着行总和相应索引所属的角度为True,并使用numpy(即避免python循环)。 我不需要存储生成的行,这些行具有不同的长度,并且如链接的问题中所解释的那样需要列表。

所以我尝试的是np.sum(角度[属于],轴= 1),但角度[属于]返回1d结果,我不能按我的意愿减少它。 我也试过np.sum(角度*属于,轴= 1),这是有效的。 但我想知道我是否可以通过只访问属于True的索引来改善时间。 属于真是大约30%的时间和角度是一个涉及角度的较长公式的简化。

UPDATE

我喜欢einsum的解决方案,但在我的实际计算中,速度很快。 我在问题中使用了角度来简化,在实践中它是一个使用角度的公式。 我怀疑这个公式是针对所有角度计算的(无论属于哪个),然后传递给einsum,它将执行计算。

这就是我所做的:

THRES_THETA和max_line_length是浮点数。 属于,角度和lines_lengths_vstacked有形状(1653,58)和np.count_nonzero(属于)/belong.size - > 0.376473287856979

 l2 = (lambda angle=angle, belong=belong, THRES_THETA=THRES_THETA, lines_lengths_vstacked=lines_lengths_vstacked, max_line_length=max_line_length:
      np.sum(belong*(0.3 * (1-(angle/THRES_THETA)) + 0.7 * (lines_lengths_vstacked/max_line_length)), axis=1)) #base method
t2 = timeit.Timer(l2)
print(t2.repeat(3, 100))

l1 = (lambda angle=angle, belong=belong, THRES_THETA=THRES_THETA, lines_lengths_vstacked=lines_lengths_vstacked, max_line_length=max_line_length:
    np.einsum('ij,ij->i', belong, 0.3 * (1-(angle/THRES_THETA)) + 0.7 * (lines_lengths_vstacked/max_line_length)))
t1 = timeit.Timer(l1)
print(t1.repeat(3, 100))

l3 = (lambda angle=angle, belong=belong:
    np.sum(angle*belong ,axis=1)) #base method
t3 = timeit.Timer(l3)
print(t3.repeat(3, 100))

l4 = (lambda angle=angle, belong=belong:
    np.einsum('ij,ij->i', belong, angle))
t4 = timeit.Timer(l4)
print(t4.repeat(3, 100))

结果如下:

[0.2505458095931187, 0.22666162878242901, 0.23591678551324263]
[0.23295411847036418, 0.21908727226505043, 0.22407296178704272]
[0.03711204915708555, 0.03149960399994978, 0.033403337575027114]
[0.025264803208228992, 0.022590580646423053, 0.024585736455331464]

如果我们查看最后两行,对应于einsum的行比使用基本方法快约30%。 但是如果我们看前两行,那么einsum方法的速度会更快,只会快0.1%左右。

我不确定这个时间是否可以改善。

你可以使用np.einsum -

np.einsum('ij,ij->i',belong,angles)

你也可以使用np.bincount ,就像这样 -

idx,_ = np.where(belong)
out = np.bincount(idx,angles[belong])

样品运行 -

In [32]: belong
Out[32]: 
array([[ True,  True,  True, False,  True],
       [False, False, False,  True,  True],
       [False, False,  True,  True,  True],
       [False, False,  True, False,  True]], dtype=bool)

In [33]: angles
Out[33]: 
array([[ 0.65429151,  0.36235607,  0.98316406,  0.08236384,  0.5576149 ],
       [ 0.37890797,  0.60705112,  0.79411002,  0.6450942 ,  0.57750073],
       [ 0.6731019 ,  0.18608778,  0.83387574,  0.80120389,  0.54971573],
       [ 0.18971255,  0.86765132,  0.82994543,  0.62344429,  0.05207639]])

In [34]: np.sum(angles*belong ,axis=1) # This worked for you, so using as baseline
Out[34]: array([ 2.55742654,  1.22259493,  2.18479536,  0.88202183])

In [35]: np.einsum('ij,ij->i',belong,angles)
Out[35]: array([ 2.55742654,  1.22259493,  2.18479536,  0.88202183])

In [36]: idx,_ = np.where(belong)
    ...: out = np.bincount(idx,angles[belong])
    ...: 

In [37]: out
Out[37]: array([ 2.55742654,  1.22259493,  2.18479536,  0.88202183])

运行时测试 -

In [52]: def sum_based(belong,angles):
    ...:     return np.sum(angles*belong ,axis=1)
    ...: 
    ...: def einsum_based(belong,angles):
    ...:     return np.einsum('ij,ij->i',belong,angles)
    ...: 
    ...: def bincount_based(belong,angles):
    ...:     idx,_ = np.where(belong)
    ...:     return np.bincount(idx,angles[belong])
    ...: 

In [53]: # Inputs
    ...: belong = np.random.rand(4000,5000)>0.7
    ...: angles = np.random.rand(4000,5000)
    ...: 

In [54]: %timeit sum_based(belong,angles)
    ...: %timeit einsum_based(belong,angles)
    ...: %timeit bincount_based(belong,angles)
    ...: 
1 loops, best of 3: 308 ms per loop
10 loops, best of 3: 134 ms per loop
1 loops, best of 3: 554 ms per loop

我会选择np.einsum

你可以使用蒙面数组 ,但是在我运行的测试中它并不(angles * belong).sum(1)快。

掩码数组方法如下所示:

sum_ang = np.ma.masked_where(~belong, angles, copy=False).sum(1).data

在这里,我们创建一个蒙版的angles数组,其中值~belong (“不属于”),被屏蔽 (排除)。 我们接受not,因为我们想要排除belong False的值。 然后沿着行.sum(1)获取总和。 sum将返回另一个屏蔽数组,因此您可以使用该屏蔽数组的.data属性获取值。

我添加了copy=False kwarg,这样代码就不会因为数组创建而变慢,但它仍然比你的(angles * belong).sum(1)方法慢,所以你应该坚持下去。

我找到了一种比einsum解决方案快3倍的方法,我认为它不会更快,所以我用其他方法回答我自己的问题。

我希望的是计算涉及角度的公式, 适用于属于True的位置。 这应该加速大约3倍,因为属于真实的大约30%的时间。

我第一次尝试使用角度[属于]将仅计算属于True的位置的公式,但是问题是结果数组是1d而我无法使用np.sum进行行减少。 解决方案是使用np.add.reduceat

reduceat可以在特定切片列表中减少ufunc (在本例中为add)。 所以我只需要创建切片列表,这样我就可以减少角度[属于]产生的1d数组。

我会展示我的代码和时间,而且应该单独说出来。

首先我用reduceat解决方案定义一个函数:

def vote_op(angle, belong, THRES_THETA, lines_lengths_vstacked, max_line_length):
    intermediate = (0.3 * (1-(angle[belong]/THRES_THETA)) + 0.7 * (lines_lengths_vstacked[belong]/max_line_length))
    b_ind = np.hstack([0, np.cumsum(np.sum(belong, axis=1))])
    votes = np.add.reduceat(intermediate, b_ind[:-1])
    return votes

然后我比较基本方法和einsum方法:

l1 = (lambda angle=angle, belong=belong, THRES_THETA=THRES_THETA, lines_lengths_vstacked=lines_lengths_vstacked, max_line_length=max_line_length:
      np.sum(belong*(0.3 * (1-(angle/THRES_THETA)) + 0.7 * (lines_lengths_vstacked/max_line_length)), axis=1))
t1 = timeit.Timer(l1)
print(t1.repeat(3, 100))

l2 = (lambda angle=angle, belong=belong, THRES_THETA=THRES_THETA, lines_lengths_vstacked=lines_lengths_vstacked, max_line_length=max_line_length:
    np.einsum('ij,ij->i', belong, 0.3 * (1-(angle/THRES_THETA)) + 0.7 * (lines_lengths_vstacked/max_line_length)))
t2 = timeit.Timer(l2)
print(t2.repeat(3, 100))

l3 = (lambda angle=angle, belong=belong, THRES_THETA=THRES_THETA, lines_lengths_vstacked=lines_lengths_vstacked, max_line_length=max_line_length:
      vote_op(angle, belong, THRES_THETA, lines_lengths_vstacked, max_line_length))
t3 = timeit.Timer(l3)
print(t3.repeat(3, 100))

和时间:

[2.866840408487671, 2.6822349628234874, 2.665520338478774]
[2.3444239421490725, 2.352450520946098, 2.4150879511222794]
[0.6846337313820605, 0.660780839464234, 0.6091473217964847]

因此, reduceat解决方案的速度提高了约3倍,并且与其他两个解决方案的结果相同。 请注意,这些结果是稍微大于以前的示例,其中:belongs,angle和lines_lengths_vstacked具有形状:(3400,170)和np.count_nonzero(属于)/belong.size-> 0.16765051903114186

更新由于np.reduceat中的一个极端情况(如在numpy版本'1.11.0rc1'中),它无法正确处理重复索引, 请参阅 ,我必须为有问题的情况添加一个hack to vote_op()函数属于False的整行。 这导致b_ind中的重复索引和投票中的错误结果。 我目前的解决方案是修补错误的值,这是有效的,但又是另一个步骤。 看到新的vote_op():

def vote_op(angle, belong, THRES_THETA, lines_lengths_vstacked, max_line_length):
    intermediate = (0.3 * (1-(angle[belong]/THRES_THETA)) + 0.7 * (lines_lengths_vstacked[belong]/max_line_length))
    b_rows = np.sum(belong, axis=1)
    b_ind = np.hstack([0, np.cumsum(b_rows)])[:-1]
    intermediate = np.hstack([intermediate, 0])
    votes = np.add.reduceat(intermediate, b_ind)
    votes[b_rows == 0] = 0
    return votes

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM