【问题标题】:Fast (< n^2) clustering algorithm快速 (< n^2) 聚类算法
【发布时间】:2011-05-23 04:33:41
【问题描述】:

我有 100 万个 5 维点,我需要将它们分成 k 个集群,其中 k

但是!我需要运行时间远低于 n^2。 n log n 左右应该没问题。我进行这种聚类的原因是为了避免计算所有 n 点的距离矩阵(这需要 n^2 时间或许多小时),而我只想计算聚类之间的距离。

我尝试了 pycluster k-means 算法,但很快意识到它太慢了。我也尝试过以下贪婪的方法:

  1. 在每个维度将空间切成 20 块。 (所以总共有 20^5 件)。我将根据它们的质心将集群存储在这些网格框中。

  2. 对于每个点,检索 r(最大边界球半径)内的网格框。如果有足够近的集群,则将其添加到该集群,否则创建一个新集群。

但是,这似乎给了我比我想要的更多的集群。我也两次实施了类似的方法,它们给出的答案截然不同。

是否有任何标准方法可以比 n^2 时间更快地进行聚类?概率算法没问题。

【问题讨论】:

标签: algorithm machine-learning cluster-analysis data-mining k-means


【解决方案1】:

考虑近似最近邻 (ANN) 算法或局部敏感散列法 (LSH)。它们不能直接解决聚类问题,但它们能够告诉您哪些点彼此“接近”。通过更改参数,您可以将 close 定义为尽可能接近。而且速度很快。

更准确地说,LSH 可以提供一个哈希函数h,这样,对于两个点xy,以及距离度量d

d(x,y) <= R1  =>  P(h(x) = h(y)) >= P1
d(x,y) >= R2  =>  P(h(x) = h(y)) <= P2

R1 &lt; R2P1 &gt; P2。所以是的,这是概率性的。您可以对检索到的数据进行后处理以得到真正的集群。

这里是LSH 的信息,包括E2LSH manual。 ANN在精神上是相似的; David Mount 有信息here,或者试试FLANN(有 Matlab 和 Python 绑定)。

【讨论】:

  • 嗯,这很有帮助。因此,鉴于我有一个近似的最近邻算法(我实际上认为 scipy kdtree 具有此功能,尽管我不知道它是否快),您是否建议使用如下算法: 1. 对于每个点,大约计算所有最近的半径小于 r 的邻居。 2. 将点从最近的最少到最多排序。 3. 制作一个包含每个点的整数簇号的列表。 4. 遍历排序列表(邻居集),将集合中的所有点分配给一个新的集群。 (一种反向贪心算法)
  • 或许可行。我喜欢将哈希表中的每个 bin 想象成“一个点”。因此,不是对一百万个单独的点进行聚类,而是对作为点集合的 bins 进行聚类,假设同一个 bin 中的点已经足够接近。但是找到这些垃圾箱很快。这只是一个想法。
  • 更新:据我估计,该软件包似乎并没有真正起作用。我也不认为它实现了欧几里得哈希。你肯定想要史蒂夫提供的原始链接:mit.edu/~andoni/LSH。它在这里链接到matlab代码vision.caltech.edu/malaa/software/research/image-search
  • Denis:FLANN 是我尝试的第一个,因为它与 Python 兼容。如果我记得,该界面很容易使用。一旦我更好地理解了这个概念,我最终写了自己的,这并不难。在可用于求解 ANN 的众多数据结构中,FLANN 为输入数据选择了最好的一种。我知道我有什么输入,所以我为我写了一个。但在灵活性和易用性方面,FLANN 还是不错的。
  • 很抱歉,但我认为 LSH 不是正确的解决方案。 LSH 被认为是用于高维空间(你不需要参考,每篇谈论 LSH 的论文都说明了这一点)。否则,kd-trees 已被证明要好得多。实际上 LSH 复杂度是伪多项式,而 kd-tree 是 O(logn)。在这里,我们使用 5-dim 点,kd-tree 应该更好。
【解决方案2】:

我有一个 Perl 模块,它完全符合您的要求 Algorithm::ClusterPoints

