簡體   English   中英

是否有可能在Python中加速這個循環?

[英]Is it possible to speed up this loop in Python?

以正常的方式在一個映射函數numpy.narraynp.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM