原文:http://blog.csdn.net/liuheng0111/article/details/52149040
1.基本Kmeans算法[1]
- 選擇K個點(diǎn)作為初始質(zhì)心??
- repeat??
- ????將每個點(diǎn)指派到最近的質(zhì)心,形成K個簇??
- ????重新計算每個簇的質(zhì)心??
- until?簇不發(fā)生變化或達(dá)到最大迭代次數(shù)??
時間復(fù)雜度:O(tKmn)瞳收,其中照藻,t為迭代次數(shù)耘沼,K為簇的數(shù)目拿愧,m為記錄數(shù)蓄诽,n為維數(shù)
空間復(fù)雜度:O((m+K)n)就漾,其中粱坤,K為簇的數(shù)目,m為記錄數(shù)冈在,n為維數(shù)
2.注意問題
(1)K如何確定
? ? ? ? kmenas算法首先選擇K個初始質(zhì)心倒慧,其中K是用戶指定的參數(shù),即所期望的簇的個數(shù)包券。這樣做的前提是我們已經(jīng)知道數(shù)據(jù)集中包含多少個簇蠢熄,但很多情況下邦尊,我們并不知道數(shù)據(jù)的分布情況滨巴,實(shí)際上聚類就是我們發(fā)現(xiàn)數(shù)據(jù)分布的一種手段灾常,這就陷入了雞和蛋的矛盾。如何有效的確定K值发魄,這里大致提供幾種方法盹牧,若各位有更好的方法,歡迎探討励幼。
1.與層次聚類結(jié)合[2]
? ? ? ? ?經(jīng)常會產(chǎn)生較好的聚類結(jié)果的一個有趣策略是汰寓,首先采用層次凝聚算法決定結(jié)果粗的數(shù)目,并找到一個初始聚類苹粟,然后用迭代重定位來改進(jìn)該聚類有滑。
2.穩(wěn)定性方法[3]
? ? ? ? 穩(wěn)定性方法對一個數(shù)據(jù)集進(jìn)行2次重采樣產(chǎn)生2個數(shù)據(jù)子集,再用相同的聚類算法對2個數(shù)據(jù)子集進(jìn)行聚類嵌削,產(chǎn)生2個具有k個聚類的聚類結(jié)果毛好,計算2個聚類結(jié)果的相似度的分布情況望艺。2個聚類結(jié)果具有高的相似度說明k個聚類反映了穩(wěn)定的聚類結(jié)構(gòu),其相似度可以用來估計聚類個數(shù)肌访。采用次方法試探多個k找默,找到合適的k值。
3.系統(tǒng)演化方法[3]
? ? ? ? ?系統(tǒng)演化方法將一個數(shù)據(jù)集視為偽熱力學(xué)系統(tǒng)吼驶,當(dāng)數(shù)據(jù)集被劃分為K個聚類時稱系統(tǒng)處于狀態(tài)K惩激。系統(tǒng)由初始狀態(tài)K=1出發(fā),經(jīng)過分裂過程和合并過程蟹演,系統(tǒng)將演化到它的穩(wěn)定平衡狀態(tài)Ki风钻,其所對應(yīng)的聚類結(jié)構(gòu)決定了最優(yōu)類數(shù)Ki。系統(tǒng)演化方法能提供關(guān)于所有聚類之間的相對邊界距離或可分程度酒请,它適用于明顯分離的聚類結(jié)構(gòu)和輕微重疊的聚類結(jié)構(gòu)魄咕。
4.使用canopy算法進(jìn)行初始劃分[4]
? ? ? ? ? 基于Canopy
Method的聚類算法將聚類過程分為兩個階段
? ? ? ?
?Stage1、聚類最耗費(fèi)計算的地方是計算對象相似性的時候蚌父,Canopy
Method在第一階段選擇簡單、計算代價較低的方法計算對象相似性毛萌,將相似的對象放在一個子集中苟弛,這個子集被叫做Canopy
,通過一系列計算得到若干Canopy阁将,Canopy之間可以是重疊的膏秫,但不會存在某個對象不屬于任何Canopy的情況,可以把這一階段看做數(shù)據(jù)預(yù)處理做盅;
?
? ? ? ? Stage2缤削、在各個Canopy 內(nèi)使用傳統(tǒng)的聚類方法(如K-means),不屬于同一Canopy 的對象之間不進(jìn)行相似性計算吹榴。
從這個方法起碼可以看出兩點(diǎn)好處:首先亭敢,Canopy
不要太大且Canopy
之間重疊的不要太多的話會大大減少后續(xù)需要計算相似性的對象的個數(shù);其次图筹,類似于K-means這樣的聚類方法是需要人為指出K的值的帅刀,通過Stage1得到的Canopy
個數(shù)完全可以作為這個K值,一定程度上減少了選擇K的盲目性远剩。
? ? ? ? ?其他方法如貝葉斯信息準(zhǔn)則方法(BIC)可參看文獻(xiàn)[5]扣溺。
(2)初始質(zhì)心的選取
(3)距離的度量
(4)質(zhì)心的計算
(5)算法停止條件
(6)空聚類的處理
3.適用范圍及缺陷
4.實(shí)現(xiàn)
- #include?<iostream>??
- #include?<sstream>??
- #include?<fstream>??
- #include?<vector>??
- #include?<math.h>??
- #include?<stdlib.h>??
- #define?k?3//簇的數(shù)目??
- using?namespace?std;??
- //存放元組的屬性信息??
- typedef?vector<double>?Tuple;//存儲每條數(shù)據(jù)記錄??
- ??
- int?dataNum;//數(shù)據(jù)集中數(shù)據(jù)記錄數(shù)目??
- int?dimNum;//每條記錄的維數(shù)??
- ??
- //計算兩個元組間的歐幾里距離??
- double?getDistXY(const?Tuple&?t1,?const?Tuple&?t2)???
- {??
- ????double?sum?=?0;??
- ????for(int?i=1;?i<=dimNum;?++i)??
- ????{??
- ????????sum?+=?(t1[i]-t2[i])?*?(t1[i]-t2[i]);??
- ????}??
- ????return?sqrt(sum);??
- }??
- ??
- //根據(jù)質(zhì)心,決定當(dāng)前元組屬于哪個簇??
- int?clusterOfTuple(Tuple?means[],const?Tuple&?tuple){??
- ????double?dist=getDistXY(means[0],tuple);??
- ????double?tmp;??
- ????int?label=0;//標(biāo)示屬于哪一個簇??
- ????for(int?i=1;i<k;i++){??
- ????????tmp=getDistXY(means[i],tuple);??
- ????????if(tmp<dist)?{dist=tmp;label=i;}??
- ????}??
- ????return?label;?????
- }??
- //獲得給定簇集的平方誤差??
- double?getVar(vector<Tuple>?clusters[],Tuple?means[]){??
- ????double?var?=?0;??
- ????for?(int?i?=?0;?i?<?k;?i++)??
- ????{??
- ????????vector<Tuple>?t?=?clusters[i];??
- ????????for?(int?j?=?0;?j<?t.size();?j++)??
- ????????{??
- ????????????var?+=?getDistXY(t[j],means[i]);??
- ????????}??
- ????}??
- ????//cout<<"sum:"<<sum<<endl;??
- ????return?var;??
- ??
- }??
- //獲得當(dāng)前簇的均值(質(zhì)心)??
- Tuple?getMeans(const?vector<Tuple>&?cluster){??
- ??????
- ????int?num?=?cluster.size();??
- ????Tuple?t(dimNum+1,?0);??
- ????for?(int?i?=?0;?i?<?num;?i++)??
- ????{??
- ????????for(int?j=1;?j<=dimNum;?++j)??
- ????????{??
- ????????????t[j]?+=?cluster[i][j];??
- ????????}??
- ????}??
- ????for(int?j=1;?j<=dimNum;?++j)??
- ????????t[j]?/=?num;??
- ????return?t;??
- ????//cout<<"sum:"<<sum<<endl;??
- }??
- ??
- void?print(const?vector<Tuple>?clusters[])??
- {??
- ????for(int?lable=0;?lable<k;?lable++)??
- ????{??
- ????????cout<<"第"<<lable+1<<"個簇:"<<endl;??
- ????????vector<Tuple>?t?=?clusters[lable];??
- ????????for(int?i=0;?i<t.size();?i++)??
- ????????{??
- ????????????cout<<i+1<<".(";??
- ????????????for(int?j=0;?j<=dimNum;?++j)??
- ????????????{??
- ????????????????cout<<t[i][j]<<",?";??
- ????????????}??
- ????????????cout<<")\n";??
- ????????}??
- ????}??
- }??
- ??
- void?KMeans(vector<Tuple>&?tuples){??
- ????vector<Tuple>?clusters[k];//k個簇??
- ????Tuple?means[k];//k個中心點(diǎn)??
- ????int?i=0;??
- ????//一開始隨機(jī)選取k條記錄的值作為k個簇的質(zhì)心(均值)??
- ????srand((unsigned?int)time(NULL));??
- ????for(i=0;i<k;){??
- ????????int?iToSelect?=?rand()%tuples.size();??
- ????????if(means[iToSelect].size()?==?0)??
- ????????{??
- ????????????for(int?j=0;?j<=dimNum;?++j)??
- ????????????{??
- ????????????????means[i].push_back(tuples[iToSelect][j]);??
- ????????????}??
- ????????????++i;??
- ????????}??
- ????}??
- ????int?lable=0;??
- ????//根據(jù)默認(rèn)的質(zhì)心給簇賦值??
- ????for(i=0;i!=tuples.size();++i){??
- ????????lable=clusterOfTuple(means,tuples[i]);??
- ????????clusters[lable].push_back(tuples[i]);??
- ????}??
- ????double?oldVar=-1;??
- ????double?newVar=getVar(clusters,means);??
- ????cout<<"初始的的整體誤差平方和為:"<<newVar<<endl;???
- ????int?t?=?0;??
- ????while(abs(newVar?-?oldVar)?>=?1)?//當(dāng)新舊函數(shù)值相差不到1即準(zhǔn)則函數(shù)值不發(fā)生明顯變化時,算法終止??
- ????{??
- ????????cout<<"第?"<<++t<<"?次迭代開始:"<<endl;??
- ????????for?(i?=?0;?i?<?k;?i++)?//更新每個簇的中心點(diǎn)??
- ????????{??
- ????????????means[i]?=?getMeans(clusters[i]);??
- ????????}??
- ????????oldVar?=?newVar;??
- ????????newVar?=?getVar(clusters,means);?//計算新的準(zhǔn)則函數(shù)值??
- ????????for?(i?=?0;?i?<?k;?i++)?//清空每個簇??
- ????????{??
- ????????????clusters[i].clear();??
- ????????}??
- ????????//根據(jù)新的質(zhì)心獲得新的簇??
- ????????for(i=0;?i!=tuples.size();?++i){??
- ????????????lable=clusterOfTuple(means,tuples[i]);??
- ????????????clusters[lable].push_back(tuples[i]);??
- ????????}??
- ????????cout<<"此次迭代之后的整體誤差平方和為:"<<newVar<<endl;???
- ????}??
- ??
- ????cout<<"The?result?is:\n";??
- ????print(clusters);??
- }??
- int?main(){??
- ??
- ????char?fname[256];??
- ????cout<<"請輸入存放數(shù)據(jù)的文件名:?";??
- ????cin>>fname;??
- ????cout<<endl<<"?請依次輸入:?維數(shù)?樣本數(shù)目"<<endl;??
- ????cout<<endl<<"?維數(shù)dimNum:?";??
- ????cin>>dimNum;??
- ????cout<<endl<<"?樣本數(shù)目dataNum:?";??
- ????cin>>dataNum;??
- ????ifstream?infile(fname);??
- ????if(!infile){??
- ????????cout<<"不能打開輸入的文件"<<fname<<endl;??
- ????????return?0;??
- ????}??
- ????vector<Tuple>?tuples;??
- ????//從文件流中讀入數(shù)據(jù)??
- ????for(int?i=0;?i<dataNum?&&?!infile.eof();?++i)??
- ????{??
- ????????string?str;??
- ????????getline(infile,?str);??
- ????????istringstream?istr(str);??
- ????????Tuple?tuple(dimNum+1,?0);//第一個位置存放記錄編號氛什,第2到dimNum+1個位置存放實(shí)際元素??
- ????????tuple[0]?=?i+1;??
- ????????for(int?j=1;?j<=dimNum;?++j)??
- ????????{??
- ????????????istr>>tuple[j];??
- ????????}??
- ????????tuples.push_back(tuple);??
- ????}??
- ??
- ????cout<<endl<<"開始聚類"<<endl;??
- ????KMeans(tuples);??
- ????return?0;??
- }??
這里是隨機(jī)選取的初始質(zhì)心莺葫,以鳶尾花的數(shù)據(jù)集為例,原數(shù)據(jù)集中1-50為一個簇枪眉,51-100為第二個簇捺檬,101到150為第三個簇:
第二次運(yùn)行結(jié)果 SSE=98.1404
。贸铜。堡纬。
第五次運(yùn)行結(jié)果 SSE=123.397
? ? ? ? ?由于初始質(zhì)心是隨機(jī)選取的,前兩次還算正常蒿秦,運(yùn)行到第五次時烤镐,第一個簇基本包括了后51-150個記錄,第二個簇和第三個簇包含了第1-50個記錄棍鳖,可能的原因就是隨機(jī)選擇初始點(diǎn)時炮叶,有兩個初始點(diǎn)都選在了1-50個記錄中。
參考:
[1]Pang-Ning Tan等著鹊杖,《數(shù)據(jù)挖掘?qū)д摗罚?011
[2]Jiawei Han等著悴灵,《數(shù)據(jù)挖掘概念與技術(shù)》,2008
[3]聚類分析中類數(shù)估計方法的實(shí)驗(yàn)比較
[4]http://www.cnblogs.com/vivounicorn/archive/2011/09/23/2186483.html
[5]一種基于貝葉斯信息準(zhǔn)則的k均值聚類方法
[6]http://www.zhihu.com/question/19640394?nr=1¬i_id=8736954