关于层次聚类的原理,可以参考我的另一篇博客:层次聚类原理。本博客主要讲解如何简单直接使用 python 来实现层次聚类。
本篇博客包含的内容:sklearn / scipy
两种方式实现层次聚类,以及在 sklearn 中通过 precomputed
参数实现自定义距离矩阵
主要是两个函数:linkage,fcluster
def linkage(y, method='single', metric='euclidean', optimal_ordering=False):
函数功能解释:执行层次/聚集聚类。
参数(Parameters):
返回值(Returns):
Z(ndarray):层次聚类编码为一个linkage矩阵。
Z共有四列组成,第 1 字段与第 2 字段分别为聚类簇的编号,在初始距离前每个初始值被从0~n-1进行标识,每生成一个新的聚类簇就在此基础上增加一对新的聚类簇进行标识,第 3 个字段表示前两个聚类簇之间的距离,第 4 个字段表示新生成聚类簇所包含的元素的个数。
参考资料:https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.linkage.html#scipy.cluster.hierarchy.linkage
def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None):
函数功能解释:从给定链接矩阵定义的层次聚类中形成平面聚类。
如对以下 5 个点进行凝聚层次聚类。
x | y | |
---|---|---|
点0 | 1 | 2 |
点1 | 2 | 3 |
点2 | -3 | 3 |
点3 | -2 | -1 |
点4 | 5 | -1 |
#!/usr/bin/env python
# encoding: utf-8
'''
@Author : pentiumCM
@Email : [email protected]
@Software: PyCharm
@File : sci_cluster.py
@Time : 2020/4/15 22:21
@desc : scipy实现层次聚类
'''
import numpy as np
from scipy.cluster.hierarchy import dendrogram, linkage, fcluster
from matplotlib import pyplot as plt
data = np.array([[1, 2], [2, 3], [-3, 3], [-2, -1], [5, -1]])
# 画点
plt.scatter(x=data[:, 0:1], y=data[:, 1:2], marker='.', color='red')
n = np.arange(data.shape[0])
for i, txt in enumerate(n):
plt.annotate(txt, (data[i:i + 1, 0:1], data[i:i + 1, 1:2]))
plt.show()
# 1. 层次聚类
# linkage方法用于计算两个聚类簇s和t之间的距离d(s,t)
# 层次聚类编码为一个linkage矩阵。
Z = linkage(data, 'average')
print("聚类过程:", Z)
# 从给定链接矩阵定义的层次聚类中形成平面聚类。
# distance:以距离为划分距离的准则
f = fcluster(Z, 4, 'distance')
print("平面聚类结果:", f)
fig = plt.figure(figsize=(5, 3))
# 将层级聚类结果以树状图表示出来
dn = dendrogram(Z)
plt.show()
算法运行结果:
聚类过程: [[0. 1. 1.41421356 2. ]
[2. 3. 4.12310563 2. ]
[5. 6. 4.75565014 4. ]
[4. 7. 6.48606798 5. ]]
平面聚类结果: [1 1 2 3 4]
聚类的主要信息在 Z = linkage(data, ‘average’)中。
Z共有四列组成,第 1 字段与第 2 字段分别为聚类簇的编号,在初始距离前每个初始值被从0~n-1进行标识,每生成一个新的聚类簇就在此基础上增加一对新的聚类簇进行标识,第 3 个字段表示前两个聚类簇之间的距离,第 4 个字段表示新生成聚类簇所包含的元素的个数。
层次聚类可以一次性聚类出所有的情况,当生成出聚类树的结果时,可以通过在聚类树上画水平线(例如在上面算法中,水平线是以簇之间距离为准则)来选择聚成几类的结果。如用户需要聚成两类:只需要从上往下画出如上图的水平线来判断聚成两类的结果,可以看出两类的结果,一类为 4,另一类为0、1、2、3。
如果需要聚成3类,只需要将水平线往下平移即可知道聚成三类的结果。
sklearn库下的层次聚类是在sklearn.cluster的 AgglomerativeClustering中:
def __init__(self, n_clusters=2, affinity="euclidean",
memory=None,
connectivity=None, compute_full_tree='auto',
linkage='ward', distance_threshold=None):
AgglomerativeClustering 类的构造函数的参数有 簇的个数 n_clusters
,连接方法 linkage
,连接度量选项 affinity
三个重要参数:
n_clusters:用户指定需要聚成几类。
linkage:选择计算簇与簇之间距离的策略,包含:ward,complete,average,single
affinity:是一个簇间距离的计算方法。 可以是 “ euclidean(欧式距离)”,“ l1”,“ l2”,“manhattan(曼哈顿距离)”,“cosine(余弦)” 或 “precomputed(预先计算)”。
如果链接为“ward”,则仅接受“欧式距离”。
如果 “precomputed(预先计算)”,则需要距离矩阵作为拟合方法的输入。
距离矩阵
的生成方法:假设用户有 n 个观测点,那么先依次构造这 n 个点两两间的距离列表,即长度为 n*(n-1)/2 的距离列表,然后通过 scipy.spatial.distance 的 dist 库的 squareform 函数就可以构造距离矩阵了。这种方式的好处是用户可以使用自己定义的方法计算任意两个观测点的距离,然后再进行聚类。后面也会给出基于自定义的距离矩阵进行的聚类算法。
参考资料:源码文档
#!/usr/bin/env python
# encoding: utf-8
'''
@Author : pentiumCM
@Email : [email protected]
@Software: PyCharm
@File : sklearn_hierarchical_cluster.py
@Time : 2020/4/23 15:00
@desc : sklearn的层次聚类
'''
import numpy as np
from matplotlib import pyplot as plt
from sklearn.cluster import AgglomerativeClustering
data = np.array([[1, 2], [2, 3], [-3, 3], [-2, -1], [5, -1]])
# 画点
plt.scatter(x=data[:, 0:1], y=data[:, 1:2], marker='.', color='red')
n = np.arange(data.shape[0])
for i, txt in enumerate(n):
plt.annotate(txt, (data[i:i + 1, 0:1], data[i:i + 1, 1:2]))
plt.show()
# 训练模型
ac = AgglomerativeClustering(n_clusters=3, affinity='euclidean', linkage='average')
clustering = ac.fit(data)
print("每个数据所属的簇编号", clustering.labels_)
print("每个簇的成员", clustering.children_)
算法运行结果:
每个数据所属的簇编号 [2 2 0 0 1]
每个簇的成员 [[0 1]
[2 3]
[5 6]
[4 7]]
clustering.labels_:表示每个数据所属于哪一个簇。
[2 2 0 0 1]:表示数据0、1分为一簇,2、3分为一簇,4分为一簇。
clustering.children_:表示每个簇中有哪些元素。
[[0 1] [2 3] [5 6] [4 7]]:首先将数据初始化为簇 0 ~ n-1,然后簇0、1合并为簇5,簇2、3合并为簇6,簇5、6合并为簇7,最后簇4、7合并。
完整算法如下:
#!/usr/bin/env python
# encoding: utf-8
'''
@Author : pentiumCM
@Email : [email protected]
@Software: PyCharm
@File : sklearn_hierarchical_cluster.py
@Time : 2020/4/23 15:00
@desc : sklearn的层次聚类
'''
import numpy as np
from matplotlib import pyplot as plt
from sklearn.cluster import AgglomerativeClustering
from sklearn.metrics.pairwise import euclidean_distances
import scipy.spatial.distance as dist
from scipy.cluster.hierarchy import dendrogram, linkage
data = np.array([[1, 2], [2, 3], [-3, 3], [-2, -1], [5, -1]])
# 画点
plt.scatter(x=data[:, 0:1], y=data[:, 1:2], marker='.', color='red')
n = np.arange(data.shape[0])
for i, txt in enumerate(n):
plt.annotate(txt, (data[i:i + 1, 0:1], data[i:i + 1, 1:2]))
plt.show()
# 聚类方式一
# 训练模型
ac = AgglomerativeClustering(n_clusters=3, affinity='euclidean', linkage='average')
clustering = ac.fit(data)
print("每个数据所属的簇编号:", clustering.labels_)
print("每个簇的成员:", clustering.children_)
# 聚类的方式二
# 自定义距离矩阵
num = data.shape[0]
dist_matrix = np.mat(np.zeros((num, num)))
for i in range(num):
for j in range(i, num):
distence = euclidean_distances(data[i:i + 1], data[j:j + 1])
dist_matrix[i:i + 1, j:j + 1] = distence
dist_matrix[j:j + 1, i:i + 1] = dist_matrix[i:i + 1, j:j + 1]
# 基于自定义的聚类矩阵进行聚类
model = AgglomerativeClustering(n_clusters=3, affinity='precomputed', linkage='average')
clustering2 = model.fit(dist_matrix)
print("自定义距离矩阵聚类方式:")
print("每个数据所属的簇编号:", clustering2.labels_)
print("每个簇的成员:", clustering2.children_)
# 调整距离矩阵的形状
dist_matrix = dist.squareform(dist_matrix)
# linkage方法用于计算两个聚类簇s和t之间的距离d(s,t)
# 层次聚类编码为一个linkage矩阵。
Z = linkage(dist_matrix, 'average')
print("聚类过程:", Z)
# 将层级聚类结果以树状图表示出来
fig = plt.figure(figsize=(5, 3))
dn = dendrogram(Z)
plt.show()
算法运行结果:
每个数据所属的簇编号: [2 2 0 0 1]
每个簇的成员: [[0 1]
[2 3]
[5 6]
[4 7]]
自定义距离矩阵聚类方式:
每个数据所属的簇编号: [2 2 0 0 1]
每个簇的成员: [[0 1]
[2 3]
[5 6]
[4 7]]
聚类过程: [[0. 1. 1.41421356 2. ]
[2. 3. 4.12310563 2. ]
[5. 6. 4.75565014 4. ]
[4. 7. 6.48606798 5. ]]
上面的算法包含了sklearn的两种聚类方式,方式二为预计算的方式:算法预先计算了 期初各个数据点之间的距离矩阵dist_matrix,然后在聚类时,affinity=‘precomputed’。
对比之下,对于层次聚类的方便理解的方式,大家可以采用scipy的方式。