看完这篇文章还不懂K-means聚类算法,就来找我

本文主要讨论k-means 聚类算法背后的基础知识和数学原理,以及如何在 scikit-learn 中实现它。

image-20240718215915120

聚类(或聚类分析)是一种技术,允许我们找到相似对象的群组,这些对象彼此之间的关系比与其他群组中的对象更密切。聚类的商业应用示例包括根据不同主题对文档、音乐和电影进行分组,或者基于共同购买行为找到共享类似兴趣的客户,作为推荐引擎的基础。

在本文中,我们将学习关于最流行的聚类算法之一:k-means 的知识,这在学术界和工业界都被广泛使用。我们将涵盖以下内容:

  • k-means 聚类的基本概念
  • k-means 算法背后的数学原理
  • k-means 的优缺点
  • 如何使用 scikit-learn 在样本数据集上实现该算法
  • 如何可视化聚类结果
  • 如何使用肘部法则选择最优的 k 值

K-Means聚类基础

正如我们将看到的那样,与其他聚类算法相比,k-means 算法极其易于实现并且在计算上非常高效,这可能解释了它的流行。k-means 算法属于原型聚类的一种。

原型聚类意味着每个聚类由一个原型表示,该原型可以是具有连续特征的相似点的质心(平均值),或者在分类特征的情况下是最具代表性或最频繁出现的点(中心点)

虽然 k-means 在识别球形状的簇方面非常出色,但该聚类算法的一个缺点是我们必须事先指定簇的数量 k。对 k 进行不恰当的选择可能导致聚类性能不佳 —— 我们将在本教程后面讨论如何选择 k。

虽然 k-means 聚类可以应用于高维数据,但为了可视化的目的,我们将使用一个简单的二维数据集来演示以下示例。

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

# create dataset
X, y = make_blobs(
   n_samples=150, n_features=2,
   centers=3, cluster_std=0.5,
   shuffle=True, random_state=0
)

# plot
plt.scatter(
   X[:, 0], X[:, 1],
   c='white', marker='o',
   edgecolor='black', s=50
)
plt.show()

image-20240718221214619

我们刚刚创建的数据集包含了150个随机生成的点,大致分成了三个密度较高的区域,这通过二维散点图进行了可视化。

在聚类的实际应用中,我们没有关于这些样本的任何真实分类信息(即作为经验证据而非推断提供的信息);否则,它将属于监督学习的范畴。因此,我们的目标是根据它们的特征相似性将样本分组,这可以通过 k-means 算法来实现,其主要步骤如下四步:

  1. 从样本点中随机选择k个中心点作为初始聚类中心
  2. 将每个样本分配给最近的中心点其中
  3. 将每个中心点移动到被分配给它的样本的中心
  4. 重复步骤 2 和 3,直到聚类分配不再变化,或达到用户定义的容差或最大迭代次数

可视化上述步骤:

kmeans

现在,下一个问题是我们如何衡量对象之间的相似性?我们可以将相似性定义为距离的相反,对于具有连续特征的样本聚类,常用的距离是在 m 维空间中两点 x 和 y 之间的平方欧氏距离。

索引 j 指的是样本点 x 和 y 的第 j 维(特征列)。

下述的方程中,我们将使用上标 i 和 j 分别表示样本索引和簇索引。

基于这个欧氏距离度量,我们可以将 k-means 算法描述为一个简单的优化问题,即通过迭代的方式最小化簇内平方误差和(Sum of Squared Errors, SSE),有时也称为簇的惯性。

这里,

是第个聚类的中心

以及,

请注意,当我们使用欧氏距离度量将 k-means 应用于真实世界的数据时,我们希望确保特征在相同的尺度上进行测量,必要时可以应用 z-score 标准化或 min-max 缩放。

scikit-learn实现K-means聚类

现在我们已经学习了 k-means 算法的工作原理,让我们使用 scikit-learn 的 cluster 模块中的 KMeans 类将其应用于我们的示例数据集。

from sklearn.cluster import KMeans

km = KMeans(
    n_clusters=3, init='random',
    n_init=10, max_iter=300
    tol=1e-04, random_state=0
)
y_km = km.fit_predict(X)

上述代码,我们将所需的簇数设置为 3。我们设置 n_init=10,以便独立运行 k-means 聚类算法 10 次,每次使用不同的随机中心点选择最终模型为 SSE 最低的模型。通过 max_iter 参数,我们指定每次单独运行的最大迭代次数(这里是 300)。

请注意,scikit-learn 中的 k-means 实现在达到最大迭代次数之前,如果收敛了会提前停止。然而,如果选择了相对较大的 max_iter 值,可能 k-means 在特定运行中无法达到收敛,这可能会导致计算开销较大。

处理收敛问题的一种方法是选择较大的 tol 值,该参数控制在簇内平方误差变化方面的容差以宣布收敛。在前面的代码中,我们选择了容差为 1e-04(等于 0.0001)。

k-means 的一个问题是可能会出现一个或多个空簇。然而,在当前的 scikit-learn 实现中已经考虑了这个问题。如果一个簇为空,算法会搜索距离空簇质心最远的样本点。然后,它将重新分配质心为这个最远的点。

