原文:http://blog.csdn.net/liuheng0111/article/details/52149040
1.基本Kmeans算法[1]
- 選擇K個(gè)點(diǎn)作為初始質(zhì)心??
- repeat??
- ????將每個(gè)點(diǎn)指派到最近的質(zhì)心,形成K個(gè)簇??
- ????重新計(jì)算每個(gè)簇的質(zhì)心??
- until?簇不發(fā)生變化或達(dá)到最大迭代次數(shù)??
時(shí)間復(fù)雜度:O(tKmn),其中,t為迭代次數(shù),K為簇的數(shù)目,m為記錄數(shù),n為維數(shù)
空間復(fù)雜度:O((m+K)n),其中,K為簇的數(shù)目,m為記錄數(shù),n為維數(shù)
2.注意問(wèn)題
(1)K如何確定
? ? ? ? kmenas算法首先選擇K個(gè)初始質(zhì)心,其中K是用戶指定的參數(shù),即所期望的簇的個(gè)數(shù)。這樣做的前提是我們已經(jīng)知道數(shù)據(jù)集中包含多少個(gè)簇,但很多情況下,我們并不知道數(shù)據(jù)的分布情況,實(shí)際上聚類就是我們發(fā)現(xiàn)數(shù)據(jù)分布的一種手段,這就陷入了雞和蛋的矛盾。如何有效的確定K值,這里大致提供幾種方法,若各位有更好的方法,歡迎探討。
1.與層次聚類結(jié)合[2]
? ? ? ? ?經(jīng)常會(huì)產(chǎn)生較好的聚類結(jié)果的一個(gè)有趣策略是,首先采用層次凝聚算法決定結(jié)果粗的數(shù)目,并找到一個(gè)初始聚類,然后用迭代重定位來(lái)改進(jìn)該聚類。
2.穩(wěn)定性方法[3]
? ? ? ? 穩(wěn)定性方法對(duì)一個(gè)數(shù)據(jù)集進(jìn)行2次重采樣產(chǎn)生2個(gè)數(shù)據(jù)子集,再用相同的聚類算法對(duì)2個(gè)數(shù)據(jù)子集進(jìn)行聚類,產(chǎn)生2個(gè)具有k個(gè)聚類的聚類結(jié)果,計(jì)算2個(gè)聚類結(jié)果的相似度的分布情況。2個(gè)聚類結(jié)果具有高的相似度說(shuō)明k個(gè)聚類反映了穩(wěn)定的聚類結(jié)構(gòu),其相似度可以用來(lái)估計(jì)聚類個(gè)數(shù)。采用次方法試探多個(gè)k,找到合適的k值。
3.系統(tǒng)演化方法[3]
? ? ? ? ?系統(tǒng)演化方法將一個(gè)數(shù)據(jù)集視為偽熱力學(xué)系統(tǒng),當(dāng)數(shù)據(jù)集被劃分為K個(gè)聚類時(shí)稱系統(tǒng)處于狀態(tài)K。系統(tǒng)由初始狀態(tài)K=1出發(fā),經(jīng)過(guò)分裂過(guò)程和合并過(guò)程,系統(tǒng)將演化到它的穩(wěn)定平衡狀態(tài)Ki,其所對(duì)應(yīng)的聚類結(jié)構(gòu)決定了最優(yōu)類數(shù)Ki。系統(tǒng)演化方法能提供關(guān)于所有聚類之間的相對(duì)邊界距離或可分程度,它適用于明顯分離的聚類結(jié)構(gòu)和輕微重疊的聚類結(jié)構(gòu)。
4.使用canopy算法進(jìn)行初始劃分[4]
? ? ? ? ? 基于Canopy
Method的聚類算法將聚類過(guò)程分為兩個(gè)階段
? ? ? ?
?Stage1、聚類最耗費(fèi)計(jì)算的地方是計(jì)算對(duì)象相似性的時(shí)候,Canopy
Method在第一階段選擇簡(jiǎn)單、計(jì)算代價(jià)較低的方法計(jì)算對(duì)象相似性,將相似的對(duì)象放在一個(gè)子集中,這個(gè)子集被叫做Canopy
,通過(guò)一系列計(jì)算得到若干Canopy,Canopy之間可以是重疊的,但不會(huì)存在某個(gè)對(duì)象不屬于任何Canopy的情況,可以把這一階段看做數(shù)據(jù)預(yù)處理;
?
? ? ? ? Stage2、在各個(gè)Canopy 內(nèi)使用傳統(tǒng)的聚類方法(如K-means),不屬于同一Canopy 的對(duì)象之間不進(jìn)行相似性計(jì)算。
從這個(gè)方法起碼可以看出兩點(diǎn)好處:首先,Canopy
不要太大且Canopy
之間重疊的不要太多的話會(huì)大大減少后續(xù)需要計(jì)算相似性的對(duì)象的個(gè)數(shù);其次,類似于K-means這樣的聚類方法是需要人為指出K的值的,通過(guò)Stage1得到的Canopy
個(gè)數(shù)完全可以作為這個(gè)K值,一定程度上減少了選擇K的盲目性。
? ? ? ? ?其他方法如貝葉斯信息準(zhǔn)則方法(BIC)可參看文獻(xiàn)[5]。
(2)初始質(zhì)心的選取
(3)距離的度量
(4)質(zhì)心的計(jì)算
(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;//存儲(chǔ)每條數(shù)據(jù)記錄??
- ??
- int?dataNum;//數(shù)據(jù)集中數(shù)據(jù)記錄數(shù)目??
- int?dimNum;//每條記錄的維數(shù)??
- ??
- //計(jì)算兩個(gè)元組間的歐幾里距離??
- 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)前元組屬于哪個(gè)簇??
- int?clusterOfTuple(Tuple?means[],const?Tuple&?tuple){??
- ????double?dist=getDistXY(means[0],tuple);??
- ????double?tmp;??
- ????int?label=0;//標(biāo)示屬于哪一個(gè)簇??
- ????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<<"個(gè)簇:"<<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個(gè)簇??
- ????Tuple?means[k];//k個(gè)中心點(diǎn)??
- ????int?i=0;??
- ????//一開始隨機(jī)選取k條記錄的值作為k個(gè)簇的質(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ā)生明顯變化時(shí),算法終止??
- ????{??
- ????????cout<<"第?"<<++t<<"?次迭代開始:"<<endl;??
- ????????for?(i?=?0;?i?<?k;?i++)?//更新每個(gè)簇的中心點(diǎn)??
- ????????{??
- ????????????means[i]?=?getMeans(clusters[i]);??
- ????????}??
- ????????oldVar?=?newVar;??
- ????????newVar?=?getVar(clusters,means);?//計(jì)算新的準(zhǔn)則函數(shù)值??
- ????????for?(i?=?0;?i?<?k;?i++)?//清空每個(gè)簇??
- ????????{??
- ????????????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<<"請(qǐng)輸入存放數(shù)據(jù)的文件名:?";??
- ????cin>>fname;??
- ????cout<<endl<<"?請(qǐng)依次輸入:?維數(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);//第一個(gè)位置存放記錄編號(hào),第2到dimNum+1個(gè)位置存放實(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為一個(gè)簇,51-100為第二個(gè)簇,101到150為第三個(gè)簇:






第二次運(yùn)行結(jié)果 SSE=98.1404
。。。



第五次運(yùn)行結(jié)果 SSE=123.397
? ? ? ? ?由于初始質(zhì)心是隨機(jī)選取的,前兩次還算正常,運(yùn)行到第五次時(shí),第一個(gè)簇基本包括了后51-150個(gè)記錄,第二個(gè)簇和第三個(gè)簇包含了第1-50個(gè)記錄,可能的原因就是隨機(jī)選擇初始點(diǎn)時(shí),有兩個(gè)初始點(diǎn)都選在了1-50個(gè)記錄中。
參考:
[1]Pang-Ning Tan等著,《數(shù)據(jù)挖掘?qū)д摗罚?011
[2]Jiawei Han等著,《數(shù)據(jù)挖掘概念與技術(shù)》,2008
[3]聚類分析中類數(shù)估計(jì)方法的實(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