最近又要開始做聚類了,之前做過的層次聚類的方法是用sklearn下的聚類,但是存在一定的缺陷:庫并沒有給我們提供足夠的函數去評價聚類效果,如果想要對層次聚類樹自定義損失函數,那麼我們就需要剖析整棵層次聚類樹了。在這樣的背景下,我仔細研究了scipy下的層次聚類的API函數,并運用到了實際項目中。
我們分兩種情況來進行讨論:1、給定了距離矩陣,2、沒給定距離矩陣
1、給定了距離矩陣:
我們可以基于距離矩陣來計算。
當然,在凝聚層次聚類的過程中,我們需要對簇間的距離進行刻畫。在scipy中,linkage的method有很多種:single、average、complete、weighted、centroid、median、ward等,這個method表示的是linkage算法,即聚類的連接配接算法。比如single表示最近距離,在計算兩個簇的距離的時候,選取兩個簇中最近的兩個觀測點作為簇間距離。average表示的是平均距離,complete表示的是最遠距離。
此外還有一個參數metric,即計算兩個觀測點的距離,由于我們給定了距離矩陣,可以直接獲得兩個觀測點的距離,但是,linkage函數并沒有給我們提供類似于sklearn的precomputed選項,好在metric可以自定義,我們自定義metric。這裡舉個例子:
# 計算距離矩陣中兩行的距離
def func(u, v):
v_list = list(v)
pos_v = v_list.index(0.0)
return u[pos_v]
這裡的兩個參數u和v,代表距離矩陣中的任意兩行,那麼要得到二者的距離,則需要找到相應的位置,如函數中所寫。
然後,我們再用linkage,使用average連接配接算法,func度量方式
z = sch.linkage(data_square, method='average', metric=func)
如此一來我們可以計算任意兩個觀測點的距離。同時,由于凝聚層次聚類會合并距離矩陣的凝聚點的行,是以,該函數還可以計算兩個簇的距離,這裡的簇被抽象成了觀測點。
然後我們再繪制聚類圖譜,可以看聚類凝聚順序。
sch.dendrogram(z)
plt.show()
另外,由于我們要對整棵樹進行剖析,我們需要對樹的節點進行分析:
tree_node = sch.to_tree(z, True)
這個函數可以根據z矩陣得到樹中的各個節點,傳回一個清單。從葉子結點開始,然後再依次是凝聚點。次序和凝聚的次序是一緻的,即先凝聚的點先放進節點清單中。假設有N個觀測點,那麼前N個點為觀測節點,後面的N-1個點為凝聚點。
node_list = tree_node[1][N:]
可以以這種方式得到凝聚點。
樹中的節點的資料結構的組織形式為[id, left, right,dist,count]:節點id,左子樹“指針”,右子樹“指針”,凝聚時的高度,以該節點為根節點的子樹的節點總數。對于葉子結點而言,left和right都為None,dist為0,count為0。
之後,我們就可以自定義損失函數來對聚類品質進行評估了,當然我們評估聚類品質的目的是找到一個好的聚類參數譬如門檻值或者聚類數目,無需人工預設。下面介紹幾種方法:
1、輪廓系數,思路是以輪廓系數為最優化目标,繪制門檻值-輪廓系數曲線,取最大點對應的門檻值。這個方法的結果取決于資料分布,資料分布太重要了!在一些情況下會産生過拟合,如果簇内有兩個簇距離特别遠,而這兩個簇内部理應繼續劃分的,但是使用輪廓系數卻隻會得到兩個簇,這嚴重影響了聚類品質。
2、考慮簇内誤差度量每個凝聚點的誤差,思路是通過設定門檻值,計算每次聚類的每個簇内的點到簇中心點的距離,然後求和,作為損失函數,采用“肘方法”或者失真函數的方法得到一個最優門檻值。在這裡,對于層次聚類而言,由于中心點不能直接計算,我們可以選擇一個簇内的點代表中心點,滿足它到簇内其他點的距離和最小。
3、根據層次聚類的層次聚類樹譜圖來發現最優聚類個數,可以在凝聚點的距離差上面做文章。
通過門檻值的方式進行聚類的函數如下,d為設定的門檻值,可以取各凝聚點對應的門檻值,即dist
labels_pred = sch.fcluster(z, t=d, criterion='distance')
2、沒有給定距離矩陣
如果我們的資料是向量形式的,那麼我們可以将資料直接以“樣本數-特征數”矩陣的方式進行輸入,也可以先轉換為距離矩陣再進行給定距離矩陣情形下的操作。
data_pdist = dist.pdist(data, metric="cosine") # 對于樣本-數目矩陣,可以計算cosine相似度
data_square = dist.squareform(data_pdist) # 生成距離矩陣
總的來說,層次聚類的方法取決于資料的形式:如果對于“樣本數-特征數”矩陣形式的資料,那麼無需距離矩陣的構造了。如果資料無法表示成“樣本數-特征數”,譬如短文本的表示,我們可能沒法或者很難使用一個向量來表達一個句子——因為單詞的太多容易造成矩陣稀疏。那麼我們可以構造一個距離矩陣,隻需得到兩兩觀測點的距離進而進行聚類分析。缺點是一旦數目很大,聚類算法的複雜度會很高,同時層次聚類要一次性将資料載入記憶體,資料量太大會報Memory Error,對于千萬級的資料無能為力。