聚类算法及其模型评估指标【Tsai Tsai】

目录

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 完整代码

1. 无监督学习与聚类算法

        “有监督学习”即模型在训练的时候,既需要特征矩阵x,也需要真实标签y。在机器学习当中,还有相当一部分算法属于“无监督学习”,无监督的算法在训练的时候只需要特征矩阵x,不需要标签。而聚类算法,就是无监督学习的代表算法。

        聚类算法又叫“无监督分类”,其目的是将数据划分成有意义或有用的组(或簇)。这种划分可以基于业务需求或建模需求来完成,也可以单纯地帮助我们探索数据的自然结构和分布。比如在商业中,如果我们手头上有大量的当前和潜在客户信息,我们可以使用聚类将客户划分成若干组,以便进一步分析和开展营销活动,最有名的客户价值判断模型RFM,就常常和聚类分析共同使用。再比如,聚类可以用于降维和矢量量化(vector quantization),可以将高维特征压缩到一列当中,常常用于图像,声音,视频等非结构化数据,可以大幅度压缩数据量。

聚类算法及其模型评估指标【Tsai Tsai】_第1张图片

聚类算法及其模型评估指标【Tsai Tsai】_第2张图片

聚类 分类
核心
将数据分成多个组,探索每个组的数据是否有联系
从已经分组的数据中去学习,把新数据放到已经分好的组中去
学习类型
无监督,无需标签进行训练
有监督,需要标签进行训练
典型算法
K-Means DBSCAN ,层次聚类,光谱聚类
决策树,贝叶斯,逻辑回归
算法输出
聚类结果是不确定的,不一定总是能够反映数据的真实分类
同样的聚类,根据不同的业务需求,可能是一个好结果,也可能是一个坏结果
分类结果是确定的
分类的优劣是客观的
不是根据业务或算法需求决定

2. KMeans

2.1 KMeans是如何工作的

         KMeans作为聚类算法的典型代表,KMeans算法将一组N个样本的特征矩阵X划分为K个无交集的簇,直观上来看是一组一组聚集在一起的数据,在一个簇中的数据就认为是同一类。簇就是聚类的结果表现。簇中所有数据的均值u通常被称为这个簇的“质心centroids)。在一个二维平面中,一簇数据点的质心的横坐标就是这一簇数据点的横坐标的均值,质心的纵坐标就是这一簇数据点的纵坐标的均值。同理可推广至高维空间。

        在KMeans算法中,簇的个数K是一个超参数,需要我们人为输入来确定。KMeans的核心任务就是根据我们设定好的K,找出K个最优的质心,并将离这些质心最近的数据分别分配到这些质心代表的簇中去。具体过程可以总结如下:

顺序 过程
1
随机抽取 K 个样本作为最初的质心
2 开始循环
2.1
将每个样本点分配到离他们最近的质心,生成 K 个簇
2.2
对于每个簇,计算所有被分到该簇的样本点的平均值作为新的质心
3
当质心的位置不再发生变化,迭代停止,聚类完成
那什么情况下,质心的位置会不再变化呢?当我们找到一个质心,在每次迭代中被分配到这个质心上的样本都是一致的,即每次新生成的簇都是一致的,所有的样本点都不会再从一个簇转移到另一个簇,质心就不会变化了。
这个过程在可以由下图来显示,我们规定,将数据分为 4 簇( K=4 ),其中白色 X 代表质心的位置:

聚类算法及其模型评估指标【Tsai Tsai】_第3张图片

在数据集多次迭代(iteration),就会有:

聚类算法及其模型评估指标【Tsai Tsai】_第4张图片

 在数据集下多次迭代(iteration),模型就会收敛。第六次迭代之后,基本上质心的位置就不再改变了,生成的簇也变得稳定。此时我们的聚类就完成了,我们可以明显看出,KMeans按照数据的分布,将数据聚集成了我们规定的4类,接下来我们就可以按照我们的业务需求或者算法需求,对这四类数据进行不同的处理。

2.2 簇内误差平方和的定义

        聚类算法聚出的类有什么含义呢?这些类有什么样的性质?我们认为,被分在同一个簇中的数据是有相似性的,而 不同簇中的数据是不同的 ,当聚类完毕之后,我们就要分别去研究每个簇中的样本都有什么样的性质,从而根据业 务需求制定不同的商业或者科技策略。我们追求“ 簇内差异小,簇外差异大 。而这个 差异 ,由 样本点到其所在簇的质心的距离 来衡量。
         对于一个簇来说,所有样本点到质心的距离之和越小,我们就认为这个簇中的样本越相似,簇内差异就越小。而距 离的衡量方法有多种,令x表示簇中的一个样本点,u表示该簇中的质心, n 表示每个样本点中的特征数目, i 表示组 成点的每个特征,则该样本点到质心的距离可以由以下距离来度量:

