K-Means 算法介绍与入门 – wiki基地


K-Means 聚类算法:从原理到实践的深度入门

数据,如今无处不在。从社交媒体的互动到电商平台的购物记录,从传感器收集的环境数据到医学影像,我们被海量信息所包围。然而,原始的数据往往是杂乱无章的,隐藏在其后的模式和洞察需要特定的工具才能揭示。在机器学习领域,一种强大的工具就是“聚类”(Clustering)。

聚类是一种无监督学习技术,它的目标是根据数据的相似性将数据集划分为若干个组(或称为簇)。与分类(Classification)不同,聚类不需要预先标记好的类别信息,它试图从数据本身的结构中发现潜在的分组。想象一下,你手里有一堆不同颜色、不同大小的水果,聚类就像是根据它们的特征(比如颜色和大小)将它们分成苹果、香蕉、橘子等不同的堆。

在众多聚类算法中,K-Means 算法无疑是最著名、应用最广泛的一种。它的思想直观、实现简单,且计算效率高,因此成为了许多聚类任务的首选入门算法。

本文将带你深入理解 K-Means 算法,从它的核心思想、工作原理,到如何选择关键参数、它的优缺点,最后通过 Python 代码展示如何在实践中应用它。无论你是数据科学的初学者,还是希望巩固聚类基础的进阶者,都能从中获益。

第一章:理解聚类——为什么我们需要将数据分组?

在深入 K-Means 之前,我们先花点时间理解聚类的意义和应用场景。

什么是聚类?

聚类是一种无监督学习任务,旨在将数据集中的对象根据它们的相似性分组,使得同一组内的对象彼此相似度较高,而不同组之间的对象相似度较低。这里的“相似性”通常通过某种距离或相似度度量来衡量,例如欧几里得距离、曼哈顿距离等。

聚类与分类的主要区别在于:
* 分类 (Classification): 监督学习,需要带有标签的训练数据来学习一个模型,该模型可以将新的数据点分配到已知的类别中。例如,根据邮件内容判断它是垃圾邮件还是非垃圾邮件。
* 聚类 (Clustering): 无监督学习,不需要带有标签的数据。算法自行探索数据结构,将数据点分成若干个“簇”,每个簇内的点具有相似的特征。我们不知道这些簇具体代表什么,但知道同一簇内的点是“一类”的。例如,根据用户的购买行为将他们分成不同的用户群体。

聚类的实际应用

聚类在各个领域都有着广泛的应用:

  1. 市场细分 (Market Segmentation): 根据客户的人口统计信息、购买历史、浏览行为等将他们分成不同的群体,以便进行有针对性的营销活动。例如,将客户分为“高消费活跃用户”、“价格敏感用户”、“新注册用户”等。
  2. 图像处理 (Image Processing):
    • 图像压缩 (Image Compression): K-Means 可以用于颜色量化,减少图像中的颜色数量,从而减小文件大小。例如,将图像中的上百万种颜色聚类成 256 种颜色。
    • 图像分割 (Image Segmentation): 根据像素的颜色、纹理、亮度等特征将图像分成不同的区域或对象。
  3. 文档分析 (Document Analysis): 将大量文档根据内容相似性进行聚类,以便于组织、浏览和发现主题。例如,将新闻文章聚类成体育、政治、娱乐等类别。
  4. 异常检测 (Anomaly Detection): 正常数据点往往会形成大的簇,而异常点则可能独立存在或形成非常小的簇。聚类可以帮助识别这些远离主要簇的点。例如,检测信用卡欺诈行为。
  5. 生物信息学 (Bioinformatics): 将基因或蛋白质根据表达模式或功能相似性进行聚类,有助于发现生物学通路和功能模块。
  6. 地理信息系统 (GIS): 分析地理位置数据,发现地理上的聚集模式,例如犯罪热点分析、商店选址等。
  7. 推荐系统 (Recommendation Systems): 将用户或物品聚类,然后基于同一簇内的相似性进行推荐。

可以说,只要你需要从无标签的数据中发现分组结构,聚类就是一种值得考虑的强大技术。

第二章:K-Means 算法的核心思想

K-Means 算法的核心思想简单而优雅:它试图将数据点分成 K 个簇,并找到每个簇的中心(称为质心或中心点),使得每个点到其所属簇的质心的距离之和最小。

