K-means 聚类算法:目标函数推导、迭代过程及可视化解析

news2024/10/25 1:25:09

一、K-means 的背景

在机器学习领域,许多任务涉及 训练模型来做预测或分类 。比如,医生可能希望通过以往的病例数据来预测某个病人未来是否会患上某种疾病,或者新闻网站可能需要根据文章的主题将新闻自动分类。这些任务通常依赖于有标签的数据,也就是每个数据点都带有一个已知的类别或输出(比如健康或生病,新闻类别)。这种学习方式被称为监督学习,因为模型是在学习数据与标签之间的关系。

带标签数据的局限性

然而,在现实中,带标签的数据往往是稀缺的,因为为数据打标签需要大量的时间和人力。例如:

  • 在图像分类中,每一张图像都需要人工标注类别,比如这是一只猫还是一只狗。
  • 在用户行为分析中,没有预定义的标签告诉我们某个用户属于哪一类人群。

标注数据不仅费时费力,还可能带来高昂的成本。因此,在许多实际应用中,收集的大量数据往往是无标签的。也就是说,数据本身虽然丰富多样,但没有一个明确的类别告诉我们每个数据点属于什么。

无监督学习的引入

针对这种无标签数据的情况,我们就需要一种不依赖标签的学习方式,这就是无监督学习。在无监督学习中,我们并没有预先定义的输出或类别,而是希望算法能从数据本身中发现其内在的结构或模式。也就是说,我们把一堆没有标签的数据交给算法,让它去 自动 寻找数据中的规律。

无监督学习不依赖已知的目标或输出,而是通过分析数据点的特征及其分布,找出数据之间的联系,并自动将这些数据划分为不同的组或模式。

聚类任务

无监督学习中的一个典型任务就是聚类。所谓聚类,就是把一组数据点根据它们的相似性分成若干个簇,使得同一个簇中的数据点相互之间差异最小,而不同簇之间的数据点差异最大。换句话说,聚类的目标是让每个簇中的数据彼此非常相似,而让簇与簇之间的数据显得明显不同。

K-means 算法:最经典的聚类方法

K-means 是聚类任务中最常用、最经典的算法之一。它的核心思想是将数据划分为 k k k 个簇,其中 k k k 是事先定义好的簇的数量。

K-means 算法有两个重要的任务:

  1. 找到每个簇的中心点,称为 质心(centroid)。质心是每个簇的代表点,它是簇内所有数据点的平均值或几何中心。
  2. 将每个数据点分配给与它距离最近的质心所属的簇。也就是说,数据点会被分配到那个最能“代表”它的簇。

K-means 的目标是通过反复调整质心的位置,使得每个数据点与它所属的质心的距离尽可能短,从而让每个簇内的数据点尽可能接近。

二、K-means 目标函数的推导

在上一部分中,我们讨论了 K-means 的背景和基本思想:将数据划分为 k k k 个簇,并将每个数据点分配给与它最近的质心。接下来,我们将从数学上一步步推导 K-means 的目标函数,并解释它的优化过程。

K-means 算法的核心目标是最小化簇内数据点与质心之间的距离。为了实现这一目标,我们首先需要对“距离”的概念进行数学上的定义,并且定义一个目标函数来量化这个目标。

1. 定义簇内数据点到质心的距离

给定一个包含 n n n 个数据点的集合 X = { x 1 , x 2 , … , x n } X = \{x_1, x_2, \dots, x_n\} X={x1,x2,,xn},每个数据点 x i ∈ R d x_i \in \mathbb{R}^d xiRd 是一个 d d d 维向量,表示数据的特征。我们的目标是将这些数据点分成 k k k 个簇,记为 C 1 , C 2 , … , C k C_1, C_2, \dots, C_k C1,C2,,Ck,其中每个簇 C j C_j Cj 中的数据点彼此之间相似性较高。

在 K-means 中,我们用 欧氏距离 来度量数据点与质心之间的距离。假设簇 C j C_j Cj 的质心为 μ j \mu_j μj,则数据点 x i x_i xi 到质心 μ j \mu_j μj 的欧氏距离可以定义为:

∥ x i − μ j ∥ 2 = ∑ l = 1 d ( x i ( l ) − μ j ( l ) ) 2 \|x_i - \mu_j\|^2 = \sum_{l=1}^{d} (x_i^{(l)} - \mu_j^{(l)})^2 xiμj2=l=1d(xi(l)μj(l))2

其中 x i ( l ) x_i^{(l)} xi(l) μ j ( l ) \mu_j^{(l)} μj(l) 分别是数据点 x i x_i xi 和质心 μ j \mu_j μj 在第 l l l 个特征上的值。

2. 定义 K-means 的目标函数

K-means 算法的目标是找到一组簇划分 { C 1 , C 2 , … , C k } \{C_1, C_2, \dots, C_k\} {C1,C2,,Ck} 和质心 { μ 1 , μ 2 , … , μ k } \{\mu_1, \mu_2, \dots, \mu_k\} {μ1,μ2,,μk},使得 簇内所有数据点与质心之间的距离之和 最小化 。我们可以将这个优化目标用数学公式表示为一个 目标函数

