Можно ли указать собственную функцию расстояния с помощью кластеризации K-средних в scikit-learn?

Можно ли указать собственную функцию расстояния с помощью кластеризации K-средних в scikit-learn?


person bmasc    schedule 03.04.2011    source источник
comment
Обратите внимание, что k-means разработан для евклидова расстояния. Он может перестать сходиться с другими расстояниями, когда среднее больше не является лучшей оценкой для центра кластера.   -  person Has QUIT--Anony-Mousse    schedule 27.03.2012
comment
почему k-means работает только с евклидовым расстоянием?   -  person curious    schedule 07.01.2014
comment
@ Anony-Mousse Неверно утверждать, что k-means рассчитано только на евклидово расстояние. Его можно изменить для работы с любой допустимой метрикой расстояния, определенной в пространстве наблюдения. Например, взгляните на статью о k-medoids.   -  person ely    schedule 15.10.2014
comment
PAM (он же k-medoids) - совсем другой алгоритм. Это связано с k-means, но намного дороже.   -  person Has QUIT--Anony-Mousse    schedule 16.10.2014
comment
@curious: среднее минимизирует квадраты разностей (= квадрат евклидова расстояния). Если вам нужна другая функция расстояния, вам нужно заменить mean на соответствующую оценку центра. K-medoids - такой алгоритм, но его поиск намного дороже.   -  person Has QUIT--Anony-Mousse    schedule 16.10.2014
comment
В некоторой степени актуально: в настоящее время существует открытый запрос на вытягивание, реализующий ядро ​​K -Средства. Когда все будет готово, вы сможете указать собственное ядро ​​для вычислений.   -  person jakevdp    schedule 27.10.2015
comment
@ely. Неверно утверждать, что k-means рассчитано только на евклидово расстояние. Нет, это не некорректно, ИМХО. K-средства и K-medoids могут быть связаны, но это разные алгоритмы с разными лежащими в основе математическими моделями и, следовательно, разными условиями сходимости. K-средство предполагает евклидово расстояние. K-medoids предполагает манхэттенское расстояние. Пожалуйста, поправьте меня, если я ошибаюсь.   -  person Chiraz BenAbdelkader    schedule 29.03.2018
comment
@ChirazBenAbdelkader Это один и тот же алгоритм с одной и той же базовой моделью. Они отличаются только конкретным расчетом используемого образца (будь то центроид группы или фактический групповой медоид). K-средство относится к семейству алгоритмов, которые все используют одну и ту же базовую модель, только с разными понятиями расстояния или разными представлениями об образце.   -  person ely    schedule 29.03.2018
comment
@ely. Частично с тобой согласен. Может быть, я раскалываю волосы. Но это действительно зависит от того, что вы считаете той же моделью. Да, Kmeans и Kmedoids основаны на одной и той же общей модели. Но они достаточно разные и, конечно, НЕ взаимозаменяемы на практике.   -  person Chiraz BenAbdelkader    schedule 30.03.2018
comment
@ChirazBenAbdelkader Многие общие алгоритмы имеют определенные вариации. Например, алгоритм SVM будет строго отличаться в педантичном смысле, если вы будете использовать ядро ​​RBF, а не полиномиальное ядро ​​и т. Д., И эти две вещи, конечно, не будут легко взаимозаменяемы на практике. Но было бы глупо сказать, что SVM с ядром RBF полностью отличается от SVM с полиномиальным ядром. Очевидно, что это тот же алгоритм, но подмножество алгоритма можно заменить гиперпараметром. То же самое и с алгоритмами k-средних.   -  person ely    schedule 30.03.2018
comment
Например, рассмотрите возможность использования разных ядер несхожести для заданного набора точек данных, например, из scipy's _ 1_. Или рассмотрите возможность минимизации нормы L1 или расхождения KL, если данные точки данных имеют ограничение разреженности или являются распределениями вероятностей соответственно. Нет причин, по которым вы не можете запустить алгоритм k-средних для этих типов данных, а просто использовать соответствующее различное расстояние между точками при минимизации функции потерь по отношению к центрам-кандидатам.   -  person ely    schedule 30.03.2018


Ответы (8)


