1.原文:http://www.cnblogs.com/jerrylead/archive/2011/04/06/2006910.html
K-means也是聚類算法中最簡單的一種了,但是里面包含的思想卻是不一般。最早我使用並實現這個算法是在學習韓爺爺那本數據挖掘的書中,那本書比較注重應用。看了Andrew Ng的這個講義后才有些明白K-means后面包含的EM思想。
聚類屬於無監督學習,以往的回歸、朴素貝葉斯、SVM等都是有類別標簽y的,也就是說樣例中已經給出了樣例的分類。而聚類的樣本中卻沒有給定y,只有特征x,比如假設宇宙中的星星可以表示成三維空間中的點集。聚類的目的是找到每個樣本x潛在的類別y,並將同類別y的樣本x放在一起。比如上面的星星,聚類后結果是一個個星團,星團里面的點相互距離比較近,星團間的星星距離就比較遠了。
K-means算法是將樣本聚類成k個簇(cluster),具體算法描述如下:
1、 隨機選取k個聚類質心點(cluster centroids)為 2、 重復下面過程直到收斂 { 對於每一個樣例i,計算其應該屬於的類 對於每一個類j,重新計算該類的質心 } |
K是我們事先給定的聚類數,代表樣例i與k個類中距離最近的那個類,
的值是1到k中的一個。質心
代表我們對屬於同一個類的樣本中心點的猜測,拿星團模型來解釋就是要將所有的星星聚成k個星團,首先隨機選取k個宇宙中的點(或者k個星星)作為k個星團的質心,然后第一步對於每一個星星計算其到k個質心中每一個的距離,然后選取距離最近的那個星團作為
,這樣經過第一步每一個星星都有了所屬的星團;第二步對於每一個星團,重新計算它的質心
(對里面所有的星星坐標求平均)。重復迭代第一步和第二步直到質心不變或者變化很小。
下圖展示了對n個樣本點進行K-means聚類的效果,這里k取2。
K-means面對的第一個問題是如何保證收斂,前面的算法中強調結束條件就是收斂,可以證明的是K-means完全可以保證收斂性。下面我們定性的描述一下收斂性,我們定義畸變函數(distortion function)如下:
J函數表示每個樣本點到其質心的距離平方和。K-means是要將J調整到最小。假設當前J沒有達到最小值,那么首先可以固定每個類的質心,調整每個樣例的所屬的類別
來讓J函數減少,同樣,固定
,調整每個類的質心
也可以使J減小。這兩個過程就是內循環中使J單調遞減的過程。當J遞減到最小時,
和c也同時收斂。(在理論上,可以有多組不同的
和c值能夠使得J取得最小值,但這種現象實際上很少見)。
由於畸變函數J是非凸函數,意味着我們不能保證取得的最小值是全局最小值,也就是說k-means對質心初始位置的選取比較感冒,但一般情況下k-means達到的局部最優已經滿足需求。但如果你怕陷入局部最優,那么可以選取不同的初始值跑多遍k-means,然后取其中最小的J對應的和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來說就是我們一開始不知道每個樣例對應隱含變量也就是最佳類別
。最開始可以隨便指定一個
給它,然后為了讓P(x,y)最大(這里是要讓J最小),我們求出在給定c情況下,J最小時的
(前面提到的其他未知參數),然而此時發現,可以有更好的
(質心與樣例
距離最小的類別)指定給樣例
,那么
得到重新調整,上述過程就開始重復了,直到沒有更好的
指定。這樣從K-means里我們可以看出它其實就是EM的體現,E步是確定隱含類別變量
,M步更新其他參數
來使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 維向量空間的映射——你總是需要顯式地或者隱式地完成這樣一個過程,然后基於某種規則進行分類,在該規則下,同組分類具有最大的相似性。
假設我們提取到原始數據的集合為(x1, x2, …, xn),並且每個xi為d維的向量,K-means聚類的目的就是,在給定分類組數k(k ≤ n)值的條件下,將原始數據分成k類
S = {S1, S2, …, Sk},在數值模型上,即對以下表達式求最小值:
這里μi 表示分類Si 的平均值。
那么在計算機編程中,其又是如何實現的呢?其算法步驟一般如下:
1、從D中隨機取k個元素,作為k個簇的各自的中心。
2、分別計算剩下的元素到k個簇中心的相異度,將這些元素分別划歸到相異度最低的簇。
3、根據聚類結果,重新計算k個簇各自的中心,計算方法是取簇中所有元素各自維度的算術平均數。
4、將D中全部元素按照新的中心重新聚類。
5、重復第4步,直到聚類結果不再變化。
6、將結果輸出。
用數學表達式來說,
設我們一共有 N 個數據點需要分為 K 個 cluster ,k-means 要做的就是最小化
這個函數,其中 在數據點 n 被歸類到 cluster k 的時候為 1 ,否則為 0 。直接尋找
和
來最小化
並不容易,不過我們可以采取迭代的辦法:先固定
,選擇最優的
,很容易看出,只要將數據點歸類到離他最近的那個中心就能保證
最小。下一步則固定
,再求最優的
。將
對
求導並令導數等於零,很容易得到
最小的時候
應該滿足:
亦即 的值應當是所有 cluster k 中的數據點的平均值。由於每一次迭代都是取到
的最小值,因此
只會不斷地減小(或者不變),而不會增加,這保證了 k-means 最終會到達一個極小值。雖然 k-means 並不能保證總是能得到全局最優解,但是對於這樣的問題,像 k-means 這種復雜度的算法,這樣的結果已經是很不錯的了。
首先 3 個中心點被隨機初始化,所有的數據點都還沒有進行聚類,默認全部都標記為紅色,如下圖所示:
然后進入第一次迭代:按照初始的中心點位置為每個數據點着上顏色,重新計算 3 個中心點,結果如下圖所示:
可以看到,由於初始的中心點是隨機選的,這樣得出來的結果並不是很好,接下來是下一次迭代的結果:
可以看到大致形狀已經出來了。再經過兩次迭代之后,基本上就收斂了,最終結果如下:
不過正如前面所說的那樣 k-means 也並不是萬能的,雖然許多時候都能收斂到一個比較好的結果,但是也有運氣不好的時候會收斂到一個讓人不滿意的局部最優解,例如選用下面這幾個初始中心點:
最終會收斂到這樣的結果:
整體來講,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’(聚類重復次數) 整數