层次聚类是一种常用的数据聚类方法,它不需要预先指定聚类的数量,而是通过构建一个层次结构来逐步合并或分裂数据点。在层次聚类中,有两种主要的方法:结构化的和非结构化的。本文将通过瑞士卷数据集的例子来探讨这两种方法的区别和应用。
首先,生成一个瑞士卷数据集。瑞士卷是一种非线性的数据集,它的形状像一个卷起来的瑞士卷面包,常用于测试聚类算法的性能。使用scikit-learn库中的make_swiss_roll
函数来生成这个数据集。
from sklearn.datasets import make_swiss_roll
n_samples = 1500
noise = 0.05
X, _ = make_swiss_roll(n_samples, noise=noise)
# 使数据集更细长
X[:, 1] *= 0.5
这段代码首先导入了必要的库,然后生成了一个包含1500个样本的瑞士卷数据集,并添加了一些噪声。接着,将数据集的第二维乘以0.5,使瑞士卷看起来更细长。
接下来,进行非结构化的层次聚类。这种方法不考虑数据点之间的连接性,只基于距离来合并数据点。使用scikit-learn库中的AgglomerativeClustering
类来实现这种方法。
from sklearn.cluster import AgglomerativeClustering
print("计算非结构化层次聚类...")
st = time.time()
ward = AgglomerativeClustering(n_clusters=6, linkage="ward").fit(X)
elapsed_time = time.time() - st
label = ward.labels_
print(f"耗时:{elapsed_time:.2f}秒")
print(f"数据点数量:{label.size}")
这段代码首先定义了非结构化层次聚类的参数,包括聚类的数量和链接方法。然后,记录了聚类开始的时间,执行聚类,计算耗时,并输出聚类结果的数据点数量。
与非结构化层次聚类不同,结构化层次聚类考虑了数据点之间的连接性。首先定义了一个k-最近邻图来表示数据点之间的连接性,然后使用这个图作为层次聚类的连接性约束。
from sklearn.neighbors import kneighbors_graph
connectivity = kneighbors_graph(X, n_neighbors=10, include_self=False)
print("计算结构化层次聚类...")
st = time.time()
ward = AgglomerativeClustering(n_clusters=6, connectivity=connectivity, linkage="ward").fit(X)
elapsed_time = time.time() - st
label = ward.labels_
print(f"耗时:{elapsed_time:.2f}秒")
print(f"数据点数量:{label.size}")
这段代码首先定义了k-最近邻图,然后使用这个图作为层次聚类的连接性约束。记录了聚类开始的时间,执行聚类,计算耗时,并输出聚类结果的数据点数量。
最后,使用matplotlib库来可视化聚类结果。分别绘制了非结构化和结构化层次聚类的3D散点图,以直观地比较两种方法的效果。
import matplotlib.pyplot as plt
fig1 = plt.figure()
ax1 = fig1.add_subplot(111, projection="3d", elev=7, azim=-80)
ax1.set_position([0, 0, 0.95, 1])
for l in np.unique(label):
ax1.scatter(X[label==l, 0], X[label==l, 1], X[label==l, 2],
color=plt.cm.jet(float(l)/np.max(label+1)), s=20, edgecolor="k")
_ = fig1.suptitle(f"无连接性约束(耗时{elapsed_time:.2f}秒)")
plt.show()
这段代码首先创建了一个3D图形,并设置了图形的位置和视角。然后,遍历每个聚类标签,绘制对应数据点的3D散点图。最后,添加了一个标题,并显示了图形。