層次聚類算法與之前所講的順序聚類有很大不同,它不再產生單一聚類,而是產生一個聚類層次。說白了就是一棵層次樹。介紹層次聚類之前,要先介紹一個概念——嵌套聚類。講的簡單點,聚類的嵌套與程序的嵌套一樣,一個聚類中R1包含了另一個R2,那這就是R2嵌套在R1中,或者說是R1嵌套了R2。具體說怎么算嵌套呢?聚類R1={{x1,x2},{x3},{x4,x5}嵌套在聚類R2={{x1,x2,x3},{x4,x5}}中,但並不嵌套在聚類R3={{x1,x4},{x3},{x2,x5}}中。
層次聚類算法是將所有的樣本點自底向上合並組成一棵樹或者自頂向下分裂成一棵樹的過程,這兩種方式分別稱為凝聚和分裂。
凝聚層次算法:
初始階段,將每個樣本點分別當做其類簇,然后合並這些原子類簇直至達到預期的類簇數或者其他終止條件。
分裂層次算法:
初始階段,將所有的樣本點當做同一類簇,然后分裂這個大類簇直至達到預期的類簇數或者其他終止條件。
兩種算法的代表:
傳統的凝聚層次聚類算法有AGENES,初始時,AGENES將每個樣本點自為一簇,然后這些簇根據某種准則逐漸合並,例如,如果簇C1中的一個樣本點和簇C2中的一個樣本點之間的距離是所有不同類簇的樣本點間歐幾里得距離最近的,則認為簇C1和簇C2是相似可合並的。
傳統的分裂層次聚類算法有DIANA,初始時DIANA將所有樣本點歸為同一類簇,然后根據某種准則進行逐漸分裂,例如類簇C中兩個樣本點A和B之間的距離是類簇C中所有樣本點間距離最遠的一對,那么樣本點A和B將分裂成兩個簇C1和C2,並且先前類簇C中其他樣本點根據與A和B之間的距離,分別納入到簇C1和C2中,例如,類簇C中樣本點O與樣本點A的歐幾里得距離為2,與樣本點B的歐幾里得距離為4,因為Distance(A,O)<Distance(B,O)那么O將納入到類簇C1中。
層次聚類算法產生一個嵌套聚類的層次,算法最多包含N步,在第t步,執行的操作就是在前t-1步的聚類基礎上生成新聚類。我這里只講合並,因為前一階段正好課題用到,另外就是合並更容易理解和實現。當然分裂其實就是合並的相反過程。
令g(Ci,Cj)為所有可能的X聚類對的函數,此函數用於測量兩個聚類之間的近鄰性,用t表示當前聚類的層次級別。通用合並算法的偽碼描述如下:
1. 初始化:
a) 選擇Â0={{x1},…,{xN}}
b) 令t=0
2. 重復執行以下步驟:
a) t=t+1
b) 在Ât-1中選擇一組(Ci,Cj),滿足
c) 定義Cq=CiÈCj,並且產生新聚類Ât=(Ât-1-{Ci,Cj})È{Cq}
直到所有向量全被加入到單一聚類中。
這一方法在t層時將兩個向量合並,那么這兩個向量在以后的聚類過程中的后繼聚類都是相同的,也就是說一旦它們走到一起,那么以后就不會再分離……(很專一哦O(∩_∩)O~)。這也就引出了這個算法的缺點,當在算法開始階段,若出現聚類錯誤,那么這種錯誤將一直會被延續,無法修改。在層次t上,有N-t個聚類,為了確定t+1層上要合並的聚類對,必須考慮(N-t)(N-t-1)/2個聚類對。這樣,聚類過程總共要考慮的聚類對數量就是(N-1)N(N+1)/6,也就是說整個算法的時間復雜度是O(N3)。
舉例來說,如果令X={x1, x2, x3, x4, x5},其中x1=[1, 1]T, x2=[2, 1]T, x3=[5, 4]T, x4=[6, 5]T, x5=[6.5, 6]T。那么合並算法執行的過程可以用下面的圖來表示。
P(X)是不相似矩陣
該算法從核心過程上來講,就是先計算出數據集中向量之間的距離,記為距離矩陣(也叫不相似矩陣)。接着通過不斷的對矩陣更新,完成聚類。矩陣更新算法的偽碼描述如下:
1. 初始化:
a) Â0={{x1},…,{xN}}
b) P0=P(X) (距離矩陣)
c) t=0
2. 重復執行以下步驟:
a) t=t+1
b) 合並Ci和Cj為Cq,這兩個聚類滿足d(Ci,Cj)=minr,s=1,…,N,r≠sd(Cr,Cs)
c) 刪除第i和j行,第i和j列,同時插入新的行和列,新的行列為新合並的類Cq與所有其他聚類之間的距離值
直到將所有向量合並到一個聚類中
大家可以看到,層次聚類算法的輸出結果總是一個聚類,這往往不是我們想要的,我們總希望算法在得到我們期望的結果后就停止。那么我們如何控制呢?常用的做法就是為算法限制一個閾值,矩陣更新過程中,總是將兩個距離最近的聚類合並,那么我們只要加入一個閾值判斷,當這個距離大於閾值時,就說明不需要再合並了,此時算法結束。這樣的閾值引入可以很好的控制算法結束時間,將層次截斷在某一層上。
2. 算法實現
MATLAB實現了層次聚類算法,基本語句如下:
1X = [1 2;2.5 4.5;2 2;4 1.5;4 2.5] ;
2Y = pdist(X,'euclid');
3Z = linkage(Y,'single');
4T = cluster(Z,'cutoff',cutoff);
MATLAB還有一個簡化的層次聚類版本,一句話搞定
1T = clusterdata(X,cutoff)
Java實現的版本:
1 package util;
2
3 import java.util.*;
4
5 public class Clusterer {
6 private List[] clusterList;
7 DisjointSets ds;
8 private static final int MAX = Integer.MAX_VALUE;
9 private int n;
10 private int cc;
11
12 // private double ori[] = {1,2,5,7,9,10};
13
14 public Clusterer(int num) {
15 ds = new DisjointSets(num);
16 n = num;
17 cc = n;
18 clusterList = new ArrayList[num];
19 for (int i = 0; i < n; i++)
20 clusterList[i] = new ArrayList();
21 }
22
23 public List[] getClusterList() {
24 return clusterList;
25 }
26
27 public void setClusterList(List[] clusterList) {
28 this.clusterList = clusterList;
29 }
30
31 public void output() {
32 int ind = 1;
33 for (int i = 0; i < n; i++) {
34 clusterList[ds.find(i)].add(i);
35 }
36 for (int i = 0; i < n; i++) {
37 if (clusterList[i].size() != 0) {
38 System.out.print("cluster " + ind + " :");
39 for (int j = 0; j < clusterList[i].size(); j++) {
40 System.out.print(clusterList[i].get(j) + " ");
41 }
42 System.out.println();
43 ind++;
44 }
45 }
46 }
47
48 /** *//**
49 * this method provides a hierachical way for clustering data.
50 *
51 * @param r
52 * denote the distance matrix
53 * @param n
54 * denote the sample num(distance matrix's row number)
55 * @param dis
56 * denote the threshold to stop clustering
57 */
58 public void cluster(double[][] r, int n, double dis) {
59 int mx = 0, my = 0;
60 double vmin = MAX;
61 for (int i = 0; i < n; i++) { // 尋找最小距離所在的行列
62 for (int j = 0; j < n; j++) {
63 if (j > i) {
64 if (vmin > r[i][j]) {
65 vmin = r[i][j];
66 mx = i;
67 my = j;
68 }
69 }
70 }
71 }
72 if (vmin > dis) {
73 return;
74 }
75 ds.union(ds.find(mx), ds.find(my)); // 將最小距離所在的行列實例聚類合並
76 double o1[] = r[mx];
77 double o2[] = r[my];
78 double v[] = new double[n];
79 double vv[] = new double[n];
80 for (int i = 0; i < n; i++) {
81 double tm = Math.min(o1[i], o2[i]);
82 if (tm != 0)
83 v[i] = tm;
84 else
85 v[i] = MAX;
86 vv[i] = MAX;
87 }
88 r[mx] = v;
89 r[my] = vv;
90 for (int i = 0; i < n; i++) { // 更新距離矩陣
91 r[i][mx] = v[i];
92 r[i][my] = vv[i];
93 }
94 cluster(r, n, dis); // 繼續聚類,遞歸直至所有簇之間距離小於dis值
95 }
96
97 /** *//**
98 *
99 * @param r
100 * @param cnum
101 * denote the number of final clusters
102 */
103 public void cluster(double[][] r, int cnum) {
104 /**//*if(cc< cnum)
105 System.err.println("聚類數大於實例數");*/
106 while (cc > cnum) {// 繼續聚類,循環直至聚類個數等於cnum
107 int mx = 0, my = 0;
108 double vmin = MAX;
109 for (int i = 0; i < n; i++) { // 尋找最小距離所在的行列
110 for (int j = 0; j < n; j++) {
111 if (j > i) {
112 if (vmin > r[i][j]) {
113 vmin = r[i][j];
114 mx = i;
115 my = j;
116 }
117 }
118 }
119 }
120 ds.union(ds.find(mx), ds.find(my)); // 將最小距離所在的行列實例聚類合並
121 double o1[] = r[mx];
122 double o2[] = r[my];
123 double v[] = new double[n];
124 double vv[] = new double[n];
125 for (int i = 0; i < n; i++) {
126 double tm = Math.min(o1[i], o2[i]);
127 if (tm != 0)
128 v[i] = tm;
129 else
130 v[i] = MAX;
131 vv[i] = MAX;
132 }
133 r[mx] = v;
134 r[my] = vv;
135 for (int i = 0; i < n; i++) { // 更新距離矩陣
136 r[i][mx] = v[i];
137 r[i][my] = vv[i];
138 }
139 cc--;
140 }
141 }
142
143 public static void main(String args[]) {
144 double[][] r = { { 0, 1, 4, 6, 8, 9 }, { 1, 0, 3, 5, 7, 8 },
145 { 4, 3, 0, 2, 4, 5 }, { 6, 5, 2, 0, 2, 3 },
146 { 8, 7, 4, 2, 0, 1 }, { 9, 8, 5, 3, 1, 0 } };
147 Clusterer cl = new Clusterer(6);
148 //cl.cluster(r, 6, 1);
149 cl.cluster(r, 3);
150 cl.output();
151 }
152
153}
154
3. 小結
層次聚類算法是非常常用的聚類算法,同時也是被廣泛研究的聚類算法。層次聚類本身分為合並和分裂兩種實現,在合並算法中,又分基於矩陣理論的合並和基於圖論的合並。本文只是初學聚類的一點體會,因此只實現了基於矩陣理論的算法,同時,用於大數據集合的層次算法如CURE,ROCK和Chameleon算法都沒有涉及,這些算法如果以后有時間,會整理發布。還有截斷點的選擇,最佳聚類數的確定都是可以研究的問題。
4. 參考文獻及推薦閱讀
[1]Pattern Recognition Third Edition, Sergios Theodoridis, Konstantinos Koutroumbas
[2]模式識別第三版, Sergios Theodoridis, Konstantinos Koutroumbas著, 李晶皎, 王愛俠, 張廣源等譯
http://www.blogjava.net/changedi/archive/2010/03/19/315963.html