启发式方法给K-Means选择较好的初始质心

教育   2024-08-30 11:30   四川  


今天云朵君和大家一起学习最简单的聚类算法K-Means算法中启发式选择初始聚类中心的方法,深入探究K-Means++算法中的细节!

一、K-Means

1. 概念

  • k-means algorithm算法:
    K-均值(K-Means)属于聚类算法,之所以称为K-均值是因为它把n个样本根据它们的属性分为k个簇(k < n),且每个簇的中心采用簇中所含值的均值计算而成。

  • 聚类:
    一种无监督的学习,事先不知道类别,自动将相似的对象归到同一簇中。
    聚类作为一种典型的数据挖掘方法,一直以来都是人工智能领域的一个研究热点,被广泛地应用于人脸图像识别、股票分析预测、搜索引擎、生物信息学等领域中。

2. K-Means算法思想

  1. 随机选择K个中心值
  2. 根据样本数据各点与中心点的距离来进行归簇
  3. 通过各个簇的均值,更新为每个簇的中心值
  4. 重复2、3,直至聚类中心的位置不再变化

3. K-Means特点

  • 适用数据类型:适用于数值型数据
  • 优点:算法原理简单,容易实现。需要调节的超参数就是一个k。
  • 缺点:在 K-Means算法中 K需要事先确定,这个 K 值的选定有时候是比较难确定。
    可能收敛到局部最小值;
    在大规模数据集上收敛速度较慢;

二、K-Means++

1.概念

  • k-means++ algorithm算法:
    由于 K-means 算法的分类结果会受到初始点的选取而有所区别,因此有提出这种算法的改进: K-Means++ 。

2.K-Means++算法思想

其实这个算法是对初始点的选择有改进,其他步骤都一样。初始质心选取的基本思路就是,初始的聚类中心之间的相互距离要尽可能的远。

  1. 随机选取一个样本作为第一个聚类中心 c1;
  2. 计算每个样本与当前已有类聚中心最短距离(即与最近一个聚类中心的距离),用 D(x)表示;
    这个值越大,表示被选取作为聚类中心的概率较大;
    最后,用轮盘法选出下一个聚类中心ci;
  3. 重复步骤2,直到选出 k 个聚类中心。

3.K-Means++特点

适用数据类型:适用于数值型数据

  • 优点:可以确定地初始化聚类中心。减少算法的迭代次数,提高算法的收敛速度。
  • 缺点:K-Means++算法从可扩展性来看,它存在一个缺点,那就是它内在的有序性特性:下一个中心点的选择依赖于已经选择的中心点。

4.K-Means++算法实现

#!/usr/bin/env python
# encoding: utf-8

import math
import random
import numpy as np
import matplotlib.pyplot as plt


def loadDataSet(fileName):
    '''
    加载测试数据集,返回一个列表,列表的元素是一个坐标
    '''

    dataList = []
    with open(fileName) as fr:
        for line in fr.readlines():
            curLine = line.strip().split('\t')
            fltLine = list(map(float, curLine))
            dataList.append(fltLine)
    return dataList


def euler_distance(point1: list, point2: list) -> float:
    """
    计算两点之间的欧拉距离,支持多维
    """

    distance = 0.0
    for a, b in zip(point1, point2):
        distance += math.pow(a - b, 2)
    return math.sqrt(distance)


def get_closest_dist(point, centroids):
    min_dist = math.inf  # 初始设为无穷大
    for i, centroid in enumerate(centroids):
        dist = euler_distance(centroid, point)
        if dist < min_dist:
            min_dist = dist
    return min_dist


def kpp_centers(data_set, k):
    """
    1.从数据集中返回 k 个对象可作为质心
    """


    # 将矩阵转为列表
    data_set = np.matrix.tolist(data_set)

    cluster_centers = []
    cluster_centers.append(random.choice(data_set))
    d = [0 for _ in range(len(data_set))]
    for _ in range(1, k):
        total = 0.0
        for i, point in enumerate(data_set):
            d[i] = get_closest_dist(point, cluster_centers)  # 与最近一个聚类中心的距离
            total += d[i]
        total *= random.random()
        for i, di in enumerate(d):  # 轮盘法选出下一个聚类中心;
            total -= di
            if total > 0:
                continue
            cluster_centers.append(data_set[i])
            break

    cluster_centers = np.mat(cluster_centers)
    return cluster_centers


def kMeans(dataSet, k):
    '''
    2.KMeans算法,返回最终的质心坐标和每个点所在的簇
    '''

    m = np.shape(dataSet)[0]  # m表示数据集的长度(个数)
    clusterAssment = np.mat(np.zeros((m, 2)))

    centroids = kpp_centers(dataSet, k)  # 保存k个初始质心的坐标
    clusterChanged = True
    iterIndex = 1  # 迭代次数
    while clusterChanged:
        clusterChanged = False
        for i in range(m):
            minDist = np.inf;
            minIndex = -1
            for j in range(k):
                distJI = np.linalg.norm(np.array(centroids[j, :]) - np.array(dataSet[i, :]))
                if distJI < minDist:
                    minDist = distJI;
                    minIndex = j
            if clusterAssment[i, 0] != minIndex: clusterChanged = True
            clusterAssment[i, :] = minIndex, minDist ** 2
            print("第%d次迭代后%d个质心的坐标:\n%s" % (iterIndex, k, centroids))  # 第一次迭代的质心坐标就是初始的质心坐标
            iterIndex += 1
        for cent in range(k):
            ptsInClust = dataSet[np.nonzero(clusterAssment[:, 0].A == cent)[0]]  # get all the point in this cluster
            centroids[cent, :] = np.mean(ptsInClust, axis=0)
    iter = (iterIndex - 1) / dataSet.shape[0]
    return centroids, clusterAssment, iter