核心概念:

  1. 簇 (Cluster): 数据点的一个分组。
  2. 质心 (Centroid): 每个簇的中心点。在 K-Means 中,质心是簇内所有数据点的平均值(向量平均)。
  3. 距离度量 (Distance Metric): 用来衡量两个数据点或一个数据点到质心之间相似性的方法。对于 K-Means,最常用的是欧几里得距离 (Euclidean Distance)。对于 n 维空间的两个点 $x = (x_1, x_2, …, x_n)$ 和 $y = (y_1, y_2, …, y_n)$,它们之间的欧几里得距离为:
    $d(x, y) = \sqrt{\sum_{i=1}^n (x_i – y_i)^2}$
    也可以使用曼哈顿距离或其他距离度量,但这会改变算法的性质(有时会衍生出 K-Medians 或其他变种)。

目标:

K-Means 算法的目标是最小化“簇内平方和”(Within-Cluster Sum of Squares, WCSS),也称为惯量 (Inertia)。WCSS 是指每个数据点到其所属簇的质心的距离的平方的总和。
$WCSS = \sum_{j=1}^K \sum_{x \in C_j} ||x – \mu_j||^2$
其中,$K$ 是簇的数量,$C_j$ 是第 $j$ 个簇,$\mu_j$ 是第 $j$ 个簇的质心,$||x – \mu_j||^2$ 是数据点 $x$ 到质心 $\mu_j$ 的欧几里得距离的平方。

K-Means 算法通过迭代的方式逐步优化这个目标函数,直到达到一个局部最优解。

第三章:K-Means 算法的工作流程(迭代优化过程)

K-Means 算法是一个迭代过程,它在两个主要步骤之间交替进行,直到收敛。这两个步骤分别是:分配(Assignment)和更新(Update)。

假设我们已经确定了要将数据分成 $K$ 个簇。算法的工作流程如下:

  1. 初始化 (Initialization):

    • 首先,需要确定簇的数量 $K$。这个选择是 K-Means 算法的一个关键挑战(我们将在后面讨论如何选择 K)。
    • 然后,随机选择 $K$ 个数据点作为初始的簇质心。这些点可以是数据集中的任意 $K$ 个点,也可以是根据某种策略(如 K-Means++)选择的点。随机选择是最简单的初始化方法,但可能导致不同的运行结果。
  2. 分配步骤 (Assignment Step 或 E-step):

    • 对于数据集中的每一个数据点,计算它到所有 $K$ 个当前质心的距离。
    • 将该数据点分配到距离最近的那个质心所代表的簇。
    • 这一步完成后,所有数据点都被分配到了 K 个簇中的一个。
  3. 更新步骤 (Update Step 或 M-step):

    • 对于每一个簇,重新计算它的质心。新的质心是该簇内所有数据点的平均值(即坐标的算术平均)。
    • 如果一个簇在分配步骤中没有任何点被分配,通常会采取一些策略,比如保留其原有的质心,或者重新随机选择一个数据点作为新的质心。
  4. 收敛判断 (Convergence Check):

    • 重复步骤 2 和 3,直到质心不再发生显著移动(例如,移动距离小于某个阈值),或者达到预设的最大迭代次数。
    • 当质心不再移动时,表示算法已经收敛,找到一个(可能是局部的)最优解。

这个迭代过程可以形象地描述为:

想象你有 $K$ 个“磁铁”(质心)散布在数据点中。
* 分配步骤: 每个数据点都像一个“铁屑”,被最近的磁铁吸引过去,形成 $K$ 堆铁屑。
* 更新步骤: 每个磁铁都移动到它当前吸引到的那一堆铁屑的“中心位置”。
* 重复: 磁铁移动后,一些铁屑可能离另一个磁铁更近了,于是它们会“跳槽”到新的磁铁那边。磁铁再根据新的铁屑堆重新调整位置。
* 收敛: 这个过程不断重复,直到磁铁的位置稳定下来,铁屑也不再跳槽。

第四章:示例与可视化(脑海中的模拟过程)

虽然我们不能直接在这里绘制图形,但我们可以通过一个简单的二维例子来模拟 K-Means 的迭代过程。

假设我们有 6 个二维数据点:
P1=(1, 1), P2=(1.5, 2), P3=(3, 4), P4=(5, 7), P5=(3.5, 5), P6=(4.5, 5)

我们要将它们分成 K=2 个簇。

步骤 1: 初始化
* 选择 K=2。
* 随机选择两个点作为初始质心。假设我们选择了 P1=(1, 1) 作为质心 C1,P4=(5, 7) 作为质心 C2。

