1. 程式人生 > >看完這篇文章,包你懂得如何用Python實現聚類演算法的層次演算法!

看完這篇文章,包你懂得如何用Python實現聚類演算法的層次演算法!

什麼是聚類

將物理或抽象物件的集合分成由類似的物件組成的多個類的過程被稱為聚類。由聚類所生成的簇是一組資料物件的集合,這些物件與同一個簇中的物件彼此相似,與其他簇中的物件相異。聚類分析又稱群分析,它是研究(樣品或指標)分類問題的一種統計分析方法。

看完這篇文章,包你懂得如何用Python實現聚類演算法的層次演算法!

 

聚類分析起源於分類學,但是聚類不等於分類。聚類與分類的不同在於,聚類所要求劃分的類是未知的。聚類分析內容非常豐富,有系統聚類法、有序樣品聚類法、動態聚類法、模糊聚類法、圖論聚類法、聚類預報法等。

起步

層次聚類( Hierarchical Clustering )是聚類演算法的一種,通過計算不同類別的相似度類建立一個有層次的巢狀的樹。

看完這篇文章,包你懂得如何用Python實現聚類演算法的層次演算法!

 

層次聚類演算法介紹

假設有 n 個待聚類的樣本,對於層次聚類演算法,它的步驟是:

  • 步驟一:(初始化)將每個樣本都視為一個聚類;步驟二:計算各個聚類之間的相似度;步驟三:尋找最近的兩個聚類,將他們歸為一類;步驟四:重複步驟二,步驟三;直到所有樣本歸為一類。
看完這篇文章,包你懂得如何用Python實現聚類演算法的層次演算法!

 

整個過程就是建立一棵樹,在建立的過程中,可以在步驟四設定所需分類的類別個數,作為迭代的終止條件,畢竟都歸為一類並不實際。

聚類之間的相似度

聚類和聚類之間的相似度有什麼來衡量呢?既然是空間中的點,可以採用距離的方式來衡量,一般有下面三種:

Single Linkage

又叫做 nearest-neighbor ,就是取兩個類中距離最近的兩個樣本的距離作為這兩個集合的距離。這種計算方式容易造成一種叫做 Chaining 的效果,兩個 cluster 明明從“大局”上離得比較遠,但是由於其中個別的點距離比較近就被合併了,並且這樣合併之後 Chaining 效應會進一步擴大,最後會得到比較鬆散的 cluster 。

Complete Linkage

這個則完全是 Single Linkage 的反面極端,取兩個集合中距離最遠的兩個點的距離作為兩個集合的距離。其效果也是剛好相反的,限制非常大。這兩種相似度的定義方法的共同問題就是指考慮了某個有特點的資料,而沒有考慮類內資料的整體特點。

Average Linkage 這種方法就是把兩個集合中的點兩兩的距離全部放在一起求均值,相對也能得到合適一點的結果。有時異常點的存在會影響均值,平常人和富豪平均一下收入會被拉高是吧,因此這種計算方法的一個變種就是取兩兩距離的中位數。

python 實現層次聚類

空間中點的距離使用歐式距離:

import math
import numpy as 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_)
本文來自網路,如有侵權,請聯絡小編刪除!