首先,它使用您在帖子中描述的算法来划分多维扇区中的点,然后使用蛮力在相邻扇区中的点之间找到聚类。

在非常退化的情况下,复杂度从 O(N) 到 O(N**2) 不等。

更新

@Denis:不,更糟:

对于d 维度,确定扇区(或小超立方体)大小s,使其对角线l 是不同簇中两点之间允许的最小距离c

l = c
l = sqrt(d * s * s)
s = sqrt(c * c / d) = c / sqrt(d)

然后你必须考虑所有与超球面接触的扇区,直径为r = 2c + l,以枢轴扇区为中心。

大致上,我们必须考虑各个方向上的ceil(r/s) 行扇区,这意味着n = pow(2 * ceil(r/s) + 1, d)

例如,对于d=5c=1,我们得到l=2.236s=0.447r=3.236n=pow(9, 5)=59049

实际上,我们必须检查较少的相邻扇区,因为这里我们正在考虑那些接触大小为(2r+1)/s 的超立方体,我们只需要检查那些接触外接超球面的扇区。

考虑到“在同一个簇上”关系的双射性质,我们还可以将必须​​检查的扇区数减半。

具体来说,Algorithm::ClusterPoints 用于d=5 检查 3903 个扇区的情况。

【讨论】:

  • 谢谢!听起来您正在做的是将空间划分为网格,然后使用比我的贪心算法更智能的方法在本地查找集群。太糟糕了,我在编程方面很平庸,不知道 perl,所以我只使用 python。此外,为了获得额外的荣誉,我实际上希望为完整的 1M 点运行我的算法,但后来能够快速看到如果我只使用各种子集(比如 500K),最好的集群会是什么。上一个答案中讨论的哈希样式方法可以实现这一点,因为我只需要重做后期处理。
  • 所以每个扇区(小超立方体)在 5d 中查看 3^5 - 1 = 242 个邻居?
  • 哇,在昏暗中快速上升。真正的运行时会很有趣。
【解决方案3】:

下面是一个小测试台,看看有多快 scipy.spatial.cKDTree 在您的数据上, 并大致了解附近点之间的距离是如何分散的。

为各种 K 运行 K-cluster 的好方法 是建立一个最近对的 MST,并去除 K-1 最长的;看 Wayne, Greedy Algorithms.

可视化集群会很有趣——使用 PCA 投影到 2d?

(只是好奇,你的 K 是 10、100、1000 吗?)

12 月 17 日添加:实际运行时间:100000 x 5 10 秒、500000 x 5 60 秒

#!/usr/bin/env python
# time scipy.spatial.cKDTree build, query

from __future__ import division
import random
import sys
import time
import numpy as np
from scipy.spatial import cKDTree as KDTree
    # http://docs.scipy.org/doc/scipy/reference/spatial.html
    # $scipy/spatial/kdtree.py is slow but clean, 0.9 has cython
__date__ = "2010-12-17 dec denis"

def clumpiness( X, nbin=10 ):
    """ how clumpy is X ? histogramdd av, max """
        # effect on kdtree time ? not much
    N, dim = X.shape
    histo = np.histogramdd( X, nbin )[0] .astype(int)  # 10^dim
    n0 = histo.size - histo.astype(bool).sum()  # uniform: 1/e^lambda
    print "clumpiness: %d of %d^%d data bins are empty  av %.2g  max %d" % (
        n0, nbin, dim, histo.mean(), histo.max())

#...............................................................................
N = 100000
nask = 0  # 0: ask all N
dim = 5
rnormal = .9
    # KDtree params --
nnear = 2  # k=nnear+1, self
leafsize = 10
eps = 1  # approximate nearest, dist <= (1 + eps) * true nearest
seed = 1

exec "\n".join( sys.argv[1:] )  # run this.py N= ...
np.random.seed(seed)
np.set_printoptions( 2, threshold=200, suppress=True )  # .2f
nask = nask or N
print "\nkdtree:  dim=%d  N=%d  nask=%d  nnear=%d  rnormal=%.2g  leafsize=%d  eps=%.2g" % (
    dim, N, nask, nnear, rnormal, leafsize, eps)

