目錄
- 簡述
- K-means聚類
- 密度聚類
- 層次聚類
一、簡述
聚類算法是常見的無監督學習(無監督學習是在樣本的標簽未知的情況下,根據樣本的內在規律對樣本進行分類)。
在監督學習中我們常根據模型的誤差來衡量模型的好壞,通過優化損失函數來改善模型。而在聚類算法中是怎么來度量模型的好壞呢?聚類算法模型的性能度量大致有兩類:
1)將模型結果與某個參考模型(或者稱為外部指標)進行對比,個人覺得認為這種方法用的比較少
2)另一種是直接使用模型的內部屬性,比如樣本之間的距離(閔可夫斯基距離)來作為評判指標,這類稱為內部指標。
其中內部指標如下:

公式解釋如下:

距離定義
二、K-means聚類
K-means算法是聚類算法的一種,實現起來比較簡單,效果也不錯。K-means的思想很簡單,對於給定的樣本集,根據樣本之間距離的大小將樣本划分為K個簇(在這里K是需要預先設定好的)
思路:在進行划分簇時要盡量讓簇內的樣本之間的距離很小,讓簇與簇之間的距離盡量大。
在給定的數據集D的條件下,將數據集划分為K類,則K-means的數學模型可以表示:


其中Ci為第i類的集合,μi為第i類的簇心(該簇內所有樣本的均值,也稱為均值向量)
2.1 算法的流程:

(數據來源 機器學習-周志華版)
主要核心有兩步:
- 計算樣本到各簇心的距離,將樣本划分到距離最小的那一簇中;
- 在將所有的樣本都划分完之后,計算各簇心的樣本均值向量,然后將該均值向量設為新的簇心。而迭代的終止條件是當前均值向量和簇心是基本一致。
中止條件:迭代次數、最小平方誤差MSE、簇中心點變化率
K-Means采用的啟發式方式很簡單,下圖就可以形象的描述過程

2.2 對於K-means有兩個可以優化的地方:
1)在初始化時,隨機選擇K個樣本作為初始的簇心,倘若此時隨機的結果不好,比如兩個隨機的簇心挨得很近,這可能會導致模型收斂的速度減慢,常見的解決方式:K-Means++的優化。
- 先隨機選取一個樣本作為簇心,計算樣本到該簇心的距離;
- 隨機選擇下一個簇心,此時選取時會傾向於選擇和與最近簇心之間距離較大的樣本作為簇心,直到選擇完K個簇心。
2)每次迭代時都要計算所有樣本和所有簇心之間的距離,若此時樣本很大,或者簇心很多時,計算代價是非常大的。
方法一:Mini Batch K-Means--因此每次隨機抽取一小批樣本來更新當前的簇心(通過無放回隨機采樣);
采用小規模的數據子集(每次訓練使用的數據集是在訓練算法的時候隨機抽取的數據子集)減少計算時間,同時試圖優化目標函數;Mini Batch K-Means算法可以減少K-Means算法的收斂時間,而且產生的結果效果只是略差於標准K-Means算法
方法二: elkan K-Means--利用了兩邊之和大於等於第三邊,以及兩邊之差小於第三邊的三角形性質,來減少距離的計算。
2.3 K-means算法的優點:
1)原理簡單,實現容易,且收斂速度也較快。
2)聚類效果較好,而且可解釋性較強。
3)參數很少,需要調的參數只有簇的個數K。
2.4 K-means算法的缺點:
1)K值的選取比較難
2)對於非凸數據集收斂比較難
3)如果隱含類別的數據不平衡,則聚類效果不佳,比如隱含類型的方差不同,方差較大的類中的樣本可能會被聚類到其他類別中,在聚類時原則上沒啥影響,但是聚類或者說無監督學習大多時候都是一些預訓練,聚類后的數據可能之后會被用於其他的分類回歸模型中
4)對噪聲和異常點比較敏感
5)迭代得到的結果只是局部最優
2.5 二分K-Means算法
解決K-Means算法對初始簇心比較敏感的問題,二分K-Means算法是一種弱化初始質心的一種算法,具體思路步驟如下:
- 將所有樣本數據作為一個簇放到一個隊列中
- 從隊列中選擇一個簇進行K-means算法划分,划分為兩個子簇,並將子簇添加到隊列中
- 循環迭代第二步操作,直到中止條件達到(聚簇數量、最小平方誤差、迭代次數等)
- 隊列中的簇就是最終的分類簇集合
從隊列中選擇划分聚簇的規則一般有兩種方式;分別如下:
- 對所有簇計算誤差和SSE(SSE也可以認為是距離函數的一種變種),選擇SSE最大的聚簇進行划分操作(優選這種策略)
- 選擇樣本數據量最多的簇進行划分操作

