聚类算法的对比与评估
1、用真实值评估聚类(ARI)
1.1 ARI(调整rand指数)
🌺有一些指标可用于评估聚类算法相对于真实聚类的结果,其中最重要的是调整rand指数和归一化互信息。
- 二者都给出了定量的度量,其最佳值为1,0表示不相关的聚类(虽然ARI可以取负值)。
📐下面我们使用ARI来比较k均值,凝聚聚类和DBSCAN算法。
X, y = make_moons(n_samples=200,noise=0.05,random_state=0)
#将数据缩放成平均值
scaler = StandardScaler()
scaler.fit(X)
X_scaled = scaler.transform(X)
#列出要使用的算法
fig, axes = plt.subplots(1,4,figsize=(15,3),subplot_kw={'xticks':(),'yticks':()})
algorithms = [KMeans(n_clusters=2),AgglomerativeClustering(n_clusters=2),DBSCAN()]
#创建一个随机簇分配
random_state = np.random.RandomState(seed=0)
random_clusters = random_state.randint(low=0,high=2,size=len(X))
#绘制随机分配
axes[0].scatter(X_scaled[:,0],X_scaled[:,1],c=random_clusters,cmap=mglearn.cm3,s=60)
axes[0].set_title("Random Assignment - ARI:{:.2f}".format(adjusted_rand_score(y,random_clusters)))
for ax, algorithm, in zip(axes[1:],algorithms):
clusters = algorithm.fit_predict(X_scaled)
ax.scatter(X_scaled[:,0],X_scaled[:,1],c=clusters,cmap=mglearn.cm3,s=60)
ax.set_title("{}-ARI:{:.2f}".format(str(algorithm),adjusted_rand_score(y,clusters)))
📣
调整rand参数给出了符合直觉的结果,随机簇分配的分数约等于0,而DBSCAN(完美找到了期望中的聚类)的分数为1.
👍
用这种方法评估聚类时,一个常见的错误是使用accuracy_score(之前在监督学习中,用于分类器评分),而不是adjusted_rand_score或者其它聚类指标。
使用精度的问题在于:它要求分配的簇标签与真实值完全匹配。
- 但簇标签本身毫无意义——唯一重要的是哪些点位于同一个簇中。
2、没有真实值的情况下评估聚类(轮廓系数)
🌺在实践中,使用诸如ARI之类的指标有一个很大的问题。
- 在应用聚类算法时,通常没有真实值来比较结果。
- 如果我们知道了数据的正确聚类,那么可以使用这一信息构建一个监督模型(比如分类器)。
- 因此,使用类似ARI和NMI的指标通常仅有助于开发算法,但对评估应用是否成功没有帮助。
🌺有一些聚类的评分指标不需要真实值,比如轮廓系数。
- 轮廓分数计算一个簇的紧致度,其值越大越好,最高分数为1.
- 虽然紧致的簇很好,但紧致度不允许复杂的形状,它们在实践中的效果并不好。
2.1 轮廓系数
📐下面我们将用轮廓系数在make_moons数据集上比较k均值,凝聚聚类和DBSCAN
#轮廓系数silhouette_score(样本点,预测的标签)
from sklearn.metrics.cluster import silhouette_score
fig, axes = plt.subplots(1,4,figsize=(15,3),subplot_kw={'xticks':(),'yticks':()})
#绘制随机分配
axes[0].scatter(X_scaled[:,0],X_scaled[:,1],c=random_clusters,cmap=mglearn.cm3,s=60)
axes[0].set_title("Random Assignment:{:.2f}".format(silhouette_score(X_scaled,random_clusters)))
for ax, algorithm, in zip(axes[1:],algorithms):
clusters = algorithm.fit_predict(X_scaled)
ax.scatter(X_scaled[:,0],X_scaled[:,1],c=clusters,cmap=mglearn.cm3,s=60)
ax.set_title("{}-ARI:{:.2f}".format(str(algorithm),silhouette_score(X_scaled,clusters)))
📣
k均值的轮廓分数最高,尽管我们可能更喜欢DBSCAN的结果
👍
(1)
对于评估聚类,稍好的策略是使用基于鲁棒性的聚类指标,
- 这种指标先向数据中添加一些噪声,或者使用不同的参数设定,然后运用算法,并对结果进行比较。
- 其思想是,如果许多算法参数和许多数据扰动返回相同的结果,那么它很可能是可信的。
(2)
即使我们得到一个鲁棒性很好的聚类或者非常高的轮廓分数,但仍然不知道聚类中是否有任何语义含义,或者聚类是否反应了数据中我们感兴趣的某个方面。
- 要想知道聚类是否对应于我们感兴趣的内容,唯一的办法就是对簇进行人工分析。
3、在人脸数据集上比较算法
🌺人脸数据集要先处理一下
people = fetch_lfw_people(min_faces_per_person=20,resize=0.7)
image_shape = people.images[0].shape
# 降低数据偏斜,每个人最多取50张图像
people = fetch_lfw_people(min_faces_per_person=20,resize=0.7)
image_shape = people.images[0].shape
# 降低数据偏斜,每个人最多取50张图像
mask = np.zeros(people.target.shape,dtype=np.bool)
for target in np.unique(people.target):
mask[np.where(people.target==target)[0][:50]]=1 #按条件查找数组元素并返回索引——np.where()
X_people = people.data[mask] #mask是一个bool列表,将显示True的行选出来做X_people
y_people = people.target[mask]
X_people=X_people / 255 #把灰度值缩放到0-1之间
X_train,X_test,y_train,y_test=train_test_split(X_people,y_people,random_state=0)
#从lfw中提取特征脸,并对数据进行transform
pca = PCA(n_components=100,whiten=True,random_state=0)
X_pca = pca.fit_transform(X_people)
3.1 用DBSCAN分析人脸数据集
#用DBSCANF分析人脸数据集
dbscan = DBSCAN()
labels = dbscan.fit_predict(X_pca)
print("Unique labels:{}".format(np.unique(labels)))
📣
我们看到,所有返回的标签都是-1,因此所有数据都被DBSCAN标记为“噪声”。我们可以通过增大eps从而扩展每个点的领域,或者减小min_samples,得到更小的点组视为簇。
#增大eps
for eps in [1,3,5,7,9,11,13]:
print("eps={}".format(eps))
dbscan = DBSCAN(eps=eps)
labels = dbscan.fit_predict(X_pca)
print("clusters present:{}".format(np.unique(labels)))
print("clusters sizes:{}".format(np.bincount(labels+1)))
#将eps=7中13个较小的簇中的点全部可视化,以详细研究这一聚类结果
dbscan = DBSCAN(eps=7,min_samples=3)
labels = dbscan.fit_predict(X_pca)
for cluster in range(max(labels)+1):
mask = labels==cluster
print('mask:{}'.format(mask))
n_images = np.sum(mask)
fig, axes = plt.subplots(1,n_images,figsize=(n_images*1.5,4),
subplot_kw={'xticks':(),'yticks':()})
for image, label, ax in zip(X_people[mask],y_people[mask],axes):
ax.imshow(image.reshape(image_shape),vmin=0,vmax=1)
ax.set_title(people.target_names[label].split()[-1])
3.2 用KMeans分析人脸数据集
🌺用DBSCAN无法创建多于一个较大的簇,凝聚聚类和KMeans可以创建大小更均匀的簇
#用K均值分析人脸数据集
#KMeans提取簇
km = KMeans(n_clusters=10,random_state=0)
labels_km = km.fit_predict(X_pca)
print("Cluster sizes k-means:{}".format(np.bincount(labels)))
#将簇中心进行可视化
#先将聚类中心旋转回原始空间
fig, axes = plt.subplots(1,10,figsize=(12,4),subplot_kw={'xticks':(),'yticks':()})
for ax, center in zip(axes.ravel(),km.cluster_centers_):
ax.imshow(pca.inverse_transform(center).reshape(image_shape),vmin=0,vmax=1)
📣
KMeans找到的簇中心是非常平滑的人脸。
mglearn.plots.plot_kmeans_faces(km,pca,X_pca,X_people,y_people,people.target_names)
📣
上图为每个簇找到的样本图像
- 簇中心在最左边,然后是5个距离中心最近的点,然后是5个距离最远的点
👍
利用更多数量的簇,算法可以找到更细微的区别,但会使得人工检查更困难
3.3 用凝聚聚类分析人脸数据集
#用凝聚聚类分析人脸数据集
from sklearn.cluster import AgglomerativeClustering
#输出用agglomerative聚类的每个簇的样本点个数
agglomerative = AgglomerativeClustering(n_clusters=10)
labels_agg = agglomerative.fit_predict(X_pca)
print("Cluster sizes agglomerative clustering:{}".format(np.bincount(labels)))
from scipy.cluster.hierarchy import ward,dendrogram
linkage_array = ward(X_pca)
plt.figure(figsize=(20,5))
dendrogram(linkage_array,p=7,truncate_mode='level',no_labels=True)
plt.xlabel("sample index")
plt.ylabel("Cluster distance")
📣
对于人脸数据集而言,似乎没有非常自然的切割点。
#将agglomerative的10个簇可视化
for label in np.unique(labels_agg):
mask = labels_agg==label #依次取出每一个簇的所有样本
fig,axes = plt.subplots(1,10,figsize=(15,8),subplot_kw={"xticks":(),'yticks':()})
axes[0].set_ylabel(np.sum(mask))
for ax, image, ylabel in zip(axes.ravel(),X_people[mask],y_people[mask]):
ax.imshow(image.reshape(image_shape),vmin=0,vmax=1)
ax.set_title(people.target_names[ylabel].split()[-1])
#用agglomerative,这次使用40个簇,并挑选一些有趣的簇进行可视化
#训练模型并预测
agglomerative = AgglomerativeClustering(n_clusters=40)
labels_agg = agglomerative.fit_predict(X_pca)
n_clusters = 40
for cluster in [6,13,22,38,39]:
mask = labels_agg==cluster
cluster_size = np.sum(mask)
fig,axes = plt.subplots(1,15,figsize=(15,8),subplot_kw={"xticks":(),'yticks':()})
axes[0].set_ylabel(np.sum(mask))
for ax, image, ylabel in zip(axes.ravel(),X_people[mask],y_people[mask]):
ax.imshow(image.reshape(image_shape),vmin=0,vmax=1)
ax.set_title(people.target_names[ylabel].split()[-1])
for i in range(cluster_size,15):
axes[i].set_visible(False)
📣
上图为将簇的数量设为40时,从聚类找到的簇中挑选的图像
4、参考文献
《python机器学习基础教程》