K-means聚類算法


1.原文:http://www.cnblogs.com/jerrylead/archive/2011/04/06/2006910.html

 K-means也是聚類算法中最簡單的一種了,但是里面包含的思想卻是不一般。最早我使用並實現這個算法是在學習韓爺爺那本數據挖掘的書中,那本書比較注重應用。看了Andrew Ng的這個講義后才有些明白K-means后面包含的EM思想。

     聚類屬於無監督學習,以往的回歸、朴素貝葉斯、SVM等都是有類別標簽y的,也就是說樣例中已經給出了樣例的分類。而聚類的樣本中卻沒有給定y,只有特征x,比如假設宇宙中的星星可以表示成三維空間中的點集clip_image002[10]。聚類的目的是找到每個樣本x潛在的類別y,並將同類別y的樣本x放在一起。比如上面的星星,聚類后結果是一個個星團,星團里面的點相互距離比較近,星團間的星星距離就比較遠了。

     在聚類問題中,給我們的訓練樣本是clip_image004,每個clip_image006,沒有了y。

     K-means算法是將樣本聚類成k個簇(cluster),具體算法描述如下:

1、 隨機選取k個聚類質心點(cluster centroids)為clip_image008[6]

2、 重復下面過程直到收斂 {

               對於每一個樣例i,計算其應該屬於的類

               clip_image009

               對於每一個類j,重新計算該類的質心

               clip_image010[6]

}

     K是我們事先給定的聚類數,clip_image012[6]代表樣例i與k個類中距離最近的那個類,clip_image012[7]的值是1到k中的一個。質心clip_image014[6]代表我們對屬於同一個類的樣本中心點的猜測,拿星團模型來解釋就是要將所有的星星聚成k個星團,首先隨機選取k個宇宙中的點(或者k個星星)作為k個星團的質心,然后第一步對於每一個星星計算其到k個質心中每一個的距離,然后選取距離最近的那個星團作為clip_image012[8],這樣經過第一步每一個星星都有了所屬的星團;第二步對於每一個星團,重新計算它的質心clip_image014[7](對里面所有的星星坐標求平均)。重復迭代第一步和第二步直到質心不變或者變化很小。

     下圖展示了對n個樣本點進行K-means聚類的效果,這里k取2。

     clip_image015

     K-means面對的第一個問題是如何保證收斂,前面的算法中強調結束條件就是收斂,可以證明的是K-means完全可以保證收斂性。下面我們定性的描述一下收斂性,我們定義畸變函數(distortion function)如下:

     clip_image016[6]

     J函數表示每個樣本點到其質心的距離平方和。K-means是要將J調整到最小。假設當前J沒有達到最小值,那么首先可以固定每個類的質心clip_image014[8],調整每個樣例的所屬的類別clip_image012[9]來讓J函數減少,同樣,固定clip_image012[10],調整每個類的質心clip_image014[9]也可以使J減小。這兩個過程就是內循環中使J單調遞減的過程。當J遞減到最小時,clip_image018[6]和c也同時收斂。(在理論上,可以有多組不同的clip_image018[7]和c值能夠使得J取得最小值,但這種現象實際上很少見)。

     由於畸變函數J是非凸函數,意味着我們不能保證取得的最小值是全局最小值,也就是說k-means對質心初始位置的選取比較感冒,但一般情況下k-means達到的局部最優已經滿足需求。但如果你怕陷入局部最優,那么可以選取不同的初始值跑多遍k-means,然后取其中最小的J對應的clip_image018[8]和c輸出。

     下面累述一下K-means與EM的關系,首先回到初始問題,我們目的是將樣本分成k個類,其實說白了就是求每個樣例x的隱含類別y,然后利用隱含類別將x歸類。由於我們事先不知道類別y,那么我們首先可以對每個樣例假定一個y吧,但是怎么知道假定的對不對呢?怎么評價假定的好不好呢?我們使用樣本的極大似然估計來度量,這里是就是x和y的聯合分布P(x,y)了。如果找到的y能夠使P(x,y)最大,那么我們找到的y就是樣例x的最佳類別了,x順手就聚類了。但是我們第一次指定的y不一定會讓P(x,y)最大,而且P(x,y)還依賴於其他未知參數,當然在給定y的情況下,我們可以調整其他參數讓P(x,y)最大。但是調整完參數后,我們發現有更好的y可以指定,那么我們重新指定y,然后再計算P(x,y)最大時的參數,反復迭代直至沒有更好的y可以指定。

     這個過程有幾個難點,第一怎么假定y?是每個樣例硬指派一個y還是不同的y有不同的概率,概率如何度量。第二如何估計P(x,y),P(x,y)還可能依賴很多其他參數,如何調整里面的參數讓P(x,y)最大。這些問題在以后的篇章里回答。

     這里只是指出EM的思想,E步就是估計隱含類別y的期望值,M步調整其他參數使得在給定類別y的情況下,極大似然估計P(x,y)能夠達到極大值。然后在其他參數確定的情況下,重新估計y,周而復始,直至收斂。

     上面的闡述有點費解,對應於K-means來說就是我們一開始不知道每個樣例clip_image020[10]對應隱含變量也就是最佳類別clip_image022[6]。最開始可以隨便指定一個clip_image022[7]給它,然后為了讓P(x,y)最大(這里是要讓J最小),我們求出在給定c情況下,J最小時的clip_image014[10](前面提到的其他未知參數),然而此時發現,可以有更好的clip_image022[8](質心與樣例clip_image020[11]距離最小的類別)指定給樣例clip_image020[12],那么clip_image022[9]得到重新調整,上述過程就開始重復了,直到沒有更好的clip_image022[10]指定。這樣從K-means里我們可以看出它其實就是EM的體現,E步是確定隱含類別變量clip_image024[6],M步更新其他參數clip_image018[9]來使J最小化。這里的隱含類別變量指定方法比較特殊,屬於硬指定,從k個類別中硬選出一個給樣例,而不是對每個類別賦予不同的概率。總體思想還是一個迭代優化過程,有目標函數,也有參數變量,只是多了個隱含變量,確定其他參數估計隱含變量,再確定隱含變量估計其他參數,直至目標函數最優。

 

