Быстрый (<n ^ 2) алгоритм кластеризации - PullRequest
24 голосов
/ 10 декабря 2010

У меня есть 1 миллион 5-мерных точек, которые мне нужно сгруппировать в k кластеров с k << 1 миллионом. В каждом кластере никакие две точки не должны быть слишком далеко друг от друга (например, они могут быть ограничивающими сферами с указанным радиусом). Это означает, что, вероятно, должно быть много кластеров размером 1. </p>

Но! Мне нужно, чтобы время выполнения было значительно ниже n ^ 2. п лог или около того должно быть хорошо. Причина, по которой я делаю эту кластеризацию, состоит в том, чтобы избежать вычисления матрицы расстояний всех n точек (что занимает n ^ 2 времени или много часов), вместо этого я хочу просто вычислить расстояния между кластерами.

Я попробовал алгоритм k-средних pycluster, но быстро понял, что он слишком медленный. Я также попробовал следующий жадный подход:

  1. Разделите пространство на 20 частей в каждом измерении. (всего 20 ^ 5 штук). Я буду хранить кластеры в этих сетках, в соответствии с их центроидами.

  2. Для каждой точки найдите ячейки сетки, которые находятся в пределах r (максимальный радиус ограничивающей сферы). Если кластера достаточно близко, добавьте его в этот кластер, в противном случае создайте новый кластер.

Однако это, кажется, дает мне больше кластеров, чем я хочу. Я также дважды применял подходы, подобные этому, и они дают совершенно разные ответы.

Существуют ли стандартные подходы к кластеризации быстрее, чем за n ^ 2 времени? Вероятностные алгоритмы в порядке.

Ответы [ 6 ]

13 голосов
/ 10 декабря 2010

Рассмотрим примерный алгоритм ближайшего соседа (ANN) или хеширование с учетом локальных условий (LSH).Они напрямую не решают проблему кластеризации, но они смогут сказать вам, какие точки «близки» друг к другу.Изменяя параметры, вы можете определить, что close должен быть настолько близким, насколько вы хотите.И это быстро.

Точнее, LSH может предоставить хеш-функцию h, такую, что для двух точек x и y и метрики расстояния d,

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

, где R1 < R2 и P1 > P2.Так что да, это вероятностный.Вы можете постобработать извлеченные данные для получения истинных кластеров.

Вот информация о LSH , включая руководство E2LSH .ANN похож по духу;У Дэвида Маунта есть информация здесь , или попробуйте FLANN (имеет привязки Matlab и Python).

6 голосов
/ 12 января 2011

Вы можете попробовать мой исследовательский проект под названием K-tree . Он хорошо масштабируется с большими входными данными относительно k-средних и образует иерархию кластеров. Компромисс состоит в том, что он производит кластеры с более высоким искажением. Он имеет среднее время выполнения O (n log n) и наихудший случай O (n ** 2), который происходит только при наличии странной топологии. Более подробная информация об анализе сложности содержится в моей магистерской диссертации . Я использовал его с очень объемными текстовыми данными, и у меня не было проблем.

Иногда в дереве могут происходить плохие расщепления, когда все данные уходят в одну сторону (кластер). Транк в SVN имеет дело с этим не так, как в текущем выпуске. Это случайное разделение данных, если есть плохое разделение. Предыдущий метод может заставить дерево стать слишком глубоким, если есть плохие расщепления.

5 голосов
/ 26 ноября 2011

Поместите данные в индекс, такой как R * -дерево (Википедия) , тогда вы можете запустить множество алгоритмов кластеризации на основе плотности (таких как DBSCAN (Википедия) или ОПТИКА (Википедия) ) в O(n log n).

Плотность кластеризации (Википедия) , кажется, именно то, что вы хотите («не слишком далеко друг от друга»)

3 голосов
/ 14 июля 2012

У людей складывается впечатление, что k-means медленный, но медлительность действительно является проблемой только для алгоритма EM (Lloyd's).Стохастические градиентные методы для k-средних на несколько порядков быстрее, чем EM (см. Www.eecs.tufts.edu/~dsculley/papers/fastkmeans.pdf).

Реализация здесь: http://code.google.com/p/sofia-ml/wiki/SofiaKMeans

3 голосов
/ 14 декабря 2010

Ниже приведен небольшой тестовый стенд, чтобы увидеть, как быстро scipy.spatial.cKDTree на ваших данных, и получить приблизительное представление о том, как расставляются расстояния между соседними точками.

Хороший способ запустить K-кластер для различных K состоит в том, чтобы построить MST из ближайших пар и удалить самый длинный K-1;см. Уэйн, Жадные алгоритмы .

Визуализация кластеров была бы интересной - проект на 2d с PCA?

(Просто любопытно, это ваши K 10, 100, 1000??)

Добавлено 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 
2 голосов
/ 10 декабря 2010

У меня есть модуль Perl, который делает именно то, что вы хотите Algorithm :: ClusterPoints .

Сначала он использует алгоритм, который вы описали в своем посте, для разделения точек в многомерных секторах, а затем использует грубую силу для поиска кластеров между точками в соседних секторах.

Сложность варьируется от O (N) до O (N ** 2) в очень ухудшенных случаях.

обновление

@ Денис: нет, это намного хуже:

Для измерений 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=5 и c=1 мы получаем l=2.236, s=0.447, r=3.236 и n=pow(9, 5)=59049

На самом деле мы должны проверять меньше соседних секторов, поскольку здесь мы рассматриваем те, которые касаются гиперкуба размером (2r+1)/s, и нам нужно проверять только те, которые касаются описанной гиперсферы.

Учитывая биективный характер отношения "находятся в одном кластере", мы также можем вдвое сократить количество секторов, которые необходимо проверить.

В частности, Algorithm :: ClusterPoints для случая, когда d=5 проверяет 3903 сектора.

...