
老李经营着一家大型餐厅,随着生意日渐兴隆,他遇到了一个甜蜜的烦恼:顾客越来越多,如何合理安排座位成为了一个大问题!
顾客随意选择座位,结果导致一家人被分散在不同的桌子上,孩子们因为不能靠近父母而哭闹。
老李决定采取措施,让相似类型的顾客坐在同一区域。他观察到:
为了进一步提升服务质量,老李在每个区域设置了一个“服务员中心点”,专门负责该区域的顾客。这样不仅能够迅速响应顾客的需求,还能让服务员更加熟悉自己区域的常客。
K均值聚类(K-Means Clustering)是一种无监督学习算法,由Stuart Lloyd于1957年提出。其目的是将n个数据点划分为k个簇(Cluster),确保同一个簇内的数据点尽可能相似,而不同簇之间的数据点尽可能不同。
K均值聚类就像是一个数据整理专家,其工作流程包括以下几个步骤:
K值表示你想将数据分成多少个组,类似于餐厅老板需要决定将餐厅分成多少个用餐区域。K值需要提前指定,是算法的一个超参数。
每个簇的“中心点”,代表该簇的平均位置,类似于每个用餐区域的“服务台”位置。计算公式为簇内所有点的坐标平均值。
通常使用欧式距离(直线距离)来衡量数据点之间的相似性,类似于测量顾客到服务台的实际步行距离。
距离 = √[(x?-x?)? + (y?-y?)?]
假设你是一家披萨店的老板,希望更好地了解客户群体。你收集了客户的两个关键数据:月消费金额(横轴)和到店频次(纵轴)。
客户A:消费200元,到店2次
客户B:消费50元,到店8次
客户C:消费180元,到店3次
客户D:消费60元,到店9次
客户E:消费220元,到店1次
客户F:消费40元,到店10次
将这些客户绘制在坐标系中,可以发现自然的分组:
通过K均值聚类,你可以发现三类客户:
类似于餐厅老板先随机指定几个“临时服务区中心”。
# 伪代码示例
import numpy as np
def initialize_centroids(X, k):
"""随机初始化k个质心"""
n_samples = X.shape[0]
random_indices = np.random.choice(n_samples, k, replace=False)
centroids = X[random_indices]
return centroids
类似于每个顾客选择离自己最近的服务台就餐。
def assign_clusters(X, centroids):
"""为每个数据点分配最近的质心"""
distances = np.sqrt(((X - centroids[:, np.newaxis])**2).sum(axis=2))
cluster_labels = np.argmin(distances, axis=0)
return cluster_labels
类似于根据每个区域实际就餐的顾客分布,重新调整服务台位置。
def update_centroids(X, cluster_labels, k):
"""更新质心位置"""
new_centroids = np.array([X[cluster_labels == i].mean(axis=0)
for i in range(k)])
return new_centroids
重复第二步和第三步,直到满足停止条件,例如质心不再变化、变化小于某个阈值或达到最大迭代次数。目标函数收敛,即WCSS(Within-Cluster Sum of Squares)不再显著改善。
WCSS(簇内平方和)是K均值聚类的目标函数,计算公式如下:
WCSS = Σ(i=1 to k) Σ(x∈Ci) ||x - μi||?
其中,k表示簇的数量,Ci表示第i个簇,x表示簇Ci中的数据点,μi表示第i个簇的质心,||x - μi||表示数据点到质心的欧式距离平方。
想象每个簇是一个“朋友圈”:
算法的目标是:最小化所有簇的WCSS总和!
原理:随着K值增加,WCSS会逐渐减小,但减小的幅度会经历一个“拐点”。操作步骤包括:
def elbow_method(X, max_k=10):
"""肘部法则选择最优K值"""
wcss_list = []
for k in range(1, max_k + 1):
kmeans = KMeans(n_clusters=k, random_state=42)
kmeans.fit(X)
wcss_list.append(kmeans.inertia_)
# 绘制肘部曲线
plt.figure(figfigsize=(10, 6))
plt.plot(range(1, max_k + 1), wcss_list, 'bo-')
plt.xlabel('K值')
plt.ylabel('WCSS')
plt.title('肘部法则选择最优K值')
plt.grid(True, alpha=0.3)
plt.show()
return wcss_list
原理:衡量每个点的聚类质量,考虑两个因素:a(i)表示与同簇其他点的平均距离(紧密度),b(i)表示与最近簇所有点的平均距离(分离度)。计算公式如下:
s(i) = [b(i) - a(i)] / max{a(i), b(i)}
解释:
业务背景:电商公司希望更好地了解客户群体。应用方式包括:
原理:将相似颜色的像素聚类,用簇中心颜色替代原像素颜色。效果:大幅减少颜色数量,实现图像压缩。
原理: 正常的数据通常会形成密集的集群,而异常数据则远离这些集群的中心。
应用: 该技术广泛应用于信用卡欺诈检测、网络入侵监测以及设备故障预测等领域。
应用背景: 自动分类新闻网站的内容、对搜索引擎的结果进行分组、整理学术论文等。
技术要点:
基础K-means实现
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
class SimpleKMeans:
"""从零实现的K-means聚类算法"""
def __init__(self, n_clusters=3, max_iter=300, tol=1e-4, random_state=None):
self.n_clusters = n_clusters
self.max_iter = max_iter
self.tol = tol
self.random_state = random_state
def _initialize_centroids(self, X):
"""随机初始化质心"""
if self.random_state:
np.random.seed(self.random_state)
n_samples = X.shape[0]
random_indices = np.random.choice(n_samples, self.n_clusters, replace=False)
return X[random_indices].copy()
def _calculate_distances(self, X, centroids):
"""计算所有点到所有质心的距离"""
distances = np.zeros((X.shape[0], self.n_clusters))
for i, centroid in enumerate(centroids):
distances[:, i] = np.linalg.norm(X - centroid, axis=1)
return distances
def _assign_clusters(self, distances):
"""为每个点分配最近的质心"""
return np.argmin(distances, axis=0)
def _update_centroids(self, X, labels):
"""更新质心位置"""
new_centroids = np.zeros((self.n_clusters, X.shape[1]))
for i in range(self.n_clusters):
cluster_points = X[labels == i]
if len(cluster_points) > 0:
new_centroids[i] = cluster_points.mean(axis=0)
else:
# 处理空簇情况
new_centroids[i] = X[np.random.choice(X.shape[0])]
return new_centroids
def fit(self, X):
"""训练K-means模型"""
# 初始化质心
self.centroids_ = self._initialize_centroids(X)
for iteration in range(self.max_iter):
# 存储旧质心
old_centroids = self.centroids_.copy()
# 计算距离并分配簇
distances = self._calculate_distances(X, self.centroids_)
self.labels_ = self._assign_clusters(distances)
# 更新质心
self.centroids_ = self._update_centroids(X, self.labels_)
# 检查收敛
centroid_shift = np.linalg.norm(self.centroids_ - old_centroids)
if centroid_shift < self.tol:
print(f"算法在第{iteration + 1}次迭代后收敛")
break
# 计算最终的WCSS
distances = self._calculate_distances(X, self.centroids_)
self.inertia_ = np.sum(np.min(distances, axis=1) ** 2)
return self
# 生成测试数据
X, true_labels = make_blobs(n_samples=300, centers=4, cluster_std=1.0,
center_box=(-10.0, 10.0), random_state=42)
# 数据标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 训练模型
kmeans = SimpleKMeans(n_clusters=4, random_state=42)
kmeans.fit(X_scaled)
print(f"WCSS: {kmeans.inertia_:.2f}")
print(f"质心位置:\n{kmeans.centroids_}")
K-means就像是数据世界的“整理专家”:它无需依赖标签,能够自主发现数据中的模式;它追求内在的有序性,让相似的数据聚集;它在简洁与效率之间找到平衡,用中心点来代表整个群体。
K-means表现优秀的场景:
K-means不适用的场景:
K-means聚类算法教导我们:简单的力量是巨大的——复杂的模式识别可以通过简单的迭代过程实现;中心点的思想使我们能够用少量的信息代表大量的数据;即使没有标签,数据本身也能讲述它的故事;在现实世界中,足够好的解决方案往往就是最佳的。在当前追求复杂算法的趋势下,K-means提醒我们,简洁优雅的方法往往最具威力!
希望这篇关于K-means聚类的详细解析能够帮助你更深入地理解这一经典算法。请记住,最好的聚类算法并不是最复杂的,而是最能揭示数据背后隐藏故事的算法!
扫码加好友,拉您进群



收藏
