Можно ли указать собственную функцию расстояния с помощью кластеризации 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)


arrow_upward
85
arrow_downward

Вот небольшой 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


arrow_upward
30
arrow_downward

Просто используйте вместо этого 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

arrow_upward
16
arrow_downward

Да, вы можете использовать метрическую функцию разницы; однако, по определению, алгоритм кластеризации 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

arrow_upward
10
arrow_downward

Существует 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

arrow_upward
5
arrow_downward

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

person Igor Fobia    schedule 31.03.2013

arrow_upward
3
arrow_downward

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

person Community    schedule 27.05.2019

arrow_upward

arrow_downward

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