J = ∑ j = 1 k ∑ x i ∈ C j ∥ x i − μ j ∥ 2 J = \sum_{j=1}^{k} \sum_{x_i \in C_j} \|x_i - \mu_j\|^2 J=j=1kxiCjxiμj2

这里:

  • J J J 是我们要最小化的目标函数,它表示所有数据点到各自质心的平方距离之和;
  • C j C_j Cj 是第 j j j 个簇,包含了所有分配给第 j j j 个簇的数据点;
  • μ j \mu_j μj 是第 j j j 个簇的质心;
  • ∥ x i − μ j ∥ 2 \|x_i - \mu_j\|^2 xiμj2 是数据点 x i x_i xi 到质心 μ j \mu_j μj 的欧氏距离的平方。

这个公式表示的是所有簇中,簇内数据点到质心的 距离平方和 ,我们希望通过选择最佳的簇划分和质心位置,使这个平方和最小化。

3. 两步优化:簇划分和质心更新

K-means 的核心优化过程可以分为两个交替的步骤,每次优化一个方面,直到目标函数 J J J 不再显著下降。这两个步骤分别是 簇分配质心更新

第一步:固定质心,优化簇分配

假设质心 { μ 1 , μ 2 , … , μ k } \{\mu_1, \mu_2, \dots, \mu_k\} {μ1,μ2,,μk} 已经固定不变,我们需要为每个数据点 x i x_i xi 分配一个簇,使得数据点 x i x_i xi 到质心的距离最小。具体来说,数据点 x i x_i xi 被分配到最近的簇 C j C_j Cj,即:

C j = { x i : ∥ x i − μ j ∥ 2 ≤ ∥ x i − μ l ∥ 2 , ∀ l ≠ j } C_j = \{x_i : \|x_i - \mu_j\|^2 \leq \|x_i - \mu_l\|^2, \forall l \neq j\} Cj={xi:xiμj2xiμl2,l=j}

这个步骤通过比较每个数据点到所有质心的距离,选择距离最小的质心对应的簇。其本质是将数据点“拉”向最近的质心。

第二步:固定簇分配,优化质心

一旦所有数据点都被分配到了对应的簇,我们接下来要更新每个簇的质心。质心的最佳选择是 簇内所有数据点的几何中心 ,即簇内数据点的平均值:

μ j = 1 ∣ C j ∣ ∑ x i ∈ C j x i \mu_j = \frac{1}{|C_j|} \sum_{x_i \in C_j} x_i μj=Cj1xiCjxi

其中 ∣ C j ∣ |C_j| Cj 是簇 C j C_j Cj 中数据点的数量, ∑ x i ∈ C j x i \sum_{x_i \in C_j} x_i xiCjxi 是簇内所有数据点的总和。

这个步骤的几何意义是,质心会“移动”到当前簇内所有数据点的中心位置。更新质心的目的是为了进一步减少簇内数据点与质心的距离。

4. 迭代优化过程

K-means 通过上述两个步骤的交替迭代,逐步优化目标函数 J J J。每一次迭代,簇分配和质心位置的变化都会导致目标函数 J J J 的值不断减小。虽然 K-means 不能保证找到全局最优解,但由于每一步都会减少目标函数的值,所以算法一定会在某个点收敛到局部最优解。通常,K-means 会在以下条件满足时停止迭代:

  • 簇分配不再发生变化;
  • 质心位置不再移动;
  • 或者达到了预设的迭代次数上限。

5. K-means 的局部最优解问题

值得注意的是,K-means 并不能保证找到全局最优解,因为它依赖于 初始质心的选择 。如果初始质心选择不当,算法可能会陷入局部最优解。为了缓解这个问题,常见的做法是多次运行 K-means,每次使用不同的初始质心,然后选择最优的结果。


三、示例数据与初始质心

我们假设有如下 6 个二维数据点:

X = { ( 1 , 2 ) , ( 1 , 4 ) , ( 3 , 2 ) , ( 5 , 8 ) , ( 7 , 8 ) , ( 9 , 11 ) } X = \{(1, 2), (1, 4), (3, 2), (5, 8), (7, 8), (9, 11)\} X={(1,2),(1,4),(3,2),(5,8),(7,8),(9,11)}

我们希望将这些点划分为 k = 2 k = 2 k=2 个簇,并且假设初始质心为:

μ 1 = ( 1 , 2 ) , μ 2 = ( 5 , 8 ) \mu_1 = (1, 2), \quad \mu_2 = (5, 8) μ1=(1,2),μ2=(5,8)

第 1 次迭代

第一步:固定质心,分配簇

