机器学习-聚类算法-Kmeans

无监督学习与聚类算法

  • 概述
    • 在此之前我们所学习到的算法模型都是属于有监督学习的模型算法,即模型需要的样本数据既需要有特征矩阵X,也需要有真实的标签y。那么在机器学习中也有一部分的算法模型是属于无监督学习分类的,所谓的无监督学习是指模型只需要使用特征矩阵X即可,不需要真实的标签y。那么聚类算法就是无监督学习中的代表之一。
  • 聚类算法
    • 聚类算法其目的是将数据划分成有意义或有用的组(或簇)。这种划分可以基于我们的业务 需求或建模需求来完成,也可以单纯地帮助我们探索数据的自然结构和分布。比如在商业中,如果我们手头有大量 的当前和潜在客户的信息,我们可以使用聚类将客户划分为若干组,以便进一步分析和开展营销活动。
  • 聚类和分类区别image.png
 

image.png

 

KMeans算法原理阐述

  • 簇与质心
    • 簇:KMeans算法将一组N个样本的特征矩阵X划分为K个无交集的簇,直观上来看是簇是一个又一个聚集在一起的数 据,在一个簇中的数据就认为是同一类。簇就是聚类的结果表现。
    • 质心:簇中所有数据的均值u通常被称为这个簇的“质心”(centroids)。
      • 在一个二维平面中,一簇数据点的质心的横坐标就是这一簇数据点的横坐标的均值,质心的纵坐标就是这一簇数据点的纵坐标的均值。同理可推广至高维空间。
      • 质心的个数也聚类后的类别数是一致的
  • 在KMeans算法中,簇的个数K是一个超参数,需要我们人为输入来确定。KMeans的核心任务就是根据我们设定好的K,找出K个最优的质心,并将离这些质心最近的数据分别分配到这些质心代表的簇中去。具体过程可以总结如下:image.png
    • 那什么情况下,质心的位置会不再变化呢?当我们找到一个质心,在每次迭代中被分配到这个质心上的样本都是一致的,即每次新生成的簇都是一致的,所有的样本点都不会再从一个簇转移到另一个簇,质心就不会变化了。
 
  • 这个过程在可以由下图来显示,我们规定,将数据分为4簇(K=4),其中白色X代表质心的位置:image.png
 
  • 聚类算法聚出的类有什么含义呢?这些类有什么样的性质?
    • 我们认为,被分在同一个簇中的数据是有相似性的,而不同簇中的数据是不同的,当聚类完毕之后,我们就要分别去研究每个簇中的样本都有什么样的性质,从而根据业务需求制定不同的商业或者科技策略。
    • 聚类算法追求“簇内差异小,簇外差异 大”:
      • 而这个“差异“,由样本点到其所在簇的质心的距离来衡量。
  • 对于一个簇来说,所有样本点到质心的距离之和越小,我们就认为这个簇中的样本越相似,簇内差异就越小。而距离的衡量方法有多种,令x表示簇中的一个样本点,u表示该簇中的质心,n表示每个样本点中的特征数目,i表示组成点的每个特征,则该样本点到质心的距离可以由以下距离来度量:image.png
 

KMeans有损失函数吗?

 

簇内平方和

  • 如我们采用欧几里得距离,则一个簇中所有样本点到质心的距离的平方和为簇内平方和。使用簇内平方和就可以表示簇内差异的大小
 

整体平方和

  • 将一个数据集中的所有簇的簇内平方和相加,就得到了整体平方和(Total Cluster Sum of Square),又叫做total inertia。Total Inertia越小,代表着每个簇内样本越相似,聚类的效果就越好。
 
  • KMeans追求的是,求解能够让簇内平方和最小化的质心。实际上,在质心不断变化不断迭代的过程中,整体平方和是越来越小的。我们可以使用数学来证明,当整体平方和最小的时候,质心就不再发生变化了。如此,K-Means的求解过程,就变成了一个最优化问题。因此我们认为:
    • 在KMeans中,我们在一个固定的簇数K下,最小化整体平方和来求解最佳质心,并基于质心的存在去进行聚类。并且,整体距离平方和的最小值其实可以使用梯度下降来求解。因此,有许多博客和教材都这样写道:簇内平方和/整体平方和是KMeans的损失函数。
  • 但是也有人认为:
    • 损失函数本质是用来衡量模型的拟合效果的(损失越小,模型的拟合效果越好),只有有着求解参数需求的算法,才会有损失函数。Kmeans不求解什么参数,它的模型本质也没有在拟合数据,而是在对数据进行一种探索。所以如果你去问大多数数据挖掘工程师,甚至是算法工程师,他们可能会告诉你说,K-Means不存在 什么损失函数,整体平方和更像是Kmeans的模型评估指标,而非损失函数。
 

