聚類演算法與K-means實現

ZhiboZhao發表於2021-09-08

聚類演算法與K-means實現

一、聚類演算法的數學描述:

區別於監督學習的演算法(迴歸,分類,預測等),無監督學習就是指訓練樣本的 label 未知,只能通過對無標記的訓練樣本的學習來揭示資料的內在規律和性質。無監督學習任務中研究最多的就是聚類演算法(clustering)。我們假定一個樣本集:

編號 色澤 根蒂 敲聲 紋理 臍部 觸感 密度 含糖率 好瓜
1 青綠 蜷縮 濁響 清晰 凹陷 硬滑 0.697 0.46
2 烏黑 蜷縮 沉悶 清晰 凹陷 硬滑 0.774 0.376
3 烏黑 蜷縮 濁響 清晰 凹陷 硬滑 0.634 0.264
4 青綠 蜷縮 沉悶 清晰 凹陷 硬滑 0.608 0.318
5 淺白 蜷縮 濁響 清晰 凹陷 硬滑 0.556 0.215

該樣本集中包含5個樣本資料,聚類演算法的目的就是將這 5 個樣本劃分為互不相交的子集,每個子集被稱為一個簇。比如,我們以色澤對樣本進行劃分,可以得到3個簇,分別為:\(\lambda_{1} = \{1,4\}, \lambda_{2} = \{2,3\},\lambda_{3} = \{5\}\)。值得說明的是,在實際的聚類過程中,沒有 “色澤” 的概念,聚類過程僅僅能自動形成簇結構,而簇所對應的概念語義是人為賦予的。用數學語言描述上述過程:

假設樣本集 \(D=\{x_{1},x_{2},...,x_{m}\}\) 包含 \(m\) 個無標記樣本,每個樣本由 \(n\) 維特徵來描述,即 \(x_{i} = \{x_{i1},x_{i2},...x_{in}\}\) 。那麼聚類演算法的目的就是將樣本集 \(D\) 劃分為 \(k\) 個不相交的簇:\(D = \bigcup_{l=1}^{k} C_{l}\),且 \(C_{i} \bigcap C_{j} = \phi,\forall i\neq j\)。我們用 \(\lambda = \{\lambda_{1}, \lambda_{2},...\lambda_{k}\}\) 來代表 \(k\) 個簇的標記,因此第 \(j\) 個簇的所有元素可以標記為 \(C_{\lambda_{j}}\) 。對應上述過程,\(m=5, k=3, \lambda=\{青綠,烏黑,淺白\}\)

二、聚類演算法的度量

每一個樣本可能有 \(n\) 個特徵描述,那麼有沒有一個度量標準來判斷聚類演算法的好壞呢?其中一個最重要的原則就是:相同簇的樣本儘可能相似,不同簇的樣本儘可能不同。聚類的效能度量有兩類:1)將聚類結果與某個參考模型進行比較,稱為外部指標;2)直接考察聚類結果而步利用任何參考模型,稱為內部指標。

2.1 外部指標法:

對資料集 \(D=\{x_{1},x_{2},...,x_{m}\}\) ,假設通過聚類給出的簇劃分為 \(C = \{C_{1}, C_{2},...,C_{k}\}\),而參考模型給出的簇劃分為\(C^{*} = \{C_{1}^{*}, C_{2}^{*},...,C_{s}^{*}\}\)。相應地,用 \(\lambda,\lambda^{*}\) 來分別表示與 \(C, C^{*}\) 對應的標記向量,於是我們定義如下:

\[a = |SS|,\quad SS = \{(x_{i},x_{j}) | \lambda_{i} = \lambda_{j}, \lambda_{i}^{*} = \lambda_{j}^{*}\} \\ b = |SD|,\quad SD = \{(x_{i},x_{j}) | \lambda_{i} = \lambda_{j}, \lambda_{i}^{*} \ne \lambda_{j}^{*}\} \\ c = |DS|,\quad DS = \{(x_{i},x_{j}) | \lambda_{i} \ne \lambda_{j}, \lambda_{i}^{*} = \lambda_{j}^{*}\} \\ d = |DD|,\quad DD = \{(x_{i},x_{j}) | \lambda_{i} \ne \lambda_{j}, \lambda_{i}^{*} \ne \lambda_{j}^{*}\} \\ \]