if rnormal > 0:  # normal point cloud, .9 => many near 1 1 1 axis
    cov = rnormal * np.ones((dim,dim)) + (1 - rnormal) * np.eye(dim)
    data = np.abs( np.random.multivariate_normal( np.zeros(dim), cov, N )) % 1
        # % 1: wrap to unit cube
else:
    data = np.random.uniform( size=(N,dim) )
clumpiness(data)
ask = data if nask == N  else random.sample( data, sample )
t = time.time()

#...............................................................................
datatree = KDTree( data, leafsize=leafsize )  # build the tree
print "%.1f sec to build KDtree of %d points" % (time.time() - t, N)

t = time.time()
distances, ix = datatree.query( ask, k=nnear+1, eps=eps )
print "%.1f sec to query %d points" % (time.time() - t, nask)

distances = distances[:,1:]  # [:,0] is all 0, point to itself
avdist = distances.mean( axis=0 )
maxdist = distances.max( axis=0 )
print "distances to %d nearest: av" % nnear, avdist, "max", maxdist

# kdtree:  dim=5  N=100000  nask=100000  nnear=2  rnormal=0.9  leafsize=10  eps=1
# clumpiness: 42847 of 10^5 data bins are empty  av 1  max 21
# 0.4 sec to build KDtree of 100000 points
# 10.1 sec to query 100000 points
# distances to 2 nearest: av [ 0.07  0.08] max [ 0.15  0.18]

# kdtree:  dim=5  N=500000  nask=500000  nnear=2  rnormal=0.9  leafsize=10  eps=1
# clumpiness: 2562 of 10^5 data bins are empty  av 5  max 80
# 2.5 sec to build KDtree of 500000 points
# 60.1 sec to query 500000 points
# distances to 2 nearest: av [ 0.05  0.06] max [ 0.13  0.13]
# run: 17 Dec 2010 15:23  mac 10.4.11 ppc 

【讨论】:

    【解决方案4】:

    您可能想试试我名为K-tree 的研究项目。它可以很好地扩展关于 k-means 的大量输入,并形成集群的层次结构。权衡是它会产生具有更高失真的集群。它的平均情况运行时间为 O(n log n),最坏情况为 O(n**2),只有在你有一些奇怪的拓扑结构时才会发生。复杂性分析的更多细节在我的Masters thesis。我已经将它用于非常高维的文本数据并且没有问题。

    有时,在所有数据都集中到一侧(集群)的树中可能会发生错误的拆分。 SVN 中的主干处理此问题的方式与当前版本不同。如果拆分错误,它会随机拆分数据。如果有错误的分裂,前面的方法会强制树变得太深。

    【讨论】:

    • K-means 与O(nki) 相匹配,所以您确定您的方法实际上是优越的吗?
    • 它似乎也与 Bisecting k-means 密切相关(应该在 O(ni) 中运行,即更好地扩展到大 k)。
    【解决方案5】:

    将数据放入R*-tree (Wikipedia)之类的索引中,然后您可以在O(n log n)中运行许多基于密度的聚类算法(例如DBSCAN (Wikipedia)OPTICS (Wikipedia))。

    Density-based clustering (Wikipedia) 似乎正是您想要的(“相距不远”)

    【讨论】:

    • R/R+/R*-Tree 和 KD-tree 似乎非常相似。如果您自己实现数据结构,KD-Tree 可能会容易得多。 OTOH,R-Tree 有利于连续添加数据,因为它是自平衡的,而 KD-Tree 如果在添加新数据后没有完全重新计算,则可能会变得低效。见this SO question
    【解决方案6】:

    人们的印象是 k-means 很慢,但慢实际上只是 EM 算法(Lloyd's)的问题。 k-means 的随机梯度方法比 EM 快几个数量级(参见 www.eecs.tufts.edu/~dsculley/papers/fastkmeans.pdf)。

    这里有一个实现:http://code.google.com/p/sofia-ml/wiki/SofiaKMeans

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-15
      • 2015-10-25
      相关资源
      最近更新 更多