K-means++算法是对传统的K-means聚类算法的一种改进,它解决了K-means算法的一个主要缺点——对初始聚类中心选择的敏感性

K-means++通过一种更合理的方式来选择初始聚类中心,使得算法更有可能找到全局最优解,而不是陷入局部最优。

K-means++算法流程:
  1. 随机选择第一个聚类中心
  • 从数据集中随机选择一个样本点作为第一个聚类中心 K-means++算法_算法
  1. 计算距离并选择剩余的聚类中心
  • 对于数据集中的每个点 K-means++算法_算法_02计算它到已选聚类中心的最短距离 K-means++算法_聚类_03
  • 选择下一个聚类中心 K-means++算法_算法_04概率K-means++算法_数据集_05 成正比。这意味着那些距离现有聚类中心较远的点有更大的机会被选为新的聚类中心。
  1. 重复上述过程
  • 直到选择了所有 K-means++算法_数据集_06
公式:

在K-means++中,对于每一个未被选作聚类中心的点 K-means++算法_算法_07计算其到最近的聚类中心的距离 K-means++算法_机器学习_08,定义为:

K-means++算法_机器学习_09

这里 K-means++算法_kmeans_10 表示向量的范数,通常是欧几里得距离:

K-means++算法_数据集_11

其中 K-means++算法_机器学习_12K-means++算法_数据集_13 分别是 $x $ 和聚类中心 K-means++算法_聚类_14 在第 K-means++算法_kmeans_15 维的坐标值,K-means++算法_kmeans_16数据的维度。

概率选择新聚类中心:

选择下一个聚类中心的概率与距离平方成正比:

K-means++算法_机器学习_17
K-means++算法_数据集_18

其中

  • K-means++算法_机器学习_19 代表单个点 K-means++算法_算法_20 到其最近的已选质心(centroid)的距离的平方。这里的 K-means++算法_kmeans_21 是点 K-means++算法_算法_20
  • K-means++算法_机器学习_23 是指对数据集K-means++算法_机器学习_24 中的所有点 K-means++算法_算法_20距离平方 K-means++算法_数据集_26

这意味着,如果一个点 K-means++算法_算法_07 到最近的聚类中心的距离较大那么它被选为下一个聚类中心的概率也较高。

通过这种方式,K-means++算法倾向于在数据集的不同区域选择聚类中心,从而避免了K-means算法可能产生的偏斜聚类中心的问题,提高了算法的稳定性和聚类质量。

python代码
import numpy as np
import random

# 数据集
data = np.array([(1, 2), (1.5, 1.8), (5, 8), (8, 8), (1, 0.6), (9, 11)])


def euclidean_distance(a, b):
    return np.sqrt(np.sum((a - b) ** 2))


def initialize_centers(data, k):
    centers = [data[random.randint(0, len(data) - 1)]]

    for _ in range(k - 1):
        # 计算数据集中每个点到一组质心(centers)的最短距离
        # euclidean_distance(x, c) for c in centers]:
        # 这是一个列表推导式,它对每一个质心 c 在 centers 列表中计算了与当前数据点 x 的欧几里得距离。

        dists = np.array([min([euclidean_distance(x, c) for c in centers]) for x in data])
        probs = dists ** 2 / np.sum(dists ** 2)
        cum_probs = np.cumsum(probs)
        rand_prob = random.random()

        for i in range(len(cum_probs)):
            if rand_prob < cum_probs[i]:
                centers.append(data[i])
                break

    return centers


k = 3
centers = initialize_centers(data, k)

print("Initial Centers:", centers)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.

这段代码实现了 K-means++ 中的质心初始化过程,下面是对每一部分的详细注释:

import numpy as np
import random

# 数据集
data = np.array([(1, 2), (1.5, 1.8), (5, 8), (8, 8), (1, 0.6), (9, 11)])
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

这里导入了必要的库 numpyrandom,并且定义了一个数据集 data,它包含了六个二维数据点。

def euclidean_distance(a, b):
    return np.sqrt(np.sum((a - b) ** 2))
  • 1.
  • 2.

euclidean_distance 函数计算两个点 ab 之间的欧几里得距离。它先计算两个点坐标的差值,然后对这些差值的平方求和,最后取平方根得到最终的距离。

def initialize_centers(data, k):
    centers = [data[random.randint(0, len(data) - 1)]]
  • 1.
  • 2.

initialize_centers 函数接收数据集 data 和预期的质心数量 k。首先,它随机选择数据集中的一个点作为第一个质心,并将其添加到 centers 列表中。

for _ in range(k - 1):
    # 计算数据集中每个点到一组质心(centers)的最短距离
    dists = np.array([min([euclidean_distance(x, c) for c in centers]) for x in data])
  • 1.
  • 2.
  • 3.

接下来的循环为剩下的 k-1 个质心选择位置。对于数据集中的每个点 x,计算它到所有已选质心的最小距离。这些距离被存储在 dists 数组中。

probs = dists ** 2 / np.sum(dists ** 2)
  • 1.

计算每个点被选为下一个质心的概率。这通过将每个点的距离平方除以所有点距离平方的总和来完成,确保概率之和等于 1。
K-means++算法_数据集_18

cum_probs = np.cumsum(probs)
rand_prob = random.random()
  • 1.
  • 2.

cum_probs 是一个累积概率数组,由 probs 的累积和构成。rand_prob 是一个介于 0 和 1 之间的随机数,将用于从累积概率分布中选择下一个质心。

for i in range(len(cum_probs)):
    if rand_prob < cum_probs[i]:
        centers.append(data[i])
        break
  • 1.
  • 2.
  • 3.
  • 4.

使用 rand_prob 来决定下一个质心。遍历 cum_probs 数组直到找到第一个大于或等于 rand_prob 的元素,此时的索引 i 就对应着下一个被选中的质心。

return centers
  • 1.

函数返回选定的质心列表 centers

k = 3
centers = initialize_centers(data, k)

print("Initial Centers:", centers)
  • 1.
  • 2.
  • 3.
  • 4.

最后,设定 k 为 3,并调用 initialize_centers 函数来初始化三个质心。打印出初始化后的质心列表。

这个过程确保了质心的初始选择是分散的,而不是聚集在一起,这可以提高 K-means 算法的性能和结果质量。