เป็นไปได้หรือไม่ที่จะระบุฟังก์ชั่นระยะทางของคุณเองโดยใช้ Scikit-Learn K-Means Clustering


172

เป็นไปได้หรือไม่ที่จะระบุฟังก์ชั่นระยะทางของคุณเองโดยใช้ Scikit-Learn K-Means Clustering


37
โปรดทราบว่าk หมายถึงถูกออกแบบมาสำหรับระยะทางยุคลิด มันอาจหยุดการบรรจบกันกับระยะทางอื่น ๆ เมื่อค่าเฉลี่ยไม่ใช่การประมาณค่าที่ดีที่สุดสำหรับกลุ่ม "ศูนย์กลาง" อีกต่อไป
มี QUIT - Anony-Mousse

2
ทำไม k-mean ใช้งานได้เฉพาะกับ Euclidean distsance เท่านั้น?
อยากรู้อยากเห็น

9
@ Anony-Mousse ไม่ถูกต้องที่จะพูดว่า k-หมายความว่าถูกออกแบบมาสำหรับระยะทางแบบยุคลิดเท่านั้น มันสามารถปรับเปลี่ยนให้ทำงานกับตัวชี้วัดระยะทางที่ถูกต้องใด ๆ ที่กำหนดไว้ในพื้นที่สังเกต ยกตัวอย่างเช่นดูที่บทความเกี่ยวกับ K-medoids
ely

5
@ จริง: ค่าเฉลี่ยลดความแตกต่างกำลังสอง (= ระยะห่างแบบยุคลิดกำลังสอง) หากคุณต้องการฟังก์ชันระยะทางที่แตกต่างกันคุณจะต้องเปลี่ยนค่าเฉลี่ยด้วยการประมาณศูนย์ที่เหมาะสม K-medoids เป็นอัลกอริธึม แต่การหา medoid นั้นแพงกว่ามาก
มี QUIT - Anony-Mousse

4
ค่อนข้างเกี่ยวข้องที่นี่: ขณะนี้มีคำขอดึงแบบเปิดที่ใช้ Kernel K-หมายถึง เมื่อเสร็จแล้วคุณจะสามารถระบุเคอร์เนลของคุณเองสำหรับการคำนวณ
jakevdp

คำตอบ:


77

นี่คือ kmeans ขนาดเล็กที่ใช้ระยะทาง 20 คี่ใด ๆ ใน scipy.spatial.distanceหรือฟังก์ชั่นผู้ใช้
ความเห็นจะได้รับการต้อนรับ (มีผู้ใช้เพียงคนเดียวจนถึงตอนนี้ไม่เพียงพอ); โดยเฉพาะอย่างยิ่งอะไรคือ N, dim, k, metric ของคุณ

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

บางบันทึกเพิ่ม 26mar 2012:

1) สำหรับระยะทางโคไซน์ก่อนอื่นให้ปรับเวกเตอร์ข้อมูลทั้งหมดเป็น | X | = 1; แล้วก็

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

รวดเร็ว สำหรับบิตเวคเตอร์ให้แยกบรรทัดฐานออกจากเวกเตอร์แทนที่จะขยายออกไปลอย (แม้ว่าบางโปรแกรมอาจขยายสำหรับคุณ) สำหรับเวกเตอร์ที่กระจัดกระจายพูด 1% ของ N, X Y ควรใช้เวลา O (2% N), space O (N); แต่ฉันไม่รู้ว่าโปรแกรมใดทำ

2) การจัดกลุ่ม Scikit เรียนรู้ ให้ภาพรวมที่ยอดเยี่ยมของ k- หมายถึง, mini-batch-k-หมายถึง ... ด้วยรหัสที่ทำงานในการฝึกอบรม scipy.sparse

3) ตรวจสอบขนาดคลัสเตอร์หลังจาก k-mean เสมอ หากคุณคาดว่าจะมีกลุ่มที่มีขนาดเท่ากัน แต่พวกมันออกมา [44 37 9 5 5] %... (เสียงที่เป็นรอยขีดข่วน)