步骤 2: 第一次分配
* 计算每个点到 C1 和 C2 的欧几里得距离:
* P1: 到 C1 距离 = 0, 到 C2 距离 = $\sqrt{(1-5)^2 + (1-7)^2} = \sqrt{16 + 36} = \sqrt{52} \approx 7.21$ -> 分配给 C1
* P2: 到 C1 距离 = $\sqrt{(1.5-1)^2 + (2-1)^2} = \sqrt{0.25 + 1} = \sqrt{1.25} \approx 1.12$, 到 C2 距离 = $\sqrt{(1.5-5)^2 + (2-7)^2} = \sqrt{12.25 + 25} = \sqrt{37.25} \approx 6.10$ -> 分配给 C1
* P3: 到 C1 距离 = $\sqrt{(3-1)^2 + (4-1)^2} = \sqrt{4 + 9} = \sqrt{13} \approx 3.61$, 到 C2 距离 = $\sqrt{(3-5)^2 + (4-7)^2} = \sqrt{4 + 9} = \sqrt{13} \approx 3.61$. 距离相等,随机分给一个(例如 C1)。 -> 分配给 C1
* P4: 到 C1 距离 = $\sqrt{(5-1)^2 + (7-1)^2} = \sqrt{16 + 36} = \sqrt{52} \approx 7.21$, 到 C2 距离 = 0 -> 分配给 C2
* P5: 到 C1 距离 = $\sqrt{(3.5-1)^2 + (5-1)^2} = \sqrt{6.25 + 16} = \sqrt{22.25} \approx 4.72$, 到 C2 距离 = $\sqrt{(3.5-5)^2 + (5-7)^2} = \sqrt{2.25 + 4} = \sqrt{6.25} = 2.5$ -> 分配给 C2
* P6: 到 C1 距离 = $\sqrt{(4.5-1)^2 + (5-1)^2} = \sqrt{12.25 + 16} = \sqrt{28.25} \approx 5.31$, 到 C2 距离 = $\sqrt{(4.5-5)^2 + (5-7)^2} = \sqrt{0.25 + 4} = \sqrt{4.25} \approx 2.06$ -> 分配给 C2

  • 第一次聚类结果:
    • 簇 1: {P1, P2, P3}
    • 簇 2: {P4, P5, P6}

步骤 3: 第一次更新质心
* 重新计算簇 1 的质心 C1′:
C1′ = (($1+1.5+3$)/3, ($1+2+4$)/3) = (5.5/3, 7/3) $\approx$ (1.83, 2.33)
* 重新计算簇 2 的质心 C2′:
C2′ = (($5+3.5+4.5$)/3, ($7+5+5$)/3) = (13/3, 17/3) $\approx$ (4.33, 5.67)

步骤 4: 第二次分配
* 使用新的质心 C1′ (1.83, 2.33) 和 C2′ (4.33, 5.67) 重新分配点:
* P1(1,1): 更靠近 C1′
* P2(1.5,2): 更靠近 C1′
* P3(3,4): 计算到 C1′ 和 C2′ 的距离…
到 C1′: $\sqrt{(3-1.83)^2 + (4-2.33)^2} = \sqrt{1.37^2 + 1.67^2} \approx \sqrt{1.88 + 2.79} \approx \sqrt{4.67} \approx 2.16$
到 C2′: $\sqrt{(3-4.33)^2 + (4-5.67)^2} = \sqrt{(-1.33)^2 + (-1.67)^2} \approx \sqrt{1.77 + 2.79} \approx \sqrt{4.56} \approx 2.14$. P3 现在更靠近 C2’。 -> 分配给 C2
* P4(5,7): 更靠近 C2′
* P5(3.5,5): 更靠近 C2′
* P6(4.5,5): 更靠近 C2′

  • 第二次聚类结果:
    • 簇 1: {P1, P2}
    • 簇 2: {P3, P4, P5, P6}

步骤 5: 第二次更新质心
* 重新计算簇 1 的质心 C1”:
C1” = (($1+1.5$)/2, ($1+2$)/2) = (1.25, 1.5)
* 重新计算簇 2 的质心 C2”:
C2” = (($3+5+3.5+4.5$)/4, ($4+7+5+5$)/4) = (16/4, 21/4) = (4, 5.25)