现在们已经预测了聚类标签 y_km,现在让我们可视化 k-means 算法在数据集中识别的聚类以及这些聚类的中心点。这些聚类的中心点存储在拟合后的 KMeans 对象的 cluster_centers_ 属性下:

# 聚类以及聚类中心可视化
plt.scatter(
    X[y_km == 00], X[y_km == 01],
    s=50, c='lightgreen',
    marker='s', edgecolor='black',
    label='cluster 1'
)

plt.scatter(
    X[y_km == 10], X[y_km == 11],
    s=50, c='orange',
    marker='o', edgecolor='black',
    label='cluster 2'
)

plt.scatter(
    X[y_km == 20], X[y_km == 21],
    s=50, c='lightblue',
    marker='v', edgecolor='black',
    label='cluster 3'
)

# plot the centroids
plt.scatter(
    km.cluster_centers_[:, 0], km.cluster_centers_[:, 1],
    s=250, marker='*',
    c='red', edgecolor='black',
    label='centroids'
)
plt.legend(scatterpoints=1)
plt.grid()
plt.show()

image-20240719161042482

在生成的散点图中,我们可以看到 k-means 算法将三个中心点放置在了每个“球体”的中心位置,考虑到这个数据集,这样的分组看起来是合理的。

手衬法(The Elbow Method)

尽管 k-means 在这个数据集上表现良好,但其中一个缺点是,在知道最优的 k 值之前,我们必须指定聚类的数量 k。在现实世界的应用中,尤其是在处理无法可视化的高维数据集时,选择聚类数量可能并不总是那么显而易见。

手肘法(Elbow Method)是一种有用的图形工具,用于估计给定任务的最优聚类数量 k。直观地说,如果 k 增加,那么聚类内的 SSE(Sum of Squared Errors,平方误差和,也常被称为“畸变”)将会减少。这是因为样本将更接近它们被分配到的中心点。然而,随着 k 的增加,SSE 减少的幅度会逐渐减小。手肘法通过绘制 SSE 关于 k 的曲线,并寻找曲线的“手肘点”(即 SSE 开始急剧减少后趋于平缓的点)来确定最优的 k 值。这个点通常被认为是 SSE 下降速率开始显著减缓的位置,因此被视为最佳聚类数量的一个合理估计。

手肘法的核心思想是识别出 k 值,在该值处畸变(即数据点与聚类中心之间的某种度量,如平方误差和)开始迅速减少。如果我们为不同的 k 值绘制畸变图,这一点将会变得更加清晰。

# 计算不同簇类的度量
distortions = []
for i in range(111):
    km = KMeans(
        n_clusters=i, init='random',
        n_init=10, max_iter=300,
        tol=1e-04, random_state=0
    )
    km.fit(X)
    distortions.append(km.inertia_)

# plot
plt.plot(range(111), distortions, marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Distortion')
plt.show()

image-20240719162015835

从生成的图中我们可以看到,手肘位于 k = 3 的位置(在k=3处值迅速减少),这证明了 k = 3 确实是对于这个数据集的一个好选择。手肘法的这一结论与我们先前通过直接观察数据集分布或运行 k-means 算法得到的聚类结果相一致,进一步验证了选择 k = 3 的合理性。

结束语

我希望你喜欢这个关于 k-means 算法的教程!我们探讨了 k-means 算法背后的基本概念和数学原理,学习了如何实现 k-means,以及如何选择最优的聚类数量 k。希望这些内容能够帮助你更好地理解和应用 k-means 算法。如果你有任何问题或想要进一步了解的内容,请随时告诉我!

欢迎扫码关注:

相关推荐

  • 图解 Kafka 架构 | 为什么那么快?
  • 冯·诺依曼的传奇人生
  • 何为“用户画像(Persona)”,它在产品设计和市场研究中发挥了什么作用呢?
  • 开眼!!!CODESYS竟然支持这么多协议
  • 37.4K Star 万人选择!!!做数据面板,用这个UI准没错
  • 麻省理工开源:《理解深度学习》,有中文版了!
  • 物种多样性的后续,难绷······
  • 字节员工“跳槽”到上海市政研室
  • 胳膊肘往外拐?AMD的AI GPU助推器增加对英伟达的支持
  • CrowdStrike更新致850万Windows设备宕机,微软紧急救火!
  • 被 GPT-4o 的价格劝退了!
  • 程序员都干过哪些很刺激的事情?
  • 成都周报丨单季度收入50亿的药企,准备A+H双上市了
  • 超21亿,国家大基金二期投了一家重庆公司丨投融周报
  • 开发者的狂欢!首屏速度飞快,服务器配置提升80%!
  • 10 个非常有创意的 Button 按钮效果【附源码】
  • 我们公司都用哪些软件?强烈推荐这些!
  • 写代码时遇到了乱码 “锟斤拷”,我有点懵
  • 70K!一个门槛极低的新方向!
  • DataOps+大模型促进数据工程创新