剛一看到上述公式會感覺有點懵,那麼我們來直觀地感受一下上述表示式描述的是什麼。以 \(a\) 為例,\(SS\) 代表在 \(C\) 中屬於同一類且在 \(C^{*}\) 中屬於同一類的樣本,那麼 \(a\) 就等於 \(SS\) 中樣本的個數。以此類推,\(b\) 代表在 \(C\) 中屬於同一類,而在 \(C^{*}\) 中屬於不同類的樣本個數;\(c\) 代表在 \(C\) 中屬於不同類,而在 \(C^{*}\) 中屬於同一類的樣本個數;\(d\) 代表在 \(C\) 中屬於不同類,而在 \(C^{*}\) 中也屬於不同類的樣本個數。看到這,有沒有類似於評價指標中的精準率,召回率?https://www.cnblogs.com/zhaozhibo/p/14954685.html。由於每個樣本對 \((x_{i},x_{j})\) 只能出現在一個集合中,因此:\(a+b+c+d = m(m-1)/2\)。基於此,我們給出幾個常用的聚類效能度量外部指標:

  1. Jaccard係數(JC係數):\(JC = \dfrac{a}{a+b+c}\)
  2. Rand指數(RI指數):\(RI = (a+d)/\dfrac{m(m-1)}{2} = \dfrac{2(a+d)}{m(m-1)}\)
  3. FM指數(FMI):\(FMI = \sqrt{\dfrac{a}{a+b}\times \dfrac{a}{a+c}}\)

其實不難理解,JC係數描述的是分類正確的樣本數佔總樣本數的比例,RI指數描述的是分類正確(正樣本和負樣本)的個數佔總的樣本比例,FMI描述的是分類正確佔 “分類正確+分類錯誤” 的比例。因此這三個指標都是在 \([0,1]\),且越大越好。

2.2 內部指標法:

僅僅考慮聚類結果的劃分:\(C = \{C_{1}, C_{2},...,C_{k}\}\),定義:

\[avg(C) = \dfrac{2}{|C|(|C|-1)}\sum_{1\le i<j \le |C|}dist(x_{i},x_{j}) \\ diam(C) = max_{1\le i<j \le |C|}dist(x_{i},x_{j}) \\ d_{min}(C_{i},C_{j}) = min_{x_{i}\in C_{i}, x_{j} \in C_{j}} dist(x_{i},x_{j}) \\ d_{cen}(C_{i},C_{j})= dist(u_{i}, u_{j}) \]

其中,\(dist(\cdot,\cdot)\) 計算兩個向量之間的距離,定義為:

\[dist(x_{i},x_{j}) = (\sum_{u=1}^{n}(|x_{iu}-x_{ju}|^{p})^{\dfrac{1}{p}}) \]

  1. \(p=1\) 時,等效為曼哈頓距離:\(dist(x_{i},x_{j}) = (\sum_{u=1}^{n}(|x_{iu}-x_{ju}|^{}))\)
  2. \(p=2\) 時,等效為歐氏距離:\(dist(x_{i},x_{j}) = \sqrt{(\sum_{u=1}^{n}(|x_{iu}-x_{ju}|^{2}))}\)