1
+1 ก่อนอื่นขอขอบคุณที่แบ่งปันการใช้งานของคุณ ฉันแค่อยากจะยืนยันว่าอัลกอริทึมนี้ใช้งานได้ดีสำหรับชุดข้อมูลของเวกเตอร์ 900 ตัวในพื้นที่ 700 มิติ ฉันแค่สงสัยว่ามันเป็นไปได้หรือไม่ที่จะประเมินคุณภาพของกลุ่มที่สร้างขึ้น ค่าใดในรหัสของคุณสามารถนำกลับมาใช้ใหม่เพื่อคำนวณคุณภาพคลัสเตอร์เพื่อช่วยในการเลือกจำนวนของกลุ่มที่ดีที่สุดได้หรือไม่?
ตำนาน

6
ตำนานคุณยินดีต้อนรับ (อัปเดตรหัสเพื่อพิมพ์รัศมี 50% / 90% ของรัศมี) "คุณภาพของกลุ่ม" เป็นหัวข้อที่มีขนาดใหญ่มาก: คุณมีคลัสเตอร์กี่ตัวคุณมีตัวอย่างการฝึกอบรมกับกลุ่มที่รู้จักเช่นจากผู้เชี่ยวชาญหรือไม่ กับจำนวนของกลุ่มเห็นดังนั้นวิธีการทำ-I-กำหนด-K-เมื่อโดยใช้-K หมายถึงการจัดกลุ่ม-คายโดยใช้-K หมายถึงการจัดกลุ่ม-
เดนิส

1
ขอบคุณอีกครั้ง. ที่จริงแล้วฉันไม่มีตัวอย่างการฝึกอบรม แต่ฉันพยายามที่จะตรวจสอบกลุ่มด้วยตนเองหลังจากการจัดหมวดหมู่ (พยายามที่จะเล่นบทบาทของผู้เชี่ยวชาญโดเมนเช่นกัน) ฉันกำลังจัดหมวดหมู่ระดับเอกสารหลังจากใช้ SVD กับเอกสารต้นฉบับและลดขนาดของเอกสารเหล่านั้น ผลลัพธ์ดูเหมือนจะดี แต่ฉันไม่แน่ใจเกี่ยวกับวิธีการตรวจสอบพวกเขา สำหรับระยะแรกในขณะที่สำรวจตัวชี้วัดความถูกต้องของคลัสเตอร์ต่าง ๆ ฉันได้พบกับวิธีของดันน์, Elbow และอื่น ๆ ไม่แน่ใจว่าจะใช้อันไหนดังนั้นคิดว่าฉันจะเริ่มต้นด้วยวิธี Elbow
ตำนาน

7
ฉันรู้ว่านี่เป็นสิ่งที่ไม่ต่อสายดินจริง ๆ แก่แล้ว แต่ฉันเพิ่งเริ่มใช้ kmeans และสะดุดกับสิ่งนี้ สำหรับผู้อ่านในอนาคตที่ล่อลวงให้ใช้รหัสนี้: ดูความคิดเห็น @ Anony-Mousse เกี่ยวกับคำถามข้างต้นก่อน! การนำไปใช้นี้เท่าที่ฉันเห็นสามารถทำได้ทำให้มีการสันนิษฐานผิด ๆ ว่าคุณยังสามารถใช้ "ค่าเฉลี่ยของคะแนนในกลุ่ม" เพื่อกำหนดค่าเซนทรอยด์ของคลัสเตอร์นั้น มันไม่มีเหตุผลอะไรเลยนอกจากระยะทางแบบยุคลิด (ยกเว้นในกรณีที่เฉพาะเจาะจงบนทรงกลมหน่วย ฯลฯ ... ) ความเห็นของ Anony-Mousse อีกครั้งในคำถามนั้นอยู่ที่จมูก
Nevoris

3
@Nevoris ใช่ผมเห็นด้วยยกเว้นสำหรับระยะทางโคไซน์: ดูที่นี่ทำไมยังทำไมไม่-k-หมายถึงการจัดกลุ่ม-ขั้นตอนวิธีการใช้งานเท่านั้น-euclidean ทางเมตริก
เดนิส

43

น่าเสียดายที่ไม่มี: scikit- เรียนรู้การใช้งานปัจจุบันของ k- หมายถึงใช้ระยะทางแบบยุคลิดเท่านั้น

