基本Kmeans算法介紹及其實(shí)現(xiàn)

原文:http://blog.csdn.net/liuheng0111/article/details/52149040

1.基本Kmeans算法[1]

  1. 選擇K個(gè)點(diǎn)作為初始質(zhì)心??
  2. repeat??
  3. ????將每個(gè)點(diǎn)指派到最近的質(zhì)心,形成K個(gè)簇??
  4. ????重新計(jì)算每個(gè)簇的質(zhì)心??
  5. 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ì)心的選取

? ? ? ? ? 選擇適當(dāng)?shù)某跏假|(zhì)心是基本kmeans算法的關(guān)鍵步驟。常見的方法是隨機(jī)的選取初始質(zhì)心,但是這樣簇的質(zhì)量常常很差。處理選取初始質(zhì)心問(wèn)題的一種常用技術(shù)是:多次運(yùn)行,每次使用一組不同的隨機(jī)初始質(zhì)心,然后選取具有最小SSE(誤差的平方和)的簇集。這種策略簡(jiǎn)單,但是效果可能不好,這取決于數(shù)據(jù)集和尋找的簇的個(gè)數(shù)。

? ? ? ? ? 第二種有效的方法是,取一個(gè)樣本,并使用層次聚類技術(shù)對(duì)它聚類。從層次聚類中提取K個(gè)簇,并用這些簇的質(zhì)心作為初始質(zhì)心。該方法通常很有效,但僅對(duì)下列情況有效:(1)樣本相對(duì)較小,例如數(shù)百到數(shù)千(層次聚類開銷較大);(2)K相對(duì)于樣本大小較小

? ? ? ? ? ?第三種選擇初始質(zhì)心的方法,隨機(jī)地選擇第一個(gè)點(diǎn),或取所有點(diǎn)的質(zhì)心作為第一個(gè)點(diǎn)。然后,對(duì)于每個(gè)后繼初始質(zhì)心,選擇離已經(jīng)選取過(guò)的初始質(zhì)心最遠(yuǎn)的點(diǎn)。使用這種方法,確保了選擇的初始質(zhì)心不僅是隨機(jī)的,而且是散開的。但是,這種方法可能選中離群點(diǎn)。此外,求離當(dāng)前初始質(zhì)心集最遠(yuǎn)的點(diǎn)開銷也非常大。為了克服這個(gè)問(wèn)題,通常該方法用于點(diǎn)樣本。由于離群點(diǎn)很少(多了就不是離群點(diǎn)了),它們多半不會(huì)在隨機(jī)樣本中出現(xiàn)。計(jì)算量也大幅減少。

? ? ? ? ? 第四種方法就是上面提到的canopy算法。

(3)距離的度量

? ? ? ? ? 常用的距離度量方法包括:歐幾里得距離和余弦相似度。兩者都是評(píng)定個(gè)體間差異的大小的。歐幾里得距離度量會(huì)受指標(biāo)不同單位刻度的影響,所以一般需要先進(jìn)行標(biāo)準(zhǔn)化,同時(shí)距離越大,個(gè)體間差異越大;空間向量余弦夾角的相似度度量不會(huì)受指標(biāo)刻度的影響,余弦值落于區(qū)間[-1,1],值越大,差異越小。但是針對(duì)具體應(yīng)用,什么情況下使用歐氏距離,什么情況下使用余弦相似度?

? ? ? ? ? 從幾何意義上來(lái)說(shuō),n維向量空間的一條線段作為底邊和原點(diǎn)組成的三角形,其頂角大小是不確定的。也就是說(shuō)對(duì)于兩條空間向量,即使兩點(diǎn)距離一定,他們的夾角余弦值也可以隨意變化。感性的認(rèn)識(shí),當(dāng)兩用戶評(píng)分趨勢(shì)一致時(shí),但是評(píng)分值差距很大,余弦相似度傾向給出更優(yōu)解。舉個(gè)極端的例子,兩用戶只對(duì)兩件商品評(píng)分,向量分別為(3,3)和(5,5),這兩位用戶的認(rèn)知其實(shí)是一樣的,但是歐式距離給出的解顯然沒(méi)有余弦值合理。[6]