原文:http://www.cnblogs.com/moondark/archive/2012/03/08/2385770.html

聚類算法——K-means(上)

 

   首先要來了解的一個概念就是聚類,簡單地說就是把相似的東西分到一組,同 Classification (分類)不同,對於一個 classifier ,通常需要你告訴它“這個東西被分為某某類”這樣一些例子,理想情況下,一個 classifier 會從它得到的訓練集中進行“學習”,從而具備對未知數據進行分類的能力,這種提供訓練數據的過程通常叫做 supervised learning (監督學習),而在聚類的時候,我們並不關心某一類是什么,我們需要實現的目標只是把相似的東西聚到一起,因此,一個聚類算法通常只需要知道如何計算相似 度就可以開始工作了,因此 clustering 通常並不需要使用訓練數據進行學習,這在 Machine Learning 中被稱作 unsupervised learning (無監督學習)。

  我們經常接觸到的聚類分析,一般都是數值聚類,一種常見的做法是同時提取 N 種特征,將它們放在一起組成一個 N 維向量,從而得到一個從原始數據集合到 N 維向量空間的映射——你總是需要顯式地或者隱式地完成這樣一個過程,然后基於某種規則進行分類,在該規則下,同組分類具有最大的相似性。

  假設我們提取到原始數據的集合為(x1x2, …, xn),並且每個xi為d維的向量,K-means聚類的目的就是,在給定分類組數k(k ≤ n)值的條件下,將原始數據分成k類 
S = {S1S2, …, Sk},在數值模型上,即對以下表達式求最小值:
\underset{\mathbf{S}} {\operatorname{arg\,min}} \sum_{i=1}^{k} \sum_{\mathbf x_j \in S_i} \left\| \mathbf x_j - \boldsymbol\mu_i \right\|^2
這里μi 表示分類S的平均值。

  那么在計算機編程中,其又是如何實現的呢?其算法步驟一般如下:

1、從D中隨機取k個元素,作為k個簇的各自的中心。

2、分別計算剩下的元素到k個簇中心的相異度,將這些元素分別划歸到相異度最低的簇。