Вот небольшой kmeans, который использует любое из 20 с лишним расстояний в scipy.spatial.distance, или пользовательская функция.
Комментарии будут приветствоваться (пока только один пользователь, этого недостаточно); в частности, каковы ваши N, dim, k, метрика?

#!/usr/bin/env python
# kmeans.py using any of the 20-odd metrics in scipy.spatial.distance
# kmeanssample 2 pass, first sample sqrt(N)

from __future__ import division
import random
import numpy as np
from scipy.spatial.distance import cdist  # $scipy/spatial/distance.py
    # http://docs.scipy.org/doc/scipy/reference/spatial.html
from scipy.sparse import issparse  # $scipy/sparse/csr.py

__date__ = "2011-11-17 Nov denis"
    # X sparse, any cdist metric: real app ?
    # centres get dense rapidly, metrics in high dim hit distance whiteout
    # vs unsupervised / semi-supervised svm

#...............................................................................
def kmeans( X, centres, delta=.001, maxiter=10, metric="euclidean", p=2, verbose=1 ):
    """ centres, Xtocentre, distances = kmeans( X, initial centres ... )
    in:
        X N x dim  may be sparse
        centres k x dim: initial centres, e.g. random.sample( X, k )
        delta: relative error, iterate until the average distance to centres
            is within delta of the previous average distance
        maxiter
        metric: any of the 20-odd in scipy.spatial.distance
            "chebyshev" = max, "cityblock" = L1, "minkowski" with p=
            or a function( Xvec, centrevec ), e.g. Lqmetric below
        p: for minkowski metric -- local mod cdist for 0 < p < 1 too
        verbose: 0 silent, 2 prints running distances
    out:
        centres, k x dim
        Xtocentre: each X -> its nearest centre, ints N -> k
        distances, N
    see also: kmeanssample below, class Kmeans below.
    """
    if not issparse(X):
        X = np.asanyarray(X)  # ?
    centres = centres.todense() if issparse(centres) \
        else centres.copy()
    N, dim = X.shape
    k, cdim = centres.shape
    if dim != cdim:
        raise ValueError( "kmeans: X %s and centres %s must have the same number of columns" % (
            X.shape, centres.shape ))
    if verbose:
        print "kmeans: X %s  centres %s  delta=%.2g  maxiter=%d  metric=%s" % (
            X.shape, centres.shape, delta, maxiter, metric)
    allx = np.arange(N)
    prevdist = 0
    for jiter in range( 1, maxiter+1 ):
        D = cdist_sparse( X, centres, metric=metric, p=p )  # |X| x |centres|
        xtoc = D.argmin(axis=1)  # X -> nearest centre
        distances = D[allx,xtoc]
        avdist = distances.mean()  # median ?
        if verbose >= 2:
            print "kmeans: av |X - nearest centre| = %.4g" % avdist
        if (1 - delta) * prevdist <= avdist <= prevdist \
        or jiter == maxiter:
            break
        prevdist = avdist
        for jc in range(k):  # (1 pass in C)
            c = np.where( xtoc == jc )[0]
            if len(c) > 0:
                centres[jc] = X[c].mean( axis=0 )
    if verbose:
        print "kmeans: %d iterations  cluster sizes:" % jiter, np.bincount(xtoc)
    if verbose >= 2:
        r50 = np.zeros(k)
        r90 = np.zeros(k)
        for j in range(k):
            dist = distances[ xtoc == j ]
            if len(dist) > 0:
                r50[j], r90[j] = np.percentile( dist, (50, 90) )
        print "kmeans: cluster 50 % radius", r50.astype(int)
        print "kmeans: cluster 90 % radius", r90.astype(int)
            # scale L1 / dim, L2 / sqrt(dim) ?
    return centres, xtoc, distances

#...............................................................................
def kmeanssample( X, k, nsample=0, **kwargs ):
    """ 2-pass kmeans, fast for large N:
        1) kmeans a random sample of nsample ~ sqrt(N) from X
        2) full kmeans, starting from those centres
    """
        # merge w kmeans ? mttiw
        # v large N: sample N^1/2, N^1/2 of that
        # seed like sklearn ?
    N, dim = X.shape
    if nsample == 0:
        nsample = max( 2*np.sqrt(N), 10*k )
    Xsample = randomsample( X, int(nsample) )
    pass1centres = randomsample( X, int(k) )
    samplecentres = kmeans( Xsample, pass1centres, **kwargs )[0]
    return kmeans( X, samplecentres, **kwargs )

