简单来说,簇就是分类结果中的类,但实际上簇并没有明确的定义,并且簇的划分没有客观标准,我们可以利用下图来理解什么是簇。该图显示了20个点和将它们划分成簇的3种不同方法。标记的形状指示簇的隶属关系。下图分别将数据划分成两部分、四部分和六部分。将2个较大的簇每一个都划分成3个子簇可能是人的视觉系统造成的假象。此外,说这些点形成4个簇可能也不无道理。该图表明簇的定义是不精确的,而好的定义依赖于数据的特性和期望的结果。
k均值是发现给定数据集的k个簇的算法。簇个数k是用户给定的,每一个簇通过其质心(centroid)来描述。
k均值聚类工作流程是这样的:首先,随机选择k个初始质心,其中k是用户指定的参数,即所期望的簇的个数。然后将数据集中的每个点指派到距离最近的质心,所有被指派到一个质心的点即为一个簇。然后,根据指派到簇的点,将每个簇的质心更新为该簇所有点的平均值。重复指派和更新步骤,直到簇不发生变化(或直到质心不发生变化)。
创建k个点作为初始质心(通常是随机选择)
当任意一个点的簇分配结果发生改变时:
对数据集中的每个点:
对每个质心:
计算质心与数据点之间的距离
将数据点分配到据其近的簇
对每个簇,计算簇中所有点的均值并将均值作为新的质心
直到簇不再发生变化或者达到大迭代次数
这个过程可以由下图来显示,我们规定,将数据分为4簇(k=4),其中白色X代表质心的位置:
聚类算法追求的是“簇内差异小,簇外差异大”。而这个“差异“,我们由样本点到其所在簇的质心的距离来衡量。
对于一个簇来说,所有样本点到质心的距离之和越小,我们就认为这个簇中的样本越相似,簇内差异就越小。而距离的衡量方法有多种,令 x x x表示簇中的一个样本点, μ \mu μ表示该簇中的质心, n n n表示每个样本点中的特征数目, i i i表示组成点 x x x的每个特征,则该样本点到质心的距离可以由以下距离来度量:
如我们采用欧几里得距离,则一个簇中所有样本点到质心的距离的平方和为:
其中, m m m为一个簇中样本的个数, j j j是每个样本的编号。这个公式被称为簇内平方和(cluster Sum of Square),又叫做Inertia。而将一个数据集中的所有簇的簇内平方和相加,就得到了整体平方和(Total Cluster Sum of Square),又叫做total inertia。Total Inertia越小,代表着每个簇内样本越相似,聚类的效果就越好。因此 K-Means追求的是,求解能够让Inertia最小化的质心。实际上,在质心不断变化不断迭代的过程中,总体平方和是越来越小的。我们可以使用数学来证明,当整体平方和最小的时候,质心就不再发生变化了。如此,K-Means的求解过程,就变成了一个最优化问题。
from sklearn.datasets import make_blobs # 允许自己创建数据
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
# 创建数据
X, y = make_blobs(n_samples=500,n_features=2,centers=4,random_state=1)
# 将生成的点画出来
fig, ax1 = plt.subplots(1)
ax1.scatter(X[:, 0],
X[:, 1],
marker='o', # 点的形状
s=8 # 点的大小
)
plt.show()
color = ["red","pink","orange","gray"]
fig, ax1 = plt.subplots(1)
for i in range(4):
ax1.scatter(X[y==i, 0], X[y==i, 1],
marker='o',
s=8,
c=color[i])
plt.show()
n_clusters = 3
cluster = KMeans(n_clusters=n_clusters, random_state=0).fit(X)
y_pred = cluster.labels_ # 查看聚好的类
y_pred
pre = cluster.fit_predict(X) # 判断X在聚类结果上会被分到哪一类,无监督学习不需要调用
pre == y_pred
centroid = cluster.cluster_centers_ # 查看质心坐标
centroid
inertia = cluster.inertia_ # 查看总距离平方和
inertia
color = ["red","pink","orange","gray"]
fig, ax1 = plt.subplots(1)
for i in range(n_clusters):
ax1.scatter(X[y_pred==i, 0], X[y_pred==i, 1],
marker='o',
s=8,
c=color[i]
)
ax1.scatter(centroid[:,0], centroid[:,1],
marker="x",
s=15,
c="black"
)
plt.show()
# 将k换成4
n_clusters = 4
cluster_ = KMeans(n_clusters=n_clusters, random_state=0).fit(X)
inertia_ = cluster_.inertia_
inertia_
在99%的情况下,我们是对没有真实标签的数据进行探索,也就是对不知道真正答案的数据进行聚类。这样的聚类,是完全依赖于评价簇内的稠密程度(簇内差异小)和簇间的离散程度(簇外差异大)来评估聚类的效果。其中轮廓系数是最常用的聚类算法的评价指标。它是对每个样本来定义的,它能够同时衡量:
1)样本与其自身所在的簇中的其他样本的相似度a,即a=样本与同一簇中所有其他点之间的平均距离;
2)样本与其他簇中的样本的相似度b,即b=样本与下一个最近的簇中得所有点之间的平均距离。
根据聚类的要求”簇内差异小,簇外差异大“,我们希望b永远大于a,并且大得越多越好。
单个样本的轮廓系数计算为:
即:
很容易理解轮廓系数范围是(-1,1),其中值越接近1表示样本与自己所在的簇中的样本很相似,并且与其他簇中的样本不相似,当样本点与簇外的样本更相似的时候,轮廓系数就为负。当轮廓系数为0时,则代表两个簇中的样本相似度一致,两个簇本应该是一个簇。
如果一个簇中的大多数样本具有比较高的轮廓系数,则簇会有较高的总轮廓系数,即整个数据集的平均轮廓系数越高,聚类越合适。如果许多样本点具有低轮廓系数甚至负值,则聚类是不合适的,聚类的超参数k可能设定得太大或者太小。
from sklearn.metrics import silhouette_score
from sklearn.metrics import silhouette_samples
silhouette_score(X,y_pred) # 总轮廓系数(分三个簇)
silhouette_score(X,cluster_.labels_) # 总轮廓系数(分四个簇)
silhouette_samples(X,y_pred) # 返回每个样本点自己的轮廓系数(三个簇)
silhouette_samples(X,cluster_.labels_) # 返回每个样本点自己的轮廓系数(四个簇)
import matplotlib.cm as cm
import numpy as np
for n_clusters in [2,3,4,5,6,7]:
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_size_inches(18, 7)
ax1.set_xlim([-0.1, 1])
ax1.set_ylim([0, X.shape[0] + (n_clusters + 1) * 10])
clusterer = KMeans(n_clusters=n_clusters, random_state=10).fit(X)
cluster_labels = clusterer.labels_
silhouette_avg = silhouette_score(X, cluster_labels)
print("For n_clusters =", n_clusters,
"The average silhouette_score is :", silhouette_avg)
sample_silhouette_values = silhouette_samples(X, cluster_labels)
y_lower = 10
for i in range(n_clusters):
ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]
ith_cluster_silhouette_values.sort()
size_cluster_i = ith_cluster_silhouette_values.shape[0]
y_upper = y_lower + size_cluster_i
color = cm.nipy_spectral(float(i)/n_clusters)
ax1.fill_betweenx(np.arange(y_lower, y_upper),ith_cluster_silhouette_values,facecolor=color,alpha=0.7)
ax1.text(-0.05, y_lower + 0.5 * size_cluster_i,str(i))
y_lower = y_upper + 10
ax1.set_title("The silhouette plot for the various clusters.")
ax1.set_xlabel("The silhouette coefficient values")
ax1.set_ylabel("Cluster label")
ax1.axvline(x=silhouette_avg, color="red", linestyle="--")
ax1.set_yticks([])
ax1.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])
colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)
ax2.scatter(X[:, 0], X[:, 1],marker='o' #点的形状
,s=8 #点的大小
,c=colors)
centers = clusterer.cluster_centers_
ax2.scatter(centers[:, 0], centers[:, 1], marker='x',c="red", alpha=1, s=200)
ax2.set_title("The visualization of the clustered data.")
ax2.set_xlabel("Feature space for the 1st feature")
ax2.set_ylabel("Feature space for the 2nd feature")
plt.suptitle(("Silhouette analysis for KMeans clustering on sample data "
"with n_clusters = %d" % n_clusters),
fontsize=14, fontweight='bold')
plt.show()