3、根據聚類結果,重新計算k個簇各自的中心,計算方法是取簇中所有元素各自維度的算術平均數。

4、將D中全部元素按照新的中心重新聚類。

5、重復第4步,直到聚類結果不再變化。

6、將結果輸出。

  用數學表達式來說,

設我們一共有 N 個數據點需要分為 K 個 cluster ,k-means 要做的就是最小化

\displaystyle J = \sum_{n=1}^N\sum_{k=1}^K r_{nk} \|x_n-\mu_k\|^2

這個函數,其中 r_{nk} 在數據點 n 被歸類到 cluster k 的時候為 1 ,否則為 0 。直接尋找 r_{nk} 和 \mu_k 來最小化 J 並不容易,不過我們可以采取迭代的辦法:先固定 \mu_k ,選擇最優的 r_{nk} ,很容易看出,只要將數據點歸類到離他最近的那個中心就能保證 J 最小。下一步則固定 r_{nk},再求最優的 \mu_k。將 J 對 \mu_k 求導並令導數等於零,很容易得到 J 最小的時候 \mu_k 應該滿足:

\displaystyle \mu_k=\frac{\sum_n r_{nk}x_n}{\sum_n r_{nk}}

亦即 \mu_k 的值應當是所有 cluster k 中的數據點的平均值。由於每一次迭代都是取到 J 的最小值,因此 J 只會不斷地減小(或者不變),而不會增加,這保證了 k-means 最終會到達一個極小值。雖然 k-means 並不能保證總是能得到全局最優解,但是對於這樣的問題,像 k-means 這種復雜度的算法,這樣的結果已經是很不錯的了。

首先 3 個中心點被隨機初始化,所有的數據點都還沒有進行聚類,默認全部都標記為紅色,如下圖所示:

iter_00

然后進入第一次迭代:按照初始的中心點位置為每個數據點着上顏色,重新計算 3 個中心點,結果如下圖所示:

iter_01

可以看到,由於初始的中心點是隨機選的,這樣得出來的結果並不是很好,接下來是下一次迭代的結果:

iter_02

可以看到大致形狀已經出來了。再經過兩次迭代之后,基本上就收斂了,最終結果如下:

iter_04

不過正如前面所說的那樣 k-means 也並不是萬能的,雖然許多時候都能收斂到一個比較好的結果,但是也有運氣不好的時候會收斂到一個讓人不滿意的局部最優解,例如選用下面這幾個初始中心點:

iter_00_bad

最終會收斂到這樣的結果:

iter_03_bad

  整體來講,K-means算法的聚類思想比較簡單明了,並且聚類效果也還算可以,算是一種簡單高效應用廣泛的 clustering 方法,接下來,我將討論其代碼實現過程。

 

聚類算法——K-means(下)

 

  K-means的源碼實現

  一般情況下,我們通過C++/Matlab/Python等語言進行實現K-means算法,結合近期我剛剛學的C++,先從C++實現談起,C++里面我們一般采用的是OpenCV庫中寫好的K-means函數,即cvKmeans2,首先來看函數原型:
  從OpenCV manual看到的是:
int cvKMeans2(const CvArr* samples, int nclusters,
        CvArr* labels, CvTermCriteria termcrit,
        int attempts=1, CvRNG* rng=0,int flags=0, 
        CvArr* centers=0,double* compactness=0);
由於除去已經確定的參數,我們自己需要輸入的為:
void cvKMeans2( 
    const CvArr* samples, //輸入樣本的浮點矩陣,每個樣本一行。 
    int cluster_count,  //所給定的聚類數目 
     * labels,    //輸出整數向量:每個樣本對應的類別標識 
     CvTermCriteria termcrit //指定聚類的最大迭代次數和/或精度(兩次迭代引起的聚類中心的移動距離)
 ); 
其使用例程為:

 1 #ifdef _CH_
