目录
1. 无监督学习与聚类算法
2. KMeans
2.1 KMeans是如何工作的
2.2 簇内误差平方和的定义
3. sklearn.cluster.KMeans
3.1 重要参数 n_clusters
3.2 重要属性 inertia_
3.3 重要属性 cluster_centers_
3.4 聚类观察 n_clusters
3.5 聚类算法的模型评估指标
3.6 轮廓系数
3.7 卡林斯基-哈拉巴斯指数 Calinski_Harabasz Index,简称CHI
4. 基于轮廓系数来选择n_clusters
4.1 代码分步详解
4.2 完整代码
“有监督学习”即模型在训练的时候,既需要特征矩阵x,也需要真实标签y。在机器学习当中,还有相当一部分算法属于“无监督学习”,无监督的算法在训练的时候只需要特征矩阵x,不需要标签。而聚类算法,就是无监督学习的代表算法。
聚类算法又叫“无监督分类”,其目的是将数据划分成有意义或有用的组(或簇)。这种划分可以基于业务需求或建模需求来完成,也可以单纯地帮助我们探索数据的自然结构和分布。比如在商业中,如果我们手头上有大量的当前和潜在客户信息,我们可以使用聚类将客户划分成若干组,以便进一步分析和开展营销活动,最有名的客户价值判断模型RFM,就常常和聚类分析共同使用。再比如,聚类可以用于降维和矢量量化(vector quantization),可以将高维特征压缩到一列当中,常常用于图像,声音,视频等非结构化数据,可以大幅度压缩数据量。
聚类 | 分类 | |
核心 |
将数据分成多个组,探索每个组的数据是否有联系
|
从已经分组的数据中去学习,把新数据放到已经分好的组中去
|
学习类型 |
无监督,无需标签进行训练
|
有监督,需要标签进行训练
|
典型算法 |
K-Means , DBSCAN ,层次聚类,光谱聚类
|
决策树,贝叶斯,逻辑回归
|
算法输出 |
聚类结果是不确定的,不一定总是能够反映数据的真实分类
同样的聚类,根据不同的业务需求,可能是一个好结果,也可能是一个坏结果
|
分类结果是确定的
分类的优劣是客观的
不是根据业务或算法需求决定
|
KMeans作为聚类算法的典型代表,KMeans算法将一组N个样本的特征矩阵X划分为K个无交集的簇,直观上来看是一组一组聚集在一起的数据,在一个簇中的数据就认为是同一类。簇就是聚类的结果表现。簇中所有数据的均值通常被称为这个簇的“质心”(centroids)。在一个二维平面中,一簇数据点的质心的横坐标就是这一簇数据点的横坐标的均值,质心的纵坐标就是这一簇数据点的纵坐标的均值。同理可推广至高维空间。
在KMeans算法中,簇的个数K是一个超参数,需要我们人为输入来确定。KMeans的核心任务就是根据我们设定好的K,找出K个最优的质心,并将离这些质心最近的数据分别分配到这些质心代表的簇中去。具体过程可以总结如下:
顺序 | 过程 |
---|---|
1 |
随机抽取 K 个样本作为最初的质心
|
2 | 开始循环 |
2.1 |
将每个样本点分配到离他们最近的质心,生成 K 个簇
|
2.2 |
对于每个簇,计算所有被分到该簇的样本点的平均值作为新的质心
|
3 |
当质心的位置不再发生变化,迭代停止,聚类完成
|
在数据集多次迭代(iteration),就会有:
在数据集下多次迭代(iteration),模型就会收敛。第六次迭代之后,基本上质心的位置就不再改变了,生成的簇也变得稳定。此时我们的聚类就完成了,我们可以明显看出,KMeans按照数据的分布,将数据聚集成了我们规定的4类,接下来我们就可以按照我们的业务需求或者算法需求,对这四类数据进行不同的处理。
如我们采用欧几里得距离,则一个簇中所有样本点到质心的距离的平方和为:
我们的Inertia是基于欧几里得距离的计算公式得来的。实际上,我们也可以使用其他距离,每个距离都有自己对应的Inertia。在过去的经验中,我们总结出不同距离所对应的质心选择方法和Inertia,在Kmeans中,只要使用了正确的质心和距离组合,无论使用什么样的距离,都可以达到不错的效果:
距离度量 | 质心 | Inertia |
---|---|---|
欧几里得距离 | 均值 |
最小化每个样本点到质心的欧式距离之和
|
曼哈顿距离 | 中位数 |
最小化每个样本点到质心的曼哈顿 距离之和
|
余弦距离 | 均值 |
最小化每个样本点到质心的余弦 距离之和
|
而这些组合,都可以由严格的数学证明来推导。在sklearn当中,我们无法选择使用的距离,只能使用欧式距离。因此,我们也无需去担忧这些距离所搭配的质心选择是如何得来的了。
inertia_:一个簇中所有样本点到质心的距离的平方和,又叫簇内平方和(cluster Sum of Square);将一个数据集中的所有簇的簇内平方和相加,就得到了整体平方和(Total Cluster Sum of Square)又叫做total inertia。
cluster_centers_ 查看质心
首先创建一个数据集,通过绘图先观察一下这个数据集的数据分布,以此来为我们聚类时输入的n_clusters做一个参考。
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
#自己创建数据集
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()
基于这个分布,使用Kmeans进行聚类
(1) n_clusters=3
from sklearn.cluster import KMeans
n_clusters=3
cluster=KMeans(n_clusters=n_clusters,random_state=0).fit(x)
#重要属性labels_,查看聚好的类别,每个样本所对应的类
y_pred=cluster.labels_
#重要属性inertia_,查看总距离平方和
inertia=cluster.inertia_
inertia #1903.4503741659241
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()
(2) n_clusters=4
n_clusters=4
cluster_=KMeans(n_clusters=n_clusters,random_state=0).fit(x)
inertia_=cluster_.inertia_
inertia_ #908.3855684760603
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]
)
ax1.scatter(centroid[:,0],centroid[:,1]
,marker='x'
,s=15
,c='black')
plt.show()
那我们可以使用什么指标呢?轮廓系数
在99%的情况下,我们是对没有真实标签的数据进行探索,也就是对不知道真正答案的数据进行聚类。这样的聚类,是完全依赖于评价簇内的稠密程度(簇内差异小)和簇间的离散程度(簇外差异大)来评估聚类的效果。其中轮廓系数是最常用的聚类算法的评价指标。它是对每个样本来定义的,它能够同时衡量: 1)样本与其自身所在的簇中的其他样本的相似度a,等于样本与同一簇中所有其他点之间的平均距离; 2)样本与其他簇中的样本的相似度b,等于样本与下一个最近的簇中得所有点之间的平均距离 根据聚类的要求”簇内差异小,簇外差异大“,我们希望b永远大于a,并且大得越多越好。
在sklearn中,我们使用模块metrics中的类silhouette_score来计算轮廓系数,它返回的是一个数据集中,所有样本的轮廓系数的均值。但我们还有同在metrics模块中的silhouette_sample,它的参数与轮廓系数一致,但返回的是数据集中每个样本自己的轮廓系数。
from sklearn.metrics import silhouette_score
from sklearn.metrics import silhouette_samples
n_clusters_0=3
cluster_0=KMeans(n_clusters=n_clusters_0,random_state=0).fit(x)
silhouette_score(x,cluster_0.labels_) #0.5882004012129721
n_clusters_1=4
cluster_1=KMeans(n_clusters=n_clusters_1,random_state=0).fit(x)
silhouette_score(x,cluster_1.labels_) #0.6505186632729437
n_clusters_2=5
cluster_2=KMeans(n_clusters=n_clusters_2,random_state=0).fit(x)
silhouette_score(x,cluster_2.labels_) #0.5746932321727458
n_clusters_3=6
cluster_3=KMeans(n_clusters=n_clusters_3,random_state=0).fit(x)
silhouette_score(x,cluster_3.labels_) #0.5150064498560357
卡林斯基-哈拉巴斯指数 | sklearn.mertics.calinski_harabasz_score(x,y_pred) |
---|---|
戴维斯-布尔丁指数 | klearn.mertics.davies_bouldin_score(x,y_pred) |
权变矩阵 | klearn.mertics.cluster.contingency.matrix(x,y_pred) |
这里我们重点了解一下卡林斯基-哈拉巴斯指数,calinski_harabasz指数越高越好。对于有k个簇的聚类而言,calinski_harabasz指数s(k)写作公式如下:
其中N是数据集的样本量,k为簇的个数,是组间离散矩阵,即不同簇之间的协方差矩阵。是簇内离散矩阵,即一个簇内数据的协方差矩阵,而Tr表示矩阵的迹。数据之间的离散程度越高,协方差矩阵的迹会越大。组内离散程度低,协方差矩阵的迹就会越小,同时,组间离散程度大,协方差矩阵的迹就会越大。因此calinski_harabasz指数越高越好。
虽然calinski_harabasz指数没有界,在凸性数据上表现虚高,但比起轮廓系数,它有一个巨大的优点就是计算非常快速。
from sklearn.metrics import calinski_harabasz_score
from time import time
#time():记下每一次time()这一行命令的时间戳
t0=time()
calinski_harabasz_score(x,y_pred)
time()-t0
0.0
t0=time()
silhouette_score(x,y_pred)
time()-t0
0.008000373840332031
对于每个n_clusters画两个图,第一个图是轮廓系数图像,是由各个簇的轮廓系数组成的横向条形图;第二个图是聚类后分布图。
for n_clusters in [2,3,4,5,6,7]:
n_clusters = n_clusters
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_size_inches(18, 7)
# 轮廓系数的取值范围在[-1,1]之间,我们希望轮廓系数至少是大于0的
# 太长的横坐标不利于可视化,所以只设定x轴的取值在[-0.1,1]之间
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_score生成的是所有样本点的轮廓系数均值
silhouette_avg = silhouette_score(x, cluster_labels) #所有样本轮廓系数均值
print("For n_clusters =", n_clusters,
"The average silhouette_score is :", silhouette_avg)
# 调用silhouette_samples,返回每个样本点的轮廓系数,即横坐标
sample_silhouette_values = silhouette_samples(x, cluster_labels) #每个样本的轮廓系数
#设定y轴上的初始取值
y_lower = 10
#接下来,对每一个簇进行循环
for i in range(n_clusters):
ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]
#sort()会改变原数据的顺序
ith_cluster_silhouette_values.sort()
#查看浙一簇有多少个样本
size_cluster_i = ith_cluster_silhouette_values.shape[0]
#设定本次循环纵坐标最大值
y_upper = y_lower + size_cluster_i
#colormap库中,使用小数来调用颜色的函数 nipy_spectral([输入任一小数来代表一个颜色])
color = cm.nipy_spectral(float(i)/n_clusters)
#fill_between是让一个范围中的柱状图都统一颜色的函数
#fill_betweenx的范围在纵坐标上,fill_betweeny的范围在横坐标上
#fill_betweenx的参数应该输入(纵坐标的下限,纵坐标的上限,x轴上的取值,柱状图的颜色)
ax1.fill_betweenx(np.arange(y_lower, y_upper)
,ith_cluster_silhouette_values
,facecolor=color
,alpha=0.7
)
#text的参数为(要显示编号的位置的横坐标,要显示编号的纵坐标,要显示的编号内容)
ax1.text(-0.05
, y_lower + 0.5 * size_cluster_i
, str(i))
# 每循环一次后改变y_lower,作为下一轮纵坐标的开始
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])
到这里,第一个图就已经完成,第二个图是聚类后的分布图,代码基本与3.4雷同,这里就不再进行详解了。
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_samples, silhouette_score
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np
for n_clusters in [2,3,4,5,6,7]:
n_clusters = n_clusters
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()
For n_clusters = 2 The average silhouette_score is : 0.7049787496083262
For n_clusters = 3 The average silhouette_score is : 0.5882004012129721
For n_clusters = 4 The average silhouette_score is : 0.6505186632729437
For n_clusters = 5 The average silhouette_score is : 0.56376469026194
For n_clusters = 6 The average silhouette_score is : 0.4504666294372765
For n_clusters = 7 The average silhouette_score is : 0.39092211029930857
完!