def cdist_sparse( X, Y, **kwargs ):
    """ -> |X| x |Y| cdist array, any cdist metric
        X or Y may be sparse -- best csr
    """
        # todense row at a time, v slow if both v sparse
    sxy = 2*issparse(X) + issparse(Y)
    if sxy == 0:
        return cdist( X, Y, **kwargs )
    d = np.empty( (X.shape[0], Y.shape[0]), np.float64 )
    if sxy == 2:
        for j, x in enumerate(X):
            d[j] = cdist( x.todense(), Y, **kwargs ) [0]
    elif sxy == 1:
        for k, y in enumerate(Y):
            d[:,k] = cdist( X, y.todense(), **kwargs ) [0]
    else:
        for j, x in enumerate(X):
            for k, y in enumerate(Y):
                d[j,k] = cdist( x.todense(), y.todense(), **kwargs ) [0]
    return d

def randomsample( X, n ):
    """ random.sample of the rows of X
        X may be sparse -- best csr
    """
    sampleix = random.sample( xrange( X.shape[0] ), int(n) )
    return X[sampleix]

def nearestcentres( X, centres, metric="euclidean", p=2 ):
    """ each X -> nearest centre, any metric
            euclidean2 (~ withinss) is more sensitive to outliers,
            cityblock (manhattan, L1) less sensitive
    """
    D = cdist( X, centres, metric=metric, p=p )  # |X| x |centres|
    return D.argmin(axis=1)

def Lqmetric( x, y=None, q=.5 ):
    # yes a metric, may increase weight of near matches; see ...
    return (np.abs(x - y) ** q) .mean() if y is not None \
        else (np.abs(x) ** q) .mean()

#...............................................................................
class Kmeans:
    """ km = Kmeans( X, k= or centres=, ... )
        in: either initial centres= for kmeans
            or k= [nsample=] for kmeanssample
        out: km.centres, km.Xtocentre, km.distances
        iterator:
            for jcentre, J in km:
                clustercentre = centres[jcentre]
                J indexes e.g. X[J], classes[J]
    """
    def __init__( self, X, k=0, centres=None, nsample=0, **kwargs ):
        self.X = X
        if centres is None:
            self.centres, self.Xtocentre, self.distances = kmeanssample(
                X, k=k, nsample=nsample, **kwargs )
        else:
            self.centres, self.Xtocentre, self.distances = kmeans(
                X, centres, **kwargs )

    def __iter__(self):
        for jc in range(len(self.centres)):
            yield jc, (self.Xtocentre == jc)

#...............................................................................
if __name__ == "__main__":
    import random
    import sys
    from time import time

    N = 10000
    dim = 10
    ncluster = 10
    kmsample = 100  # 0: random centres, > 0: kmeanssample
    kmdelta = .001
    kmiter = 10
    metric = "cityblock"  # "chebyshev" = max, "cityblock" L1,  Lqmetric
    seed = 1

    exec( "\n".join( sys.argv[1:] ))  # run this.py N= ...
    np.set_printoptions( 1, threshold=200, edgeitems=5, suppress=True )
    np.random.seed(seed)
    random.seed(seed)

    print "N %d  dim %d  ncluster %d  kmsample %d  metric %s" % (
        N, dim, ncluster, kmsample, metric)
    X = np.random.exponential( size=(N,dim) )
        # cf scikits-learn datasets/
    t0 = time()
    if kmsample > 0:
        centres, xtoc, dist = kmeanssample( X, ncluster, nsample=kmsample,
            delta=kmdelta, maxiter=kmiter, metric=metric, verbose=2 )
    else:
        randomcentres = randomsample( X, ncluster )
        centres, xtoc, dist = kmeans( X, randomcentres,
            delta=kmdelta, maxiter=kmiter, metric=metric, verbose=2 )
    print "%.0f msec" % ((time() - t0) * 1000)

    # also ~/py/np/kmeans/test-kmeans.py

Некоторые примечания добавлены 26 марта 2012 г .:

1) для косинусного расстояния сначала нормализуйте все векторы данных на | X | = 1; тогда

cosinedistance( X, Y ) = 1 - X . Y = Euclidean distance |X - Y|^2 / 2

быстро. Для битовых векторов сохраняйте нормы отдельно от векторов, а не расширяйте их до чисел с плавающей запятой (хотя некоторые программы могут расширяться за вас). Для разреженных векторов, скажем, 1% от N, X. Y должно занять время O (2% N), пробел O (N); но я не знаю, какие программы это делают.

2) Scikit-learn кластеризация дает отличный обзор k-средних, мини- batch-k-means ... с кодом, который работает с матрицами scipy.sparse.

3) Всегда проверяйте размеры кластера после k-средних. Если вы ожидаете кластеров примерно одинакового размера, но они выходят [44 37 9 5 5] % ... (звук царапины в голове).

person denis    schedule 05.04.2011
comment
+1 Прежде всего, спасибо, что поделились своей реализацией. Я просто хотел подтвердить, что алгоритм отлично работает для моего набора данных из 900 векторов в 700-мерном пространстве. Мне просто интересно, можно ли также оценить качество сгенерированных кластеров. Можно ли повторно использовать какое-либо из значений в вашем коде для вычисления качества кластера, чтобы помочь в выборе количества оптимальных кластеров? - person Legend; 11.07.2011
comment
Легенда, пожалуйста. (Обновлен код для печати кластера с радиусом 50% / 90%). Качество кластеров - это обширная тема: сколько у вас кластеров, есть ли у вас обучающие образцы с известными кластерами, например от экспертов? О количестве кластеров см. SO how-do-i-define-k-when-using-k -means-clustering -when-using-k-means-clustering - person denis; 11.07.2011
comment
Еще раз, спасибо. На самом деле, у меня нет обучающих образцов, но я пытаюсь проверить кластеры вручную после классификации (также пытаясь сыграть роль эксперта в предметной области). Я выполняю классификацию на уровне документа после применения SVD к некоторым исходным документам и уменьшения их размера. Результаты кажутся хорошими, но я не знал, как их проверить. На начальном этапе, исследуя различные метрики валидности кластера, я наткнулся на индекс Данна, метод локтя и т. Д., Не был уверен, какой из них использовать, поэтому подумал, что начну с метода локтя. - person Legend; 11.07.2011
comment
И, конечно же, я также пытался вычислить ширину силуэта, чтобы определить качество кластера, но на первый взгляд это показалось довольно дорогостоящим, хотя я не уверен, пропустил ли я что-то очевидное. - person Legend; 11.07.2011
comment
Не уверен, что вы столкнулись с этим, но я пытался использовать свой другой пример (stackoverflow.com/questions/6645895/), используя этот код для разделения 10 точек на три кластера, и это вызывает ошибку: ValueError: operands could not be broadcast together with shapes (0) (2) Просто подумал, что я Сообщите вам об этом, поскольку это как-то связано с вашим последним дополнением. - person Legend; 12.07.2011
comment
Что говорит kmeans (verbose = 1)? (kmeanssample имеет смысл только для больших N; nsample = max (2 * np.sqrt (N), 10 * k) может быть или не быть разумным.) - person denis; 12.07.2011
comment
О, понятно ... verbose = 1 в моем случае работает правильно, без ошибок. - person Legend; 12.07.2011
comment
Я заметил кое-что еще. Например, при построении кривой искажения нам необходимо построить график искажения для всех значений k, если наклон кривой не настолько велик, чтобы наблюдать, какой k выбрать. Если это так (или, возможно, данные все-таки не так плотно упакованы), нам, возможно, придется установить k ~ N. Строка nsample = max( 2*np.sqrt(N), 10*k ) в вашем коде не позволит мне выбрать большие k. Я временно решил эту проблему, установив размер выборки вручную, но есть ли у вас другие предложения? - person Legend; 13.07.2011
comment
Какие у вас N, dim и k, пожалуйста? Вы можете вызвать kmeanssample (... nsample = something ‹N), но нет смысла разбивать N точек на k ~ N кластеров ?? - person denis; 13.07.2011
comment
Да, это правильно. В данном случае N = 700, dim = 350. Я изменяю k, чтобы получить график качества искажения или кластера. Похоже, что в данных большой размерности становится сложно применить кластеризацию, поэтому я искал, как выглядит искажение. Кстати, k~N должен был сказать k близко к N. Извините за обозначения. - person Legend; 13.07.2011
comment
Спасибо за код Денис. Он отлично работает для плотных матриц, но не работает на разреженных матрицах (lil_matrix). Я получаю: kmeans: X (893, 25854) центров (9, 25854) delta = 0.001 maxiter = 100 metric = cosine Traceback (последний вызов последний): File ‹stdin›, строка 1, в ‹module› File tweetsClustering .py, строка 424, в computeClusters metric = cosine, p = 2, verbose = 1) Файл kmeans.py, строка 74, в центре kmeans [jc] = X [c] .mean (axis = 0) Файл / usr / lib / python2.7 / dist-packages / scipy / sparse / lil.py, строка 201, в getitem i, j = index ValueError: слишком много значений для распаковки - person ScienceFriction; 25.03.2012
comment
@ScienceFriction, извините за это, посмотрим на это. Подходит ли вам Scikit-learn кластеризация? (также добавлены некоторые примечания в конце выше). - person denis; 26.03.2012
comment
Я знаю, что это освобождает от заземления что-то действительно старое, но я только начал использовать kmeans и наткнулся на это. Для будущих читателей, которые захотят использовать этот код: сначала ознакомьтесь с комментариями @ Anony-Mousse по заданному выше вопросу! Эта реализация, насколько я понимаю, делает неправильное предположение, что вы можете каким-то образом использовать среднее значение точек в кластере для определения центроида этого кластера. Это не имеет смысла ни для чего другого, кроме евклидова расстояния (кроме очень конкретных случаев на единичной сфере и т. Д.). Опять же, комментарии Анони-Мусс по этому вопросу прямо в носу. - person Nevoris; 19.12.2017
comment
@Nevoris, да, я согласен, кроме косинусного расстояния: см. здесь, а также почему-k-означает-алгоритм-кластеризации-использовать-только-евклидово-расстояние-метрика - person denis; 19.12.2017
comment
тот факт, что алгоритм больше не реализован на C ++ или fortran, означает ли это, что эта версия намного медленнее? - person Seymour; 05.02.2018
comment
@Seymour, что медленнее - время запускать, время жизни процессора, а не ясность и адаптируемость? См., Например, python-with-numpy-scipy-vs- pure-c-for-big-data-analysis и сотни подобных вопросов. - person denis; 05.02.2018
comment
Прохладный! Так что это хороший момент для python, не так ли? Потому что если я напишу свои собственные kmeans на R, это будет невероятно медленнее, чем реализованное в fortran! - person Seymour; 05.02.2018
comment
@Seymour, нет: для каких проблем, размера, разреженности, ясности? Ожидайте ОГРОМНЫХ различий, больше связанных с тестовыми примерами / целями, чем с языком. См. 100 вопросов и ответов в разделе stats.stackexchange.com/questions/tagged/k-means+r - person denis; 05.02.2018

К сожалению, нет: текущая реализация k-средних в scikit-learn использует только евклидовы расстояния.

Распространение k-средних на другие расстояния нетривиально, и ответ Дениса выше не является правильным способом реализации k-средних для других показателей.

person ogrisel    schedule 03.04.2011
comment
Почему реализация, данная Денисом, неверна? - person Clumsy cat; 25.11.2020
comment
Например, для расстояния Манхэттена (Минковски с p = 1) вам понадобится специальный алгоритм (K-Medoids en.wikipedia.org/wiki/K-medoids), который внутренне сильно отличается от K-средних. - person ogrisel; 08.12.2020

Просто используйте вместо этого nltk там, где вы можете это сделать, например

from nltk.cluster.kmeans import KMeansClusterer
NUM_CLUSTERS = <choose a value>
data = <sparse matrix that you would normally give to scikit>.toarray()

kclusterer = KMeansClusterer(NUM_CLUSTERS, distance=nltk.cluster.util.cosine_distance, repeats=25)
assigned_clusters = kclusterer.cluster(data, assign_clusters=True)
person mdubez    schedule 12.09.2016
comment
Насколько эффективна эта реализация? Кажется, что потребуется целая вечность, чтобы сгруппировать всего 5 тыс. Точек (в измерении 100). - person Nikana Reklawyks; 17.01.2017
comment
В измерении 100 кластеризация 1k точек занимает 1 секунду на запуск (repeats), 1,5k точек занимает 2 минуты, а 2k занимает ... слишком много времени. - person Nikana Reklawyks; 17.01.2017
comment
Действительно; согласно комментарию @ Anony-Mousse ниже, кажется, что косинусное расстояние может иметь проблемы сходимости. Для меня это действительно случай «мусора в мусоре»: вы можете использовать любую функцию расстояния, какую захотите, но если эта функция нарушает предположения алгоритма, не ожидайте, что она даст значимые результаты! - person Chiraz BenAbdelkader; 29.03.2018