(4)質(zhì)心的計(jì)算

? ? ? ? ?對(duì)于距離度量不管是采用歐式距離還是采用余弦相似度,簇的質(zhì)心都是其均值,即向量各維取平均即可。對(duì)于距離對(duì)量采用其它方式時(shí),這個(gè)還沒(méi)研究過(guò)。

(5)算法停止條件

? ? ? ? ?一般是目標(biāo)函數(shù)達(dá)到最優(yōu)或者達(dá)到最大的迭代次數(shù)即可終止。對(duì)于不同的距離度量,目標(biāo)函數(shù)往往不同。當(dāng)采用歐式距離時(shí),目標(biāo)函數(shù)一般為最小化對(duì)象到其簇質(zhì)心的距離的平方和,如下:


? ? ? ? ?當(dāng)采用余弦相似度時(shí),目標(biāo)函數(shù)一般為最大化對(duì)象到其簇質(zhì)心的余弦相似度和,如下:


(6)空聚類的處理

? ? ? ? ? ?如果所有的點(diǎn)在指派步驟都未分配到某個(gè)簇,就會(huì)得到空簇。如果這種情況發(fā)生,則需要某種策略來(lái)選擇一個(gè)替補(bǔ)質(zhì)心,否則的話,平方誤差將會(huì)偏大。一種方法是選擇一個(gè)距離當(dāng)前任何質(zhì)心最遠(yuǎn)的點(diǎn)。這將消除當(dāng)前對(duì)總平方誤差影響最大的點(diǎn)。另一種方法是從具有最大SSE的簇中選擇一個(gè)替補(bǔ)的質(zhì)心。這將分裂簇并降低聚類的總SSE。如果有多個(gè)空簇,則該過(guò)程重復(fù)多次。另外,編程實(shí)現(xiàn)時(shí),要注意空簇可能導(dǎo)致的程序bug。

3.適用范圍及缺陷

? ? ? ? ? ?Kmenas算法試圖找到使平凡誤差準(zhǔn)則函數(shù)最小的簇。當(dāng)潛在的簇形狀是凸面的,簇與簇之間區(qū)別較明顯,且簇大小相近時(shí),其聚類結(jié)果較理想。前面提到,該算法時(shí)間復(fù)雜度為O(tKmn),與樣本數(shù)量線性相關(guān),所以,對(duì)于處理大數(shù)據(jù)集合,該算法非常高效,且伸縮性較好。但該算法除了要事先確定簇?cái)?shù)K和對(duì)初始聚類中心敏感外,經(jīng)常以局部最優(yōu)結(jié)束,同時(shí)對(duì)“噪聲”和孤立點(diǎn)敏感,并且該方法不適于發(fā)現(xiàn)非凸面形狀的簇或大小差別很大的簇。