步骤 6: 第三次分配
* 使用新的质心 C1” (1.25, 1.5) 和 C2” (4, 5.25) 重新分配点:
* P1(1,1): 靠近 C1”
* P2(1.5,2): 靠近 C1”
* P3(3,4): 靠近 C2”
* P4(5,7): 靠近 C2”
* P5(3.5,5): 靠近 C2”
* P6(4.5,5): 靠近 C2”

  • 第三次聚类结果:
    • 簇 1: {P1, P2}
    • 簇 2: {P3, P4, P5, P6}

步骤 7: 收敛判断
* 比较第三次聚类结果与第二次聚类结果。簇的成员没有发生变化。
* 比较新的质心 C1” (1.25, 1.5) 和 C2” (4, 5.25) 与前一次的质心 C1′ (1.83, 2.33) 和 C2′ (4.33, 5.67)。它们移动了位置,但点的归属没有改变,这意味着质心将会继续移动,直到它们成为当前簇的精确中心。
* 在实际实现中,通常检查质心移动的距离是否小于某个阈值,或者簇分配是否不再改变。在这个例子中,点的分配已经稳定,算法收敛。

最终聚类结果:簇 1 包含 {P1, P2},簇 2 包含 {P3, P4, P5, P6}。质心分别是 (1.25, 1.5) 和 (4, 5.25)。

这个过程展示了 K-Means 如何通过迭代地重新分配点和更新质心来逐步优化聚类结果。

第五章:选择合适的 K 值

K-Means 算法的一个主要挑战是需要事先指定簇的数量 K。选择一个合适的 K 值对聚类结果至关重要。如果 K 太小,可能会将本来应该分开的簇合并;如果 K 太大,可能会将一个自然的簇分割成多个小的簇,或者将离群点各自形成一个簇。

虽然没有一个普适性的最佳方法来确定 K,但有几种常用的启发式方法:

1. 肘部法则 (Elbow Method)

肘部法则可能是最流行且直观的方法。它的思想是:随着 K 的增加,簇内平方和 (WCSS) 或惯量 (Inertia) 会逐渐减小(因为每个点离自己的质心更近了)。当 K 等于数据集中实际的簇数量时,再增加 K,WCSS 的下降速度会显著变缓。我们将 WCSS 随 K 值变化的曲线绘制出来,曲线会呈现一个“肘部”形状,肘部对应的 K 值通常被认为是较优的选择。

步骤:
1. 对于一系列的 K 值(例如从 1 到 10 或更多),分别运行 K-Means 算法。
2. 记录每次运行 K-Means 后的 WCSS 值。
3. 绘制一条以 K 值为横轴,WCSS 值为纵轴的曲线。
4. 观察曲线,找到 WCSS 下降率突然变缓的“肘部”,该点的 K 值即为推荐的簇数量。

缺点: 肘部可能不总是清晰可见,有时曲线呈现平滑下降,难以判断。

2. 轮廓系数 (Silhouette Score)

轮廓系数是一种衡量聚类效果的指标,它结合了簇内紧密度和簇间分离度。对于数据集中的每个点,计算其轮廓系数:

$s = \frac{b – a}{\max(a, b)}$

其中:
* $a$ 是该点到其所属簇内所有其他点的平均距离(簇内紧密度)。$a$ 越小,表示点与同簇的点越紧密。
* $b$ 是该点到最近的不同簇中所有点的平均距离(簇间分离度)。$b$ 越大,表示点与最近的不同簇的点越分离。

轮廓系数 $s$ 的取值范围是 [-1, 1]:
* 接近 1: 表示该点被很好地聚类到其所属簇中,并且远离其他簇。
* 接近 0: 表示该点靠近两个簇的边界,可能被错误地分配。
* 接近 -1: 表示该点可能被错误地分配到当前簇,它实际上更应该属于另一个簇。

整个数据集的平均轮廓系数可以用来评估整体的聚类效果。我们计算不同 K 值下的平均轮廓系数,选择使平均轮廓系数最大的 K 值。

步骤:
1. 对于一系列的 K 值,分别运行 K-Means 算法。
2. 对于每次聚类结果,计算所有点的平均轮廓系数。
3. 选择平均轮廓系数最高的 K 值。

缺点: 计算轮廓系数需要计算所有点之间的距离,对于大数据集计算成本较高。

3. 其他方法

还有其他一些选择 K 值的方法,例如 Gap Statistic、Davies-Bouldin Index 等,但肘部法则和轮廓系数是最常用的。

在实际应用中,选择 K 值也常常依赖于业务领域的知识。例如,如果你知道你的客户群体大致可以分为 3-4 种类型,那么可以先尝试 K=3 或 K=4。最好的方法往往是结合多种指标和领域知识进行判断。