其中最重要的函数即寻找质心的函数kpp_centers(data_set, k)。这个 Python 函数 kpp_centers 实现了 k-means++ 算法中的质心初始化步骤。k-means++ 是一种用于 k-means 聚类算法的改进方法,它能够提高聚类的质量并减少迭代次数。

函数接受两个参数:

  • data_set: 包含数据点的 NumPy 矩阵。
  • k: 需要初始化的质心数量。

函数的主要逻辑如下:

  1. 转换数据集:
  • 使用 np.matrix.tolist(data_set) 将输入的 NumPy 矩阵转换为列表形式,以便于后续处理。
  • 初始化质心列表:
    • 创建一个空列表 cluster_centers 用于存储选中的质心。
    • 从数据集中随机选择一个数据点作为第一个质心,并将其添加到 cluster_centers 列表中。
  • 计算每个数据点到最近质心的距离:
    • 初始化一个长度与数据集相同且所有元素均为 0 的列表 d,用于存储每个数据点到最近质心的距离。
    • 使用循环遍历数据集中的每个数据点,并调用 get_closest_dist(point, cluster_centers) 函数来计算距离。
    • 这里假设 get_closest_dist 函数已经定义好,它接收一个数据点和质心列表,返回该点到这些质心中最近的那个质心的距离。
  • 选择剩余的质心:
    • 使用循环选择剩余的 k-1 个质心。
    • 对于每个可能的质心,根据其到当前已选质心的距离来确定其被选中的概率。这里使用的是轮盘赌选择法。
    • 计算 d 中所有距离的总和 total。
    • 生成一个介于 0 和 total 之间的随机数,用于确定下一个质心。
    • 通过减去每个数据点的距离值 di 并检查结果是否大于 0 来确定下一个质心。当结果小于等于 0 时,当前数据点就被选作下一个质心。
  • 返回质心列表:
    • 最后,将 cluster_centers 列表转换回 NumPy 矩阵形式并返回。

    这个函数实现了一个简单而有效的质心初始化过程,使得初始质心能够在数据空间中分散开来,从而避免了 k-means 算法对初始质心的选择过于敏感的问题。

    通过一个简单的例子来说明 total *= random.random() 在 k-means++ 质心初始化中的作用。假设我们有一个二维数据集,其中包含以下五个数据点:

    • P1(1, 1)
    • P2(2, 2)
    • P3(10, 10)
    • P4(11, 11)
    • P5(12, 12)

    我们的目标是使用 k-means 算法将这些点聚类为两个簇。为了开始这个过程,我们需要初始化两个质心。k-means++ 提供了一种方法来选择初始质心,使得它们尽可能地分散。

    1. 随机选择第一个质心:
    • 我们随机选择一个数据点作为第一个质心 C1。假设我们选择了 P1(1, 1) 作为 C1。
  • 计算每个点到最近质心的距离:
    • 对于剩下的四个点 (P2, P3, P4, P5),我们需要计算它们与 C1 的欧几里得距离。
    • d(P2, C1) = √((2-1)² + (2-1)²) = √2 ≈ 1.41
    • d(P3, C1) = √((10-1)² + (10-1)²) = √162 ≈ 12.73
    • d(P4, C1) = √((11-1)² + (11-1)²) = √200 ≈ 14.14
    • d(P5, C1) = √((12-1)² + (12-1)²) = √242 ≈ 15.55
  • 计算总距离 (total):
    • 将所有距离加起来得到总距离 total。
    • total = d(P2, C1) + d(P3, C1) + d(P4, C    - total ≈ 1.41 + 12.73 + 14.14 + 15.55 ≈ 43.83
  • 生成随机数 (total *= random.random()):
    • 假设 random.random() 返回了一个介于 0 和 1 之间的随机数,比如 0.6。
    • total *= 0.6
    • 新的 total ≈ 43.83 * 0.6 ≈ 26.3
  • 选择第二个质心:
    • 我们需要从剩下的点中选择一个新的质心。我们将从 total 中减去每个点的距离,直到 total 变为负数或 0。
    • total -= d(P2, C1) ≈ 26.3 - 1.41 ≈ 24.89
    • total -= d(P3, C1) ≈ 24.89 - 12.73 ≈ 12.16
    • total -= d(P4, C1) ≈ 12.16 - 14.14 ≈ -1.98
    • 当 total 变为负数时,我们停止,并选择最后一个减去距离的点作为新的质心。在这种情况下,我们选择了 P4(11, 11) 作为第二个质心 C2。

    通过这种方式,k-means++ 确保了初始质心之间的距离较大,从而可以更好地代表数据集的不同部分。这种方法可以显著减少 k-means 算法陷入局部最优解的风险。


    🏴‍☠️宝藏级🏴‍☠️ 原创公众号『数据STUDIO』内容超级硬核。公众号以Python为核心语言,垂直于数据科学领域,包括可戳👉 PythonMySQL数据分析数据可视化机器学习与数据挖掘爬虫 等,从入门到进阶!

    长按👇关注- 数据STUDIO -设为星标,干货速递

    数据STUDIO
    点击领取《Python学习手册》,后台回复「福利」获取。『数据STUDIO』专注于数据科学原创文章分享,内容以 Python 为核心语言,涵盖机器学习、数据分析、可视化、MySQL等领域干货知识总结及实战项目。
     最新文章