Có thể chỉ định chức năng khoảng cách của riêng bạn bằng cách sử dụng Phân cụm K-Means không?


171

Có thể chỉ định chức năng khoảng cách của riêng bạn bằng cách sử dụng Phân cụm K-Means không?


37
Lưu ý rằng k-mean được thiết kế cho khoảng cách Euclide . Nó có thể ngừng hội tụ với các khoảng cách khác, khi giá trị trung bình không còn là ước tính tốt nhất cho "tâm" cụm.
Có QUIT - Anony-Mousse

2
Tại sao k-nghĩa là chỉ hoạt động với khoảng cách Euclide?
tò mò

9
@ Anony-Mousse Không đúng khi nói rằng phương tiện k chỉ được thiết kế cho khoảng cách Euclide. Nó có thể được sửa đổi để làm việc với bất kỳ số liệu khoảng cách hợp lệ nào được xác định trên không gian quan sát. Ví dụ, hãy xem bài viết về k-medoid .
ely

5
@cantly: giá trị trung bình giảm thiểu chênh lệch bình phương (= bình phương khoảng cách Euclide). Nếu bạn muốn một hàm khoảng cách khác, bạn cần thay thế giá trị trung bình bằng một ước lượng trung tâm thích hợp. K-medoid là một thuật toán như vậy, nhưng việc tìm kiếm medoid đắt hơn nhiều.
Có QUIT - Anony-Mousse

4
Một số điều có liên quan ở đây: hiện tại có một yêu cầu kéo mở triển khai Kernel K-Means. Khi kết thúc, bạn sẽ có thể chỉ định hạt nhân của riêng mình để tính toán.
jakevdp

Câu trả lời:


77

Đây là một km nhỏ sử dụng bất kỳ khoảng cách 20 lẻ nào trong scipy.spatial.distance hoặc hàm người dùng.
Nhận xét sẽ được hoan nghênh (điều này chỉ có một người dùng cho đến nay, không đủ); cụ thể, N, dim, k, metric của bạn là gì?

#!/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

Một số ghi chú được thêm vào 26mar 2012:

1) cho khoảng cách cosin, trước tiên, chuẩn hóa tất cả các vectơ dữ liệu thành | X | = 1; sau đó

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

là nhanh Đối với các vectơ bit, giữ các chỉ tiêu riêng biệt với các vectơ thay vì mở rộng ra thành phao (mặc dù một số chương trình có thể mở rộng cho bạn). Đối với các vectơ thưa thớt, giả sử 1% N, X. Y nên mất thời gian O (2% N), không gian O (N); nhưng tôi không biết chương trình nào làm điều đó.

2) Phân nhóm học Scikit cung cấp một cái nhìn tổng quan tuyệt vời về k- , mini-batch-k-mean ... với mã hoạt động trên ma trận scipy.spude.

3) Luôn kiểm tra kích thước cụm sau k-mean. Nếu bạn đang mong đợi các cụm có kích thước gần bằng nhau, nhưng chúng phát ra [44 37 9 5 5] %... (âm thanh gãi đầu).


1
+1 Trước hết, cảm ơn bạn đã chia sẻ việc thực hiện của bạn. Tôi chỉ muốn xác nhận rằng thuật toán hoạt động tuyệt vời cho tập dữ liệu 900 vectơ của tôi trong không gian 700 chiều. Tôi chỉ tự hỏi nếu nó cũng có thể đánh giá chất lượng của các cụm được tạo ra. Có thể sử dụng lại bất kỳ giá trị nào trong mã của bạn để tính toán chất lượng cụm để hỗ trợ chọn số cụm tối ưu không?
Truyền thuyết

6
Truyền thuyết, bạn được chào đón. (Cập nhật mã để in cụm 50% / 90% bán kính). "Chất lượng cụm" là một chủ đề lớn: bạn có bao nhiêu cụm, bạn có các mẫu đào tạo với các cụm đã biết, ví dụ từ các chuyên gia? Về số lượng cụm, hãy xem SO how-do-i-xác định-k-khi-sử dụng-k-nghĩa-cụm -khi-sử dụng-k-nghĩa-phân cụm
chối

1
Cảm ơn bạn một lần nữa. Trên thực tế, tôi không có các mẫu đào tạo nhưng tôi đang cố gắng xác minh các cụm theo cách thủ công sau khi phân loại (cũng cố gắng đóng vai trò của chuyên gia tên miền). Tôi đang thực hiện phân loại cấp độ tài liệu sau khi áp dụng SVD cho một số tài liệu gốc và giảm kích thước của chúng. Kết quả có vẻ tốt nhưng tôi không chắc chắn về cách xác nhận chúng. Đối với giai đoạn ban đầu, trong khi khám phá các số liệu hiệu lực của cụm khác nhau, tôi đã xem qua Chỉ số của Dunn, phương pháp Elbow, v.v. không thực sự chắc chắn nên sử dụng phương pháp nào để nghĩ rằng tôi sẽ bắt đầu với phương pháp Elbow.
Truyền thuyết

