[英]Is it possible to speed up this loop in Python?
以正常的方式在一個映射函數numpy.narray
像np.array[map(some_func,x)]
或vectorize(f)(x)
不能提供的索引。 以下代碼只是許多應用程序中常見的簡單示例。
dis_mat = np.zeros([feature_mat.shape[0], feature_mat.shape[0]])
for i in range(feature_mat.shape[0]):
for j in range(i, feature_mat.shape[0]):
dis_mat[i, j] = np.linalg.norm(
feature_mat[i, :] - feature_mat[j, :]
)
dis_mat[j, i] = dis_mat[i, j]
有沒有辦法加快速度?
謝謝您的幫助! 加速此代碼的最快方法是使用@ user2357112評論的函數:
from scipy.spatial.distance import pdist,squareform
dis_mat = squareform(pdist(feature_mat))
如果feature_mat
很小, @ Julien的方法也很好,但是當feature_mat
是2000乘2000時,它需要近40 GB的內存。
SciPy具有專門用於計算您正在計算的成對距離類型的功能。 它是scipy.spatial.distance.pdist
,它以精簡格式生成距離,基本上只存儲距離矩陣的上三角形,但您可以使用scipy.spatial.distance.squareform
將結果轉換為方形:
from scipy.spatial.distance import pdist, squareform
distance_matrix = squareform(pdist(feature_mat))
這樣可以避免使用直接矢量化解決方案所需的巨大中間陣列,因此速度更快,適用於更大的輸入。 它失去的時機使用代數運算有一種方法dot
處理繁重 ,雖然。
如果您決定需要歐幾里德距離以外的其他內容,則pdist
還支持各種備用距離指標。
# Manhattan distance!
distance_matrix = squareform(pdist(feature_mat, 'cityblock'))
# Cosine distance!
distance_matrix = squareform(pdist(feature_mat, 'cosine'))
# Correlation distance!
distance_matrix = squareform(pdist(feature_mat, 'correlation'))
# And more! Check out the docs.
您可以創建新軸並廣播:
dis_mat = np.linalg.norm(feature_mat[:,None] - feature_mat, axis=-1)
定時:
feature_mat = np.random.rand(100,200)
def a():
dis_mat = np.zeros([feature_mat.shape[0], feature_mat.shape[0]])
for i in range(feature_mat.shape[0]):
for j in range(i, feature_mat.shape[0]):
dis_mat[i, j] = np.linalg.norm(
feature_mat[i, :] - feature_mat[j, :]
)
dis_mat[j, i] = dis_mat[i, j]
def b():
dis_mat = np.linalg.norm(feature_mat[:,None] - feature_mat, axis=-1)
%timeit a()
100 loops, best of 3: 20.5 ms per loop
%timeit b()
100 loops, best of 3: 11.8 ms per loop
考慮可以做什么,並在kxk
矩陣上使用np.dot
優化,在小內存位置(kxk):
def c(m):
xy=np.dot(m,m.T) # O(k^3)
x2=y2=(m*m).sum(1) #O(k^2)
d2=np.add.outer(x2,y2)-2*xy #O(k^2)
d2.flat[::len(m)+1]=0 # Rounding issues
return np.sqrt(d2) # O (k^2)
為了比較:
def d(m):
return squareform(pdist(m))
以下是ak * k初始矩陣的'時間(it)':
這兩個算法是O(k ^ 3),但c(m)
通過np.dot
得到作業的O(k ^ 3)部分, np.dot
是線性代數的關鍵節點,它受益於所有優化 (多核等) 。 pdist
只是在源代碼中看到的循環。
這解釋了大數組的15倍因子,即使pdist
僅通過計算一半項來利用矩陣的對稱性。
我想避免混合使用NumPy和for
循環的一種方法是使用允許替換的索引創建器版本創建索引數組:
import numpy as np
from itertools import product, chain
from scipy.special import comb
def comb_index(n, k):
count = comb(n, k, exact=True, repetition=True)
index = np.fromiter(chain.from_iterable(product(range(n), repeat=k)),
int, count=count*k)
return index.reshape(-1, k)
然后,我們簡單地獲取每個數組對,計算它們之間的差異,重新生成結果數組,並采用數組中每一行的范數:
reshape_mat = np.diff(feature_mat[comb_index(feature_mat.shape[0], 2), :], axis=1).reshape(-1, feature_mat.shape[1])
dis_list = np.linalg.norm(reshape_mat, axis=-1)
請注意, dis_list
只是所有n*(n+1)/2
可能norms
。 它的運行速度與他提供的feature_mat
的另一個答案的速度接近,當比較我們最大部分的字節大小時,
(feature_mat[:,None] - feature_mat).nbytes == 16000000
而
np.diff(feature_mat[comb_index(feature_mat.shape[0], 2), :], axis=1).reshape(-1, feature_mat.shape[1]).nbytes == 8080000
對於大多數投入,我只使用一半的存儲:仍然不是最理想的,但是邊際改善。
基於np.triu_indices
,如果你真的想用純NumPy做這個:
s = feature_mat.shape[0]
i, j = np.triu_indices(s, 1) # All possible combinations of indices
dist_mat = np.empty((s, s)) # Don't waste time filling with zeros
np.einsum('ii->i', dist_mat)[:] = 0 # When you can just fill the diagonal
dist_mat[i, j] = dist_mat[j, i] = np.linalg.norm(feature_mat[i] - feature_mat[j], axis=-1)
# Vectorized version of your original process
這種方法相對於廣播的好處是你可以用塊來做:
n = 10000000 # Based on your RAM available
for k in range (0, i.size, n):
i_ = i[k: k + n]
j_ = j[k: k + n]
dist_mat[i_, j_] = dist_mat[j_, i_] = \
np.linalg.norm(feature_mat[i_] - feature_mat[j_], axis = -1)
讓我們從函數的重寫開始:
dist(mat, i, j):
return np.linalg.norm(mat[i, :] - mat[j, :])
size = feature_mat.shape[0]
for i in range(size):
for j in range(size):
dis_mat[i, j] = dist(feature_mat, i, j)
這可以用(稍微更多)矢量化形式重寫為:
v = [dist(feature_map, i, j) for i in range(size) for j in range(size)]
dist_mat = np.array(v).reshape(size, size)
請注意,我們仍然依賴於Python而不是NumPy進行某些計算,但它是向量化的一步。 還要注意dist(i, j)
是對稱的,因此我們可以進一步減少大約一半的計算。 或許考慮:
v = [dist(feature_map, i, j) for i in range(size) for j in range(i + 1)]
現在棘手的一點是將這些計算值分配給dist_mat
的正確元素。
它的執行速度取決於計算dist(i, j)
的成本。 對於小feature_mat
,重新計算的成本不足以擔心這一點。 但是對於大型矩陣,你絕對不想重新計算。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.