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

时间:2016-03-17 13:48:59

标签: python numpy

此问题类似于this一个。

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

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

更新

我喜欢使用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%左右。

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

3 个答案:

答案 0 :(得分:2)

您可以使用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一个!

答案 1 :(得分:1)

您可以使用masked arrays,但在我运行的测试中,(angles * belong).sum(1)更快。

掩码数组方法如下所示:

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

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

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

答案 2 :(得分:0)

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

我所希望的是计算涉及属于真的位置的角度的公式。这应该加速大约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倍,并且与其他两个解决方案的结果相同。 请注意,这些结果仅用于比之前更大的示例: 属于,角度和lines_lengths_vstacked有形状:(3400,170) 和np.count_nonzero(所属)/belong.size->0.16765051903114186

<强>更新 由于np.reduceat中的一个极端情况(如numpy版本&#39; 1.11.0rc1&#39;),它无法正确处理重复索引,see,我不得不添加一个黑客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