第六章:K-Means 算法的优缺点与局限性

理解 K-Means 的优势和不足,有助于我们在合适的场景使用它,并对结果有正确的预期。

优点 (Advantages)

  1. 简单且易于理解: K-Means 的原理非常直观,基于距离和均值计算,容易向非专业人士解释。
  2. 计算效率高: 对于大规模数据集,K-Means 的运行速度相对较快。其时间复杂度大致为 $O(n \cdot k \cdot d \cdot i)$,其中 $n$ 是数据点数量,$k$ 是簇数量,$d$ 是数据维度,$i$ 是迭代次数。在实践中,$i$ 和 $k$ 通常远小于 $n$,因此效率较高。
  3. 容易实现: 算法步骤清晰,编程实现相对简单。
  4. 结果易于解释: 簇质心代表了每个簇的中心特征,可以通过分析质心的属性来理解各个簇的意义。

缺点与局限性 (Disadvantages & Limitations)

  1. 需要预先指定 K 值: 如前所述,如何选择最优的 K 值是一个挑战,且对聚类结果影响很大。
  2. 对初始质心敏感: K-Means 算法的结果(包括最终的簇划分和 WCSS)可能会受到初始质心选择的影响。不同的初始质心可能导致算法收敛到不同的局部最优解。为缓解这个问题,实践中通常会多次运行 K-Means(使用不同的随机初始化),然后选择 WCSS 最小的那次结果。K-Means++ 初始化方法可以更智能地选择初始质心,提高收敛到全局最优或更好局部最优的可能性。
  3. 对离群点敏感: 由于质心是簇内所有点的平均值,离群点会显著拉动质心的位置,从而影响聚类结果。K-Medoids 等算法使用簇内实际数据点作为中心(medoid),对离群点更鲁棒。
  4. 倾向于发现球状且大小相似的簇: K-Means 基于欧几里得距离,隐式地假设簇是凸形且大致呈球状。它难以处理非球状、环状、链状等形状不规则的簇。同时,如果簇的大小(点数量)差异很大,K-Means 也可能表现不佳。
  5. 不适用于非数值型数据: K-Means 基于均值计算质心,因此要求输入数据是数值型的。对于类别型数据,需要进行适当的编码或使用其他聚类算法。
  6. 无法处理噪声数据: K-Means 会尝试将所有点都分配到一个簇中,包括噪声点,这可能会污染簇的特征。DBSCAN 等算法能够区分噪声点。

了解这些局限性非常重要。在应用 K-Means 之前,应该先对数据进行探索性分析,了解数据的分布和可能存在的簇形状,以判断 K-Means 是否适合该任务。

第七章:数据预处理的重要性

在使用 K-Means(以及大多数基于距离的算法)之前,进行适当的数据预处理至关重要。

1. 特征缩放 (Feature Scaling)

K-Means 算法使用距离度量(如欧几里得距离)来确定点之间的相似性以及点到质心的距离。如果不同的特征具有非常不同的量纲或数值范围,那么具有较大数值范围的特征将在距离计算中占据主导地位,从而压倒其他特征的影响。

例如,一个数据集中包含“年收入”(几万到几百万)和“年龄”(几岁到一百多岁)两个特征。在不缩放的情况下计算距离,年收入的差异会远大于年龄的差异,聚类结果将几乎完全由年收入决定。

为了避免这种情况,需要对特征进行缩放,使得所有特征处于相似的数值范围内。常用的缩放方法包括:
* 标准化 (Standardization): 将特征缩放到均值为 0,标准差为 1 的分布。
$x’ = \frac{x – \mu}{\sigma}$
其中 $\mu$ 是均值,$\sigma$ 是标准差。
* 归一化 (Normalization): 将特征缩放到 [0, 1] 或 [-1, 1] 的范围内。
$x’ = \frac{x – x_{min}}{x_{max} – x_{min}}$

在实践中,通常推荐使用标准化。

2. 处理离群点 (Handling Outliers)

离群点会拉动质心的位置,对 K-Means 结果产生负面影响。可以考虑在运行 K-Means 之前检测并移除(或转换)明显的离群点。但这需要谨慎,因为有些离群点本身可能就是我们想要识别的异常数据。

3. 处理缺失值 (Handling Missing Values)

K-Means 无法直接处理包含缺失值的数据点。需要使用适当的方法填充缺失值(例如,使用均值、中位数、众数填充,或者使用更复杂的插补技术)。