2.6 k-medoids
k-medoids 和 k-means 不一樣的地方在於中心點的選取,在 k-means 中,我們將中心點取為當前 cluster 中所有數據點的平均值。並且我們已經證明在固定了各個數據點的 assignment 的情況下,這樣選取的中心點能夠把目標函數 J 最小化。然而在 k-medoids 中,我們將中心點的選取限制在當前 cluster 所包含的數據點的集合中。換句話說,在 k-medoids 算法中,我們將從當前 cluster 中選取這樣一個點——它到其他所有(當前 cluster 中的)點的距離之和最小——作為中心點。k-means 和 k-medoids 之間的差異就類似於一個數據樣本的均值 (mean) 和中位數 (median) 之間的差異:前者的取值范圍可以是連續空間中的任意值,而后者只能在給樣本給定的那些點里面選。
二、密度聚類與DBSCAN
DBSCAN(Density-Based Spatial Clustering of Applications with Noise,具有噪聲的基於密度的聚類方法)是一種很典型的密度聚類算法,和K-Means,BIRCH這些一般只適用於凸樣本集的聚類相比,DBSCAN既可以適用於凸樣本集,也可以適用於非凸樣本集。
同一類別的樣本,他們之間是緊密相連的,通過樣本密度來考察樣本之間的可連接性,並基於可連接樣本不斷擴展聚類簇以獲得最終的聚類結果。因此密度聚類也是不需要提前設置簇數K的值的。DBSCAN是基於一組領域來描述樣本集的緊密程度的。
密度聚類方法的指導思想: 只要樣本點的密度大於某個閾值,則將該樣本添加到最近的簇中。不太恰當的比喻:類似傳銷,畫圈找點發展下線,比較適合做檢測,找到離散點
DBSCAN算法相關的概念:
給定數據集D{x1, x2, ......xn}
1)ϵ- 鄰域:對於xi ,其鄰域中包含了所有與xi 的距離小於ϵ 的樣本
2)核心對象:對於任一樣本xj∈D,如果其ϵ-鄰域對應的N ϵ (xj)至少包含MinPts個樣本,即如果|N ϵ (xj)| ≥ MinPts,則xj是核心對象
3)密度直達:如果xi 位於xj 的ϵ-鄰域中,且xj 是核心對象。
4)密度可達:對於xi和xj,如果存在樣本樣本序列p1, p2,..., pT,滿足p1=xi,pT=xj,且pt+1由pt 密度直達,則稱xj 由xi 密度可達。也就是說,密度可達滿足傳遞性。此時序列中的傳遞樣本p1,p2,...,pT−1均為核心對象,因為只有核心對象才能使其他樣本密度直達。注意密度可達也不滿足對稱性,這個可以由密度直達的不對稱性得出
5)密度相連:對於xi和xj,如果存在核心對象樣本xk,使xi和xj均由xk密度可達,則稱xi和xj密度相連。注意密度相連關系是滿足對稱性的.
如下圖所示,對於給定的MinPts=3,x1是核心對象,x2由x1密度直達,x3由x1密度可達,x3由x4密度相連。

DBSCAN的核心思想:
有密度可達關系導出的最大的密度相連樣本集合。即分類簇C必須滿足:
1)連接性:xi∈ C,可以直接推出xi 與xj 密度相連,同簇內的元素必須滿足密度相連
2)最大性:xi∈ C,xj 有xi 密度可達可以推出xj ∈C
DBSCAN算法:

數據來源 機器學習-周志華版
1)根據預先給定的(ϵ,MinPts)確定核心對象的集合
2)從核心對象集合中任選一核心對象,找出由其密度可達的對象生成聚類簇
3)從核心對象中除掉已選的核心對象和已分配到簇中的核心對象(這說明在一個簇內肯能存在多個核心對象)
4)重復2,3 步驟,直到所有的核心對象被用完
DBSACN算法理解起來還是比較簡單的,但是也存在一些問題:
1)在所有的核心對象都被用完之后,可能還是會存在一些樣本點沒有被分配到任何簇中,此時我們認為這些樣本點是異常點
2)對於有的樣本可能會屬於多個核心對象,而且這些核心對象不是密度直達的,那么在我們的算法中事實上是采用了先來先到的原則
與K-means算法相比,DBSCAN算法有兩大特點
一是不需要預先設定簇數K的值
二是DBSCAN算法同時適用於凸集和非凸集,而K-means只適用於凸集,在非凸集上可能無法收斂。對於DBSCAN算法適用於稠密的數據或者是非凸集的數據,此時DBSCAB算法的效果要比K-means算法好很多。因此若數據不是稠密的,我們一般不用DBSCAN算法。
DBSCAN算法的優點:
1)可以對任意形狀的稠密數據進行聚類(包括非凸集)
2)可以在聚類是發現異常點,對異常點不敏感
3)聚類結果比較穩定,不會有什么偏倚,而K-means中初始值對結果有很大的影響
DBSCAN算法的缺點:
1)樣本集密度不均勻時,聚類間距相差很大時,聚類效果不佳
2)樣本集較大時,收斂時間長,可以用KD樹進行最近鄰的搜索
3)需要調試的參數比K-means多些,需要去調試ϵ 和MinPts參數(聯合調參,不同的組合的結果都不一樣)
四、層次聚類和AGNES算法
層次(Hierarchical methods)聚類試圖在不同的層次上對數據集進行划分,從而形成樹形的聚類結構,傳統的層次聚類算法主要分為兩大類算法:
- 凝聚的層次聚類:AGNES算法(AGglomerative NESting)==>采用自底向上的策略。最初將每個對象作為一個簇,然后這些簇根據某些准則被一步一步合並,兩個簇間的距離可以由這兩個不同簇中距離最近的數據點的相似度來確定;聚類的合並過程反復進行直到所有的對象滿足簇數目。
- 分裂的層次聚類:DIANA算法(DIvisive ANALysis)==>采用自頂向下的策略。首先將所有對象置於一個簇中,然后按照某種既定的規則逐漸細分為越來越小的簇(比如最大的歐式距離),直到達到某個終結條件(簇數目或者簇距離達到閾值)。
本文重點介紹:AGNES

4.1AGNES的核心思想:
- 先將數據集中的每個樣本看作一個初始聚類簇
- 每次迭代時找出距離最近的兩個簇進行合並,依次迭代知道簇類的個數達到我們指定的個數K為止。
這種方法的好處是隨着簇類數量的減少,需要計算的距離也會越來越少,而且相對K-means,不需要考慮初始化時隨機簇心對模型到來的影響。在這里主要有三種計算策略:

4.2 AGNES具體的算法流程:
(1) 將每個對象看作一類,計算兩兩之間的最小距離;
(2) 將距離最小的兩個類合並成一個新類;
(3) 重新計算新類與所有類之間的距離;
(4) 重復(2)、(3),直到所有類最后合並成一類。

數據來源 機器學習-周志華版
優點:
1,距離和規則的相似度容易定義,限制少;
2,不需要預先制定聚類數;
3,可以發現類的層次關系;
4,可以聚類成其它形狀
缺點:

