今天云朵君和大家一起学习最简单的聚类算法K-Means算法中启发式选择初始聚类中心的方法,深入探究K-Means++算法中的细节!
一、K-Means
1. 概念
k-means algorithm算法:
K-均值(K-Means)属于聚类算法,之所以称为K-均值是因为它把n个样本根据它们的属性分为k个簇(k < n),且每个簇的中心采用簇中所含值的均值计算而成。聚类:
一种无监督的学习,事先不知道类别,自动将相似的对象归到同一簇中。
聚类作为一种典型的数据挖掘方法,一直以来都是人工智能领域的一个研究热点,被广泛地应用于人脸图像识别、股票分析预测、搜索引擎、生物信息学等领域中。
2. K-Means算法思想
随机选择K个中心值 根据样本数据各点与中心点的距离来进行归簇 通过各个簇的均值,更新为每个簇的中心值 重复2、3,直至聚类中心的位置不再变化
3. K-Means特点
适用数据类型:适用于数值型数据 优点:算法原理简单,容易实现。需要调节的超参数就是一个k。 缺点:在 K-Means算法中 K需要事先确定,这个 K 值的选定有时候是比较难确定。
可能收敛到局部最小值;
在大规模数据集上收敛速度较慢;
二、K-Means++
1.概念
k-means++ algorithm算法:
由于 K-means 算法的分类结果会受到初始点的选取而有所区别,因此有提出这种算法的改进: K-Means++ 。
2.K-Means++算法思想
其实这个算法是对初始点的选择有改进,其他步骤都一样。初始质心选取的基本思路就是,初始的聚类中心之间的相互距离要尽可能的远。
随机选取一个样本作为第一个聚类中心 c1; 计算每个样本与当前已有类聚中心最短距离(即与最近一个聚类中心的距离),用 D(x)表示;
这个值越大,表示被选取作为聚类中心的概率较大;
最后,用轮盘法选出下一个聚类中心ci;重复步骤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: 需要初始化的质心数量。
函数的主要逻辑如下:
转换数据集:
使用 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++ 提供了一种方法来选择初始质心,使得它们尽可能地分散。
随机选择第一个质心:
我们随机选择一个数据点作为第一个质心 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 = d(P2, C1) + d(P3, C1) + d(P4, C - total ≈ 1.41 + 12.73 + 14.14 + 15.55 ≈ 43.83
假设 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为核心语言,垂直于数据科学领域,包括可戳👉 Python|MySQL|数据分析|数据可视化|机器学习与数据挖掘|爬虫 等,从入门到进阶!
长按👇关注- 数据STUDIO -设为星标,干货速递