第八章:使用 Python 实现 K-Means

Python 生态系统提供了非常成熟的机器学习库 scikit-learn,其中包含了 K-Means 算法的实现。下面我们通过一个简单的例子来演示如何在 Python 中使用 scikit-learn 进行 K-Means 聚类。

“`python

导入必要的库

import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs # 用于生成模拟数据
from sklearn.preprocessing import StandardScaler # 用于数据标准化
from sklearn.metrics import silhouette_score # 用于计算轮廓系数

——————————

步骤 1: 生成模拟数据

——————————

生成包含 300 个样本、2 个特征、实际有 3 个簇的数据

random_state 用于保证每次生成的数据相同

X, y = make_blobs(n_samples=300, centers=3, cluster_std=1.0, random_state=42)

X 是特征数据 (300×2 矩阵)

y 是实际的簇标签 (虽然是无监督学习,make_blobs 返回y是为了验证或可视化方便)

可视化原始数据 (如果实际数据有标签,可以这样查看)

plt.figure(figsize=(8, 6))
plt.scatter(X[:, 0], X[:, 1], s=50) # s控制点的大小
plt.title(“Original Data (Simulated)”)
plt.xlabel(“Feature 1”)
plt.ylabel(“Feature 2”)
plt.grid(True)
plt.show()

——————————

步骤 2: 数据预处理 – 标准化

——————————

K-Means 对特征尺度敏感,进行标准化是好的实践

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

——————————

步骤 3: 应用 K-Means 算法

——————————

假设我们知道或猜测簇的数量 K=3

kmeans = KMeans(n_clusters=3, # 指定簇的数量
init=’k-means++’, # 初始质心选择策略,’k-means++’ 是推荐的改进方法
n_init=10, # 使用不同的随机初始化运行算法10次,选择结果最好的
max_iter=300, # 每次运行的最大迭代次数
random_state=42)# 随机种子,保证结果可复现

训练模型 (拟合数据)

kmeans.fit(X_scaled)

获取聚类结果

labels = kmeans.labels_ # 每个数据点所属的簇标签 (0, 1, 2…)
centroids = kmeans.cluster_centers_ # 簇的质心坐标 (标准化后的)
inertia = kmeans.inertia_ # 簇内平方和 (WCSS)

print(f”簇标签 (前10个): {labels[:10]}”)
print(f”标准化后的质心: \n{centroids}”)
print(f”最终的 WCSS (Inertia): {inertia:.2f}”)

——————————

步骤 4: 可视化聚类结果

——————————

注意:质心是在标准化数据上计算的,如果要在原始数据上可视化,需要将质心逆标准化

也可以直接在标准化后的数据上可视化

centroids_original_scale = scaler.inverse_transform(centroids)

plt.figure(figsize=(8, 6))

使用不同的颜色绘制不同簇的数据点

plt.scatter(X[:, 0], X[:, 1], c=labels, s=50, cmap=’viridis’)

绘制质心 (黑色X标记)

plt.scatter(centroids_original_scale[:, 0], centroids_original_scale[:, 1], c=’black’, s=200, marker=’X’, label=’Centroids’)
plt.title(“K-Means Clustering Results (on Original Data)”)
plt.xlabel(“Feature 1”)
plt.ylabel(“Feature 2”)
plt.legend()
plt.grid(True)
plt.show()

——————————

步骤 5: 如何选择 K 值 (肘部法则示例)

——————————

wcss = [] # 用于存储不同 K 值下的 WCSS

尝试不同的 K 值

k_range = range(1, 11) # 从 K=1 到 K=10

for k in k_range:
# 对于每个 K 值,运行 K-Means 并计算 WCSS
# 注意:这里 n_init 同样建议设置为较高的值,如10或更多,以获得更好的结果
kmeans_k = KMeans(n_clusters=k, init=’k-means++’, n_init=10, max_iter=300, random_state=42)
kmeans_k.fit(X_scaled)
wcss.append(kmeans_k.inertia_) # inertia_ 就是 WCSS

绘制肘部法则曲线

plt.figure(figsize=(8, 6))
plt.plot(k_range, wcss, marker=’o’)
plt.title(‘Elbow Method for Optimal K’)
plt.xlabel(‘Number of Clusters (K)’)
plt.ylabel(‘WCSS (Inertia)’)
plt.xticks(k_range) # 设置 x 轴刻度为整数 K 值
plt.grid(True)
plt.show()

根据肘部法则曲线,我们可以观察 WCSS 下降率变缓的那个点,作为选择 K 的参考。

在本例中,可以看到 K=3 附近有一个明显的“弯曲”。

——————————

步骤 6: 如何选择 K 值 (轮廓系数示例)

——————————

轮廓系数至少需要有两个簇,所以 K 从 2 开始

silhouette_scores = []
k_range_silhouette = range(2, 11)

for k in k_range_silhouette:
kmeans_k = KMeans(n_clusters=k, init=’k-means++’, n_init=10, max_iter=300, random_state=42)
kmeans_k.fit(X_scaled)
# 计算轮廓系数,需要原始数据和聚类标签
score = silhouette_score(X_scaled, kmeans_k.labels_)
silhouette_scores.append(score)

绘制轮廓系数曲线

plt.figure(figsize=(8, 6))
plt.plot(k_range_silhouette, silhouette_scores, marker=’o’)
plt.title(‘Silhouette Score for Optimal K’)
plt.xlabel(‘Number of Clusters (K)’)
plt.ylabel(‘Average Silhouette Score’)
plt.xticks(k_range_silhouette)
plt.grid(True)
plt.show()

根据轮廓系数曲线,选择轮廓系数最高的 K 值。

在本例中,K=3 的轮廓系数最高。

——————————

步骤 7: 使用模型预测新数据点的簇

——————————

生成一些新的点 (在标准化后的空间)

new_points_scaled = scaler.transform([[0, 0], [4, 6], [-3, 1]])

使用训练好的 K-Means 模型进行预测

predicted_labels = kmeans.predict(new_points_scaled)

print(f”新点在原始尺度的坐标: \n{scaler.inverse_transform(new_points_scaled)}”)
print(f”新点的预测簇标签: {predicted_labels}”)

可以再次可视化,将新点添加到聚类图中

plt.figure(figsize=(8, 6))
plt.scatter(X[:, 0], X[:, 1], c=labels, s=50, cmap=’viridis’, alpha=0.6, label=’Original Data’)
plt.scatter(centroids_original_scale[:, 0], centroids_original_scale[:, 1], c=’black’, s=200, marker=’X’, label=’Centroids’)

使用不同的标记绘制新点

plt.scatter(scaler.inverse_transform(new_points_scaled)[:, 0], scaler.inverse_transform(new_points_scaled)[:, 1],
c=predicted_labels, s=300, marker=’*’, edgecolor=’red’, label=’New Points’)

plt.title(“K-Means Clustering with New Points”)
plt.xlabel(“Feature 1”)
plt.ylabel(“Feature 2”)
plt.legend()
plt.grid(True)
plt.show()
“`