对每个数据点计算其与两个质心的欧氏距离,然后将其分配给最近的质心。

  • ( 1 , 2 ) (1, 2) (1,2)

    • μ 1 = ( 1 , 2 ) \mu_1 = (1, 2) μ1=(1,2) 的距离:
      ∥ ( 1 , 2 ) − ( 1 , 2 ) ∥ 2 = 0 \| (1, 2) - (1, 2) \|^2 = 0 (1,2)(1,2)2=0
    • μ 2 = ( 5 , 8 ) \mu_2 = (5, 8) μ2=(5,8) 的距离:
      ∥ ( 1 , 2 ) − ( 5 , 8 ) ∥ 2 = ( 1 − 5 ) 2 + ( 2 − 8 ) 2 = 4 2 + 6 2 = 52 \| (1, 2) - (5, 8) \|^2 = (1 - 5)^2 + (2 - 8)^2 = 4^2 + 6^2 = 52 (1,2)(5,8)2=(15)2+(28)2=42+62=52
      分配给 C 1 C_1 C1
  • ( 1 , 4 ) (1, 4) (1,4)

    • μ 1 \mu_1 μ1 的距离:
      ∥ ( 1 , 4 ) − ( 1 , 2 ) ∥ 2 = ( 4 − 2 ) 2 = 4 \| (1, 4) - (1, 2) \|^2 = (4 - 2)^2 = 4 (1,4)(1,2)2=(42)2=4
    • μ 2 \mu_2 μ2 的距离:
      ∥ ( 1 , 4 ) − ( 5 , 8 ) ∥ 2 = ( 1 − 5 ) 2 + ( 4 − 8 ) 2 = 32 \| (1, 4) - (5, 8) \|^2 = (1 - 5)^2 + (4 - 8)^2 = 32 (1,4)(5,8)2=(15)2+(48)2=32
      分配给 C 1 C_1 C1
  • ( 3 , 2 ) (3, 2) (3,2)

    • μ 1 \mu_1 μ1 的距离:
      ∥ ( 3 , 2 ) − ( 1 , 2 ) ∥ 2 = 4 \| (3, 2) - (1, 2) \|^2 = 4 (3,2)(1,2)2=4
    • μ 2 \mu_2 μ2 的距离:
      ∥ ( 3 , 2 ) − ( 5 , 8 ) ∥ 2 = ( 3 − 5 ) 2 + ( 2 − 8 ) 2 = 40 \| (3, 2) - (5, 8) \|^2 = (3 - 5)^2 + (2 - 8)^2 = 40 (3,2)(5,8)2=(35)2+(28)2=40
      分配给 C 1 C_1 C1
  • ( 5 , 8 ) (5, 8) (5,8)

    • μ 1 \mu_1 μ1 的距离:
      ∥ ( 5 , 8 ) − ( 1 , 2 ) ∥ 2 = 52 \| (5, 8) - (1, 2) \|^2 = 52 (5,8)(1,2)2=52
    • μ 2 \mu_2 μ2 的距离:
      ∥ ( 5 , 8 ) − ( 5 , 8 ) ∥ 2 = 0 \| (5, 8) - (5, 8) \|^2 = 0 (5,8)(5,8)2=0
      分配给 C 2 C_2 C2
  • ( 7 , 8 ) (7, 8) (7,8)

    • μ 1 \mu_1 μ1 的距离:
      ∥ ( 7 , 8 ) − ( 1 , 2 ) ∥ 2 = 72 \| (7, 8) - (1, 2) \|^2 = 72 (7,8)(1,2)2=72
    • μ 2 \mu_2 μ2 的距离:
      ∥ ( 7 , 8 ) − ( 5 , 8 ) ∥ 2 = 4 \| (7, 8) - (5, 8) \|^2 = 4 (7,8)(5,8)2=4
      分配给 C 2 C_2 C2
  • ( 9 , 11 ) (9, 11) (9,11)

    • μ 1 \mu_1 μ1 的距离:
      ∥ ( 9 , 11 ) − ( 1 , 2 ) ∥ 2 = 145 \| (9, 11) - (1, 2) \|^2 = 145 (9,11)(1,2)2=145
    • μ 2 \mu_2 μ2 的距离:
      ∥ ( 9 , 11 ) − ( 5 , 8 ) ∥ 2 = 25 \| (9, 11) - (5, 8) \|^2 = 25 (9,11)(5,8)2=25
      分配给 C 2 C_2 C2

结果:
C 1 = { ( 1 , 2 ) , ( 1 , 4 ) , ( 3 , 2 ) } C_1 = \{(1, 2), (1, 4), (3, 2)\} C1={(1,2),(1,4),(3,2)}
C 2 = { ( 5 , 8 ) , ( 7 , 8 ) , ( 9 , 11 ) } C_2 = \{(5, 8), (7, 8), (9, 11)\} C2={(5,8),(7,8),(9,11)}

如图所示:
在这里插入图片描述