4.實(shí)現(xiàn)

  1. #include?<iostream>??
  2. #include?<sstream>??
  3. #include?<fstream>??
  4. #include?<vector>??
  5. #include?<math.h>??
  6. #include?<stdlib.h>??
  7. #define?k?3//簇的數(shù)目??
  8. using?namespace?std;??
  9. //存放元組的屬性信息??
  10. typedef?vector<double>?Tuple;//存儲(chǔ)每條數(shù)據(jù)記錄??
  11. ??
  12. int?dataNum;//數(shù)據(jù)集中數(shù)據(jù)記錄數(shù)目??
  13. int?dimNum;//每條記錄的維數(shù)??
  14. ??
  15. //計(jì)算兩個(gè)元組間的歐幾里距離??
  16. double?getDistXY(const?Tuple&?t1,?const?Tuple&?t2)???
  17. {??
  18. ????double?sum?=?0;??
  19. ????for(int?i=1;?i<=dimNum;?++i)??
  20. ????{??
  21. ????????sum?+=?(t1[i]-t2[i])?*?(t1[i]-t2[i]);??
  22. ????}??
  23. ????return?sqrt(sum);??
  24. }??
  25. ??
  26. //根據(jù)質(zhì)心,決定當(dāng)前元組屬于哪個(gè)簇??
  27. int?clusterOfTuple(Tuple?means[],const?Tuple&?tuple){??
  28. ????double?dist=getDistXY(means[0],tuple);??
  29. ????double?tmp;??
  30. ????int?label=0;//標(biāo)示屬于哪一個(gè)簇??
  31. ????for(int?i=1;i<k;i++){??
  32. ????????tmp=getDistXY(means[i],tuple);??
  33. ????????if(tmp<dist)?{dist=tmp;label=i;}??
  34. ????}??
  35. ????return?label;?????
  36. }??
  37. //獲得給定簇集的平方誤差??
  38. double?getVar(vector<Tuple>?clusters[],Tuple?means[]){??
  39. ????double?var?=?0;??
  40. ????for?(int?i?=?0;?i?<?k;?i++)??
  41. ????{??
  42. ????????vector<Tuple>?t?=?clusters[i];??
  43. ????????for?(int?j?=?0;?j<?t.size();?j++)??
  44. ????????{??
  45. ????????????var?+=?getDistXY(t[j],means[i]);??
  46. ????????}??
  47. ????}??
  48. ????//cout<<"sum:"<<sum<<endl;??
  49. ????return?var;??
  50. ??
  51. }??
  52. //獲得當(dāng)前簇的均值(質(zhì)心)??
  53. Tuple?getMeans(const?vector<Tuple>&?cluster){??
  54. ??????
  55. ????int?num?=?cluster.size();??
  56. ????Tuple?t(dimNum+1,?0);??
  57. ????for?(int?i?=?0;?i?<?num;?i++)??
  58. ????{??
  59. ????????for(int?j=1;?j<=dimNum;?++j)??
  60. ????????{??
  61. ????????????t[j]?+=?cluster[i][j];??
  62. ????????}??
  63. ????}??
  64. ????for(int?j=1;?j<=dimNum;?++j)??
  65. ????????t[j]?/=?num;??
  66. ????return?t;??
  67. ????//cout<<"sum:"<<sum<<endl;??
  68. }??
  69. ??
  70. void?print(const?vector<Tuple>?clusters[])??
  71. {??
  72. ????for(int?lable=0;?lable<k;?lable++)??
  73. ????{??
  74. ????????cout<<"第"<<lable+1<<"個(gè)簇:"<<endl;??
  75. ????????vector<Tuple>?t?=?clusters[lable];??
  76. ????????for(int?i=0;?i<t.size();?i++)??
  77. ????????{??
  78. ????????????cout<<i+1<<".(";??
  79. ????????????for(int?j=0;?j<=dimNum;?++j)??
  80. ????????????{??
  81. ????????????????cout<<t[i][j]<<",?";??
  82. ????????????}??
  83. ????????????cout<<")\n";??
  84. ????????}??
  85. ????}??
  86. }??
  87. ??
  88. void?KMeans(vector<Tuple>&?tuples){??
  89. ????vector<Tuple>?clusters[k];//k個(gè)簇??
  90. ????Tuple?means[k];//k個(gè)中心點(diǎn)??
  91. ????int?i=0;??
  92. ????//一開始隨機(jī)選取k條記錄的值作為k個(gè)簇的質(zhì)心(均值)??
  93. ????srand((unsigned?int)time(NULL));??
  94. ????for(i=0;i<k;){??
  95. ????????int?iToSelect?=?rand()%tuples.size();??
  96. ????????if(means[iToSelect].size()?==?0)??
  97. ????????{??
  98. ????????????for(int?j=0;?j<=dimNum;?++j)??
  99. ????????????{??
  100. ????????????????means[i].push_back(tuples[iToSelect][j]);??
  101. ????????????}??
  102. ????????????++i;??
  103. ????????}??
  104. ????}??
  105. ????int?lable=0;??
  106. ????//根據(jù)默認(rèn)的質(zhì)心給簇賦值??
  107. ????for(i=0;i!=tuples.size();++i){??
  108. ????????lable=clusterOfTuple(means,tuples[i]);??
  109. ????????clusters[lable].push_back(tuples[i]);??
  110. ????}??
  111. ????double?oldVar=-1;??
  112. ????double?newVar=getVar(clusters,means);??
  113. ????cout<<"初始的的整體誤差平方和為:"<<newVar<<endl;???
  114. ????int?t?=?0;??
  115. ????while(abs(newVar?-?oldVar)?>=?1)?//當(dāng)新舊函數(shù)值相差不到1即準(zhǔn)則函數(shù)值不發(fā)生明顯變化時(shí),算法終止??
  116. ????{??
  117. ????????cout<<"第?"<<++t<<"?次迭代開始:"<<endl;??
  118. ????????for?(i?=?0;?i?<?k;?i++)?//更新每個(gè)簇的中心點(diǎn)??
  119. ????????{??
  120. ????????????means[i]?=?getMeans(clusters[i]);??
  121. ????????}??
  122. ????????oldVar?=?newVar;??
  123. ????????newVar?=?getVar(clusters,means);?//計(jì)算新的準(zhǔn)則函數(shù)值??
  124. ????????for?(i?=?0;?i?<?k;?i++)?//清空每個(gè)簇??
  125. ????????{??
  126. ????????????clusters[i].clear();??
  127. ????????}??
  128. ????????//根據(jù)新的質(zhì)心獲得新的簇??
  129. ????????for(i=0;?i!=tuples.size();?++i){??
  130. ????????????lable=clusterOfTuple(means,tuples[i]);??
  131. ????????????clusters[lable].push_back(tuples[i]);??
  132. ????????}??
  133. ????????cout<<"此次迭代之后的整體誤差平方和為:"<<newVar<<endl;???
  134. ????}??
  135. ??
  136. ????cout<<"The?result?is:\n";??
  137. ????print(clusters);??
  138. }??
  139. int?main(){??
  140. ??
  141. ????char?fname[256];??
  142. ????cout<<"請(qǐng)輸入存放數(shù)據(jù)的文件名:?";??
  143. ????cin>>fname;??
  144. ????cout<<endl<<"?請(qǐng)依次輸入:?維數(shù)?樣本數(shù)目"<<endl;??
  145. ????cout<<endl<<"?維數(shù)dimNum:?";??
  146. ????cin>>dimNum;??
  147. ????cout<<endl<<"?樣本數(shù)目dataNum:?";??
  148. ????cin>>dataNum;??
  149. ????ifstream?infile(fname);??
  150. ????if(!infile){??
  151. ????????cout<<"不能打開輸入的文件"<<fname<<endl;??
  152. ????????return?0;??
  153. ????}??
  154. ????vector<Tuple>?tuples;??
  155. ????//從文件流中讀入數(shù)據(jù)??
  156. ????for(int?i=0;?i<dataNum?&&?!infile.eof();?++i)??
  157. ????{??
  158. ????????string?str;??
  159. ????????getline(infile,?str);??
  160. ????????istringstream?istr(str);??
  161. ????????Tuple?tuple(dimNum+1,?0);//第一個(gè)位置存放記錄編號(hào),第2到dimNum+1個(gè)位置存放實(shí)際元素??
  162. ????????tuple[0]?=?i+1;??
  163. ????????for(int?j=1;?j<=dimNum;?++j)??
  164. ????????{??
  165. ????????????istr>>tuple[j];??
  166. ????????}??
  167. ????????tuples.push_back(tuple);??
  168. ????}??
  169. ??
  170. ????cout<<endl<<"開始聚類"<<endl;??
  171. ????KMeans(tuples);??
  172. ????return?0;??
  173. }??

這里是隨機(jī)選取的初始質(zhì)心,以鳶尾花的數(shù)據(jù)集為例,原數(shù)據(jù)集中1-50為一個(gè)簇,51-100為第二個(gè)簇,101到150為第三個(gè)簇:


第一次運(yùn)行結(jié)果 SSE=97.5905


第二次運(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&noti_id=8736954

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容