代码解释:

  1. 导入库: 导入 numpy 用于数值计算,matplotlib.pyplot 用于绘图,sklearn.cluster.KMeans 用于 K-Means 算法,sklearn.datasets.make_blobs 用于快速生成模拟聚类数据,sklearn.preprocessing.StandardScaler 用于数据标准化,sklearn.metrics.silhouette_score 用于计算轮廓系数。
  2. 生成数据: make_blobs 是一个非常方便的函数,可以生成指定数量、维度和簇数量的模拟数据,非常适合用于演示聚类算法。
  3. 数据标准化: 使用 StandardScaler 对数据进行标准化,确保每个特征的尺度相似,避免量纲差异影响距离计算。这是使用 K-Means 前的重要步骤。
  4. 应用 K-Means: 创建 KMeans 对象,指定参数:
    • n_clusters: 指定要寻找的簇数量。
    • init: 初始质心选择方法,'k-means++' 是默认且推荐的方法,它会智能地选择初始质心,以提高算法的收敛速度和结果的质量。
    • n_init: 算法将以不同的初始质心运行多少次。scikit-learn 默认是 10 次,推荐保留这个值或设置更大,算法会返回 WCSS 最小的那次运行结果,以降低陷入局部最优的风险。
    • max_iter: 单次运行 K-Means 算法的最大迭代次数。
    • random_state: 设置随机种子,使得每次运行代码时结果保持一致(因为初始化过程通常包含随机性)。
    • 调用 fit(X_scaled) 在标准化后的数据上训练模型。
  5. 获取结果: 训练完成后,可以通过 kmeans.labels_ 获取每个数据点的簇标签,kmeans.cluster_centers_ 获取每个簇的质心坐标,kmeans.inertia_ 获取最终的 WCSS 值。
  6. 可视化: 使用 matplotlib 将原始数据点根据其聚类标签着色,并将质心标记出来。为了在原始数据尺度上看到质心,需要对质心进行逆标准化(使用 scaler.inverse_transform)。
  7. 选择 K (肘部法则): 循环尝试从 1 到 10 的 K 值,计算每次的 WCSS,并将 K 与 WCSS 绘制成图,通过观察“肘部”来选择合适的 K。
  8. 选择 K (轮廓系数): 循环尝试从 2 到 10 的 K 值,计算每次的平均轮廓系数,并将 K 与平均轮廓系数绘制成图,选择轮廓系数最高的 K。
  9. 预测新点: 使用训练好的模型 kmeans.predict() 方法可以将新的数据点分配到已有的簇中。注意,新的点也需要进行与训练数据相同的预处理(标准化)。

