Python 距离矩阵的并行构造

Python 距离矩阵的并行构造,python,performance,parallel-processing,distance,hierarchical-clustering,Python,Performance,Parallel Processing,Distance,Hierarchical Clustering,我研究了大量多维向量的层次聚集聚类,发现最大的瓶颈是距离矩阵的构造。此任务的简单实现如下(在Python中): 我想知道哪种方法是为这个例程添加并行性的最佳方法。一种简单的方法是中断外循环并将其分配给多个作业,例如,如果您有10个处理器,则为不同的i范围创建10个不同的作业,然后将结果连接起来。然而,这种“横向”解决方案似乎并不完全正确。是否有其他用于此任务的并行算法(或现有库)?非常感谢您提供的任何帮助。我怀疑您能比scipy模块中的pdist更快地得到帮助。也许这就是为什么它说 请注意,应避

我研究了大量多维向量的层次聚集聚类,发现最大的瓶颈是距离矩阵的构造。此任务的简单实现如下(在Python中):


我想知道哪种方法是为这个例程添加并行性的最佳方法。一种简单的方法是中断外循环并将其分配给多个作业,例如,如果您有10个处理器,则为不同的
i
范围创建10个不同的作业,然后将结果连接起来。然而,这种“横向”解决方案似乎并不完全正确。是否有其他用于此任务的并行算法(或现有库)?非常感谢您提供的任何帮助。

我怀疑您能比
scipy
模块中的
pdist
更快地得到帮助。也许这就是为什么它说

请注意,应避免将引用传递给以下对象之一: 此库中定义的距离函数。例如:

dm=pdist(X,sokalsneath) 将计算中向量之间的成对距离 X使用Python函数sokalsneath。这将导致 sokalsneath被称为n choose 2次,其中 效率低下。相反,优化后的C版本更具吸引力 高效,我们使用以下语法将其称为:

dm=pdist(X,‘sokalsneath’) 因此,如果使用
pdist(X,'cosine')
,则不使用Python函数。在我看来,当我运行它时,它似乎只使用一个内核,所以如果你有很多内核,你可能会更快地得到它。但请记住,要实现这一点,您的本机实现必须与SciPy一样快。这不是小事。你宁愿耐心一点,或者选择一种不同的聚类方法,例如。G支持空间索引的算法


看起来
scikit-learn
有一个并行版本的pdist,名为


其中
n_作业=-1
指定将使用所有CPU。

请参阅@agartland answer-您可以在中指定
n_作业
,或使用
n_作业
参数在中查找聚类算法。例如
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是否有此功能的并行实现。

如果您决定自己协调多处理,您可能希望在CPU之间平均分配计算数量,以便最大限度地缩短计算时间。然后,对的回复可能会很方便。

除了@agartland建议的内容之外,我还喜欢使用或使用它来获得压缩距离向量。这是由提供的精确输出

重要的是要注意
k
kwarg用于
triu_索引
控制对角线的偏移。默认值
k=0
将返回零的对角线以及实际距离值,应设置为
k=1
,以避免出现这种情况

对于大型数据集,我遇到了一个问题,
pairwise_distance
在从工作线程返回值时从
struct.unpack
引发
ValueError
。因此,我对
成对距离的使用被分块在下面

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
要快得多,并且可以很好地根据可用内核的数量进行扩展


N.B.我认为还值得指出的是,过去对于
scipy.cluster.hierarchy.linkage
的参数存在一些混淆,因为文档在某一点上表明用户可以传递压缩的或方形的距离向量/矩阵()。事实并非如此,传递给链接的值应该是压缩距离向量或原始观测的m x n数组

这不是scipy.spatial.distance.cdist(XA,XB,'cosine')所做的吗实际上是这样的,但是这些方法是并行的吗?我目前使用的是
pdist
,但时间太长了。虽然没有并行化,但可能要快得多,因为您将在本机C代码而不是python中完成更多的工作。是的,我知道这一点。我只是在寻找一个比我目前使用的
pdist
更快的解决方案。我提供代码是为了让人们了解任务的顺序形式,帮助人们提出并行版本。问得好。向量比较是并行化的理想选择,而且确实应该有一种方法来构造矩阵,这种方法在实现上既高效又并行。请注意,它通过
N
距离矩阵计算完整的
N
(其中
N
是观察数),而
pdist
计算压缩距离矩阵(长度为
的一维数组)((N**2)-N)/2
。当然,您可以从一种类型的距离矩阵转换到另一种类型的距离矩阵,但是
成对距离的内存使用考虑因素在于,它会生成一组您可能不需要的数据,具体取决于您的使用情况。但是
scipy
中的
pdist
仅使用1个线程/进程,这是非常缓慢的s看起来很有希望!我们可以使用reduce_func以稀疏格式逐块返回其输出吗? dm = pdist(X, 'sokalsneath')
from sklearn.metrics.pairwise import pairwise_distances

D = pairwise_distances(X = v, metric = 'cosine', n_jobs = -1)
#!/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")
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)