2,奇異值也能產生很大影響;
3,算法很可能聚類成鏈狀
關於層次聚類,除了AGNES算法之外,還有BIRCH算法,BIRCH算法適用於數據量大,簇類K的數量較多的情況下,這種算法只需要遍歷一遍數據集既可以完成聚類,運行速度很快。BIRCH算法利用了一個類似於B+樹的樹結構來幫助我們快速聚類,一般我們將它稱為聚類特征數(簡稱CF Tree),BIRCH算法屬於自上向下的層次聚類算法(根據數據集的導入自上而下不斷的分裂加層來構建CF 樹),CF 樹中的每個葉節點就對應着一個簇。因此BIRCH算法事實上就是在構建一顆樹,構建完之后,樹的葉節點就是對應的簇,葉節點中的樣本就是每個簇內的樣本。BIRCH適用於大樣本集,收斂速度快,且不需要設定簇數K的值,但是要設定樹的結構約束值(比如葉節點中樣本的個數,內節點中樣本的個數),此外BIRCH算法對於數據特征維度很大的樣本(比如大於20維)不適合。
BIRCH算法(平衡迭代削減聚類法):聚類特征使用3元組進行一個簇的相關信息,通過構建滿足分枝因子和簇直徑限制的聚類特征樹來求聚類,聚類特征樹其實是一個具有兩個參數分枝因子和類直徑的高度平衡樹;分枝因子規定了樹的每個節點的子女的最多個數,而類直徑體現了對這一類點的距離范圍;非葉子節點為它子女的最大特征值;聚類特征樹的構建可以是動態過程的,可以隨時根據數據對模型進行更新操作。
優缺點:
適合大規模數據集,線性效率;
只適合分布呈凸形或者球形的數據集、需要給定聚類個數和簇之間的相關參數;
說明:
1、K-Means和KNN差異和相似:
- 區別:K-Means是無監督學習的聚類算法,沒有樣本輸出;而KNN是監督學習的分類算法,有對應的類別輸出。KNN基本不需要訓練,對測試集里面的點,只需要找到在訓練集中最近的k個點,用這最近的k個點的類別來決定測試點的類別。而K-Means則有明顯的訓練過程,找到k個類別的最佳質心,從而決定樣本的簇類別。
- 相似點:兩個算法都包含一個過程,即找出和某一個點最近的點。兩者都利用了最近鄰(nearest neighbors)的思想。
2、k-Means和K-Medoids
| k-Means |
K-Medoids |
| 初始據點隨機選取 |
初始隨機據點限定在樣本點中 |
| 取出同一類別的所有樣本,求每一列的平均值,得到新的中心向量, 使用Means(均值)作為聚點,對outliers(極值)很敏感. |
遍歷中心樣本,該中心樣本划分出來的該簇樣本,遍歷該簇樣本,找出離所有樣本距離最小的樣本,代替舊中心。使用Medoids(中位數)作為聚點。 |
| 中心點不一定是序列上的點 |
中心點一定在序列類,並且距離各點最小 |
| 對數據要求高,要求數據點處於歐式空間中 |
可適用類別(categorical)類型的特征 |
| 時間復雜度:O(n*k*t),t為迭代次數 |
時間復雜度:O(n^2 *k*t),t為迭代次數 |
| K-Means 算法對小規模數據集較高效(efficient for smaller data sets) |
K-Medoids算法對大規模數據性能更好,但伸縮性較差 |
| 相同點 |
|
| 都有可能陷入局部最優解的困境之中 |
|
| K的含義相同,都需要開始人為設定簇數目 |
|
| 都是無監督算法 |
3、k-Means和K-Medoids的算法實現:
k-Means:
1 # Author:yifan
2 #K-means
3 from numpy import *
4 import time
5 import matplotlib.pyplot as plt
6 import numpy as np
7
8 # euclDistance函數計算兩個向量之間的歐氏距離
9 def euclDistance(vector1, vector2):
10 return sqrt(sum(power(vector2 - vector1, 2)))
11 # initCentroids選取任意數據集中任意樣本點作為初始均值點
12 # dataSet: 數據集, k: 人為設定的聚類簇數目
13 # centroids: 隨機選取的初始均值點
14 def initCentroids(dataSet, k):
15 numSamples, dim = dataSet.shape
16 centroids = zeros((k, dim)) #k行dim的0矩陣
17 for i in range(k):
18 index = int(random.uniform(0, numSamples)) #從0到numSamples中隨機選取一個數字
19 centroids[i, :] = dataSet[index, :] #隨機選出k個數字,即為本函數的目的
20 # print(centroids)
21 return centroids
22
23 # kmeans: k-means聚類功能主函數
24 # 輸入:dataSet-數據集,k-人為設定的聚類簇數目
25 # 輸出:centroids-k個聚類簇的均值點,clusterAssment-聚類簇中的數據點
26 def kmeans(dataSet, k):
27 numSamples = dataSet.shape[0]
28 clusterAssment = mat(zeros((numSamples, 2)))
29 # clusterAssment第一列存儲當前點所在的簇
30 # clusterAssment第二列存儲點與質心點的距離
31 clusterChanged = True #用於遍歷的標記
32 ## 步驟一: 初始化均值點
33 centroids = initCentroids(dataSet, k)
34 while clusterChanged:
35 clusterChanged = False
36 ## 遍歷每一個樣本點
37 for i in range(numSamples):
38 minDist = 100000.0 # minDist:最近距離,初始定一個較大的值
39 minIndex = 0 # minIndex:最近的均值點編號
40 ## 步驟二: 尋找最近的均值點
41 for j in range(k):
42 distance = euclDistance(centroids[j, :], dataSet[i, :]) #每個點和中心點的距離,共有k個值
43 if distance < minDist:
44 #循環去找最小的那個
45 minDist = distance
46 minIndex = j
47 ## 步驟三: 更新所屬簇
48 if clusterAssment[i, 0] != minIndex:
49 clusterChanged = True
50 clusterAssment[i, :] = minIndex, minDist**2 #記錄序號和點與質心點的距離
51 ## 步驟四: 更新簇的均值點
52 for j in range(k):
53 pointsInCluster = dataSet[nonzero(clusterAssment[:, 0] == j)[0]] #當前屬於j類的序號
54 print(pointsInCluster)
55 print('ddddd')
56 # print(clusterAssment[:, 0])
57 centroids[j, :] = mean(pointsInCluster, axis = 0) #按照 列計算均值
58 print ('Congratulations, cluster complete!')
59 return centroids, clusterAssment
60
61 # showCluster利用pyplot繪圖顯示聚類結果(二維平面)
62 # 輸入:dataSet-數據集,k-聚類簇數目,centroids-聚類簇的均值點,clusterAssment-聚類簇中數據點
63 def showCluster(dataSet, k, centroids, clusterAssment):
64 numSamples, dim = dataSet.shape
65 if dim != 2:
66 print ("Sorry, the dimension of your data is not 2!")
67 return 1
68 mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', '<r', 'pr']
69 if k > len(mark):
70 return 1
71 # 畫出所有的樣本點
72 for i in range(numSamples):
73 markIndex = int(clusterAssment[i, 0])
74 plt.plot(dataSet[i, 0], dataSet[i, 1], mark[markIndex])
75 mark = ['Dr', 'Db', 'Dg', 'Dk', '^b', '+b', 'sb', 'db', '<b', 'pb']
76 # 標記簇的質心
77 for i in range(k):
78 plt.plot(centroids[i, 0], centroids[i, 1], mark[i], markersize = 12)
79 plt.show()
80
81
82 ## step 1: 構造數據
83 matrix1=np.random.random((12,2))
84 matrix2=np.random.random((12,2))
85 matrix3=np.random.random((12,2))
86 matrix4=np.random.random((12,2))
87 for i in range(12):
88 matrix2[i,0] = matrix2[i,0]+2
89 matrix3[i,1] = matrix3[i,1]+2
90 matrix4[i,:] = matrix4[i,:]+2
91 dataSet = np.vstack((matrix1,matrix2,matrix3,matrix4))
92 # print(dataSet)
93 ## step 2: 開始聚類...
94 # dataSet = mat(dataSet)
95 k = 4
centroids, clusterAssment = kmeans(dataSet, k)
## step 3: 顯示聚類結果
showCluster(dataSet, k, centroids, clusterAssment)
K-Medoids:
1 # Author:yifan
2 from numpy import *
3 import time
4 import matplotlib.pyplot as plt
5 import numpy as np
6
7 # euclDistance函數計算兩個向量之間的歐氏距離
8 def euclDistance(vector1, vector2):
9 return sqrt(sum(power(vector2 - vector1, 2)))
10 # initCentroids選取任意數據集中任意樣本點作為初始均值點
11 # dataSet: 數據集, k: 人為設定的聚類簇數目
12 # centroids: 隨機選取的初始均值點
13 def initCentroids(dataSet, k):
14 numSamples, dim = dataSet.shape
15 centroids = zeros((k, dim)) #k行dim的0矩陣
16 for i in range(k):
17 index = int(random.uniform(0, numSamples)) #從0到numSamples中隨機選取一個數字
18 centroids[i, :] = dataSet[index, :] #隨機選出k個數字,即為本函數的目的
19 # print(centroids)
20 return centroids
21 #定義每個點到其他點的距離和。步驟四的更新簇的均值點時會用到
22 def costsum( vector1,matrix1):
23 sum = 0
24 for i in range(matrix1.shape[0]):
25 sum += euclDistance(matrix1[i,:], vector1)
26 return sum
27 # kmediod: k-mediod聚類功能主函數
28 # 輸入:dataSet-數據集,k-人為設定的聚類簇數目
29 # 輸出:centroids-k個聚類簇的均值點,clusterAssment-聚類簇中的數據點
30 def kmediod(dataSet, k):
31 numSamples = dataSet.shape[0]
32 clusterAssment = mat(zeros((numSamples, 2)))
33 # clusterAssment第一列存儲當前點所在的簇
34 # clusterAssment第二列存儲點與質心點的距離
35 clusterChanged = True #用於遍歷的標記
36 ## 步驟一: 初始化均值點
37 centroids = initCentroids(dataSet, k)
38 while clusterChanged:
39 clusterChanged = False
40 ## 遍歷每一個樣本點
41 for i in range(numSamples):
42 minDist = 100000.0 # minDist:最近距離,初始定一個較大的值
43 minIndex = 0 # minIndex:最近的均值點編號
44 ## 步驟二: 尋找最近的均值點
45 for j in range(k):
46 distance = euclDistance(centroids[j, :], dataSet[i, :]) #每個點和中心點的距離,共有k個值
47 if distance < minDist:
48 #循環去找最小的那個
49 minDist = distance
50 minIndex = j
51 ## 步驟三: 更新所屬簇
52 if clusterAssment[i, 0] != minIndex:
53 clusterChanged = True
54 clusterAssment[i, :] = minIndex, minDist**2 #記錄序號和點與質心點的距離
55 ## 步驟四: 更新簇核心點
56 for j in range(k):
57 pointsInCluster = dataSet[nonzero(clusterAssment[:, 0] == j)[0]] #當前屬於j類的序號
58 mincostsum = costsum(centroids[j,:],pointsInCluster)
59 for point in range(pointsInCluster.shape[0]):
60 cost = costsum( pointsInCluster[point, :],pointsInCluster)
61 if cost < mincostsum:
62 mincostsum = cost
63 centroids[j, :] = pointsInCluster[point, :]
64 print ('Congratulations, cluster complete!')
65 return centroids, clusterAssment
66
67 # showCluster利用pyplot繪圖顯示聚類結果(二維平面)
68 # 輸入:dataSet-數據集,k-聚類簇數目,centroids-聚類簇的均值點,clusterAssment-聚類簇中數據點
69 def showCluster(dataSet, k, centroids, clusterAssment):
70 numSamples, dim = dataSet.shape
71 if dim != 2:
72 print ("Sorry, the dimension of your data is not 2!")
73 return 1
74 mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', '<r', 'pr']
75 if k > len(mark):
76 return 1
77 # 畫出所有的樣本點
78 for i in range(numSamples):
79 markIndex = int(clusterAssment[i, 0])
80 plt.plot(dataSet[i, 0], dataSet[i, 1], mark[markIndex])
81 mark = ['Dr', 'Db', 'Dg', 'Dk', '^b', '+b', 'sb', 'db', '<b', 'pb']
82 # 標記簇的質心
83 for i in range(k):
84 plt.plot(centroids[i, 0], centroids[i, 1], mark[i], markersize = 12)
85 plt.show()
86
87
88 ## step 1: 構造數據
89 matrix1=np.random.random((12,2))
90 matrix2=np.random.random((12,2))
91 matrix3=np.random.random((12,2))
92 matrix4=np.random.random((12,2))
93 for i in range(12):
94 matrix2[i,0] = matrix2[i,0]+2
95 matrix3[i,1] = matrix3[i,1]+2
96 matrix4[i,:] = matrix4[i,:]+2
97 dataSet = np.vstack((matrix1,matrix2,matrix3,matrix4))
98 # print(dataSet)
99 ## step 2: 開始聚類...
100 # dataSet = mat(dataSet)
101 k = 4
102 centroids, clusterAssment = kmediod(dataSet, k)
## step 3: 顯示聚類結果
showCluster(dataSet, k, centroids, clusterAssment)
結果均為;

附件一:手寫推導過程練習

