Python机器学习笔记(十五、聚类算法的对比和评估)
用真实世界的数据集对k均值、凝聚聚类和DBSCAN算法进行比较。
1. 用真实值评估聚类
评估聚类算法对真实世界数据集的聚类结果,可以用调整rand指数ARI和归一化互信息NMI。
调整rand指数 (adjusted rand index,ARI)和归一化互信息(normalized mutual information,NMI),二者都给出了定量的度量,最佳值为1,0表示不相关的聚类。
示例,使用 ARI 来比较 k 均值、凝聚聚类和 DBSCAN 算法。
import numpy as np
import matplotlib.pyplot as plt
import mglearn
from sklearn.cluster import KMeans
from sklearn.datasets import make_moons
from sklearn.cluster import AgglomerativeClustering
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
from sklearn.metrics.cluster import adjusted_rand_score
X, y = make_moons(n_samples=200, noise=0.05, random_state=0)
# 将数据缩放成平均值为0、方差为1
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(algorithm.__class__.__name__, adjusted_rand_score(y, clusters)))
plt.show()
输出图形:
上图是:利用监督 ARI 分数在two_moons数据集上比较随机分配、k 均值、凝聚聚类和DBSCAN。调整 rand 指数给出了符合直觉的结果,随机簇分配的分数为 0,而 DBSCAN(完美地找到了期望中的聚类)的分数为 1。
用这种方式评估聚类时,一个常见的错误是使用accuracy_score而不是adjusted_rand_ score、normalized_mutual_info_score或其他聚类指标。使用精度的问题在于,它要求分配的簇标签与真实值完全匹配。但簇标签本身毫无意义——唯一重要的是哪些点位于同一个簇中。
from sklearn.metrics.cluster import adjusted_rand_score
from sklearn.metrics import accuracy_score
# 这两种点标签对应于相同的聚类
clusters1 = [0, 0, 1, 1, 0]
clusters2 = [1, 1, 0, 0, 1]
# 精度为0,因为二者标签完全不同
print("Accuracy: {:.2f}".format(accuracy_score(clusters1, clusters2)))
# 调整rand分数为1,因为二者聚类完全相同
print("ARI: {:.2f}".format(adjusted_rand_score(clusters1, clusters2)))
输出结果:
Accuracy: 0.00
ARI: 1.00
2. 在没有真实值的情况下评估聚类
上面评估聚类算法的方法在实践中,使用诸如ARI之类的指标有一个很大的问题。在应用聚类算法时,通常没有真实值来比较结果。如果我们知道了数据的正确聚类,那么可以使用这一信息构建一个监督模型(比如分类器)。因此,使用类似ARI和NMI的指标通常仅有助于开发算法,但对评估应用是否成功没有帮助。
有一些聚类的评分指标不需要真实值,比如轮廓系数(silhouette coeffcient)。但它们在实践中的效果并不好。轮廓分数计算一个簇的紧致度,其值越大越好,最高分数为 1。虽然紧致的簇很好,但紧致度不允许复杂的形状。下面代码示例,利用轮廓分数在 two_moons 数据集上比较 k 均值、凝聚聚类和 DBSCAN:
import numpy as np
import matplotlib.pyplot as plt
import mglearn
from sklearn.cluster import KMeans
from sklearn.datasets import make_moons
from sklearn.cluster import AgglomerativeClustering
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
from sklearn.metrics.cluster import silhouette_score
X, y = make_moons(n_samples=200, noise=0.05, random_state=0)
# 将数据缩放成平均值为0、方差为1
scaler = StandardScaler()
scaler.fit(X)
X_scaled = scaler.transform(X)
fig, axes = plt.subplots(1, 4, figsize=(15, 3), subplot_kw={'xticks': (), 'yticks': ()})
# 创建一个随机的簇分配,作为参考
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: {:.2f}".format(silhouette_score(X_scaled, random_clusters)))
algorithms = [KMeans(n_clusters=2), AgglomerativeClustering(n_clusters=2), DBSCAN()]
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("{} : {:.2f}".format(algorithm.__class__.__name__, silhouette_score(X_scaled, clusters)))
plt.show()
输出:
上图四:利用无监督的轮廓分数在 two_moons 数据集上比较随机分配、k 均值、凝聚聚类和 DBSCAN(更符合直觉的 DBSCAN 的轮廓分数低于 k 均值找到的分配)。
如上图所见,k 均值的轮廓分数最高,尽管我们可能更喜欢 DBSCAN 的结果。对于评估聚类,稍好的策略是使用基于鲁棒性的(robustness-based)聚类指标。这种指标先向数据中添加一些噪声,或者使用不同的参数设定,然后运行算法,并对结果进行比较。其思想是,如果许多算法参数和许多数据扰动返回相同的结果,那么它很可能是可信的。
即使我们得到一个鲁棒性很好的聚类或者非常高的轮廓分数,但仍然不知道聚类中是否有任何语义含义,或者聚类是否反映了数据中我们感兴趣的某个方面。回到人脸图像的例子,我们希望找到类似人脸的分组,比如男人和女人、老人和年轻人,或者有胡子的人和没胡子的人。假设我们将数据分为两个簇,关于哪些点应该被聚类在一起,所有算法的结果一致。我们仍不知道找到的簇是否以某种方式对应于我们感兴趣的概念。算法找到的可能是侧视图和正面视图、夜间拍摄的照片和白天拍摄的照片,或者iPhone拍摄的照片和安卓手机拍摄的照片。要想知道聚类是否对应于我们感兴趣的内容,唯一的办法就是对簇进行人工分析。
3. 在人脸数据集上比较算法
将 k 均值、DBSCAN 和凝聚聚类算法应用于 Wild 数据集中的 Labeled Faces,查看它们是否找到了有趣的结构。我们将使用数据的特征脸表示,它由包含 100 个成分的 PCA(whiten=True) 生成:
import numpy as np
from sklearn.cluster import DBSCAN
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
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
X_people = people.data[mask]
y_people = people.target[mask]
# 将灰度值缩放到0到1之间,而不是在0到255之间
# 以得到更好的数据稳定性
X_people = X_people / 255.
# 从lfw数据中提取特征脸,并对数据进行变换
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)
# 用 DBSCAN 分析人脸数据集。
# 应用默认参数的DBSCAN
dbscan = DBSCAN()
labels = dbscan.fit_predict(X_pca)
print("Unique labels: {}".format(np.unique(labels)))
用DBSCAN分析人脸数据集输出:Unique labels: [-1] 所有返回的标签都是-1,因此所有数据都被 DBSCAN 标记为“噪声”。我们可以改变两个参数来改进这一点:第一,增大 eps,从而扩展每个点的邻域;第二, 减小 min_samples,从而将更小的点组视为簇。首先尝试改变 min_samples:
import numpy as np
from sklearn.cluster import DBSCAN
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
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
X_people = people.data[mask]
y_people = people.target[mask]
# 将灰度值缩放到0到1之间,而不是在0到255之间
# 以得到更好的数据稳定性
X_people = X_people / 255.
# 从lfw数据中提取特征脸,并对数据进行变换
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)
# 用 DBSCAN 分析人脸数据集。
# 改变min_samples
dbscan = DBSCAN(min_samples=3)
labels = dbscan.fit_predict(X_pca)
print("Unique labels: {}".format(np.unique(labels)))
输出结果:Unique labels: [-1] 。考虑由三个点构成的组,所有点也都被标记为噪声。再增大eps:
import numpy as np
from sklearn.cluster import DBSCAN
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
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
X_people = people.data[mask]
y_people = people.target[mask]
# 将灰度值缩放到0到1之间,而不是在0到255之间
# 以得到更好的数据稳定性
X_people = X_people / 255.
# 从lfw数据中提取特征脸,并对数据进行变换
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)
# 用 DBSCAN 分析人脸数据集。
# 改变min_samples,增大eps
dbscan = DBSCAN(min_samples=3, eps=15)
labels = dbscan.fit_predict(X_pca)
print("Unique labels: {}".format(np.unique(labels)))
改变min_samples,增大eps后输出:Unique labels: [-1 0]
从输出可以看出,使用更大的 eps(其值为 15),只得到了单一簇和噪声点。我们可以利用这一结果找出 “噪声”相对于其他数据的形状。为了进一步理解发生的事情,我们查看有多少点是噪声, 有多少点在簇内:
import numpy as np
from sklearn.cluster import DBSCAN
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
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
X_people = people.data[mask]
y_people = people.target[mask]
# 将灰度值缩放到0到1之间,而不是在0到255之间
# 以得到更好的数据稳定性
X_people = X_people / 255.
# 从lfw数据中提取特征脸,并对数据进行变换
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)
# 用 DBSCAN 分析人脸数据集。
# 改变min_samples,增大eps
dbscan = DBSCAN(min_samples=3, eps=15)
labels = dbscan.fit_predict(X_pca)
print("Unique labels: {}".format(np.unique(labels)))
# 计算所有簇中的点数和噪声中的点数。
# bincount不允许负值,所以我们需要加1。
# 结果中的第一个数字对应于噪声点。
print("Number of points per cluster: {}".format(np.bincount(labels + 1)))
输出结果:Number of points per cluster: [ 37 2026] 有27个噪声点,下面查看所有噪声点:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
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
X_people = people.data[mask]
y_people = people.target[mask]
# 将灰度值缩放到0到1之间,而不是在0到255之间
# 以得到更好的数据稳定性
# X_people = X_people / 255.
# 从lfw数据中提取特征脸,并对数据进行变换
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)
# 用 DBSCAN 分析人脸数据集。
# 改变min_samples,增大eps
dbscan = DBSCAN(min_samples=3, eps=15)
labels = dbscan.fit_predict(X_pca)
noise = X_people[labels==-1]
fig, axes = plt.subplots(3, 9, subplot_kw={'xticks': (), 'yticks': ()}, figsize=(12, 4))
for image, ax in zip(noise, axes.ravel()):
ax.imshow(image.reshape(image_shape), vmin=0, vmax=1)
plt.show()
输出结果:
这些是人脸数据集中被DBSCAN标记为噪声的样本,将这些图像与随机选择的人脸图像样本进行比较,我们可以猜测它们被标记为噪声的原因:第2行第9张图像显示一个人正在用玻璃杯喝水,还有人戴帽子的图像,在第2行第6张图像中,人脸前面有一只手。其他图像都包含奇怪的角度,或者太近或太宽的剪切。
这种类型的分析——尝试找出“奇怪的那一个”——被称为异常值检测(outlier detection)。如果这是一个真实的应用,那么我们可能会尝试更好地裁切图像,以得到更加均匀的数据。对于照片中的人有时戴着帽子、喝水或在面前举着某物,我们能做的事情很少。但需要知道它们是数据中存在的问题,我们应用任何算法都需要解决这些问题。
如果想要找到更有趣的簇,而不是一个非常大的簇,那么需要将 eps 设置得更小,取值在15和0.5(默认值)之间。我们来看一下 eps 不同取值对应的结果:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
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
X_people = people.data[mask]
y_people = people.target[mask]
# 将灰度值缩放到0到1之间,而不是在0到255之间
# 以得到更好的数据稳定性
# X_people = X_people / 255.
# 从lfw数据中提取特征脸,并对数据进行变换
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)
# 用 DBSCAN 分析人脸数据集。
for eps in [1, 3, 5, 7, 9, 11, 13]:
print("\neps={}".format(eps))
dbscan = DBSCAN(eps=eps, min_samples=3)
labels = dbscan.fit_predict(X_pca)
print("Clusters present: {}".format(np.unique(labels)))
print("Cluster sizes: {}".format(np.bincount(labels + 1)))
输出结果:
eps=1
Clusters present: [-1]
Cluster sizes: [2063]
eps=3
Clusters present: [-1]
Cluster sizes: [2063]
eps=5
Clusters present: [-1 0]
Cluster sizes: [2059 4]
eps=7
Clusters present: [-1 0 1 2 3 4 5 6]
Cluster sizes: [1954 75 4 14 6 4 3 3]
eps=9
Clusters present: [-1 0 1]
Cluster sizes: [1199 861 3]
eps=11
Clusters present: [-1 0]
Cluster sizes: [ 403 1660]
eps=13
Clusters present: [-1 0]
Cluster sizes: [ 119 1944]
对于较小的 eps,所有点都被标记为噪声。eps=7 时,我们得到许多噪声点和许多较小的簇。eps=9 时,仍得到许多噪声点,但我们得到了一个较大的簇和一些较小的簇。从 eps=11 开始,我们仅得到一个较大的簇和噪声。 有趣的是,较大的簇从来没有超过一个。最多有一个较大的簇包含大多数点,还有一些较小的簇。这表示数据中没有两类或三类非常不同的人脸图像,而是所有图像或多或少地都与其他图像具有相同的相似度(或不相似度)。eps=7 的结果看起来最有趣,它有许多较小的簇。我们可以通过将较小的簇中的点全部可视化来深入研究这一聚类:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
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
X_people = people.data[mask]
y_people = people.target[mask]
# 将灰度值缩放到0到1之间,而不是在0到255之间
# 以得到更好的数据稳定性
# X_people = X_people / 255.
# 从lfw数据中提取特征脸,并对数据进行变换
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)
# 用 DBSCAN 分析人脸数据集。
dbscan = DBSCAN(min_samples=3, eps=7)
labels = dbscan.fit_predict(X_pca)
for cluster in range(max(labels) + 1):
mask = labels == cluster
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])
plt.show()
会输出7个图像:
在每个簇内,人脸方向和面部表情也是固定的。有些簇中包含多个人的面孔,但他们的方向和表情都相似。这就是我们将 DBSCAN 算法应用于人脸数据集的分析结论。我们这里进行了人工分析,不同于监督学习中基于 R2 分数或精度的更为自动化的搜索方法。
用 k 均值分析人脸数据集
利用 DBSCAN 无法创建多于一个较大的簇。凝聚聚类和 k 均值更可能创建均匀大小的簇,但我们需要设置簇的目标个数。可以将簇的数量设置为数据集中的已知人数,虽然无监督聚类算法不太可能完全找到它们。相反,我们可以首先设置一个比较小的簇的数量,比如 10 个,这样我们可以分析每个簇:
import numpy as np
from sklearn.cluster import KMeans
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
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
X_people = people.data[mask]
y_people = people.target[mask]
# 从lfw数据中提取特征脸,并对数据进行变换
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)
# 用k均值提取簇
km = KMeans(n_clusters=10, random_state=0)
labels_km = km.fit_predict(X_pca)
print("Cluster sizes k-means: {}".format(np.bincount(labels_km)))
输出:Cluster sizes k-means: [ 5 286 190 275 185 493 1 286 2 340]
k 均值聚类将数据划分为大小相似的簇,我们可以通过将簇中心可视化来进一步分析 k 均值的结果,由于我们是在PCA生成的表示中进行聚类,因此需要使用 pca.inverse_transform 将簇中心旋转回到原始空间并可视化:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
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
X_people = people.data[mask]
y_people = people.target[mask]
# 从lfw数据中提取特征脸,并对数据进行变换
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)
# 用k均值提取簇
km = KMeans(n_clusters=10, random_state=0)
labels_km = km.fit_predict(X_pca)
fig, axes = plt.subplots(2, 5, subplot_kw={'xticks': (), 'yticks': ()}, figsize=(12, 4))
for center, ax in zip(km.cluster_centers_, axes.ravel()):
ax.imshow(pca.inverse_transform(center).reshape(image_shape), vmin=0, vmax=1)
plt.show()
输出:
k 均值找到的簇中心是非常平滑的人脸。这并不奇怪,因为每个簇中心都是 脸图像的平均。使用降维的 PCA 表示,可以增加图像的平滑度。聚类似乎捕捉到人脸的不同方向、不同表情,以及是否有衬衫领子。下面代码更详细的视图,对每个簇中心给出了簇中 5 张最典型的图像(该簇中与簇中心距离最近的图像)与 5 张最不典型的图像(该簇中与簇中心距离最远的图像):
import mglearn
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
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
X_people = people.data[mask]
y_people = people.target[mask]
# 将灰度值缩放到0到1之间,而不是在0到255之间
# 以得到更好的数据稳定性
# X_people = X_people / 255.
# 从lfw数据中提取特征脸,并对数据进行变换
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)
# 用k均值提取簇
km = KMeans(n_clusters=10, random_state=0)
labels_km = km.fit_predict(X_pca)
mglearn.plots.plot_kmeans_faces(km, pca, X_pca, X_people, y_people, people.target_names)
plt.show()
输出:
从输出图像证实了我们之前的直觉,也证实了其他簇中方向的重要性。不过 “非典型的”点与簇中心不太相似,而且它们的分配似乎有些随意。这可以归因于以下事实:k均值对所有数据点进行划分,不像 DBSCAN 那样具有“噪声”点的概念。利用更多数量的簇,算法可以找到更细微的区别。但添加更多的簇会使得人工检查更加困难。
用凝聚聚类分析人脸数据集
import numpy as np
from sklearn.cluster import AgglomerativeClustering
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
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
X_people = people.data[mask]
y_people = people.target[mask]
# 从lfw数据中提取特征脸,并对数据进行变换
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)
# 用ward凝聚聚类提取簇
agglomerative = AgglomerativeClustering(n_clusters=10)
labels_agg = agglomerative.fit_predict(X_pca)
print("Cluster sizes agglomerative clustering: {}".format(np.bincount(labels_agg)))
输出:Cluster sizes agglomerative clustering: [264 100 275 553 49 64 546 52 51 109]
凝聚聚类生成的也是大小相近的簇,其大小在 49 和 553 之间。这比 k 均值生成的簇更不均匀,但比 DBSCAN 生成的簇要更加均匀。通过计算 ARI 来度量凝聚聚类和 k 均值给出的两种数据划分是否相似:
from sklearn.metrics.cluster import adjusted_rand_score
import numpy as np
from sklearn.cluster import KMeans
from sklearn.cluster import AgglomerativeClustering
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
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
X_people = people.data[mask]
y_people = people.target[mask]
# 从lfw数据中提取特征脸,并对数据进行变换
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)
# 用k均值提取簇
km = KMeans(n_clusters=10, random_state=0)
labels_km = km.fit_predict(X_pca)
# 用ward凝聚聚类提取簇
agglomerative = AgglomerativeClustering(n_clusters=10)
labels_agg = agglomerative.fit_predict(X_pca)
print("ARI: {:.2f}".format(adjusted_rand_score(labels_agg, labels_km)))
输出结果:ARI: 0.09
ARI 只有 0.09,说明 labels_agg 和 labels_km 这两种聚类的共同点很少。这并不奇怪,原因在于以下事实:对于k均值,远离簇中心的点似乎没有什么共同点。下面我们绘制树状图来展示,但是我们将限制图中树的深度,因为如果分支到2063个数据点,图像将密密麻麻无法阅读:
from scipy.cluster.hierarchy import dendrogram, ward
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
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
X_people = people.data[mask]
y_people = people.target[mask]
# 从lfw数据中提取特征脸,并对数据进行变换
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)
linkage_array = ward(X_pca)
# 现在我们为包含簇之间距离的linkage_array绘制树状图
plt.figure(figsize=(20, 5))
dendrogram(linkage_array, p=7, truncate_mode='level', no_labels=True)
plt.xlabel("Sample index")
plt.ylabel("Cluster distance")
plt.show()
输出图像:
要想创建 10 个簇,我们在顶部有 10 条竖线的位置将树横切。对于人脸数据而言,似乎没有非常自然的切割点。有一些分支代表更为不同的组,但似乎没有一个特别合适的簇的数量。这并不奇怪,因为 DBSCAN 的结果是试图将所有的点都聚类在一起。我们将 10 个簇可视化,注意,在凝聚聚类中没有簇中心的概念,只是给出了每个簇的前几个点。在第一张图像的左侧给出了每个簇中的点的数量:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import AgglomerativeClustering
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
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
X_people = people.data[mask]
y_people = people.target[mask]
# 从lfw数据中提取特征脸,并对数据进行变换
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)
# 用ward凝聚聚类提取簇
agglomerative = AgglomerativeClustering(n_clusters=10)
labels_agg = agglomerative.fit_predict(X_pca)
n_clusters = 10
for cluster in range(n_clusters):
mask = labels_agg == cluster
fig, axes = plt.subplots(1, 10, subplot_kw={'xticks': (), 'yticks': ()}, figsize=(15, 8))
axes[0].set_ylabel(np.sum(mask))
for image, label, asdf, ax in zip(X_people[mask], y_people[mask], labels_agg[mask], axes):
ax.imshow(image.reshape(image_shape), vmin=0, vmax=1)
ax.set_title(people.target_names[label].split()[-1], fontdict={'fontsize': 9})
plt.show()
输出了10个图像:
虽然某些簇似乎具有语义上的主题,但许多簇都太大而实际上很难是均匀的。为了得到更加均匀的簇,我们可以再次运行算法,这次使用 40 个簇,并挑选出一些特别有趣的簇:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import AgglomerativeClustering
from sklearn.datasets import fetch_lfw_people
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
image_shape = people.images[0].shape
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
X_people = people.data[mask]
y_people = people.target[mask]
# 从lfw数据中提取特征脸,并对数据进行变换
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)
# 用ward凝聚聚类提取簇
agglomerative = AgglomerativeClustering(n_clusters=40)
labels_agg = agglomerative.fit_predict(X_pca)
print("cluster sizes agglomerative clustering: {}".format(np.bincount(labels_agg)))
n_clusters = 40
for cluster in [10, 13, 19, 22, 36]: # 手动挑选“有趣的”簇
mask = labels_agg == cluster
fig, axes = plt.subplots(1, 15, subplot_kw={'xticks': (), 'yticks': ()}, figsize=(15, 8))
cluster_size = np.sum(mask)
axes[0].set_ylabel("#{}: {}".format(cluster, cluster_size))
for image, label, asdf, ax in zip(X_people[mask], y_people[mask], labels_agg[mask], axes):
ax.imshow(image.reshape(image_shape), vmin=0, vmax=1)
ax.set_title(people.target_names[label].split()[-1], fontdict={'fontsize': 9})
for i in range(cluster_size, 15):
axes[i].set_visible(False)
plt.show()
输出结果和图像:
cluster sizes agglomerative clustering: [139 35 23 2 111 39 106 33 5 161 60 41 70 17 30 20 134 40 23 38 56 264 4 35 44 16 29 135 25 37 42 34 3 17 31 3 21 27 76 37]
这里聚类挑选出的似乎是“深色皮肤且微笑”“有领子的衬衫”“微笑的女性”“萨达姆” 和“高额头”。如果进一步详细分析,我们还可以利用树状图找到这些高度相似的簇。
聚类方法小结:聚类的应用与评估是一个非常定性的过程,通常在数据分析的探索阶段很有帮助。学习了三种聚类算法:k 均值、DBSCAN 和凝聚聚类。这三种算法都可以控制聚类的粒度(granularity)。k 均值和凝聚聚类允许指定想要的簇的数量,而 DBSCAN 允许用 eps 参数定义接近程度,从而间接影响簇的大小。三种方法都可以用于大型的现实世界数据集,都相对容易理解,也都可以聚类成多个簇。每种算法的优点稍有不同。k 均值可以用簇的平均值来表示簇,还可以被看作一种分解方法,每个数据点都由其簇中心表示。DBSCAN 可以检测到没有分配任何簇的“噪声点”,还可以帮助自动判断簇的数量。与其他两种方法不同,它允许簇具有复杂的形状,正如我们在 two_moons 的例子中所看到的那样。DBSCAN 有时会生成大小差别很大的簇,这可能是它的优点,也可能是缺点。凝聚聚类可以提供数据的可能划分的整个层次结构,可以通过树状图轻松查看。
对无监督学习算法的小结:无监督学习算法可用于探索性数据分析和预处理。找到数据的正确表 示对于监督学习和无监督学习的成功通常都至关重要,预处理和分解方法在数据准备中具有重要作用。分解、流形学习和聚类都是加深数据理解的重要工具,在没有监督信息的情况下,也是理解数据的仅有的方法。即使是在监督学习中,探索性工具对于更好地理解数据性质也很重要。通常来说,很难量化无监督算法的有用性,但这不妨碍使用它们来深入理解数据。学完这些方法,基本掌握了机器学习从业者每天使用的所有必要的学习算法。
另外:建议在 scikit-learn 中包含的二维玩具数据和现实世界数据集(比如 digits、iris 和 cancer 数据集)上尝试聚类和分解方法的学习使用,多动手试试。