0x01 層次聚類簡介
層次聚類算法(Hierarchical Clustering)將數據集划分為一層一層的clusters,后面一層生成的clusters基於前面一層的結果。層次聚類算法一般分為兩類:
- Divisive 層次聚類:又稱自頂向下(top-down)的層次聚類,最開始所有的對象均屬於一個cluster,每次按一定的准則將某個cluster 划分為多個cluster,如此往復,直至每個對象均是一個cluster。
- Agglomerative 層次聚類:又稱自底向上(bottom-up)的層次聚類,每一個對象最開始都是一個cluster,每次按一定的准則將最相近的兩個cluster合並生成一個新的cluster,如此往復,直至最終所有的對象都屬於一個cluster。
下圖直觀的給出了層次聚類的思想以及以上兩種聚類策略的異同:
層次聚類算法是一種貪心算法(greedy algorithm),因其每一次合並或划分都是基於某種局部最優的選擇。
0x02 自頂向下的層次聚類算法(Divisive)
2.1 Hierarchical K-means算法
Hierarchical K-means算法是“自頂向下”的層次聚類算法,用到了基於划分的聚類算法那K-means,算法思路如下:
- 首先,把原始數據集放到一個簇C,這個簇形成了層次結構的最頂層
- 使用K-means算法把簇C划分成指定的K個子簇Ci,i=1,2,…,k,形成一個新的層
- 對於步驟2所生成的K個簇,遞歸使用K-means算法划分成更小的子簇,直到每個簇不能再划分(只包含一個數據對象)或者滿足設定的終止條件。
如下圖,展示了一組數據進行了二次K-means算法的過程:
Hierarchical K-means算法一個很大的問題是,一旦兩個點在最開始被划分到了不同的簇,即使這兩個點距離很近,在后面的過程中也不會被聚類到一起。
對於以上的例子,紅色橢圓框中的對象聚類成一個簇可能是更優的聚類結果,但是由於橙色對象和綠色對象在第一次K-means就被划分到不同的簇,之后也不再可能被聚類到同一個簇。
Bisecting k-means聚類算法,即二分k均值算法,是分層聚類(Hierarchical clustering)的一種。更多關於二分k均值法,可以查看聚類算法之K-Means。
0x03 自底向上的層次聚類算法(Agglomerative)
層次聚類的合並算法通過計算兩類數據點間的相似性,對所有數據點中最為相似的兩個數據點進行組合,並反復迭代這一過程。簡單的說層次聚類的合並算法是通過計算每一個類別的數據點與所有數據點之間的距離來確定它們之間的相似性,距離越小,相似度越高。並將距離最近的兩個數據點或類別進行組合,生成聚類樹。
相比於Hierarchical K-means算法存在的問題,Agglomerative Clustering算法能夠保證距離近的對象能夠被聚類到一個簇中,該算法采用的“自底向上”聚類的思路。
3.1 Agglomerative算法示例
對於如下數據:
1、 將A到F六個點,分別生成6個簇
2、 找到當前簇中距離最短的兩個點,這里我們使用單連鎖的方式來計算距離,發現A點和B點距離最短,將A和B組成一個新的簇,此時簇列表中包含五個簇,分別是{A,B},{C},{D},{E},{F},如下圖所示:
3、重復步驟2、發現{C}和{D}的距離最短,連接之,然后是簇{C,D}和簇{E}距離最短,依次類推,直到最后只剩下一個簇,得到如下所示的示意圖:
4、此時原始數據的聚類關系是按照層次來組織的,選取一個簇間距離的閾值,可以得到一個聚類結果,比如在如下紅色虛線的閾值下,數據被划分為兩個簇:簇{A,B,C,D,E}和簇{F}
Agglomerative聚類算法的優點是能夠根據需要在不同的尺度上展示對應的聚類結果,缺點同Hierarchical K-means算法一樣,一旦兩個距離相近的點被划分到不同的簇,之后也不再可能被聚類到同一個簇,即無法撤銷先前步驟的工作。另外,Agglomerative性能較低,並且因為聚類層次信息需要存儲在內存中,內存消耗大,不適用於大量級的數據聚類。
3.2 對象之間的距離衡量
衡量兩個對象之間的距離的方式有多種,對於數值類型(Numerical)的數據,常用的距離衡量准則有 Euclidean 距離、Manhattan 距離、Chebyshev 距離、Minkowski 距離等等。
3.3 Cluster 之間的距離衡量
除了需要衡量對象之間的距離之外,層次聚類算法還需要衡量cluster之間的距離,常見的cluster之間的衡量方法有 Single-link 方法、Complete-link 方法、UPGMA(Unweighted Pair Group Method using arithmetic Averages)方法、WPGMA(Weighted Pair Group Method using arithmetic Averages)方法、Centroid 方法(又稱 UPGMC,Unweighted Pair Group Method using Centroids)、Median 方法(又稱 WPGMC,weighted Pair Group Method using Centroids)、Ward 方法。前面四種方法是基於圖的,因為在這些方法里面,cluster是由樣本點或一些子cluster(這些樣本點或子cluster之間的距離關系被記錄下來,可認為是圖的連通邊)所表示的;后三種方法是基於幾何方法的(因而其對象間的距離計算方式一般選用 Euclidean 距離),因為它們都是用一個中心點來代表一個cluster。
假設Ci和Cj為兩個cluster,則前四種方法定義的Ci和Cj之間的距離如下表所示:
簡單的理解:
- Single Linkage:方法是將兩個組合數據點中距離最近的兩個數據點間的距離作為這兩個組合數據點的距離。這種方法容易受到極端值的影響。兩個很相似的組合數據點可能由於其中的某個極端的數據點距離較近而組合在一起。
- Complete Linkage:Complete Linkage的計算方法與Single Linkage相反,將兩個組合數據點中距離最遠的兩個數據點間的距離作為這兩個組合數據點的距離。Complete Linkage的問題也與Single Linkage相反,兩個不相似的組合數據點可能由於其中的極端值距離較遠而無法組合在一起。
- Average Linkage:Average Linkage的計算方法是計算兩個組合數據點中的每個數據點與其他所有數據點的距離。將所有距離的均值作為兩個組合數據點間的距離。這種方法計算量比較大,但結果比前兩種方法更合理。
其中 Single-link 定義兩個 cluster 之間的距離為兩個 cluster 之間距離最近的兩個對象間的距離,這樣在聚類的過程中就可能出現鏈式效應,即有可能聚出長條形狀的 cluster;而 Complete-link 則定義兩個 cluster 之間的距離為兩個 cluster 之間距離最遠的兩個對象間的距離,這樣雖然避免了鏈式效應,但其對異常樣本點(不符合數據集的整體分布的噪聲點)卻非常敏感,容易產生不合理的聚類;UPGMA 正好是 Single-link 和 Complete-link 的一個折中,其定義兩個 cluster 之間的距離為兩個 cluster 之間兩個對象間的距離的平均值;而 WPGMA 則計算的是兩個 cluster 之間兩個對象之間的距離的加權平均值,加權的目的是為了使兩個 cluster 對距離的計算的影響在同一層次上,而不受 cluster 大小的影響(其計算方法這里沒有給出,因為在運行層次聚類算法時,我們並不會直接通過樣本點之間的距離之間計算兩個 cluster 之間的距離,而是通過已有的 cluster 之間的距離來計算合並后的新的 cluster 和剩余cluster 之間的距離,這種計算方法將由下一部分中的 Lance-Williams 方法給出)。
Centroid/UPGMC方法給每一個cluster 計算一個質心,兩個 cluster 之間的距離即為對應的兩個質心之間的距離,一般計算方法如下:
3.4 常用Agglomerative算法
0x04 利用 Scipy 實現層次聚類
4.1 生成測試數據
from sklearn.datasets import make_blobs import matplotlib.pyplot as plt centers = [[1, 1], [-1, -1], [1, -1]] X, labels_true = make_blobs(n_samples=750, centers=centers, cluster_std=0.4, random_state=0) plt.figure(figsize=(10, 8)) plt.scatter(X[:, 0], X[:, 1], c='b') plt.show()
4.2 SciPy層次聚類實現
SciPy 里面進行層次聚類非常簡單,直接調用 linkage 函數,一行代碼即可搞定:
from scipy.cluster.hierarchy import linkage Z = linkage(X, method='ward', metric='euclidean') print(Z.shape) print(Z[: 5])
以上即進行了一次cluster間距離衡量方法為Ward、樣本間距離衡量准則為Euclidean距離的Agglomerative層次聚類,其中method參數可以為’single’、’complete’、’average’、’weighted’、’centroid’、’median’、’ward’中的一種,分別對應我們前面講到的 Single-link、Complete-link、UPGMA、WPGMA、UPGMC、WPGMC、Ward 方法,而樣本間的距離衡量准則也可以由 metric 參數調整。
linkage 函數的返回值 Z 為一個維度(n-1)*4的矩陣,記錄的是層次聚類每一次的合並信息,里面的 4 個值分別對應合並的兩個cluster的序號、兩個cluster之間的距離以及本次合並后產生的新的 cluster 所包含的樣本點的個數。
4.3 畫出樹形圖
SciPy 中給出了根據層次聚類的結果 Z 繪制樹形圖的函數dendrogram,我們由此畫出本次實驗中的最后 20 次的合並過程。
from scipy.cluster.hierarchy import dendrogram plt.figure(figsize=(10, 8)) dendrogram(Z, truncate_mode='lastp', p=20, show_leaf_counts=False, leaf_rotation=90, leaf_font_size=15, show_contracted=True) plt.show()
得到的樹形圖如下所示:
可以看到,該樹形圖的最后兩次合並相比之前合並過程的合並距離要大得多,由此可以說明最后兩次合並是不合理的;因而對於本數據集,該算法可以很好地區分出 3 個 cluster(和實際相符),分別在上圖中由三種顏色所表示。
4.4、獲取聚類結果
在得到了層次聚類的過程信息 Z 后,我們可以使用 fcluster 函數來獲取聚類結果。可以從兩個維度來得到距離的結果,一個是指定臨界距離 d,得到在該距離以下的未合並的所有 cluster 作為聚類結果;另一個是指定 cluster 的數量 k,函數會返回最后的 k 個 cluster 作為聚類結果。使用哪個維度由參數 criterion 決定,對應的臨界距離或聚類的數量則由參數 t 所記錄。fcluster 函數的結果為一個一維數組,記錄每個樣本的類別信息。
from scipy.cluster.hierarchy import fcluster # 根據臨界距離返回聚類結果 d = 15 labels_1 = fcluster(Z, t=d, criterion='distance') print(labels_1[: 100]) # 打印聚類結果 print(len(set(labels_1))) # 看看在該臨界距離下有幾個 cluster # 根據聚類數目返回聚類結果 k = 3 labels_2 = fcluster(Z, t=k, criterion='maxclust') print(labels_2[: 100]) list(labels_1) == list(labels_2) # 看看兩種不同維度下得到的聚類結果是否一致 # 聚類的結果可視化,相同的類的樣本點用同一種顏色表示 plt.figure(figsize=(10, 8)) plt.scatter(X[:, 0], X[:, 1], c=labels_2, cmap='prism') plt.show()
上圖的聚類結果和實際的數據分布基本一致,但有幾點值得注意,一是在聚類之前我們沒法知道合理的聚類的數目或者最大的距離臨界值,只有在得到全部的層次聚類信息並對其進行分析后我們才能預估出一個較為合理的數值;二是本次實驗的數據集比較簡單,所以聚類的結果較好,但對於復雜的數據集(比如非凸的、噪聲點比較多的數據集),層次聚類算法有其局限性。
4.5 比較不同方法下的聚類結果
最后,我們對同一份樣本集進行了 cluster 間距離衡量准則分別為 Single-link、Complete-link、UPGMA(Average)和 Ward 的 Agglomerative 層次聚類,取聚類數目為 3,代碼如下:
from time import time import numpy as np from sklearn.datasets import make_blobs from scipy.cluster.hierarchy import linkage, fcluster from sklearn.metrics.cluster import adjusted_mutual_info_score import matplotlib.pyplot as plt # 生成樣本點 centers = [[1, 1], [-1, -1], [1, -1]] X, labels = make_blobs(n_samples=750, centers=centers, cluster_std=0.4, random_state=0) # 可視化聚類結果 def plot_clustering(X, labels, title=None): plt.scatter(X[:, 0], X[:, 1], c=labels, cmap='prism') if title is not None: plt.title(title, size=17) plt.axis('off') plt.tight_layout() # 進行 Agglomerative 層次聚類 linkage_method_list = ['single', 'complete', 'average', 'ward'] plt.figure(figsize=(10, 8)) ncols, nrows = 2, int(np.ceil(len(linkage_method_list) / 2)) plt.subplots(nrows=nrows, ncols=ncols) for i, linkage_method in enumerate(linkage_method_list): print('method %s:' % linkage_method) start_time = time() Z = linkage(X, method=linkage_method) labels_pred = fcluster(Z, t=3, criterion='maxclust') print('Adjust mutual information: %.3f' % adjusted_mutual_info_score(labels, labels_pred)) print('time used: %.3f seconds' % (time() - start_time)) plt.subplot(nrows, ncols, i + 1) plot_clustering(X, labels_pred, '%s linkage' % linkage_method) plt.show()
可以得到 4 種方法下的聚類結果如圖所示:
在上面的過程中,我們還為每一種聚類產生的結果計算了一個用於評估聚類結果與樣本的真實類之間的相近程度的AMI(Adjust Mutual Information)量,該量越接近於 1 則說明聚類算法產生的類越接近於真實情況。程序的打印結果如下:
method single: Adjust mutual information: 0.002 time used: 0.010 seconds method complete: Adjust mutual information: 0.840 time used: 0.021 seconds method average: Adjust mutual information: 0.945 time used: 0.027 seconds method ward: Adjust mutual information: 0.956 time used: 0.021 seconds
從上面的圖和 AMI 量的表現來看,Single-link 方法下的層次聚類結果最差,它幾乎將所有的點都聚為一個 cluster,而其他兩個 cluster 則都僅包含個別稍微有點偏離中心的樣本點,這充分體現了 Single-link 方法下的“鏈式效應”,也體現了 Agglomerative 算法的一個特點,即“贏者通吃”(rich getting richer): Agglomerative 算法傾向於聚出不均勻的類,尺寸大的類傾向於變得更大,對於 Single-link 和 UPGMA(Average) 方法尤其如此。由於本次實驗的樣本集較為理想,因此除了 Single-link 之外的其他方法都表現地還可以,但當樣本集變復雜時,上述“贏者通吃” 的特點會顯現出來。
0x05 利用 Sklearn 實現層次聚類
除了Scipy外,scikit-learn也提供了層次聚類的方法sklearn.cluster.AgglomerativeClustering,使用示例
from sklearn.datasets import make_blobs from sklearn.cluster import AgglomerativeClustering import matplotlib.pyplot as plt # 生成樣本點 centers = [[1, 1], [-1, -1], [1, -1]] X, labels = make_blobs(n_samples=750, centers=centers, cluster_std=0.4, random_state=0) clustering = AgglomerativeClustering(n_clusters=3, linkage='ward').fit(X) plt.figure(figsize=(10, 8)) plt.scatter(X[:, 0], X[:, 1], c=clustering.labels_, cmap='prism') plt.show()
參考鏈接:
0x06 轉載
https://www.biaodianfu.com/hierarchical-clustering.html