2 #pragma package <opencv>
3 #endif
4
5 #define CV_NO_BACKWARD_COMPATIBILITY
6
7 #ifndef _EiC
8 #include "cv.h"
9 #include "highgui.h"
10 #include <stdio.h>
11 #endif
12
13 int main( int argc, char** argv )
14 {
15 #define MAX_CLUSTERS 5 //設置類別的顏色,個數(《=5)
16 CvScalar color_tab[MAX_CLUSTERS];
17 IplImage* img = cvCreateImage( cvSize( 500, 500 ), 8, 3 );
18 CvRNG rng = cvRNG(-1);
19 CvPoint ipt;
20
21 color_tab[0] = CV_RGB(255,0,0);
22 color_tab[1] = CV_RGB(0,255,0);
23 color_tab[2] = CV_RGB(100,100,255);
24 color_tab[3] = CV_RGB(255,0,255);
25 color_tab[4] = CV_RGB(255,255,0);
26
27 cvNamedWindow( "clusters", 1 );
28
29 for(;;)
30 {
31 char key;
32 int k, cluster_count = cvRandInt(&rng)%MAX_CLUSTERS + 1;
33 int i, sample_count = cvRandInt(&rng)%1000 + 1;
34 CvMat* points = cvCreateMat( sample_count, 1, CV_32FC2 );
35 CvMat* clusters = cvCreateMat( sample_count, 1, CV_32SC1 );
36 cluster_count = MIN(cluster_count, sample_count);
37
38 /** generate random sample from multigaussian distribution */
39 for( k = 0; k < cluster_count; k++ )
40 {
41 CvPoint center;
42 CvMat point_chunk;
43 center.x = cvRandInt(&rng)%img->width;
44 center.y = cvRandInt(&rng)%img->height;
45 cvGetRows( points, &point_chunk, k*sample_count/cluster_count,
46 k == cluster_count - 1 ? sample_count :
47 (k+1)*sample_count/cluster_count, 1 );
48
49 cvRandArr( &rng, &point_chunk, CV_RAND_NORMAL,
50 cvScalar(center.x,center.y,0,0),
51 cvScalar(img->width*0.1,img->height*0.1,0,0));
52 }
53
54 /** shuffle samples */
55 for( i = 0; i < sample_count/2; i++ )
56 {
57 CvPoint2D32f* pt1 = (CvPoint2D32f*)points->data.fl + cvRandInt(&rng)%sample_count;
58 CvPoint2D32f* pt2 = (CvPoint2D32f*)points->data.fl + cvRandInt(&rng)%sample_count;
59 CvPoint2D32f temp;
60 CV_SWAP( *pt1, *pt2, temp );
61 }
62
63 printf( "iterations=%d\n", cvKMeans2( points, cluster_count, clusters,
64 cvTermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 10, 1.0 ),
65 5, 0, 0, 0, 0 ));
66
67 cvZero( img );
68
69 for( i = 0; i < sample_count; i++ )
70 {
71 int cluster_idx = clusters->data.i[i];
72 ipt.x = (int)points->data.fl[i*2];
73 ipt.y = (int)points->data.fl[i*2+1];
74 cvCircle( img, ipt, 2, color_tab[cluster_idx], CV_FILLED, CV_AA, 0 );
75 }
76
77 cvReleaseMat( &points );
78 cvReleaseMat( &clusters );
79
80 cvShowImage( "clusters", img );
81
82 key = (char) cvWaitKey(0);
83 if( key == 27 || key == 'q' || key == 'Q' ) // 'ESC'
84 break;
85 }
86
87 cvDestroyWindow( "clusters" );
88 return 0;
89 }
90
91 #ifdef _EiC
92 main(1,"kmeans.c");
93 #endif

  至於cvKmeans2函數的具體實現細節,可參見OpenCV源碼

  下面是Python的實現代碼(網上所找):

 1  #!/usr/bin/python