API:sklearn.cluster.KMeans

  • class 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’)
 
  • 重要参数:
    • n_clusters
      • n_clusters是KMeans中的k,表示着我们告诉模型我们要分几类。这是KMeans当中唯一一个必填的参数,默认为8 类,但通常我们的聚类结果会是一个小于8的结果。通常,在开始聚类之前,我们并不知道n_clusters究竟是多少, 因此我们要对它进行探索。
    • random_state
      • 用于初始化质心的生成器。
 
  • KMeans的首次探索
    • 当我们拿到一个数据集,如果可能的话,我们希望能够通过绘图先观察一下这个数据集的数据分布,以此来为我们聚类时输入的n_clusters做一个参考。 首先,我们来自己创建一个数据集使用make_blobs。这样的数据集是我们自己创建,所以是有标签的。

手动分类的散点图

from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt

# 创建数据
# n_samples=500 原始数据有500行
# n_features=2  原始数据有2特征维度
# centers=4  原始数据有4个类别
X,y = make_blobs(n_samples=500, n_features=2, centers=4, random_state=10)

X.shape  # (500, 2)
y.shape  # y.shape

# 将原始已经有类别的样本数据绘制在散点图中,每一个类别使用不同颜色来表示
color = ['red','pink','orange','gray']
fig,ax1 = plt.subplots(1) # 生成1个坐标系
for i in range(4):
    # 将X中y==i的类别的行第0列拿到,s为像素的大小
    ax1.scatter(X[y==i,0],X[y==i,1],c=color[i],s=8) 

Kmeans分类

# 用4个簇训练模型
cluster = KMeans(n_clusters=4)
cluster.fit(X)

# 重要属性Labels_,查看聚类后的类别,每个样本所对应的类
y_pred = cluster.labels_

# 重要属性cLuster_centers_,查看质心
centroid = cluster.cluster_centers_

array([[-6.20071147,  5.13256421],
       [ 5.49063417, -9.5824711 ],
       [ 2.82312124,  4.91302359],
       [-0.06527291, -5.46786296]])

# 重要属性inertia_,查看总距离平方和(整体平方和)
inertia = cluster.inertia_  # 871.7858513546287

color = ['red','pink','orange','gray']
fig,ax1 = plt.subplots(1) # 生成1个坐标系
for i in range(4):
    # 将X中y==i的类别的行第0列拿到,s为像素的大小
    ax1.scatter(X[y_pred==i,0],X[y_pred==i,1],c=color[i],s=8) 
  

聚类-predict

Kmeans对结果的预测

  • KMeans算法通常情况是不需要预测结果的,因为该算法本质上是在对未知分类数据的探索。但是在某些情况下我们也可以使用predict进行预测操作。
  • 我们什么时候需要predict呢?
    • 当数据量太大的时候!
      • 其实我们不必使用所有的数据来寻找质心,少量的数据就可以帮助我们确定质心了。
      • 当我们数据量非常大的时候,我们可以使用部分数据来帮助我们确认质心
      • 剩下的数据的聚类结果,使用predict来调用
X.shape  (500, 2)

# 使用200组数据来寻找质心
c = KMeans(n_clusters=4,random_state=10)
c.fit(X[0:200])

c.predict(X[200:]) # 和 labels_返回的结果一样,都是他的分到的类别
  • 总结:数据量非常大的时候,效果会好。但从运行得出这样的结果,肯定与直接fit全部数据会不一致。有时候,当我们不要求那么精确,或者我们的数据量实在太大,那我们可以使用这种方法,使用接口predict。如果数据量还行,不是特别大,直接使用fit之后调用属性.labels_提出来聚类的结果。

逐步增加质心的数量,查看不同的Inertia

  •   随着簇增多,整体平方和变小
# 如果我们把猜测的簇数换成5
cluster = KMeans(n_clusters=5)
cluster.fit(X)
cluster.inertia_   # 789.3135030851906