质心 ( 1 , 2 ) (1, 2) (1,2) ( 5 , 8 ) (5, 8) (5,8) 的位置使得 左侧点(如 ( 1 , 2 ) (1, 2) (1,2) 明显离 ( 1 , 2 ) (1, 2) (1,2) 质心更近,因此形成了偏向左侧的蓝色区域。相对的, ( 5 , 8 ) (5, 8) (5,8) 的吸引范围较小,但主要覆盖了右上部分的数据点。

决策边界(Voronoi 图的等高线):

  • 决策边界是两个质心之间的等距离线,即:
    ∥ x − μ 1 ∥ = ∥ x − μ 2 ∥ \|x - \mu_1\| = \|x - \mu_2\| xμ1=xμ2
  • 决策边界上的点与两个质心距离相等,成为簇的分界线。在二维平面上,如果质心之间的距离用欧氏距离度量,这条线通常是一条直线

第二步:固定簇,更新质心

计算每个簇的质心(即簇内所有点的平均值)。

  • C 1 C_1 C1
    数据点: ( 1 , 2 ) , ( 1 , 4 ) , ( 3 , 2 ) (1, 2), (1, 4), (3, 2) (1,2),(1,4),(3,2)
    μ 1 = 1 3 ( ( 1 , 2 ) + ( 1 , 4 ) + ( 3 , 2 ) ) = ( 1 + 1 + 3 3 , 2 + 4 + 2 3 ) = ( 1.67 , 2.67 ) \mu_1 = \frac{1}{3} \left((1, 2) + (1, 4) + (3, 2)\right) = \left(\frac{1 + 1 + 3}{3}, \frac{2 + 4 + 2}{3}\right) = (1.67, 2.67) μ1=31((1,2)+(1,4)+(3,2))=(31+1+3,32+4+2)=(1.67,2.67)

  • C 2 C_2 C2
    数据点: ( 5 , 8 ) , ( 7 , 8 ) , ( 9 , 11 ) (5, 8), (7, 8), (9, 11) (5,8),(7,8),(9,11)
    μ 2 = 1 3 ( ( 5 , 8 ) + ( 7 , 8 ) + ( 9 , 11 ) ) = ( 5 + 7 + 9 3 , 8 + 8 + 11 3 ) = ( 7 , 9 ) \mu_2 = \frac{1}{3} \left((5, 8) + (7, 8) + (9, 11)\right) = \left(\frac{5 + 7 + 9}{3}, \frac{8 + 8 + 11}{3}\right) = (7, 9) μ2=31((5,8)+(7,8)+(9,11))=(35+7+9,38+8+11)=(7,9)


第 2 次迭代

第一步:固定质心 μ 1 = ( 1.67 , 2.67 ) \mu_1 = (1.67, 2.67) μ1=(1.67,2.67) μ 2 = ( 7 , 9 ) \mu_2 = (7, 9) μ2=(7,9),重新分配簇

对每个数据点计算与这两个质心的欧氏距离,并将其分配给距离较近的簇。

  • ( 1 , 2 ) (1, 2) (1,2)

    • μ 1 = ( 1.67 , 2.67 ) \mu_1 = (1.67, 2.67) μ1=(1.67,2.67) 的距离:
      ∥ ( 1 , 2 ) − ( 1.67 , 2.67 ) ∥ 2 = ( 1 − 1.67 ) 2 + ( 2 − 2.67 ) 2 = ( − 0.67 ) 2 + ( − 0.67 ) 2 = 0.4489 + 0.4489 = 0.8978 \| (1, 2) - (1.67, 2.67) \|^2 = (1 - 1.67)^2 + (2 - 2.67)^2 = (-0.67)^2 + (-0.67)^2 = 0.4489 + 0.4489 = 0.8978 (1,2)(1.67,2.67)2=(11.67)2+(22.67)2=(0.67)2+(0.67)2=0.4489+0.4489=0.8978
    • μ 2 = ( 7 , 9 ) \mu_2 = (7, 9) μ2=(7,9) 的距离:
      ∥ ( 1 , 2 ) − ( 7 , 9 ) ∥ 2 = ( 1 − 7 ) 2 + ( 2 − 9 ) 2 = ( − 6 ) 2 + ( − 7 ) 2 = 36 + 49 = 85 \| (1, 2) - (7, 9) \|^2 = (1 - 7)^2 + (2 - 9)^2 = (-6)^2 + (-7)^2 = 36 + 49 = 85 (1,2)(7,9)2=(17)2+(29)2=(6)2+(7)2=36+49=85
      分配给 C 1 C_1 C1
  • ( 1 , 4 ) (1, 4) (1,4)

    • μ 1 \mu_1 μ1 的距离:
      ∥ ( 1 , 4 ) − ( 1.67 , 2.67 ) ∥ 2 = ( 1 − 1.67 ) 2 + ( 4 − 2.67 ) 2 = 0.4489 + 1.7689 = 2.2178 \| (1, 4) - (1.67, 2.67) \|^2 = (1 - 1.67)^2 + (4 - 2.67)^2 = 0.4489 + 1.7689 = 2.2178 (1,4)(1.67,2.67)2=(11.67)2+(42.67)2=0.4489+1.7689=2.2178
    • μ 2 \mu_2 μ2 的距离:
      ∥ ( 1 , 4 ) − ( 7 , 9 ) ∥ 2 = ( 1 − 7 ) 2 + ( 4 − 9 ) 2 = 36 + 25 = 61 \| (1, 4) - (7, 9) \|^2 = (1 - 7)^2 + (4 - 9)^2 = 36 + 25 = 61 (1,4)(7,9)2=(17)2+(49)2=36+25=61
      分配给 C 1 C_1 C1
  • ( 3 , 2 ) (3, 2) (3,2)

    • μ 1 \mu_1 μ1 的距离:
      ∥ ( 3 , 2 ) − ( 1.67 , 2.67 ) ∥ 2 = ( 3 − 1.67 ) 2 + ( 2 − 2.67 ) 2 = 1.7689 + 0.4489 = 2.2178 \| (3, 2) - (1.67, 2.67) \|^2 = (3 - 1.67)^2 + (2 - 2.67)^2 = 1.7689 + 0.4489 = 2.2178 (3,2)(1.67,2.67)2=(31.67)2+(22.67)2=1.7689+0.4489=2.2178
    • μ 2 \mu_2 μ2 的距离:
      ∥ ( 3 , 2 ) − ( 7 , 9 ) ∥ 2 = ( 3 − 7 ) 2 + ( 2 − 9 ) 2 = 16 + 49 = 65 \| (3, 2) - (7, 9) \|^2 = (3 - 7)^2 + (2 - 9)^2 = 16 + 49 = 65 (3,2)(7,9)2=(37)2+(29)2=16+49=65
      分配给 C 1 C_1 C1
  • ( 5 , 8 ) (5, 8) (5,8)

    • μ 1 \mu_1 μ1 的距离:
      ∥ ( 5 , 8 ) − ( 1.67 , 2.67 ) ∥ 2 = ( 5 − 1.67 ) 2 + ( 8 − 2.67 ) 2 = 11.0889 + 28.2889 = 39.3778 \| (5, 8) - (1.67, 2.67) \|^2 = (5 - 1.67)^2 + (8 - 2.67)^2 = 11.0889 + 28.2889 = 39.3778 (5,8)(1.67,2.67)2=(51.67)2+(82.67)2=11.0889+28.2889=39.3778
    • μ 2 \mu_2 μ2 的距离:
      ∥ ( 5 , 8 ) − ( 7 , 9 ) ∥ 2 = ( 5 − 7 ) 2 + ( 8 − 9 ) 2 = 4 + 1 = 5 \| (5, 8) - (7, 9) \|^2 = (5 - 7)^2 + (8 - 9)^2 = 4 + 1 = 5 (5,8)(7,9)2=(57)2+(89)2=4+1=5
      分配给 C 2 C_2 C2
  • ( 7 , 8 ) (7, 8) (7,8)

    • μ 1 \mu_1 μ1 的距离:
      ∥ ( 7 , 8 ) − ( 1.67 , 2.67 ) ∥ 2 = ( 7 − 1.67 ) 2 + ( 8 − 2.67 ) 2 = 28.2889 + 28.2889 = 56.5778 \| (7, 8) - (1.67, 2.67) \|^2 = (7 - 1.67)^2 + (8 - 2.67)^2 = 28.2889 + 28.2889 = 56.5778 (7,8)(1.67,2.67)2=(71.67)2+(82.67)2=28.2889+28.2889=56.5778
    • μ 2 \mu_2 μ2 的距离:
      ∥ ( 7 , 8 ) − ( 7 , 9 ) ∥ 2 = ( 7 − 7 ) 2 + ( 8 − 9 ) 2 = 0 + 1 = 1 \| (7, 8) - (7, 9) \|^2 = (7 - 7)^2 + (8 - 9)^2 = 0 + 1 = 1 (7,8)(7,9)2=(77)2+(89)2=0+1=1
      分配给 C 2 C_2 C2
  • ( 9 , 11 ) (9, 11) (9,11)

    • μ 1 \mu_1 μ1 的距离:
      ∥ ( 9 , 11 ) − ( 1.67 , 2.67 ) ∥ 2 = ( 9 − 1.67 ) 2 + ( 11 − 2.67 ) 2 = 53.5556 + 69.5556 = 123.1112 \| (9, 11) - (1.67, 2.67) \|^2 = (9 - 1.67)^2 + (11 - 2.67)^2 = 53.5556 + 69.5556 = 123.1112 (9,11)(1.67,2.67)2=(91.67)2+(112.67)2=53.5556+69.5556=123.1112
    • μ 2 \mu_2 μ2 的距离:
      ∥ ( 9 , 11 ) − ( 7 , 9 ) ∥ 2 = ( 9 − 7 ) 2 + ( 11 − 9 ) 2 = 4 + 4 = 8 \| (9, 11) - (7, 9) \|^2 = (9 - 7)^2 + (11 - 9)^2 = 4 + 4 = 8 (9,11)(7,9)2=(97)2+(119)2=4+4=8
      分配给 C 2 C_2 C2

  • C 1 = { ( 1 , 2 ) , ( 1 , 4 ) , ( 3 , 2 ) } C_1 = \{(1, 2), (1, 4), (3, 2)\} C1={(1,2),(1,4),(3,2)}
  • C 2 = { ( 5 , 8 ) , ( 7 , 8 ) , ( 9 , 11 ) } C_2 = \{(5, 8), (7, 8), (9, 11)\} C2={(5,8),(7,8),(9,11)}

和第 1 次迭代的簇分配一致,因此算法在此处已经 收敛,可以停止迭代。

质心更新为 ( 1.67 , 2.67 ) (1.67, 2.67) (1.67,2.67) ( 7 , 9 ) (7, 9) (7,9) 后,可以看到:
在这里插入图片描述

  • 蓝色质心的范围稍微向右上方扩大,因为质心移动后,它变得对部分中间位置的数据点更有吸引力。
  • 红色区域则变得更加集中在右上角的部分,反映出质心更新后的范围变化。

通过上述计算,我们可以看到,第 2 次迭代的簇分配与第 1 次相同,说明质心位置已经稳定,簇划分不再变化。此时算法达到了收敛条件,可以停止。

最终结果为:

  • 簇 1: { ( 1 , 2 ) , ( 1 , 4 ) , ( 3 , 2 ) } \{(1, 2), (1, 4), (3, 2)\} {(1,2),(1,4),(3,2)}
    质心: μ 1 = ( 1.67 , 2.67 ) \mu_1 = (1.67, 2.67) μ1=(1.67,2.67)
  • 簇 2: { ( 5 , 8 ) , ( 7 , 8 ) , ( 9 , 11 ) } \{(5, 8), (7, 8), (9, 11)\} {(5,8),(7,8),(9,11)}
    质心: μ 2 = ( 7 , 9 ) \mu_2 = (7, 9) μ2=(7,9)

此例展示了 K-means 的完整迭代过程。即使数据简单,K-means 也展示了其通过不断优化目标函数、最小化簇内距离平方和的能力。


总结:K-means 的代码逻辑

通过上述步骤,我们可以总结出 K-means 算法的核心逻辑:

  1. 初始化:
    随机选择 k k k 个质心。

  2. 迭代直到收敛:

    • 簇分配步骤:
      对每个数据点,计算其与所有质心的距离,并将其分配给最近的簇。
    • 质心更新步骤:
      计算每个簇的新的质心(簇内所有点的平均值)。
  3. 返回结果:
    输出最终的簇划分和质心位置。


四、实现代码

在本节中,我们将使用 K-means 算法对一组复杂的数据进行聚类。首先,我们将展示初始数据点的分布情况,然后逐步讲解 K-means 算法的每个步骤,包括初始化质心、簇分配、质心更新以及可视化迭代过程。

初始数据点分布

我们使用 make_blobs 函数生成一个包含 300 个样本、3 个簇的随机数据集。初始数据点分布情况如下所示:

初始数据点分布

1. 初始化质心

在 K-means 算法中,第一步是随机选择 k k k 个质心。以下是初始化质心的代码:

# 1. 随机初始化 k 个质心
def initialize_centroids(data_points, k):
    np.random.seed(42)  # 设置随机种子以确保可复现性
    random_indices = np.random.choice(data_points.shape[0], size=k, replace=False)
    return data_points[random_indices]

随机选择初始化质心后的可视化效果如下:

随机选择初始化质心

2. 簇分配步骤

在这个步骤中,我们将每个数据点分配给距离最近的质心。以下是相关代码:

# 2. 分配数据点到最近的质心
def assign_clusters(data_points, centroids):
    distances = np.linalg.norm(data_points[:, np.newaxis] - centroids, axis=2)  # 计算距离
    return np.argmin(distances, axis=1)  # 返回距离最小的索引

3. 质心更新步骤

在每次迭代中,我们需要更新每个簇的质心。质心是簇内所有数据点的平均值。以下是更新质心的代码:

# 3. 更新质心
def update_centroids(data_points, cluster_assignments, k):
    return np.array([data_points[cluster_assignments == j].mean(axis=0) for j in range(k)])  # 计算每个簇的均值

4. 迭代过程与可视化

我们将实现 K-means 算法的迭代过程,并可视化每一步的聚类结果。以下是用于绘制每次迭代状态的函数:

# 4. 迭代绘制和聚类过程
def plot_kmeans_iteration(centroids, data_points, cluster_assignments=None, iteration=0):
    plt.figure(figsize=(8, 6))
    if cluster_assignments is not None:
        plt.scatter(data_points[:, 0], data_points[:, 1], c=cluster_assignments, cmap='viridis', marker='o')  # 根据簇分配上色
    else:
        plt.scatter(data_points[:, 0], data_points[:, 1], c='blue', marker='o', label='Data Points')

    # 绘制质心
    plt.scatter(centroids[:, 0], centroids[:, 1], c='red', marker='X', s=200, label='Centroids')

    plt.title(f'K-means Iteration {iteration}')
    plt.xlabel('X-axis')
    plt.ylabel('Y-axis')
    plt.grid(True)
    plt.legend()
    plt.show()

K-means 算法实现

最后,我们将结合上述步骤实现完整的 K-means 算法。该算法将进行迭代直至收敛:

def kmeans(data_points, k, max_iters=10):
    # 初始化质心
    centroids = initialize_centroids(data_points, k)
    plot_kmeans_iteration(centroids, data_points, iteration=0)  # 初始状态
    for iteration in range(max_iters):
        # 分配簇
        cluster_assignments = assign_clusters(data_points, centroids)
        plot_kmeans_iteration(centroids, data_points, cluster_assignments, iteration + 1)  # 绘制当前簇分配
        # 更新质心
        new_centroids = update_centroids(data_points, cluster_assignments, k)

        # 如果质心没有变化,则收敛
        if np.all(centroids == new_centroids):
            break
        centroids = new_centroids  # 更新质心

    # 最终结果
    plot_kmeans_iteration(centroids, data_points, cluster_assignments, 'Final')  # 绘制最终结果
    return centroids, cluster_assignments

运行 K-means

最后,执行 K-means 算法:

# 运行 K-means
final_centroids, final_cluster_assignments = kmeans(data_points, k=3, max_iters=10)

在每次迭代中,我们可以观察到数据点的簇分配逐步优化,直到达到最终的聚类结果。以下是 K-means 聚类过程中的几个重要步骤的可视化结果:

  • 初始状态:
    初始状态

  • 第一次迭代:
    第一次迭代

  • 最终结果:
    最终结果

完整代码

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

# 生成较为复杂的聚类数据
data_points, _ = make_blobs(n_samples=300, centers=3, cluster_std=1.0, random_state=42)


# K-means 的代码逻辑

# 1. 随机初始化 k 个质心
def initialize_centroids(data_points, k):
    np.random.seed(42)
    random_indices = np.random.choice(data_points.shape[0], size=k, replace=False)
    return data_points[random_indices]


# 2. 分配数据点到最近的质心
def assign_clusters(data_points, centroids):
    distances = np.linalg.norm(data_points[:, np.newaxis] - centroids, axis=2)
    return np.argmin(distances, axis=1)


# 3. 更新质心
def update_centroids(data_points, cluster_assignments, k):
    return np.array([data_points[cluster_assignments == j].mean(axis=0) for j in range(k)])


# 4. 迭代绘制和聚类过程
def plot_kmeans_iteration(centroids, data_points, cluster_assignments=None, iteration=0):
    plt.figure(figsize=(8, 6))
    if cluster_assignments is not None:
        plt.scatter(data_points[:, 0], data_points[:, 1], c=cluster_assignments, cmap='viridis', marker='o')
    else:
        plt.scatter(data_points[:, 0], data_points[:, 1], c='blue', marker='o', label='Data Points')

    # 绘制质心
    plt.scatter(centroids[:, 0], centroids[:, 1], c='red', marker='X', s=200, label='Centroids')

    plt.title(f'K-means Iteration {iteration}')
    plt.xlabel('X-axis')
    plt.ylabel('Y-axis')
    plt.grid(True)
    plt.legend()
    plt.show()


# K-means 算法实现,迭代过程
def kmeans(data_points, k, max_iters=10):
    # 初始化质心
    centroids = initialize_centroids(data_points, k)
    plot_kmeans_iteration(centroids, data_points, iteration=0)  # 初始状态
    for iteration in range(max_iters):
        # 分配簇
        cluster_assignments = assign_clusters(data_points, centroids)
        plot_kmeans_iteration(centroids, data_points, cluster_assignments, iteration + 1)
        # 更新质心
        new_centroids = update_centroids(data_points, cluster_assignments, k)

        # 如果质心没有变化,则收敛
        if np.all(centroids == new_centroids):
            break
        centroids = new_centroids

    # 最终结果
    plot_kmeans_iteration(centroids, data_points, cluster_assignments, 'Final')
    return centroids, cluster_assignments


# 运行 K-means
final_centroids, final_cluster_assignments = kmeans(data_points, k=3, max_iters=10)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2210972.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt之TCP收发图片的例子

一.效果 二.实现 1.发图片 void MainWindow::slotSendImage() {matrix.rotate(90);QPixmap tempPixmap = pixmap.transformed(matrix);QBuffer buffer;tempPixmap.save(&buffer,"jpg");ui->labelImage->setPixmap(tempPixmap);int dataLength = buffer.d…

UE4 材质学习笔记09(雨水水坑着色器/完整雨水着色器)

一.雨水水坑着色器 要用到这样一个噪声贴图,我们要做的就是,做出水坑并让水坑在这种浑浊的噪点中产生,因此水坑将从最暗的斑点生长,然后随着它继续占据越来越亮的像素而生长 现在水坑将从上到下投射到世界空间中,所以…

C++:模拟priority_queue

目录 priority_queue的介绍 概念 特点 priority_queue的使用 基本操作 演示代码 ​编辑 priority_queue的模拟实现 仿函数 向上调整和向下调整 模拟实现的代码 priority_queue的介绍 概念 在C标准库中,priority_queue是一个基于优先级堆的容器适配器。…

设计感十足的喇叭裤来咯,亲子款get~

微喇叭的设计,时尚感爆棚,瞬间让宝贝成为冬日里的小潮人。而且这种设计非常显瘦,能够很好地修饰宝贝的腿型,穿上秒变大长腿。表面磨毛质感,摸起来舒软绒顺,温柔气质,下脚隐形拉链,拉…

如何通过 Nginx 只允许 www 域名访问并禁止裸域名访问

个人名片 🎓作者简介:java领域优质创作者 🌐个人主页:码农阿豪 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[2435024119qq.com] &#x1f4f1…

learn C++ NO.23——map、set的模拟实现

STL库的实现方式 map和set的底层用的红黑树是一样的吗?从容器特点的角度出发,这里个容器的底层应该分别用key搜索模型的红黑树和key value 搜索模型的红黑树。但是,从库的设计角度出发,这两者用同一份红黑树代码更好。而STL就是用…

第十五章 RabbitMQ延迟消息之延迟插件

目录 一、引言 二、延迟插件安装 2.1. 下载插件 2.2. 安装插件 2.3. 确认插件是否生效 三、核心代码 四、运行效果 五、总结 一、引言 上一章我们讲到通过死信队列组合消息过期时间来实现延迟消息,但相对而言这并不是比较好的方式。它的代码实现相对来说比…

Java->排序

目录 一、排序 1.概念 2.常见的排序算法 二、常见排序算法的实现 1.插入排序 1.1直接插入排序 1.2希尔排序(缩小增量法) 1.3直接插入排序和希尔排序的耗时比较 2.选择排序 2.1直接选择排序 2.2堆排序 2.3直接选择排序与堆排序的耗时比较 3.交换排序 3.1冒泡排序…

你知道C++多少——继承

🌈个人主页:小新_- 🎈个人座右铭:“成功者不是从不失败的人,而是从不放弃的人!”🎈 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝 🏆所属专栏&#xff1…

蓝桥杯模块三:蜂鸣器和继电器的基本控制

模块训练题目: 一、蜂鸣器电路图 1.电路图 2.电路分析 138译码器控制Y5,Y5控制Y5C,Y5C低电平控制芯片开启P0口控制ULN2003继而控制蜂鸣器端口和继电器端口 二、程序代码 1.138译码器控制端口函数 建立初始化函数选择锁存器 2.实现题目功能 在LED代…

24-10-13-读书笔记(二十五)-《一只特立独行的猪》([中] 王小波)用一生来学习艺术

文章目录 《一只特立独行的猪》([中] 王小波)目录阅读笔记记录总结 《一只特立独行的猪》([中] 王小波) 十月第五篇,放慢脚步,秋季快要过去了,要步入冬季了,心中也是有些跌宕起伏&am…

Guitar Pro怎么制作伴奏谱,吉他谱制作软件guitar pro教程

在诸多教学吉他谱制作软件中Guitar Pro是一款非常优秀的软件,它是专为吉他和其他弦乐器设计,且能提供乐谱编辑、音轨录制和播放、和弦与音阶库等功能的强大软件。Guitar Pro不仅具有强大的乐谱编辑功能,其用户界面也易于上手,更支…

ThingsBoard规则链节点:Script节点详解

引言 脚本节点简介 用法 含义 应用场景 实际项目运用示例 智能楼宇管理系统 工业自动化生产线 结论 引言 ThingsBoard是一个功能强大的物联网平台,它支持设备管理、数据收集与处理以及实时监控。其核心组件之一是规则引擎,允许用户定义复杂的业务…

vue特效,一片动态星空

vue实现漂亮星空&#xff0c;超级简单 1.创建vue项目&#xff1a; vue create demo 2.注册vuecli : npm i element-ui -S 3.加载依赖 &#xff1a;npm i 4.运行项目 :npm run serve <!DOCTYPE html> <html lang"en"> <head><…

JavaWeb 19 AJAX

"我就是希望你好&#xff0c;就像很多人希望我好一样&#xff0c;特别简单&#xff0c;特别真挚。也不为了什么&#xff0c;就是希望你好" —— 24.10.13 一、什么是AJAX AJAX Asynchronous JavaScript and XML(异步的JavaScript和XML) AJAX不是新的编程语言&…

仿新版QQ的聊天小软件

仿新版QQ的聊天小软件 文章说明核心源码效果展示源码下载 文章说明 新开一个聊天组件的项目的想法主要来源于想学习一下消息队列的使用&#xff0c;后来在书写界面和一些功能模块时&#xff0c;又想到可以抽离出来&#xff0c;分别写几篇文章&#xff0c;主要介绍扫码登陆、消息…

MQ快速入门【详细】个人笔记 讲解通俗易懂

1.同步通讯和异步通讯 同步通讯&#xff1a;如果举个例子来说&#xff0c;同步通讯就像是两个人在打电话&#xff0c;一方说的话&#xff0c;能够立马传给另一方&#xff0c;消息的时效性非常高&#xff0c;但是相对的&#xff0c;只能是给一个人通讯&#xff0c;如果这个时候&…

React (三) 创建安装脚手架,类组件与函数式组件;生命周期;父子通信props;插槽;非父子通信Context

文章目录 一、脚手架的创建与安装1. 认识脚手架2. 安装脚手架3. 创建react项目4. 项目结构 二、从0编写三、组件化开发1. 什么是组件化开发2. 类组件3. render函数4. 函数式组件 四、生命周期1. 挂载Mount2. 更新Update3. 卸载Unmount4. 不常用的生命周期 五、父子组件通信1. 父…

数据结构之队列(python)

华子目录 1.队列存储结构1.1队列基本介绍1.2队列的实现方式 2.顺序队列2.1顺序队列的介绍2.2顺序队列的简单实现2.3代码实现 3.链式队列和基本操作3.1链式队列数据入队3.2链式队列数据出队3.3队列的链式表示和实现 1.队列存储结构 1.1队列基本介绍 队列的两端都"开口&qu…

springcloud之服务集群注册与发现 Eureka

前言 1&#xff1a;对于能提供完整领域服务接口功能的RPC而言&#xff0c;例如&#xff1b;gRPC、Thrift、Dubbo等&#xff0c;服务的注册与发现都是核心功能中非常重要的一环&#xff0c;使得微服务得到统一管理。 2&#xff1a;在分布式领域中有个著名的CAP理论&#xff1b;…