มันไม่สำคัญเลยที่จะขยาย k-mean ไปยังระยะทางอื่นและคำตอบของ denis ข้างต้นไม่ใช่วิธีที่ถูกต้องในการนำ k-mean ไปใช้กับการวัดอื่น


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

3
ในมิติข้อมูล 100 การรวมกลุ่ม 1k คะแนนใช้เวลา 1 วินาทีต่อการวิ่ง ( repeats), 1.5k คะแนนใช้เวลา 2 นาทีและ 2k ใช้เวลานานเกินไป
Nikana Reklawyks

2
แท้จริง; ตามความคิดเห็น @ Anony-Mousse ด้านล่างดูเหมือนว่าระยะทางโคไซน์อาจมีปัญหาการลู่เข้า สำหรับฉันนี่เป็นกรณีของขยะเข้า - ออก: คุณสามารถใช้ฟังก์ชันระยะทางใดก็ได้ที่คุณต้องการ แต่ถ้าฟังก์ชันนั้นละเมิดสมมติฐานของอัลกอริทึมอย่าคาดหวังว่ามันจะให้ผลลัพธ์ที่มีความหมาย!
Chiraz BenAbdelkader

15

ใช่คุณสามารถใช้ฟังก์ชั่นการวัดความแตกต่างได้ อย่างไรก็ตามตามคำนิยามอัลกอริธึมการจัดกลุ่ม k-mean อาศัยระยะทาง eucldiean จากค่าเฉลี่ยของแต่ละคลัสเตอร์

คุณสามารถใช้ตัวชี้วัดที่แตกต่างกันดังนั้นแม้ว่าคุณจะยังคงคำนวณค่าเฉลี่ยคุณสามารถใช้บางอย่างเช่นระยะทาง mahalnobis


25
1: ผมขอเน้นนี้การเฉลี่ยเป็นเพียงที่เหมาะสมสำหรับฟังก์ชั่นระยะทางที่กำหนดเช่นระยะทางยุคลิด สำหรับฟังก์ชั่นระยะทางอื่นคุณจะต้องแทนที่ฟังก์ชั่นการประมาณค่ากลางคลัสเตอร์ด้วย!
แล้ว - Anony-Mousse

2
@ anony-มูส สิ่งที่ฉันควรจะเปลี่ยนเมื่อฉันใช้ระยะทางโคไซน์เช่น?
อยากรู้อยากเห็น

6
ฉันไม่รู้ ฉันไม่เห็นข้อพิสูจน์เรื่องการบรรจบกับโคไซน์ ฉันเชื่อว่ามันจะมาบรรจบกันหากข้อมูลของคุณไม่เป็นลบและทำให้เป็นมาตรฐานกับหน่วยทรงกลมเพราะจากนั้นมันก็คือค่าเฉลี่ย k ในพื้นที่เวคเตอร์ที่แตกต่างกัน
มี QUIT - Anony-Mousse

1
ฉันเห็นด้วยกับ @ Anony-Mousse สำหรับฉันนี่เป็นเพียงกรณีของขยะเข้าขยะ: คุณสามารถเรียกใช้ K-mean ด้วยฟังก์ชันระยะทางใดก็ได้ที่คุณต้องการ แต่ถ้าฟังก์ชันนั้นละเมิดสมมติฐานพื้นฐานของอัลกอริทึมอย่าคาดหวังว่ามันจะสร้างความหมาย ผล!
Chiraz BenAbdelkader

@ Anony-Mousse แต่วิธีการใช้ K- หมายถึงโดยใช้ระยะทาง mahalnobis?
เซซิเลีย

7

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

อันที่จริงผมไม่ได้ทดสอบรหัสนี้ แต่ที่ปูด้วยหินมันเข้าด้วยกันจากตั๋วและตัวอย่างรหัส


ต้องการ Matplotlib ติดตั้งซึ่งต้องการ "Python เป็นเฟรมเวิร์กบน Mac OS X" :(
CpILL


3

Sklearn Kmeansใช้ระยะทางยุคลิด ไม่มีพารามิเตอร์เมตริก นี้กล่าวว่าถ้าคุณกำลังจัดกลุ่มอนุกรมเวลา , คุณสามารถใช้tslearnแพคเกจหลามเมื่อคุณสามารถระบุตัวชี้วัด ( dtw, softdtw, euclidean)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.