# 如果我们把猜测的簇数换成6
cluster = KMeans(n_clusters=6)
cluster.fit(X)
cluster.inertia_  # 708.2459105012695

面试高危问题:如何衡量聚类算法的效果?

  • 聚类模型的结果不是某种标签输出,并且聚类的结果是不确定的,其优劣由业务需求或者算法需求来决定,并且没有永远的正确答案。
    • 那我们如何衡量聚类的效果呢?

聚类算法的模型评估指标

  • 记得我们说过,KMeans的目标是确保“簇内差异小,簇外差异大”,我们就可以通过衡量簇内差异来衡量聚类的效果。我们刚才说过,簇内平方和是用距离来衡量簇内差异的指标,因此,我们是否可以使用簇内平方和来作为聚类的衡量指标呢?簇内平方和越小模型越好嘛?
  • 可以,但是这个指标的缺点和极限太大。 

簇内平方和Inertia的缺点:

  • 1.首先,它不是有界的。我们只知道,Inertia是越小越好,是0最好,但我们不知道,一个较小的Inertia究竟有没有 达到模型的极限,能否继续提高。
  • 2.它的计算太容易受到特征数目的影响,数据维度很大的时候,Inertia的计算量会爆炸,不适合用来一次次评估模型。
  • 3.它会受到超参数K的影响,在我们之前的常识中其实我们已经发现,随着K越大,Inertia注定会越来越小,但 这并不代表模型的效果越来越好了
  • 4.使用Inertia作为评估指标,会让聚类算法在一些细长簇,环形簇,或者不规则形状的 流形时表现不佳:image.png
  • 那我们可以使用什么指标呢?
    • 轮廓系数

轮廓系数

  • 在99%的情况下,我们是对没有真实标签的数据进行探索,也就是对不知道真正答案的数据进行聚类。这样的聚 类,是完全依赖于评价簇内的稠密程度(簇内差异小)和簇间的离散程度(簇外差异大)来评估聚类的效果。其中 轮廓系数是最常用的聚类算法的评价指标。它是对每个样本来定义的,它能够同时衡量:
    • 1)样本与其自身所在的簇中的其他样本的相似度a,等于样本与同一簇中所有其他点之间的平均距离
    • 2)样本与其他簇中的样本的相似度b,等于样本与下一个最近的簇中的所有点之间的平均距离 根据聚类的要求”簇内差异小,簇外差异大“,我们希望b永远大于a,并且大得越多越好。
    • 单个样本的轮廓系数计算为:image.png
  • 很容易理解轮廓系数范围是(-1,1):
    • 其中值越接近1表示样本与自己所在的簇中的样本很相似,并且与其他簇中的样本不相似,当样本点与簇外的样本更相似的时候,轮廓系数就为负。
    • 当轮廓系数为0时,则代表两个簇中的样本相似度一致,两个簇本应该是一个簇。可以总结为轮廓系数越接近于1越好,负数则表示聚类效果非常差。
  • 如果一个簇中的大多数样本具有比较高的轮廓系数,则簇会有较高的总轮廓系数,则整个数据集的平均轮廓系数越高,则聚类是合适的:
    • 如果许多样本点具有低轮廓系数甚至负值,则聚类是不合适的,聚类的超参数K可能设定得 太大或者太小。
  • silhouette_score计算轮廓系数
    • 在sklearn中,我们使用模块metrics中的类silhouette_score来计算轮廓系数,它返回的是一个数据集中,所有样本的轮廓系数的均值。
  • silhouette_sample
    • 但我们还有同在metrics模块中的silhouette_sample,它的参数与轮廓系数一致,但返回的 是数据集中每个样本自己的轮廓系数

计算轮廓系数

from sklearn.metrics import silhouette_samples,silhouette_score

# silhouette_score返回的是一个数据集中,所有样本的轮廓系数的均值
silhouette_score(X,labels=cluster.labels_)  # 每个样本的分类

0.5524670176812047

# 返回的 是数据集中每个样本自己的轮廓系数
silhouette_samples(X,cluster.labels_).sum()/X.shape[0]

0.5524670176812047

 

posted @ 2020-07-30 17:32  闲酒肆中听风吟  阅读(689)  评论(0编辑  收藏  举报