\(u_{i}\) 代表第 \(i\) 個簇的中心點,即:\(u_{i} = \dfrac{1}{|C_{i}|}\sum_{i=1}^{|C_{i}|} x_{i}\)。顯然,\(avg(C)\) 代表簇 \(C\) 中任意兩個樣本距離的平均值,\(diam(C)\) 代表簇 \(C\) 內任意兩個樣本之間距離的最大值,\(d_{min}(C_{i},C_{j})\) 代表不同的簇中最近的兩個樣本之間的距離,\(d_{cen}(C_{i},C_{j})\) 代表不同簇中心點之間的距離。根據我們 “相同簇的樣本儘可能相似,不同簇的樣本儘可能不同” 的原則,\(avg(C_{i})\) 越小越好,而 \(d_{cen}(C_{i},C_{j})\) 越大越好;\(d_{min}(C_{i},C_{j})\) 越大越好,\(diam(C)\) 越小越好。因此我們有如下的內部度量指標:

  1. DB指數:\(DBI = \dfrac{1}{k}\sum_{i=1}^{k}max(\dfrac{avg(C_{i})+avg(C_{j})}{d_{cen}(C_{i},C_{j})})\)
  2. Dunn指數:\(min\{min\{ \dfrac{d_{min}(C_{i}, C_{j})}{max_{1 \le l \le k}diam(C_{l})}\}\}\)

顯然,DB越小越好,而DI越大越好。

三、K-means聚類演算法的實現

3.1 演算法描述

給定樣本集 \(D=\{x_{1},x_{2},...,x_{m}\}\) ,“K 均值” 演算法針對聚類所得簇劃分 \(C = \{C_{1}, C_{2},...,C_{k}\}\) 最小化平方誤差:

\[E = \sum_{i=1}^{k}\sum_{x \in C_{i}}||x-u_{i}||_{2}^{2} \]

其中,\(u_{i}\) 是簇 \(C_{i}\) 的均值向量,於是 \(E\) 描述了簇內樣本圍繞簇均值向量的緊密程度,\(E\) 值越小,則簇內樣本的相似度越高。但是優化起來並不容易,需要考慮樣本集 \(D\) 的所有可能的簇劃分,因此 \(k\) 均值採取了貪心策略,通過迭代優化來近似求解。具體的流程如下:

  1. 從樣本集中隨機選取 \(k\) 個向量作為初始均值向量 \(\{u_{1}, u_{2}, ..., u_{k}\}\),且簇 \(C_{i}\) 設定為空;
  2. 分別計算將所有的樣本與初始均值向量 \(\{u_{1}, u_{2}, ..., u_{k}\}\) 的距離,將與 \(u_{i}\) 最小距離的樣本 \(x_{j}\) 劃分入 \(C_{i}\),並以此類推得到第一輪的迭代結果
  3. 重新更新簇 \(C_{i}\) 的均值,\(\{u_{1}^{'}, u_{2}^{'}, ..., u_{k}^{'}\}\),然後重複步驟2
  4. 直到下一輪與上一輪的結果相同,停止更新

3.2 程式碼實現

# k-means cluster 
def kmeans(dataSet, k): 
    numSamples = dataSet.shape[0] 
    # first column stores which cluster this sample belongs to, 
    # second column stores the error between this sample and its centroid 
    clusterAssment = mat(zeros((numSamples, 2))) 
    clusterChanged = True 
 
    ## step 1: init centroids 
    centroids = initCentroids(dataSet, k) 
    while clusterChanged: 
        clusterChanged = False 
        ## for each sample 
        for i in xrange(numSamples): 
            minDist  = 100000.0 
            minIndex = 0 
            ## for each centroid 
            ## step 2: find the centroid who is closest 
            for j in range(k): 
                distance = euclDistance(centroids[j, :], dataSet[i, :]) 
                if distance < minDist: 
                    minDist  = distance 
                    minIndex = j 
             
            ## step 3: update its cluster 
            if clusterAssment[i, 0] != minIndex: 
                clusterChanged = True 
                clusterAssment[i, :] = minIndex, minDist**2 
 
        ## step 4: update centroids 
        for j in range(k): 
            pointsInCluster = dataSet[nonzero(clusterAssment[:, 0].A == j)[0]] 
            centroids[j, :] = mean(pointsInCluster, axis = 0) 
 
    print 'Congratulations, cluster complete!' 
    return centroids, clusterAssment 

相關文章