2
3 from __future__ import with_statement
4 import cPickle as pickle
5 from matplotlib import pyplot
6 from numpy import zeros, array, tile
7 from scipy.linalg import norm
8 import numpy.matlib as ml
9 import random
10
11 def kmeans(X, k, observer=None, threshold=1e-15, maxiter=300):
12 N = len(X)
13 labels = zeros(N, dtype=int)
14 centers = array(random.sample(X, k))
15 iter = 0
16
17 def calc_J():
18 sum = 0
19 for i in xrange(N):
20 sum += norm(X[i]-centers[labels[i]])
21 return sum
22
23 def distmat(X, Y):
24 n = len(X)
25 m = len(Y)
26 xx = ml.sum(X*X, axis=1)
27 yy = ml.sum(Y*Y, axis=1)
28 xy = ml.dot(X, Y.T)
29
30 return tile(xx, (m, 1)).T+tile(yy, (n, 1)) - 2*xy
31
32 Jprev = calc_J()
33 while True:
34 # notify the observer
35 if observer is not None:
36 observer(iter, labels, centers)
37
38 # calculate distance from x to each center
39 # distance_matrix is only available in scipy newer than 0.7
40 # dist = distance_matrix(X, centers)
41 dist = distmat(X, centers)
42 # assign x to nearst center
43 labels = dist.argmin(axis=1)
44 # re-calculate each center
45 for j in range(k):
46 idx_j = (labels == j).nonzero()
47 centers[j] = X[idx_j].mean(axis=0)
48
49 J = calc_J()
50 iter += 1
51
52 if Jprev-J < threshold:
53 break
54 Jprev = J
55 if iter >= maxiter:
56 break
57
58 # final notification
59 if observer is not None:
60 observer(iter, labels, centers)
61
62 if __name__ == '__main__':
63 # load previously generated points
64 with open('cluster.pkl') as inf:
65 samples = pickle.load(inf)
66 N = 0
67 for smp in samples:
68 N += len(smp[0])
69 X = zeros((N, 2))
70 idxfrm = 0
71 for i in range(len(samples)):
72 idxto = idxfrm + len(samples[i][0])
73 X[idxfrm:idxto, 0] = samples[i][0]
74 X[idxfrm:idxto, 1] = samples[i][1]
75 idxfrm = idxto
76
77 def observer(iter, labels, centers):
78 print "iter %d." % iter
79 colors = array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
80 pyplot.plot(hold=False) # clear previous plot
81 pyplot.hold(True)
82
83 # draw points
84 data_colors=[colors[lbl] for lbl in labels]
85 pyplot.scatter(X[:, 0], X[:, 1], c=data_colors, alpha=0.5)
86 # draw centers
87 pyplot.scatter(centers[:, 0], centers[:, 1], s=200, c=colors)
88
89 pyplot.savefig('kmeans/iter_%02d.png' % iter, format='png')
90
91 kmeans(X, 3, observer=observer)

  matlab的kmeans實現代碼可直接參照其kmeans(X,k)函數的實現源碼。

Matlab函數kmeans:K-均值聚類  

轉自:http://www.aiseminar.cn/html/93/t-693.html

 

K-means聚類算法采用的是將N*P的矩陣X划分為K個類,使得類內對象之間的距離最大,而類之間的距離最小。

使用方法:
Idx=Kmeans(X,K)
[Idx,C]=Kmeans(X,K) 
[Idx,C,sumD]=Kmeans(X,K) 
[Idx,C,sumD,D]=Kmeans(X,K) 
[…]=Kmeans(…,’Param1’,Val1,’Param2’,Val2,…)

各輸入輸出參數介紹:

X N*P的數據矩陣
K 表示將X划分為幾類,為整數
Idx N*1的向量,存儲的是每個點的聚類標號
C K*P的矩陣,存儲的是K個聚類質心位置
sumD 1*K的和向量,存儲的是類間所有點與該類質心點距離之和
D N*K的矩陣,存儲的是每個點與所有質心的距離

[…]=Kmeans(…,'Param1',Val1,'Param2',Val2,…)
這其中的參數Param1、Param2等,主要可以設置為如下:

1. ‘Distance’(距離測度)
‘sqEuclidean’ 歐式距離(默認時,采用此距離方式)
‘cityblock’ 絕度誤差和,又稱:L1
‘cosine’ 針對向量
‘correlation’  針對有時序關系的值
‘Hamming’ 只針對二進制數據

2. ‘Start’(初始質心位置選擇方法)
‘sample’ 從X中隨機選取K個質心點
‘uniform’ 根據X的分布范圍均勻的隨機生成K個質心
‘cluster’ 初始聚類階段隨機選擇10%的X的子樣本(此方法初始使用’sample’方法)
matrix 提供一K*P的矩陣,作為初始質心位置集合

3. ‘Replicates’(聚類重復次數)  整數


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM