scikit-learn K-Means Clusteringを使用して独自の距離関数を指定することは可能ですか?


172

scikit-learn K-Means Clusteringを使用して独自の距離関数を指定することは可能ですか?


37
k-meansはユークリッド距離用に設計されていることに注意してください。平均がクラスターの「中心」の最良の推定値ではなくなった場合、他の距離との収束が停止することがあります。
QUITあり-Anony-Mousse 2012年

2
なぜk-meansはユークリッド距離でのみ機能するのですか?
好奇心旺盛な

9
@ Anony-Mousse k-meansはユークリッド距離に対してのみ設計されていると言うのは誤りです。観測空間で定義された有効な距離メトリックで機能するように変更できます。たとえば、k-medoidsに関する記事をご覧ください
2014年

5
@curious:平均は二乗差(=二乗ユークリッド距離)を最小化します。別の距離関数が必要な場合は、平均を適切な中心推定で置き換える必要があります。K-medoidsはそのようなアルゴリズムですが、medoidを見つけるのははるかにコストがかかります。
QUITあり--Anony-Mousse 2014年

4
ここでやや関連性があります:現在、カーネルK平均を実装するオープンプルリクエストがあります。完了すると、計算用に独自のカーネルを指定できるようになります。
jakevdp 2015年

回答:


77

ここでは20余りの距離のいずれかを使用しています小さな関数kmeansだ scipy.spatial.distance、またはユーザ関数を。
コメントを歓迎します(これまでのところ、ユーザーは1人しかいないため、十分ではありません)。特に、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

2012年3月26日に追加されたメモの一部:

1)コサイン距離の場合、最初にすべてのデータベクトルを| X |に正規化します。= 1; その後

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

速いです。ビットベクタの場合、フロートに展開するのではなく、ノルムをベクタとは別に保持します(一部のプログラムは展開する場合があります)。スパースベクトルの場合、N、Xの1%と言います。Yには時間がかかりますO(2%N)、スペースO(N); しかし、どのプログラムがそれを行うのかわかりません。

2) Scikit-learnクラスタリング は、scipy.sparseマトリックスで機能するコードを使用して、k平均、ミニバッチk平均の優れた概要を提供します。

3)k-meansの後には常にクラスターサイズを確認してください。ほぼ同じサイズのクラスターを期待している[44 37 9 5 5] %のに、それらが出てくる場合 (頭を引っかくような音)。


1
+1まず、実装を共有していただきありがとうございます。アルゴリズムが700次元空間の900ベクトルのデータセットでうまく機能することを確認したかっただけです。生成されたクラスターの品質を評価することも可能かどうか疑問に思っていました。コードの値のいずれかを再利用してクラスターの品質を計算し、最適なクラスターの数を選択することはできますか?
レジェンド

6
伝説、どういたしまして。(クラスターを50%/ 90%半径で印刷するようにコードを更新しました)。「クラスターの品質」は大げさなトピックです。クラスターはいくつありますか。たとえば、エキスパートなど、既知のクラスターを使用したトレーニングサンプルはありますか?クラスターの数については、SO how-do-i-determine-k-when-using-k-means-clustering -when-using-k-means-clustering
denis

1
もう一度ありがとうございます。実際、私にはトレーニングサンプルはありませんが、分類後に手動でクラスターを検証しようとしています(ドメインエキスパートの役割を果たすことも試みています)。一部の元のドキュメントにSVDを適用し、それらの次元を削減した後、ドキュメントレベルの分類を実行しています。結果は良いように見えますが、それらを検証する方法がわかりませんでした。最初の段階では、さまざまなクラスターの有効性メトリックを調査しているときに、ダンのインデックスやエルボーメソッドなどに出会いましたが、どれを使用すればよいかわからなかったため、エルボーメソッドから始めることにしました。
レジェンド

7
これが本当に古いものを発掘していることは知っていますが、私はkmeansの使用を始めたばかりで、これに遭遇しました。将来の読者がこのコードを使いたくなったら、まず上記の質問に対する@ Anony-Mousseコメントをチェックしてください!私が見る限り、この実装は、「クラスター内のポイントの平均」を使用してそのクラスターの重心を決定できるという誤った仮定を行っています。これは、ユークリッド距離以外には意味がありません(単位球などの非常に特殊な場合を除く)。ここでも、質問に対するAnony-Mousseのコメントは正解です。
Nevoris

3
@Nevoris、はい、同意します。コサイン距離を除きます。理由についてはこちらも参照してください。理由は、また、なぜ
denis

43

残念ながらありません。現在のk平均のscikit-learnの実装では、ユークリッド距離のみが使用されます。

k-meansを他の距離に拡張することは簡単ではなく、上のdenisの答えは他のメトリックにk-meansを実装する正しい方法ではありません。


26

これを実行できる場所では、代わりに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)

4
この実装はどの程度効率的ですか?わずか5kポイント(次元100)のクラスター化には、永遠にかかるようです。
Nikana Reklawyks 2017年

3
ディメンション100では、1kポイントのクラスタリングには実行ごとに1秒かかり(repeats)、1.5kポイントには2分かかり、2kポイントには時間がかかりすぎます。
Nikana Reklawyks 2017年

2
確かに; 以下の@ Anony-Mousseコメントに従って、コサイン距離には収束の問題があるようです。私にとって、これは実際にガベージインガベージアウトの場合です。どのような距離関数を使用してもかまいませんが、その関数がアルゴリズムの仮定に違反している場合は、意味のある結果が得られると期待しないでください。
Chiraz BenAbdelkader

15

はい、差分メトリック関数を使用できます。ただし、定義により、k平均クラスタリングアルゴリズムは、各クラスターの平均からのユークリッド距離に依存します。

別のメトリックを使用することができるため、平均を計算している場合でも、マハルノビス距離のようなものを使用できます。


25
+1:この平均を取ることは、ユークリッド距離などの特定の距離関数にのみ適していることを強調しておきます。他の距離関数については、クラスター中心推定関数も置き換える必要があります!
QUITあり-Anony-Mousse 2012年

2
@ Anony-Mousse。たとえばコサイン距離を使用すると、何を変更する必要がありますか?
好奇心旺盛な

6
知りません。私はコサインとの収束の証拠を見たことがありません。データが負ではなく、単位球に正規化されている場合は、収束します。それは、本質的に、異なるベクトル空間でのk平均であるためです。
QUITあり--Anony-Mousse 2014年

1
@ Anony-Mousseに同意します。私にとって、これはガベージインガベージアウトの単なる例です。距離平均関数を使用してK平均を実行できますが、その関数がアルゴリズムの根本的な仮定に違反している場合は、意味のあるものになると期待しないでください。結果!
Chiraz BenAbdelkader

@ Anony-Mousseしかし、マハルノビス距離を使用してK-meansを実装する方法は?
セシリア

7

python / C ++のpyclusteringがあり(とても高速です)、カスタムメトリック関数を指定できます

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()

実際、私はこのコードをテストしていませんが、チケットサンプルコードからまとめまし


「Mac OS XのフレームワークとしてのPython」を必要とするMatplotlibがインストールされている必要があります:(
CpILL


3

Sklearn Kmeansユークリッド距離を使用します。メトリックパラメータはありません。これは、あなたがクラスタリングしている場合、言った時系列を、あなたが使用することができますtslearnあなたが指定することができたときに、メトリック(Pythonパッケージをdtwsoftdtweuclidean)を。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.