7
Tôi biết điều này là không có gì đó thực sự cũ, nhưng tôi chỉ bắt đầu với việc sử dụng kmeans và tình cờ thấy điều này. Đối với những độc giả tương lai muốn sử dụng mã này: trước tiên hãy xem bình luận @ Anony-Mousse về câu hỏi trên! Việc triển khai này, theo như tôi có thể thấy, đang đưa ra giả định sai rằng bạn bằng cách nào đó vẫn có thể sử dụng "giá trị trung bình của các điểm trong một cụm" để xác định trọng tâm của cụm đó. Điều này không có ý nghĩa gì đối với bất cứ điều gì khác ngoài khoảng cách Euclide (ngoại trừ trong các trường hợp rất cụ thể trên quả cầu đơn vị, v.v ...). Một lần nữa ý kiến ​​của Anony-Mousse về câu hỏi nằm ngay trên mũi.
Nevoris

3
@Nevoris, vâng tôi đồng ý, trừ trường hợp khoảng cách cosin: xem ở đây cho lý do tại sao, cũng tại sao-không-k-means-clustering-thuật toán sử dụng chỉ Euclide-xa-metric
denis

43

Thật không may là: scikit-learn hiện tại của k-mean chỉ sử dụng khoảng cách Euclide.

Việc mở rộng phương tiện k sang các khoảng cách khác không phải là chuyện nhỏ và câu trả lời của denis ở trên không phải là cách chính xác để thực hiện phương tiện k cho các số liệu khác.


26

Chỉ cần sử dụng nltk thay vì nơi bạn có thể làm điều này, vd

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
Làm thế nào hiệu quả là thực hiện này? Nó dường như mất vĩnh viễn để phân cụm ít nhất là 5k điểm (trong chiều 100).
Nikana Reklawyks

3
Trong thứ nguyên 100, phân cụm 1k điểm mất 1 giây mỗi lần chạy ( repeats), 1,5k điểm mất 2 phút và 2k mất ... quá lâu.
Nikana Reklawyks

2
Thật; theo nhận xét @ Anony-Mousse bên dưới, có vẻ như khoảng cách cosin có thể có vấn đề hội tụ. Đối với tôi, đây thực sự là một trường hợp rác thải: bạn có thể sử dụng bất kỳ hàm khoảng cách nào bạn muốn, nhưng nếu hàm đó vi phạm các giả định của thuật toán, đừng hy vọng nó sẽ tạo ra kết quả có ý nghĩa!
Chiraz BenAbdelkader

15

Có, bạn có thể sử dụng hàm số liệu khác nhau; tuy nhiên, theo định nghĩa, thuật toán phân cụm k-mean phụ thuộc vào khoảng cách eucldiean từ giá trị trung bình của mỗi cụm.

Bạn có thể sử dụng một số liệu khác nhau, vì vậy mặc dù bạn vẫn đang tính toán trung bình bạn có thể sử dụng một cái gì đó như khoảng cách mahalnobis.


25
+1: Hãy để tôi nhấn mạnh việc lấy giá trị trung bình này chỉ phù hợp với các hàm khoảng cách nhất định, chẳng hạn như khoảng cách Euclide . Đối với các hàm khoảng cách khác, bạn cũng cần thay thế hàm ước tính trung tâm cụm!
Có QUIT - Anony-Mousse

2
@ Anony-Mousse. Tôi phải thay đổi điều gì khi tôi sử dụng khoảng cách cosin chẳng hạn?
tò mò

6
Tôi không biết. Tôi chưa thấy bằng chứng nào cho sự hội tụ với Cosine. Tôi tin rằng nó sẽ hội tụ nếu dữ liệu của bạn không âm và được chuẩn hóa thành hình cầu đơn vị, vì về cơ bản, đó là phương tiện k trong một không gian vectơ khác.
Có QUIT - Anony-Mousse

1
Tôi đồng ý với @ Anony-Mousse. Đối với tôi, đây chỉ là một trường hợp rác thải: bạn có thể chạy phương tiện K với bất kỳ hàm khoảng cách nào bạn muốn, nhưng nếu hàm đó vi phạm các giả định cơ bản của thuật toán, đừng hy vọng nó tạo ra ý nghĩa các kết quả!
Chiraz BenAbdelkader

@ Anony-Mousse nhưng làm thế nào để thực hiện K-nghĩa bằng cách sử dụng khoảng cách mahalnobis?
Trương Bá Chi

7

pyclustering là python / C ++ (rất nhanh!) Và cho phép bạn chỉ định một hàm số liệu tùy chỉnh

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

Trên thực tế, tôi đã không kiểm tra mã này nhưng đã ghép nó lại với nhau từ ví dụ .


cần Matplotlib được cài đặt, cần "Python làm khung trên Mac OS X" :(
CpILL


3

Sklearn Kmeans sử dụng khoảng cách Euclide . Nó không có tham số số liệu. Này cho biết, nếu bạn đang phân nhóm chuỗi thời gian , bạn có thể sử dụng tslearngói python, khi bạn có thể chỉ định một thước đo ( dtw, softdtw, euclidean).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.