轉自:http://www.cnblogs.com/lzxwalex/p/7708266.html
C#下實現的基礎K-MEANS多維聚類
#本文PDF版下載
#本文代碼下載
前言
最近由於上C # 課的時候,老師提到了-我們的課程成績由幾個部分組成.分別是「最終作品展示」「小組合作聊天記錄評分」「組內成員匿名互評」「報告書評分」這四項綜合評價.老師希望我能夠通過這四個項目對所有同學進行聚類,然後根據離每簇的中心距離來評價最終的分數.由於我沒有接觸過這方面的算法,所以就選了實現較爲方便並且直觀的聚類方法K-MEANS.所以下文中就會對我這次學習到的一些心得進行分享.由於是C # 課程,所以本次的算法將以C# 爲例子進行介紹.
聚類&K-Means
聚類
百科上對於聚類的解釋是這樣的:
將物理或抽象對象的集合分成由類似的對象組成的多個類的過程被稱爲聚類.由聚類所生成的簇是一組數據對象的集合,這些對象與同一個簇中的對象彼此相似,與其他簇中的對象相異.
簡單的來說聚類就是將相似的歸在一起產生一個簇,使不相似的分在不同的簇裏.
聚類和分類的區別
分類:從特定的數據中根據人爲打上的數據表淺進行數據挖掘,做出判斷.是將類別已知.並且樣本數據已經做了標記的數據進行歸類.是一個有監督的過程.
聚類:目的是分類數據,但是在分類結束前我們都不知道數據是怎麼分類的,有什麼特點,只是根據算法,自動的將相似性的分類在了一起.數據本身沒有標記.本身也沒有類別.通過聚類的算法將相似性高的數據通過算法打上的標籤歸類在一起.是一種無監督的過程.
K-Means算法
K-Means算法是基於距離(我在這次中使用了歐幾里德距離)的聚類算法 , 採用距離或者特徵向量作爲相似程度的考量,數據之間的距離/向量餘弦越接近, 其相似度就越大.在K-Means聚類算法中-簇是由距離較爲相近的數據對象構成的,故用K-Means算法的目的是想要得到數據對象相對緊湊且獨立的不同簇.
「評分系統」和K-Means的結合
這次老師讓我做的就是希望我能通過給出的「最終作品展示」「小組合作聊天記錄評分」「組內成員匿名互評」「報告書評分」這四個維度的數據對所有同學的成績使用K-Means聚成四個類,分別對應90-100/80-90/70-80/60-70/不及格這五個等第.分出登第後,五個類的中心分別是95/85/75/65/55,根據離數據中心的歐幾里得距離按比例來決定最終的成績.這就是老師讓我實現的功能.
K-Means的非適用性
K-Means算法適應於連續形式的數據,由於數據的分佈不同,有時候就無法分得準確的類,比如下面的兩種形式
1.非標準正態分佈
K-Means用於表示聚類趨勢只是說在數據符合正態分佈或偏態不太嚴重時纔是合理的.如果偏態嚴重,或者有異常的極大極小值,對均數影響很大,這時K-MEANS不能代表聚類的趨勢.
故在實際的樣本中,我們作出的假設可能會並不適用,這個時候用基礎的K-Means算法就不是特別的恰當了.這個時候我們就應該用到一些改進的算法,比如 Kernel K-means和Spectral Clustering.這兩種算法會在後面的文章中進行介紹.
2.非均勻分佈
由於分佈的不均勻,疏密程度就會對中心的選取造成影響.就會偏向左側較密的地方.如下所示:
K-Means的優點和缺點
優點:
1. 算法簡單並且效率很高,迭代次數.
2. K-Means聚類算法的時間複雜度是O(nkt) {n數據量/k簇的個數/t算法迭代次數}故時間複雜度近似爲線性.
3. 自身具有優化迭代的特點,通過不斷的計算中心來修正聚類結果.
4. 對於數據有着很好的伸縮性.
缺點:
1. 需要指定K的值 我們在每次聚類之前需要指定數據會被分成幾類即要有幾個中心點.
2. 對於離羣值較爲敏感 由於在每次聚類之後都要重新生成簇的中心.而中心是根據所有簇種數據對象的”質心”來的,所以當數據集不大的時候,這些離羣的特殊點對中心就會產生很大的影響.這可以通過對數據集進行預處理,篩出離羣的點.離羣的點可以全部歸類爲第(k+1)類,因爲它們本身有着非常獨特的性質,是我們分析時候可以研究的對象.
3. 初始點的選取對結果會產生影響 初始的點我們一般通過隨機選定,但是隨機選出的點可能點和點之間並不合理,會造成最終聚類的結果會不相同.
4. 最終的聚類結果是一個球形的簇 由於使用了歐幾里得距離,最終一定是以簇中心爲球心(圓心)的一個球形(圓形)的簇.
5. 維度對聚類結果的貢獻無法得到 無法確定哪一個維度對聚類結果的產生的影響比較大.
K-Means的一些細節
歐幾里德距離
歐幾里德距離簡稱歐式距離,是我們平時經常用到的一個距離度量公式,具體就是基礎求距離公式,並且拓展至了n維空間.我們以三維空間舉一個例子,如下圖所示.A,B點根據歐幾里得距離公式算出來的就是中間灰色線上方的距離公式
“質心”公式計算
“質心”的選取是因爲每一次聚類後都需要通過簇中的數據對象生成新的中心,而生成新中心的方式就是產生所有數據對象的”質心”.公式也可以拓展至n維,這裏我們以3維的空間爲例子
A/B/C三個點的”質心”就是圖中的灰點,座標爲((X1+X2+X3)/3),(Y1+Y2+Y3)/3,(Z1+Z2+Z3)/3)拓展到n維就是((X1+…+Xn)/n,…,((Z1+…Zn)/n)).
K-Means算法實現步驟
這裏我們介紹的是K-Means算法的本身步驟,沒有加上任何的優化方式,總共分爲 5 步,分別如下:
1. 首先,就像上文中說的一樣.K-Means算法必須提供k即簇的樹目.使我們能夠通過聚類算法得到k個分組
2. 從我們要進行聚類的數據集中隨機選擇k個點作爲每個簇的初始中心
3. 通過一定的計算方法(歐幾里德距離)來計算每個數據對象距離每一個簇中心的距離.並且得到距離最近的簇的中心點,那麼這個數據對象就被歸類到了這個簇中.
4. 當所有的點都被聚類後,對於每一個簇算出新的中心.(通過算法找到”質心”).
5. 迭代3,4兩個步驟,直到4步驟選出來的新中心和原來的中心小於某個我們默認的閾值(幾乎中心不再發生變化),算法停止.我們已經通過聚類算法得到了我們最終的結果.
接下來我們通過舉例子的方法來更好的理解一下K-Means算法,我們隨機生成了七個點,它們的位置即座標如下圖所示
很明顯,我們如果有監督的將他們分類,那麼結果一定是如下圖所示的情況
那麼我們按照K-Means算法的步驟,看一下聚類的過程是怎麼樣的.
1.首先我們先隨機選擇兩個點作爲簇的中心,就選A1/B3這兩個點
2.計算麼個數據對象距離各個簇中心的距離
對於A2 - 距A1的距離就是((1-2)^2 + (1-2)^2)^(1/2) = √2/距A2的距離就是((1-6)^2+(1-3)^2)^(1/2) = √29 由於√29>√2,所以A2被歸類爲以A1爲中心的簇,同理,其他的點經過歸類後的結果如下圖所示:
3.重新計算每個簇的中心
我們現在得到了兩個簇,現在我們把每個簇裏的數據對象的中心求出來,根據質心的求法,我們可以知道紅色A組的新中心是((2+1+3)/3,(2+1+1)/3) = (2,4/3),棕色B組的新中心是((5+6+6+7)/4,(4+4+3+2)/4) = (6,3)這個時候我們發現B的中心就是B3這個點,所以對於B組而言不需要再次進行聚類了.
對於A組我們可以發現,A1,A2,A3距離新的中心(2,4/3)相等,故再進行計算新中心的話中心也不會發生改變,所以聚類停止.
4.最終的聚類結果就是A1/A2/A3一組.B1/B2/B3/B4一組.
K-Means在C#下算法的實現
(在這裏我展示的是沒有優化過的K-Means實現)
1.首先是初始化中心.
根據我們設定的簇的數量,我們可以隨機從所有的數據對象中選擇k個點作爲初始的中心點.這裏只需要使用Random類的Next方法即可,所以此步驟不進行介紹.
2.聚類步驟的實現
代碼特點解釋:
1. 我的程序是先將數據從Excel的Xls文件中導出到了C#控件中的ListView裏,這個控件的名字是:XlsDataSh,在程序的代碼裏可以看到這個控件的名字.
2. 由於我們的Xls文件中並不是只有這四個項的數據,其中的其他幾項是另外的一些信息,有着編號/姓名/學號姓息分別佔了ListView的0/1/2這三列,而3-6這四個列分別是對應四個維度的.這就是爲什麼循環中經常會出現類似for (int k = 3; k < 7; k++)的原意.
我們先來看一下聚類這個方法中的運行步驟,如下所示:
解釋:
1. ArrayList中存儲的事每一個簇所擁有的數據的編號.所以當我們每一次重新計算出了簇的中心之後,會對數據所在的簇進行重新聚類,故首先我們要把ArrayList中所存儲的數據進行清除.
2. 判斷簇爲0是一個重要的步驟.這也是我“偷懶”了之後用的方法,由於我們的初始中心是隨意選擇的,所以很有可能選擇的數據中心再一次聚類之後沒有分得任何的數據點,這時候這個類就是無效的.我們應該對它進行處理,而我用的方法就是偷懶的讓它再次隨機選定中心,直到每個類都一定能分到其他的數據點.
3. ClassNum - 設定的簇的數目.即k
4. RowCount - 數據對象的個數.即n
下面就是聚類方法中的代碼:
private void Cluster() { int tmpclass = 0; double tmpClusDis = 0, tmpClusMinDis = 0; //清除每一個簇裏原來的項 for (int i = 0; i < ClassNum; i++) { ClusterAssem[i].Clear(); } //進行歐幾里德距離計算並聚類 for (int i = 0; i < RowCount - 1; i++) { tmpClusMinDis = vurMax; for (int j = 0; j < ClassNum; j++) { tmpClusDis = 0; //這裏是取出每個維度的數據進行加和 for (int k = 3; k < 7; k++) { tmpClusDis += Math.Pow((System.Convert.ToDouble(XlsDataSh.Items[i].SubItems[k].Text) - CenterArrayParams[j, k - 3]), 2); } if (tmpClusDis < tmpClusMinDis) { tmpclass = j; tmpClusMinDis = tmpClusDis; } } ClusterAssem[tmpclass].Add(i); } //重新初始化 if (ClusterAssem[0].Count == 0 || ClusterAssem[1].Count == 0 || ClusterAssem[2].Count == 0 || ClusterAssem[3].Count == 0 || ClusterAssem[4].Count == 0) { InitCenter();//重新初始化中心的方法. Cluster(); } }
3.重新生成每個簇的中心
解釋:
ClusterAssem - 存儲每一個簇中包含的數據對象ArrayList
RenewCenterArrayParams - 存儲每個簇中心的各個維度的ArrayList
private void RenewCenter() { double tmpSameDis = 0; for (int i = 0; i < ClassNum; i++) { for (int k = 3; k < 7; k++) { tmpSameDis = 0; //遍歷每個簇的點,求出中心點 foreach (object n in ClusterAssem[i]) { tmpSameDis += System.Convert.ToDouble(XlsDataSh.Items[System.Convert.ToInt16(n)].SubItems[k].Text); } RenewCenterArrayParams[i, k - 3] = (tmpSameDis * 1.0 / (ClusterAssem[i].Count+1)); } } }
4.計算結束的標記
結束標誌的意圖就是當中心不再發生變化或小於一個閾值的時候就停止算法的進行了.
解釋:
1. BalEndFlag:用於存儲前一次的中心加和用於和本次進行比對
private Boolean CalEndFlag() { double tmpDifferDis = 0, tmpSameDis = 0; for (int i = 0; i < ClassNum; i++) { tmpSameDis = 0; for (int j = 0; j < 4; j++) { tmpSameDis += Math.Pow((RenewCenterArrayParams[i, j] - CenterArrayParams[i, j]),2); } tmpDifferDis += Math.Pow(tmpSameDis,1.0/2); } //判斷中心點和前一次的偏移 if ((BalEndFlag - tmpDifferDis) <= EndFlag) return false; else { //算法沒有結束,所以將新的中心賦給用於計算的ArrayList for (int i = 0; i < ClassNum; i++) { for (int j = 0; j < 4; j++) { CenterArrayParams[i, j] = RenewCenterArrayParams[i, j]; } } BalEndFlag = tmpDifferDis; tmpDifferDis = 0; return true; } }
結果展示
下面就是我做的基於K—Means的自動聚類評分系統的界面.首先我們先看看有監督下的聚類是否符合預期,如下圖所示:
我們通過上圖的紅色框中可以看出這個聚類的結果和我們的預期是十分的符合的.下面就是我通過隨機的生成方法生成的數據,來觀察K-Means算法在無監督下的運行結果.如下圖所示:
以上就是本次探究的最終成果展示.
K—Means的收斂性
K-Means算法一定是收斂的,否則就會陷入一直尋找新的”質心”而沒有最終的結果了.具體的證明步驟較爲複雜.通過參考一些文章(會在下文中參考中標出)涉及到了E-M算法.將一個聚類問題轉化爲一個最大似然估計問題可證明K-Means是E-M算法的一個特例.在E-M算法下,求得的參數一定是收斂的.在K-Means算法中目標是使損失函數最小,所以在E-step時,找到一個最逼近目標的函數,在M-step時,固定這個函數(數據對象的分配),更新均值(簇的中心),所以最後一定會收斂了.
基礎K-Means算法的優化
由於K-Means是非常成熟的算法,所以有很多先輩們改良了這個算法,在這裏僅僅只是介紹在各個方面有哪些能夠參考改進的方法.
* 通過上文,我們可以知道.K-Means算法容易受到離羣值的干擾.所以我們可以通過預處理比如「LOF算法」來先檢測出離羣點.單獨處理這部分點.
* K-Means的k值在沒有指定的時候是很難選擇的,這個時候,可以通過「k-Means算法的k值自適應優化方法」
* 聚類的中心的選擇.由於本文是隨機選擇初始的k個點,但是這是不合理的,應該是選擇數據中距離儘可能遠的K個點,這裏也有一個算法「Canopy算法」
* 現在有許多改進了的K-Means的算法,比較出名的有「K-means++」「ISODATA」「Kernel K-means」等,在後面的文章中我會進行分享.
參考
1.「K-Means算法的收斂性和如何快速收斂超大的KMeans? - 大數據躺過的坑 - 博客園」(傳送門)(http://www.cnblogs.com/zlslch/p/6965209.html?utm_source=itdadao&utm_medium=referral)
2.「請問如何用數學方法證明K-means是EM算法的特例?」(傳送門)https://www.zhihu.com/question/49972233?sort=created