聚類演算法與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\) 代表在 \(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\)。基於此,我們給出幾個常用的聚類效能度量外部指標:
- Jaccard係數(JC係數):\(JC = \dfrac{a}{a+b+c}\)
- Rand指數(RI指數):\(RI = (a+d)/\dfrac{m(m-1)}{2} = \dfrac{2(a+d)}{m(m-1)}\)
- 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}\}\),定義:
其中,\(dist(\cdot,\cdot)\) 計算兩個向量之間的距離,定義為:
- 當 \(p=1\) 時,等效為曼哈頓距離:\(dist(x_{i},x_{j}) = (\sum_{u=1}^{n}(|x_{iu}-x_{ju}|^{}))\)
- 當 \(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)\) 越小越好。因此我們有如下的內部度量指標:
- DB指數:\(DBI = \dfrac{1}{k}\sum_{i=1}^{k}max(\dfrac{avg(C_{i})+avg(C_{j})}{d_{cen}(C_{i},C_{j})})\)
- 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}\}\) 最小化平方誤差:
其中,\(u_{i}\) 是簇 \(C_{i}\) 的均值向量,於是 \(E\) 描述了簇內樣本圍繞簇均值向量的緊密程度,\(E\) 值越小,則簇內樣本的相似度越高。但是優化起來並不容易,需要考慮樣本集 \(D\) 的所有可能的簇劃分,因此 \(k\) 均值採取了貪心策略,通過迭代優化來近似求解。具體的流程如下:
- 從樣本集中隨機選取 \(k\) 個向量作為初始均值向量 \(\{u_{1}, u_{2}, ..., u_{k}\}\),且簇 \(C_{i}\) 設定為空;
- 分別計算將所有的樣本與初始均值向量 \(\{u_{1}, u_{2}, ..., u_{k}\}\) 的距離,將與 \(u_{i}\) 最小距離的樣本 \(x_{j}\) 劃分入 \(C_{i}\),並以此類推得到第一輪的迭代結果
- 重新更新簇 \(C_{i}\) 的均值,\(\{u_{1}^{'}, u_{2}^{'}, ..., u_{k}^{'}\}\),然後重複步驟2
- 直到下一輪與上一輪的結果相同,停止更新
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