通过这个代码示例,你应该能够理解如何在实际中使用 K-Means 算法以及如何利用肘部法则和轮廓系数辅助选择 K 值。

第九章:K-Means 的变种与相关算法(简述)

K-Means 算法有许多变种,以及一些解决 K-Means 局限性的其他聚类算法:

  1. K-Means++: 前面代码中使用的初始化方法。它不是完全随机选择初始质心,而是选择彼此之间距离较远的初始质心,这有助于避免一些较差的局部最优解,提高算法的稳定性和收敛速度。这是 K-Means 的标准初始化方法。
  2. Mini-Batch K-Means: 针对超大规模数据集设计的 K-Means 变种。它不像标准 K-Means 那样在每次迭代中使用所有数据点计算质心,而是使用随机抽取的小批量数据来更新质心。这显著提高了大数据集上的计算效率,但可能会牺牲一定的聚类质量。
  3. K-Medoids (PAM): 与 K-Means 类似,也需要指定 K 值,也基于迭代过程。但它的簇中心是簇内一个实际的数据点(称为 medoid,众数点),而不是所有点的均值。K-Medoids 对离群点更鲁棒,但计算成本通常比 K-Means 高。
  4. 层次聚类 (Hierarchical Clustering): 不需要在开始时指定 K 值,它构建一个簇的层次结构(树状图,Dendrogram),可以根据需要在这个层次结构的不同层面选择簇的数量。分为凝聚式 (Agglomerative) 和分裂式 (Divisive) 两种。
  5. DBSCAN (Density-Based Spatial Clustering of Applications with Noise): 一种基于密度的聚类算法。它能发现任意形状的簇,并且能够区分噪声点(不属于任何簇的点)。DBSCAN 不需要指定 K 值,但需要指定两个参数:邻域半径 ($\epsilon$) 和最小样本数 (min_samples)。它不适合处理密度变化较大的数据集。
  6. 高斯混合模型 (Gaussian Mixture Models, GMM): 一种基于概率模型的聚类方法。它假设数据是由 K 个高斯分布混合生成的,通过最大化似然估计来找到每个高斯分布的参数(均值、方差、权重),从而得到聚类结果。GMM 可以发现非球状的簇,且提供每个点属于每个簇的概率,结果比 K-Means 更具柔性。但计算成本更高,且对初始化敏感。

了解这些变种和相关算法,有助于你在面对不同的聚类问题时,选择最合适的工具。

结论

K-Means 算法作为最基础、最流行的聚类算法之一,以其简单、高效和易于理解的特点,在数据科学领域占据着重要地位。它通过迭代地将数据点分配给最近的质心并更新质心位置,从而将数据划分为 K 个簇。

本文详细介绍了 K-Means 的核心思想、工作流程、如何使用肘部法则和轮廓系数辅助选择 K 值,分析了它的优缺点,强调了数据预处理的重要性,并提供了完整的 Python 实现示例。

尽管 K-Means 存在对 K 值敏感、对初始化敏感、对离群点敏感以及倾向于发现球状簇等局限性,但通过合适的预处理、多次运行以及结合领域知识选择 K 值,它仍然是解决许多实际聚类问题的强大工具。

对于初学者来说,掌握 K-Means 是迈入聚类领域的重要一步。在此基础上,可以进一步学习和探索 K-Means 的变种以及其他更复杂的聚类算法,如层次聚类、DBSCAN、GMM 等,以应对更广泛的数据聚类挑战。

数据就在那里,潜藏着未知的模式和价值。K-Means 算法为你提供了一把钥匙,帮助你打开探索数据深层结构的大门。现在,是时候动手实践,去发现你数据中的隐藏分组了!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部