Да, вы можете использовать метрическую функцию разницы; однако, по определению, алгоритм кластеризации k-средних основывается на эвклдиановом расстоянии от среднего значения для каждого кластера.

Вы можете использовать другую метрику, поэтому, даже если вы все еще вычисляете среднее значение, вы можете использовать что-то вроде расстояния Махалнобиса.

person Adam    schedule 26.03.2012
comment
+1: Позвольте мне подчеркнуть, что взятие среднего подходит только для определенных функций расстояния, таких как евклидово расстояние. Для других функций расстояния вам также необходимо заменить функцию оценки центра кластера! - person Has QUIT--Anony-Mousse; 27.03.2012
comment
@ Анони-Мусс. Что я должен изменить, например, используя косинусное расстояние? - person curious; 07.01.2014
comment
Я не знаю. Я не видел доказательства сходимости с косинусом. Я считаю, что он сойдется, если ваши данные неотрицательны и нормализованы к единичной сфере, потому что тогда это, по сути, k-средства в другом векторном пространстве. - person Has QUIT--Anony-Mousse; 07.01.2014
comment
Я согласен с @ Anony-Mousse. Для меня это просто случай «мусора в мусоре»: вы можете запускать K-средние с любой функцией расстояния, которую хотите, но если эта функция нарушает базовые допущения алгоритма, не ожидайте, что она даст значимые полученные результаты! - person Chiraz BenAbdelkader; 29.03.2018
comment
@ Anony-Mousse, но как реализовать K-средства, используя расстояние Махалнобиса? - person Cecilia; 28.07.2019
comment
@Cecila, поскольку использование среднего арифметического якобы также минимизирует расстояния Махаланобиса, этот случай прост (хотя у меня нет доказательства). Хотя в таких случаях я бы выбрал GMM, а не k-means. - person Has QUIT--Anony-Mousse; 29.07.2019

Существует pyclustering, который представляет собой python / C ++ (так что это быстро!) И позволяет вам указать пользовательскую метрическую функцию.

from pyclustering.cluster.kmeans import kmeans
from pyclustering.utils.metric import type_metric, distance_metric

user_function = lambda point1, point2: point1[0] + point2[0] + 2
metric = distance_metric(type_metric.USER_DEFINED, func=user_function)

# create K-Means algorithm with specific distance metric
start_centers = [[4.7, 5.9], [5.7, 6.5]];
kmeans_instance = kmeans(sample, start_centers, metric=metric)

# run cluster analysis and obtain results
kmeans_instance.process()
clusters = kmeans_instance.get_clusters()

На самом деле я не тестировал этот код, но собрал его из тикета и пример кода.

person CpILL    schedule 07.08.2018
comment
необходимо установить Matplotlib, которому нужен Python в качестве фреймворка в Mac OS X :( - person CpILL; 07.08.2018

k-means of Spectral Python позволяет использовать L1 (Manhattan ) расстояние.

person Igor Fobia    schedule 31.03.2013

Sklearn Kmeans использует евклидово расстояние. У него нет метрического параметра. При этом, если вы группируете временные ряды, вы можете использовать пакет tslearn python, когда вы можете указать метрику (dtw, softdtw, euclidean).

person Community    schedule 27.05.2019

person    schedule
comment
пожалуйста, подумайте о том, чтобы добавить также несколько словесных комментариев - person Jan Stránský; 02.09.2020