Hierarchical Agglomerative Clustering (HAC)
===
###### tags: `Clustering`
---
## *介紹*
### 演算法
上一篇講了分群演算法 (Clustering Algorithm)中的 K-Means,這次我們要講的是另一種常見的演算法,***階層式凝聚分群 (HAC)***。
HAC是階層式分群的一種,方法是由下至上建立起樹狀的分群層次結構(又稱作dendrogram)。最下層是將每一筆資料是作為一個群集 (稱為**leaf**),隨著階層向上移動,資料會相互成為一個個較小的群集,再往上,成對的群集相互合併,使群集總數遞減,最後合為一群我們稱為**Root**。
<center><img src='https://i.imgur.com/iWhaoI7.png' width='350'></center><br/>
<center><img src='https://i.imgur.com/zC6PcFs.png' width='600'>
</center>
### 度量標準
群集合併時,我們需要度量群集間的相異度(dissimilarity),來做為合併的依據。其中,相異度即群之間的距離度量,用距離以及鏈結標準 (linkage) 來實現,在不同的距離計算方式與鏈結標準下,分群的結果也會不同。
(想像一下,在資料分布上,相似的資料會有相似的特徵表現,代表他們在分布上距離相近。所以,資料之間的相異程度可以用距離來度量,距離越遠的代表他們的相異程度越大。)
<center><img src='https://i.imgur.com/WQqgW4r.png' width=360></center>
<center> <font size=2>圖中,對data 1來說,與data 3的相似度比data 2高</font></center>
* <strong>對數字資料來說,常用的距離的算法有 : </strong>
Euclidean distance : $\parallel a-b \parallel_2=\sqrt{\sum_{i}(a_i-b_i)^2}$<br/>
Squared Euclidean distance : $\parallel a-b \parallel_2^2=\sum_{i}(a_i-b_i)^2$<br/>
Manhattan distance : $\|a-b\|_1=\sum_{i}|a_i-b_i|$<br/>
* <strong>鏈結標準(即如何定義群集之間的距離) </strong><br/>
對群集$C_i$與$C_j$之間,決定合併的鏈結標準有:<br/>
<strong>1. single-linkage agglomerative algorithm : 選定兩群集內最近的兩筆資料之間的距離作為參考標準</strong>
$$d(C_i,C_j)=\min_{a_k \in C_i,b_t \in C_j}d(a_k,b_t)$$
<center><img src='https://i.imgur.com/QeKama8.png' width='360'></center>
<strong>2. complete-linkage agglomerative algorithm : 選定兩群集內最遠的兩筆資料之間的距離作為參考標準</strong>
$$d(C_i,C_j)=\max_{a_k \in C_i,b_t \in C_j}d(a_k,b_t)$$
<center><img src='https://i.imgur.com/doS8xkc.png' width=360></center>
<strong>3. average-linkage agglomerative algorithm : 選定兩群集之間資料的平均距離作為參考標準</strong></br>
$$d(C_i,C_j)=\sum_{a_k \in C_i}\sum_{b_t \in C_j}\frac{d(a_k,b_t)}{|C_i||C_j|}$$
<center><img src='https://i.imgur.com/oVpyq4b.png' width='400'></center>
<font size=2>其中$|C_i|,|C_j|$代表群集裡的資料個數</font><br/>
<strong>4. centroid-linkage agglomerative algorithm : 選定兩群集質心之間的距離作為參考的標準</strong><br/>
$$d(C_i,C_j)=d(c_i,c_j)_{c_i\in C_i,c_j\in C_j}$$$$其中,質心 c_i=\frac{1}{|C_i|}\sum_{a_k\in C_i}a_k$$<br/>
<center><img src='https://i.imgur.com/oAy1LKo.png' width='400'></center><br/>
<strong>5. Ward’s Minimum Variance Method (Ward's Method) : 選定兩個群集合併後,群集中每一筆資料到群集中心的距離平方和作為參考標準</strong><br/>(ward's Method 的距離標準只限於歐式距離)<br/>
$$d(C_i,C_j)=\sum_{x\in C_i\bigcup C_j}(x-\mu)^2,其中\mu=\frac{1}{|C_k|}\sum_{C_i\bigcup C_j}x$$<br/>
## *運作流程*
#### 1. 選定分群數 N
#### 2. 每一筆資料視為一個群集<br/>
<center><img src='https://i.imgur.com/wTXWLpt.png' width='360'></center><br/>
#### 3. 計算群集之間的距離<br/><pre> 其中,決定距離的標準就是linkage,但最開始皆是由歐式距離來決定</pre>
<center><img src='https://i.imgur.com/gSEOGnG.png' width='360'></center><br/>
#### 4. 選擇linkage最小的兩個群集合併(每一次合併一定只有兩個群集合併)<br/>
<center><img src='https://i.imgur.com/SN4L6WQ.png' width='360'></center><br/>
#### 5. 重複步驟3.4.直到總分群數為N
![]()
<center><img src='https://i.imgur.com/7g2OlcH.png' width='360'><img src='https://i.imgur.com/1SeJy5t.png' width='360'></center><br/>
<center><img src='https://i.imgur.com/byL8MNs.png' width='360'><img src='https://i.imgur.com/8zIGdLW.png' width='360'></center>
## *檢驗(Validation)*
<strong>檢驗方式與K-means的方法相同,分為三種:</strong>
<strong>
1. Internal cluster validation
2. External cluster validation
3. Relative cluster validation
</strong>
### Internal measure
<strong>
1. Silhouette Coefficient
2. Calinski-Harabasz Index(CH Index)
3. Davies-Bounldin Index(DBI)
</strong>
### Relative measure
<strong>
1. Elbow Method
2. Average Silhouette Method
</strong>
---
[複習連結 - -詳細介紹](https://hackmd.io/0vxwwPqdSzCQ7taN_CfgdA?view#%E5%88%86%E7%BE%A4%E7%B5%90%E6%9E%9C%E6%AA%A2%E9%A9%97Clustering-Validation)
## *HAC 範例*
#### <font size=4>引入套件</font>
```python=
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import AgglomerativeClustering
from sklearn.metrics import silhouette_score,calinski_harabasz_score,davies_bouldin_score
```
#### <font size=4>從sklearn的資料集,引入鳶尾花的資料集(iris dataset)</font>
```python=
from sklearn.datasets import load_iris
iris=load_iris()
```
#### 初始假定分群個數K(例中K=5),利用散點圖觀察分群結果
```python=
agg_5=AgglomerativeClustering(n_clusters=5,linkage='ward').fit(iris.data)
label_5=agg_5.labels_
plt.scatter(iris.data[:,2],iris.data[:,3],c=label_5,cmap='brg')
plt.show()
```
<center><img src='https://i.imgur.com/LfKb2EG.png' width='400'></center>
#### 但觀察指標發現分群的結果並不是很好
```python=
#用驗證指標,觀查分群的好壞
method=[silhouette_score,calinski_harabasz_score,davies_bouldin_score]
name=['silhouette_score','calinski_harabasz_score','davies_bouldin_score']
score_5=[]
for validation in method:
score_5.append(validation(iris.data,label_5))
Tb_5=pd.DataFrame(data=score_5,columns=['score(k=5)'],index=name)
Tb_5
```
<center><img src='https://i.imgur.com/hg8LGlk.png' width='300'></center>
#### 當我們不知道應該如何分群的時候,可以利用Relative mesure來找到最好的分群數。<br/>但因為HAC的套件裡沒有計算群集內的距離平方和的特性(Attribute),所以我們需要自己定義函式
```python=
#找出各群集質心
def centroid(model,centroid_num):
sum=[[0]]*centroid_num
count=[0]*centroid_num
for i in range(centroid_num):
for index,Class in enumerate(model.labels_):
if Class == i:
sum[i]+=(iris.data[index])
count[i]+=1
center=np.array(sum)/np.array(count).reshape((centroid_num,1))
return center
#計算群集內資料到對應的群集質心的距離平方和
def Inertia(model,centroid_num):
from scipy.spatial import distance_matrix
from scipy.spatial import distance
lt=[]
for index,Class in enumerate(model.labels_):
lt.append(distance.euclidean(iris.data[index],centroid(model,centroid_num)[Class])**2)
inertia=np.array(lt).sum()
return inertia
```
#### 定義好函式後,我們就可以開式計算,找出最好的分群數了
```python=
distoration=[]
sil_score=[]
cal_score=[]
dav_score=[]
K=range(2,10)
for k in K:
Hac = AgglomerativeClustering(n_clusters=k,linkage='ward').fit(iris.data)
distoration.append(Inertia(Hac,k))
for k in K:
Hac = AgglomerativeClustering(n_clusters=k,linkage='ward').fit(iris.data)
sil_score.append(silhouette_score(iris.data,Hac.labels_))
for k in K:
Hac = AgglomerativeClustering(n_clusters=k,linkage='ward').fit(iris.data)
cal_score.append(calinski_harabasz_score(iris.data,Hac.labels_))
for k in K:
Hac = AgglomerativeClustering(n_clusters=k,linkage='ward').fit(iris.data)
dav_score.append(davies_bouldin_score(iris.data,Hac.labels_))
#將圖畫出來
plt.figure(figsize=(10,5))
plt.title('Elbow method',fontsize=16)
plt.xlabel('cluster number(k)',fontsize=14)
plt.ylabel('distoration',fontsize=14)
plt.plot(K,distoration,'bx-')
plt.axvline(x=3,linestyle='dashed')
plt.show()
plt.figure(figsize=(10,5))
plt.title('Silhouette coefficient',fontsize=16)
plt.xlabel('cluster number(k)',fontsize=14)
plt.ylabel('silhoue score',fontsize=14)
plt.plot(K,sil_score,'bx-')
plt.axvline(x=3,linestyle='dashed')
plt.show()
plt.figure(figsize=(10,5))
plt.title('CH index',fontsize=16)
plt.xlabel('cluster number(k)',fontsize=14)
plt.ylabel('CH score',fontsize=14)
plt.plot(K,cal_score,'bx-')
plt.axvline(x=3,linestyle='dashed')
plt.show()
plt.figure(figsize=(10,5))
plt.title('DBI',fontsize=16)
plt.xlabel('cluster number(k)',fontsize=14)
plt.ylabel('DB score',fontsize=14)
plt.plot(K,dav_score,'bx-')
plt.axvline(x=3,linestyle='dashed')
plt.show()
```
![](https://i.imgur.com/n9dc2vo.png)<br/>
![](https://i.imgur.com/4TxTVU0.png)<br/>
![](https://i.imgur.com/IYswrxA.png)<br/>
![](https://i.imgur.com/2LN4FVU.png)<br/>
#### 可以從上圖知道,綜合起來,最佳的分群數是3群,我們將分群數設為3群,再做一次HAC觀察結果
```python=
#將群數設定為3,對資料做K-means分群
agg_3=AgglomerativeClustering(n_clusters=3,linkage='ward').fit(iris.data)
label_3=agg_3.labels_
#將分群的結果用分布圖顯示
plt.title('Petal(Predict)',fontsize=16)
plt.xlabel(iris.feature_names[2],fontsize=14)
plt.ylabel(iris.feature_names[3],fontsize=14)
plt.scatter(iris.data[:,2],iris.data[:,3],c=label_3,cmap='brg')
plt.show()
```
<center><img src='https://i.imgur.com/srXaDp5.png' width='400'></center>
#### 與之前分5群做比較觀察分群結果是否有變好
```python=
method=[silhouette_score,calinski_harabasz_score,davies_bouldin_score]
name=['silhouette_score','calinski_harabasz_score','davies_bouldin_score']
score_3=[]
for validation in method:
score_3.append(validation(iris.data,label_3))
Tb_3=pd.DataFrame(data=score_3,columns=['score(k=3)'],index=name)
table=pd.concat([Tb_5,Tb_3],axis=1)
table['Variability']=table['score(k=3)']-table['score(k=5)']
table
```
<center><img src='https://i.imgur.com/ukUr2OX.png' width='500'></center>
#### 由上面的表格可以看出分3群的結果確實比分5群的結果要來的好
#### 接下來,我們還可以對linkage的方法去作分析,找出iris的資料集使用哪一種linkage,分群結果會最好
```python=
Linkage=['single','complete','average','ward']
#算出檢驗指標
score=[]
for validation in method:
lt=[]
for link in Linkage:
model=AgglomerativeClustering(n_clusters=3,linkage=link).fit(iris.data)
model_label=model.labels_
lt.append(validation(iris.data,model_label))
score.append(lt)
#算出距離平方和
iner=[]
for link in Linkage:
model=AgglomerativeClustering(n_clusters=3,linkage=link).fit(iris.data)
model_label=model.labels_
iner.append(Inertia(model=model,centroid_num=3))
iner=np.array(iner).reshape((1,4))
#因為計算方法不同,所以將Inertia與其他指標分開算,再將它們合併
Iner=pd.DataFrame(data=iner,columns=Linkage,index=['Inertia'])
Score=pd.DataFrame(data=score,columns=Linkage,index=name)
table_link=pd.concat([Score,Iner],axis=0)
#找出各指標下最好的方法
Value=np.array(table_link)
better=[]
for i in range(2):
better.append(table_link.columns[Value[i].argmax()])
for j in range(2,4):
better.append(table_link.columns[Value[j].argmin()])
table_link['Better']=better
table_link
```
<center><img src='https://i.imgur.com/ML42Zmn.png' width='580'></center><br/>
#### 由上面的圖可以看出來,對iris的資料即來說,Ward's Method 的結果是較好的
---
參考:
[維基百科 - Hierarchical clustering](https://en.wikipedia.org/wiki/Hierarchical_clustering#cite_note-6)
[AI - Ch19 機器學習(7), 分群/聚類:階層式分群法 Clustering: Hierarchical Clustering](https://mropengate.blogspot.com/2015/06/ai-ch17-6-clustering-hierarchical.html)
[sklearn-clustering](https://scikit-learn.org/stable/modules/clustering.html#hierarchical-clustering)
[Hierarchical Clustering 階層式分群 | Clustering 資料分群 |](https://jamleecute.web.app/%e7%b5%b1%e8%a8%88-r%e8%aa%9e%e8%a8%80-%e5%88%86%e7%be%a4%e5%88%86%e6%9e%90-clustering-hierarchical-clustering-%e9%9a%8e%e5%b1%a4%e5%bc%8f%e5%88%86%e7%be%a41/)