聚类算法及其模型评估指标【Tsai Tsai】_第5张图片

如我们采用欧几里得距离,则一个簇中所有样本点到质心的距离的平方和为:

聚类算法及其模型评估指标【Tsai Tsai】_第6张图片

其中, m 为一个簇中样本的个数, j 是每个样本的编号。这个公式被称为 簇内平方和 cluster Sum of Square, 又叫做Inertia 。而将一个数据集中的所有簇的簇内平方和相加,就得到了整体平方和( Total Cluster Sum of Square,又叫做 total inertia Total Inertia 越小,代表着每个簇内样本越相似,聚类的效果就越好。 因此 KMeans 追求的是,求解能够让 Inertia 最小化的质心 。实际上,在质心不断变化不断迭代的过程中,总体平方和是越来越小的。我们可以使用数学来证明,当整体平方和最小的时候,质心就不再发生变化了。如此,K-Means 的求解过程,就变成了一个最优化问题。

        我们的Inertia是基于欧几里得距离的计算公式得来的。实际上,我们也可以使用其他距离,每个距离都有自己对应的Inertia。在过去的经验中,我们总结出不同距离所对应的质心选择方法和Inertia,在Kmeans中,只要使用了正确的质心和距离组合,无论使用什么样的距离,都可以达到不错的效果:

距离度量 质心 Inertia
欧几里得距离 均值
最小化每个样本点到质心的欧式距离之和
曼哈顿距离 中位数
最小化每个样本点到质心的曼哈顿 距离之和
余弦距离 均值
最小化每个样本点到质心的余弦 距离之和

 而这些组合,都可以由严格的数学证明来推导。在sklearn当中,我们无法选择使用的距离,只能使用欧式距离。因此,我们也无需去担忧这些距离所搭配的质心选择是如何得来的了。

3. sklearn.cluster.KMeans

sklearn.cluster.KMeans ( n_clusters=8 , init=’k-means++’ , n_init=10 , max_iter=300 , tol=0.0001 ,
precompute_distances=’auto’ , verbose=0 , random_state=None , copy_x=True , n_jobs=None , algorithm=’auto’ )

3.1 重要参数 n_clusters

        n_clusters是 KMeans 中的 k ,表示着我们告诉模型我们要分几类。这是 KMeans 当中唯一一个必填的参数,默认为 8类,但通常我们的聚类结果会是一个小于8 的结果。通常,在开始聚类之前,我们并不知道 n_clusters 究竟是多少,因此我们要对它进行探索。

3.2 重要属性 inertia_

        inertia_:一个簇中所有样本点到质心的距离的平方和,又叫簇内平方和(cluster Sum of Square);将一个数据集中的所有簇的簇内平方和相加,就得到了整体平方和(Total Cluster Sum of Square)又叫做total inertia

3.3 重要属性 cluster_centers_ 

        cluster_centers_ 查看质心

3.4 聚类观察 n_clusters

        首先创建一个数据集,通过绘图先观察一下这个数据集的数据分布,以此来为我们聚类时输入的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()

聚类算法及其模型评估指标【Tsai Tsai】_第7张图片

 基于这个分布,使用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()

聚类算法及其模型评估指标【Tsai Tsai】_第8张图片

(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()

聚类算法及其模型评估指标【Tsai Tsai】_第9张图片

3.5 聚类算法的模型评估指标

        KMeans的目标是确保 簇内差异小,簇外差异大 ”,Inertia是用距离来衡量簇内差异的指标,Inertia可以 作为聚类的衡量指标,但是这个指标的缺点和极限太大。
        (1).首先,它不是有界的。我们只知道,Inertia 是越小越好,是 0 最好,但我们不知道,一个较小的 Inertia 究竟有没有 达到模型的极限,能否继续提高。
        (2).第二,它的计算太容易受到特征数目的影响,数据维度很大的时候, Inertia 的计算量会陷入维度诅咒之中,计算量 会爆炸,不适合用来一次次评估模型。
        (3).第三, Inertia 对数据的分布有假设,它假设数据满足凸分布(即数据在二维平面图像上看起来是一个凸函数的样 子),并且它假设数据是各向同性的( isotropic ),即是说数据的属性在不同方向上代表着相同的含义。但是现实 中的数据往往不是这样。所以使用 Inertia 作为评估指标,会让聚类算法在一些细长簇,环形簇,或者不规则形状的 流形时表现不佳:
聚类算法及其模型评估指标【Tsai Tsai】_第10张图片

那我们可以使用什么指标呢?轮廓系数 

 3.6 轮廓系数

        在99%的情况下,我们是对没有真实标签的数据进行探索,也就是对不知道真正答案的数据进行聚类。这样的聚类,是完全依赖于评价簇内的稠密程度(簇内差异小)和簇间的离散程度(簇外差异大)来评估聚类的效果。其中轮廓系数是最常用的聚类算法的评价指标。它是对每个样本来定义的,它能够同时衡量: 1)样本与其自身所在的簇中的其他样本的相似度a,等于样本与同一簇中所有其他点之间的平均距离;  2)样本与其他簇中的样本的相似度b,等于样本与下一个最近的簇中得所有点之间的平均距离 根据聚类的要求”簇内差异小,簇外差异大,我们希望b永远大于a,并且大得越多越好。

 单个样本的轮廓系数计算为:\frac{b-a}{max(a,b)}

 这个公式可以被解析为:\left\{\begin{matrix} & \\ 1-\frac{a}{b} , if a<b & \\ 0,if a=b & \\\frac{b}{a}-1 ,if a>b \end{matrix}\right.

        很容易理解轮廓系数范围是 (-1,1) ,其中值越接近 1 表示样本与自己所在的簇中的样本很相似,并且与其他簇中的样本不相似,当样本点与簇外的样本更相似的时候,轮廓系数就为负。当轮廓系数为0时,则代表两个簇中的样本相似度一致,两个簇本应该是一个簇。 如果一个簇中的大多数样本具有比较高的轮廓系数,则簇会有较高的总轮廓系数,则整个数据集的平均轮廓系数越 ,则聚类是合适的。如果许多样本点具有低轮廓系数甚至负值,则聚类是不合适的,聚类的超参数 K 可能设定得 太大或者太小。

         在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
通过比较以上不同的n_clusters对应的轮廓系数,显然当n_cluster=4时模型效果表现较好。
轮廓系数有很多优点,它在有限空间中取值,使得我们对模型的聚类效果有一个 参考 。并且,轮廓系数对数据的分布没有假设,因此在很多数据集上都表现良好。但它在每个簇的分割比较清洗时表现最好。但轮廓系数也有缺陷,它在凸型的类上表现会虚高,比如基于密度进行的聚类,或通过DBSCAN 获得的聚类结果,如果使用轮廓系数来衡量,则会表现出比真实聚类效果更高的分。

3.7 卡林斯基-哈拉巴斯指数 Calinski_Harabasz Index,简称CHI

        除了轮廓系数是最常用的,还有卡林斯基-哈拉巴斯指数(Calinski-Harabasz Index),戴维斯-布尔丁指数(Davies-Bouldin)以及权变矩阵(Contingency Matrix)可以使用。
卡林斯基-哈拉巴斯指数 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)写作公式如下:

聚类算法及其模型评估指标【Tsai Tsai】_第11张图片

 其中N是数据集的样本量,k为簇的个数,{B_{k}}^{}是组间离散矩阵,即不同簇之间的协方差矩阵。W_{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

4. 案例:基于轮廓系数来选择n_clusters

4.1 代码分步详解

        对于每个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雷同,这里就不再进行详解了。

4.2 完整代码

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

聚类算法及其模型评估指标【Tsai Tsai】_第12张图片

For n_clusters = 3 The average silhouette_score is : 0.5882004012129721

聚类算法及其模型评估指标【Tsai Tsai】_第13张图片

For n_clusters = 4 The average silhouette_score is : 0.6505186632729437

聚类算法及其模型评估指标【Tsai Tsai】_第14张图片

For n_clusters = 5 The average silhouette_score is : 0.56376469026194

聚类算法及其模型评估指标【Tsai Tsai】_第15张图片

For n_clusters = 6 The average silhouette_score is : 0.4504666294372765

聚类算法及其模型评估指标【Tsai Tsai】_第16张图片

For n_clusters = 7 The average silhouette_score is : 0.39092211029930857

聚类算法及其模型评估指标【Tsai Tsai】_第17张图片

完!

你可能感兴趣的:(机器学习,聚类,算法,机器学习)