并行构造距离矩阵

时间:2012-06-28 18:56:29

标签: python performance parallel-processing distance hierarchical-clustering

我在大量多维向量上进行分层凝聚聚类,我注意到最大的瓶颈是构造距离矩阵。这项任务的一个天真的实现如下(在Python中):

''' v = an array (N,d), where rows are the observations
and columns the dimensions'''
def create_dist_matrix(v):
   N = v.shape[0]
   D = np.zeros((N,N))
   for i in range(N):
      for j in range(i+1):
          D[i,j] = cosine(v[i,:],v[j,:]) # scipy.spatial.distance.cosine()
   return D

我想知道哪个是为这个例程添加一些并行性的最佳方法。一种简单的方法是打破并将外循环分配给许多作业,例如如果您有10个处理器,则为i的不同范围创建10个不同的作业,然后连接结果。然而,这种“横向”解决方案似乎并不合适。是否有任何其他并行算法(或现有库)用于此任务?任何帮助都将受到高度赞赏。

5 个答案:

答案 0 :(得分:12)

看起来scikit-learn有一个名为pairwise_distances

的pdist并行版本
from sklearn.metrics.pairwise import pairwise_distances

D = pairwise_distances(X = v, metric = 'cosine', n_jobs = -1)

其中n_jobs = -1指定将使用所有CPU。

答案 1 :(得分:3)

请参阅@agartland答案 - 您可以在sklearn.metrics.pairwise.pairwise_distances中指定n_jobs,或在sklearn.cluster处查找具有n_jobs参数的群集算法。 E. g。 sklearn.cluster.KMeans

但是,如果你喜欢冒险,你可以实现自己的计算。例如,如果您需要scipy.cluster.hierarchy.linkage的1D距离矩阵,则可以使用:

#!/usr/bin/env python3
from multiprocessing import Pool
import numpy as np
from time import time as ts


data = np.zeros((100,10)) # YOUR data: np.array[n_samples x m_features]
n_processes = 4           # YOUR number of processors
def metric(a, b):         # YOUR dist function
    return np.sum(np.abs(a-b)) 


n = data.shape[0]
k_max = n * (n - 1) // 2  # maximum elements in 1D dist array
k_step = n ** 2 // 500    # ~500 bulks
dist = np.zeros(k_max)    # resulting 1D dist array


def proc(start):
    dist = []
    k1 = start
    k2 = min(start + k_step, k_max)
    for k in range(k1, k2):
        # get (i, j) for 2D distance matrix knowing (k) for 1D distance matrix
        i = int(n - 2 - int(np.sqrt(-8 * k + 4 * n * (n - 1) - 7) / 2.0 - 0.5))
        j = int(k + i + 1 - n * (n - 1) / 2 + (n - i) * ((n - i) - 1) / 2)
        # store distance
        a = data[i, :]
        b = data[j, :]
        d = metric(a, b)
        dist.append(d)
    return k1, k2, dist


ts_start = ts()
with Pool(n_processes) as pool:
    for k1, k2, res in pool.imap_unordered(proc, range(0, k_max, k_step)):
        dist[k1:k2] = res
        print("{:.0f} minutes, {:,}..{:,} out of {:,}".format(
            (ts() - ts_start)/60, k1, k2, k_max))


print("Elapsed %.0f minutes" % ((ts() - ts_start) / 60))
print("Saving...")
np.savez("dist.npz", dist=dist)
print("DONE")

您知道,scipy.cluster.hierarchy.linkage实现不是并行的,其复杂性至少为O(N * N)。我不确定scipy是否具有此函数的并行实现。

答案 2 :(得分:2)

我怀疑你会比pdist模块中的scipy更快地得到它。可能这就是它说

的原因
  

请注意,您应该避免将引用传递给其中一个   此库中定义的距离函数。例如,:

dm = pdist(X, sokalsneath)
  

将计算向量之间的成对距离   X使用Python函数sokalsneath。这会导致   sokalsneath被称为n选择2次,其中   效率低下。相反,优化的C版本更多   高效,我们使用以下语法调用它:。

     

dm = pdist(X, 'sokalsneath')
  因此,如果您使用pdist(X, 'cosine'),则不使用Python函数。当我运行它时,似乎它只使用一个核心,所以如果你有很多核心,你可能会更快。但请记住,要实现这一点,您的原生实现必须与SciPy一样快。这不会是微不足道的。你宁愿耐心等待或采用不同的聚类方法,例如: G。支持空间索引的算法。

答案 3 :(得分:0)

如果您决定自己编排多处理,则可能需要在CPU之间平均分配计算次数,以便最大限度地缩短计算时间。然后回复this question on equally splitting the diagonal matrix可能会派上用场。

答案 4 :(得分:0)

除了@agartland提出的建议之外,我还喜欢将pairwise_distancespairwise_disances_chunkednumpy.triu_indices一起使用以获取凝聚距离矢量。这是scipy.spatial.distance.pdist

提供的确切输出

请注意,k的{​​{1}} kwarg控制对角线的偏移。默认值triu_indices将返回零的对角线以及实际距离值,因此应将其设置为k=0以避免这种情况。

对于大型数据集,我遇到一个问题,当从工作线程返回值时,k=1pairwise_distances引发ValueError。因此,我在下面使用struct.unpack

pairwise_distances_chunked

对我来说,这比使用gen = pairwise_distances_chunked(X, method='cosine', n_jobs=-1) Z = np.concatenate(list(gen), axis=0) Z_cond = Z[np.triu_indices(Z.shape[0], k=1) 快得多,并且可以与可用内核数很好地扩展。

注意事项,我认为值得一提的是,过去pdist的论点有些混乱,因为文档在某一时刻指示用户可以通过一个简洁的或正方形距离矢量/矩阵(linkage() function mistakes distance matrix as observation vectors #2614)。实际上并非如此,传递给链接的值应该是压缩距离矢量或原始观测值的m x n数组。