2016-03-17 119 views
1

這個問題類似於this之一。numpy 2d布爾數組索引與沿一個軸減少

我有一個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%。但是如果我們看一下前兩行的話,那麼艾索姆法的加速比較小,大約快0.1%。

我不確定這個時間是否可以改善。

回答

0

我發現比einsum解決方案快了約3倍,我不認爲它可以得到更快,所以我用這個其他方法回答我自己的問題。

我所希望的是計算公式涉及角度只是爲屬於True的位置。大約30%的時間屬於「真」,這應該加快大約3倍。

我第一次使用角度[屬於]會嘗試只計算屬於True的位置的公式,但問題是得到的數組是1d,我無法用np.sum進行行減少。解決方案是使用np.add.reduceat

reduceat可以在特定切片列表中減少ufunc(在這種情況下,添加)。所以我只需要創建切片列表,以便減少由[所有]角度產生的1d陣列。

我會顯示我的代碼和時間,並應該自己說話。

第一I定義與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倍s更快,並且與其他兩個結果相同。 注意,這些結果是針對比以前稍大示例,其中: 屬於,角度和lines_lengths_vstacked具有形狀:(3400,170) 和np.count_nonzero(屬於)/belong.size-> 0.16765051903114186

更新 由於np.reduceat中的角落案例(如numpy版本'1.11.0rc1'),它無法正確處理重複的索引,see,我不得不添加一個hack到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 
1

您可以使用masked arrays這一點,但在測試中我跑這是(angles * belong).sum(1)更快。

一名蒙面陣列的方法是這樣的:

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

在這裏,我們正在創建的angles一個蒙面數組,其中值~belong(「不屬於」),是屏蔽(不含)。我們採取而不是,因爲我們想排除belong中的值爲False。然後取.sum(1)行的總和。 sum將返回另一個掩碼數組,因此您可以使用該掩碼數組的.data屬性來獲取值。

我添加了copy=False kwarg,這樣這個代碼不會因陣列創建而變慢,但它仍然比您的(angles * belong).sum(1)方法慢,所以您應該堅持這一點。

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之一!

+0

感謝您的回答。艾森豪斯的方法看起來非常有趣。然而,在實際計算中,我使用einsum vs based的速度增益很小。我認爲這可能是因爲不是角度而是我有一個更大的操作,無論如何對於整個矩陣必須進行計算,然後才進入艾斯法姆。我將編輯我的問題,以顯示我的測試結果與真實性以及涉及角度的實際公式 – martinako