层次聚类是一种常用的数据聚类方法,它不需要预先指定聚类的数量,而是通过逐步合并或分裂的方式构建一个聚类树。在本例中,将探讨在数据中施加连接图以捕获局部结构的效果。连接图简单地说是20个最近邻的图。施加连接图有两个主要优势:首先,使用稀疏连接矩阵的聚类通常速度更快;其次,当使用连接矩阵时,单链接、平均链接和完全链接是不稳定的,并且倾向于创建几个迅速增长的聚类。实际上,平均链接和完全链接通过考虑两个聚类之间的所有距离来合并它们,从而对抗这种渗透行为,而单链接则通过仅考虑聚类之间的最短距离来夸大这种行为。连接图打破了平均链接和完全链接的这种机制,使它们更类似于更脆弱的单链接。对于非常稀疏的图,这种效果更加明显(尝试减少kneighbors_graph中的邻居数量),尤其是完全链接。特别是,图中的邻居数量非常少,会施加一种接近单链接的几何形状,这是众所周知的具有这种渗透不稳定性的。
为了生成样本数据,首先创建了一个包含1500个样本的数据集,这些样本分布在一个螺旋形的路径上,并且加入了一些随机噪声。然后,创建了一个捕捉局部连接性的图,邻居数量的多少会影响聚类结果的均匀性和计算时间。邻居数量较多会得到更均匀的聚类,但可能会忽略数据的局部流形结构。
在本例中,使用了四种不同的链接方法:平均链接、完全链接、Ward链接和单链接。对于每种链接方法,都分别在有无连接图的情况下进行了聚类,并计算了每种方法的运行时间。通过比较不同链接方法和不同连接图设置下的聚类结果,可以更好地理解连接图对聚类结果的影响。
以下是实现这一聚类分析的Python代码示例:
import time
import matplotlib.pyplot as plt
import numpy as np
from sklearn.cluster import AgglomerativeClustering
from sklearn.neighbors import kneighbors_graph
# 生成样本数据
n_samples = 1500
np.random.seed(0)
t = 1.5 * np.pi * (1 + 3 * np.random.rand(1, n_samples))
x = t * np.cos(t)
y = t * np.sin(t)
X = np.concatenate((x, y))
X += 0.7 * np.random.randn(2, n_samples)
X = X.T
# 创建捕捉局部连接性的图
knn_graph = kneighbors_graph(X, 30, include_self=False)
for connectivity in (None, knn_graph):
for n_clusters in (30, 3):
plt.figure(figsize=(10, 4))
for index, linkage in enumerate(("average", "complete", "ward", "single")):
plt.subplot(1, 4, index + 1)
model = AgglomerativeClustering(linkage=linkage, connectivity=connectivity, n_clusters=n_clusters)
t0 = time.time()
model.fit(X)
elapsed_time = time.time() - t0
plt.scatter(X[:, 0], X[:, 1], c=model.labels_, cmap=plt.cm.nipy_spectral)
plt.title("linkage=%s\n(time %.2f s)" % (linkage, elapsed_time), fontdict=dict(verticalalignment="top"))
plt.axis("equal")
plt.axis("off")
plt.subplots_adjust(bottom=0, top=0.83, wspace=0, left=0, right=1)
plt.suptitle("n_cluster=%i, connectivity=%r" % (n_clusters, connectivity is not None), size=17)
plt.show()
通过上述代码,可以看到在不同的链接方法和连接图设置下,聚类结果的分布情况。这有助于理解不同链接方法的特点以及连接图对聚类结果的影响。