看完這篇文章,包你懂得如何用Python實現聚類算法的層次算法!
什麽是聚類
將物理或抽象對象的集合分成由類似的對象組成的多個類的過程被稱為聚類。由聚類所生成的簇是一組數據對象的集合,這些對象與同一個簇中的對象彼此相似,與其他簇中的對象相異。聚類分析又稱群分析,它是研究(樣品或指標)分類問題的一種統計分析方法。
聚類分析起源於分類學,但是聚類不等於分類。聚類與分類的不同在於,聚類所要求劃分的類是未知的。聚類分析內容非常豐富,有系統聚類法、有序樣品聚類法、動態聚類法、模糊聚類法、圖論聚類法、聚類預報法等。
起步
層次聚類( Hierarchical Clustering )是聚類算法的一種,通過計算不同類別的相似度類創建一個有層次的嵌套的樹。
層次聚類算法介紹
假設有 n 個待聚類的樣本,對於層次聚類算法,它的步驟是:
- 步驟一:(初始化)將每個樣本都視為一個聚類;步驟二:計算各個聚類之間的相似度;步驟三:尋找最近的兩個聚類,將他們歸為一類;步驟四:重復步驟二,步驟三;直到所有樣本歸為一類。
整個過程就是建立一棵樹,在建立的過程中,可以在步驟四設置所需分類的類別個數,作為叠代的終止條件,畢竟都歸為一類並不實際。
聚類之間的相似度
聚類和聚類之間的相似度有什麽來衡量呢?既然是空間中的點,可以采用距離的方式來衡量,一般有下面三種:
Single Linkage
又叫做 nearest-neighbor ,就是取兩個類中距離最近的兩個樣本的距離作為這兩個集合的距離。這種計算方式容易造成一種叫做 Chaining 的效果,兩個 cluster 明明從“大局”上離得比較遠,但是由於其中個別的點距離比較近就被合並了,並且這樣合並之後 Chaining 效應會進一步擴大,最後會得到比較松散的 cluster 。
Complete Linkage
這個則完全是 Single Linkage 的反面極端,取兩個集合中距離最遠的兩個點的距離作為兩個集合的距離。其效果也是剛好相反的,限制非常大。這兩種相似度的定義方法的共同問題就是指考慮了某個有特點的數據,而沒有考慮類內數據的整體特點。
Average Linkage 這種方法就是把兩個集合中的點兩兩的距離全部放在一起求均值,相對也能得到合適一點的結果。有時異常點的存在會影響均值,平常人和富豪平均一下收入會被拉高是吧,因此這種計算方法的一個變種就是取兩兩距離的中位數。
python 實現層次聚類
空間中點的距離使用歐式距離:
import math import numpyas np def euler_distance(point1: np.ndarray, point2: list) -> float: """
計算兩點之間的歐拉距離,支持多維
""" distance = 0.0 for a, b in zip(point1, point2): distance += math.pow(a - b, 2) return math.sqrt(distance)
定義聚類數的節點:
class ClusterNode(object): def __init__(self, vec, left=None, right=None, distance=-1, id=None, count=1): """ :param vec: 保存兩個數據聚類後形成新的中心 :param left: 左節點 :param right: 右節點 :param distance: 兩個節點的距離 :param id: 用來標記哪些節點是計算過的 :param count: 這個節點的葉子節點個數 """ self.vec = vec self.left = left self.right = right self.distance = distance self.id = id self.count = count
vec 表示合並後的聚類中心,是一個點,代表整個聚類的位置;distance 表示左節點和右節點的距離。
計算層次聚類算法的類:
class Hierarchical(object): def __init__(self, k = 1): assert k > 0 self.k = k self.labels = None def fit(self, x): nodes = [ClusterNode(vec=v, id=i) for i,v in enumerate(x)] distances = {} point_num, future_num = np.shape(x) # 特征的維度 self.labels = [ -1 ] * point_num currentclustid = -1 while len(nodes) > self.k: min_dist = math.inf nodes_len = len(nodes) closest_part = None # 表示最相似的兩個聚類 for i in range(nodes_len - 1): for j in range(i + 1, nodes_len): # 為了不重復計算距離,保存在字典內 d_key = (nodes[i].id, nodes[j].id) if d_key not in distances: distances[d_key] = euler_distance(nodes[i].vec, nodes[j].vec) d = distances[d_key] if d < min_dist: min_dist = d closest_part = (i, j) # 合並兩個聚類 part1, part2 = closest_part node1, node2 = nodes[part1], nodes[part2] new_vec = [ (node1.vec[i] * node1.count + node2.vec[i] * node2.count ) / (node1.count + node2.count) for i in range(future_num)] new_node = ClusterNode(vec=new_vec, left=node1, right=node2, distance=min_dist, id=currentclustid, count=node1.count + node2.count) currentclustid -= 1 del nodes[part2], nodes[part1] # 一定要先del索引較大的 nodes.append(new_node) self.nodes = nodes self.calc_label() def calc_label(self): """ 調取聚類的結果 """ for i, node in enumerate(self.nodes): # 將節點的所有葉子節點都分類 self.leaf_traversal(node, i) def leaf_traversal(self, node: ClusterNode, label): """ 遞歸遍歷葉子節點 """ if node.left == None and node.right == None: self.labels[node.id] = label if node.left: self.leaf_traversal(node.left, label) if node.right: self.leaf_traversal(node.right, label)
最後將聚類的列表標記保存於 labels 中。
測試
與 sklearn 進行對比:
iris = datasets.load_iris() my = Hierarchical(4) my.fit(iris.data) print(np.array(my.labels)) sk = cluster.AgglomerativeClustering(4) sk.fit(iris.data) print(sk.labels_)
得到輸出:
[3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3 3 3 3 1 1 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 0 1 2 1 2 2 2 2 1 2 2 2 2
2 2 1 1 2 2 2 2 1 2 1 2 1 2 2 1 1 2 2 2 2 2 1 2 2 2 2 1 2 2 2 1 2 2 2 1 2
2 1]
[1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 3 2 3 2 3 2 3 3 2 3 2 3 2 3 3 2 3 2 2 2 2
2 2 2 0 2 3 3 3 3 2 3 2 2 2 3 3 3 2 3 3 3 3 3 2 3 3 0 2 0 0 0 0 3 0 0 0 0
0 0 2 2 0 0 0 0 2 0 2 0 2 0 0 2 2 0 0 0 0 0 2 2 0 0 0 2 0 0 0 2 0 0 0 2 0
0 2]
結果還算是理想的。
層次聚類的優缺點
優點:
- 一次性得到聚類樹,後期再分類無需重新計算;相似度規則容易定義;可以發現類別的層次關系。
缺點:
計算復雜度高,不適合數據量大的;算法很可能形成鏈狀。
本文到此告一段落,喜歡本文的小夥伴或者覺得本文對你有幫助可以點播關註或轉發喔。
最後
小編精心推薦一個學習Python的好去處,如有想來的小夥伴可以私信小編PT。這裏有免費的學習資料可以領取喔!
文末附上實驗源碼:
# coding: utf-8 # 層次聚類 import math import numpy as np from sklearn import datasets from sklearn import cluster def euler_distance(point1: np.ndarray, point2: list) -> float: """ 計算兩點之間的歐拉距離,支持多維 """ distance = 0.0 for a, b in zip(point1, point2): distance += math.pow(a - b, 2) return math.sqrt(distance) class ClusterNode(object): def __init__(self, vec, left=None, right=None, distance=-1, id=None, count=1): """ :param vec: 保存兩個數據聚類後形成新的中心 :param left: 左節點 :param right: 右節點 :param distance: 兩個節點的距離 :param id: 用來標記哪些節點是計算過的 :param count: 這個節點的葉子節點個數 """ self.vec = vec self.left = left self.right = right self.distance = distance self.id = id self.count = count class Hierarchical(object): def __init__(self, k = 1): assert k > 0 self.k = k self.labels = None def fit(self, x): nodes = [ClusterNode(vec=v, id=i) for i,v in enumerate(x)] distances = {} point_num, future_num = np.shape(x) # 特征的維度 self.labels = [ -1 ] * point_num currentclustid = -1 while len(nodes) > self.k: min_dist = math.inf nodes_len = len(nodes) closest_part = None # 表示最相似的兩個聚類 for i in range(nodes_len - 1): for j in range(i + 1, nodes_len): # 為了不重復計算距離,保存在字典內 d_key = (nodes[i].id, nodes[j].id) if d_key not in distances: distances[d_key] = euler_distance(nodes[i].vec, nodes[j].vec) d = distances[d_key] if d < min_dist: min_dist = d closest_part = (i, j) # 合並兩個聚類 part1, part2 = closest_part node1, node2 = nodes[part1], nodes[part2] new_vec = [ (node1.vec[i] * node1.count + node2.vec[i] * node2.count ) / (node1.count + node2.count) for i in range(future_num)] new_node = ClusterNode(vec=new_vec, left=node1, right=node2, distance=min_dist, id=currentclustid, count=node1.count + node2.count) currentclustid -= 1 del nodes[part2], nodes[part1] # 一定要先del索引較大的 nodes.append(new_node) self.nodes = nodes self.calc_label() def calc_label(self): """ 調取聚類的結果 """ for i, node in enumerate(self.nodes): # 將節點的所有葉子節點都分類 self.leaf_traversal(node, i) def leaf_traversal(self, node: ClusterNode, label): """ 遞歸遍歷葉子節點 """ if node.left == None and node.right == None: self.labels[node.id] = label if node.left: self.leaf_traversal(node.left, label) if node.right: self.leaf_traversal(node.right, label) iris = datasets.load_iris() my = Hierarchical(4) my.fit(iris.data) print(np.array(my.labels)) sk = cluster.AgglomerativeClustering(4) sk.fit(iris.data) print(sk.labels_)
本文來自網絡,如有侵權,請聯系小編刪除!
看完這篇文章,包你懂得如何用Python實現聚類算法的層次算法!