K-Means算法详解
一、数学公式
K-Means的核心是最小化簇内平方误差和(Within-Cluster Sum of Squares, WCSS),目标函数定义如下:
设样本集为 X={x1,x2,...,xn}X = \{x_1, x_2, ..., x_n\}X={x1,x2,...,xn}(其中 xi∈Rdx_i \in \mathbb{R}^dxi∈Rd 为d维特征向量),聚类目标是将样本划分为 kkk 个簇 C1,C2,...,CkC_1, C_2, ..., C_kC1,C2,...,Ck,每个簇的中心为 μj∈Rd\mu_j \in \mathbb{R}^dμj∈Rd(j=1,2,...,kj=1,2,...,kj=1,2,...,k)。
目标函数:
J=∑j=1k∑xi∈Cj∥xi−μj∥2 J = \sum_{j=1}^k \sum_{x_i \in C_j} \| x_i - \mu_j \|^2 J=j=1∑kxi∈Cj∑∥xi−μj∥2
其中 ∥xi−μj∥2\| x_i - \mu_j \|^2∥xi−μj∥2 是样本 xix_ixi 与簇中心 μj\mu_jμj 的欧氏距离平方(简化计算,与欧氏距离最小化等价)。
二、推导过程
K-Means通过交替优化 (类似EM算法)求解目标函数 JJJ,分为两个核心步骤:
1. 分配步骤(E步:确定簇归属)
固定簇中心 μ1,...,μk\mu_1, ..., \mu_kμ1,...,μk,为每个样本 xix_ixi 分配到距离最近的簇 ,即:
Cj={xi∣∥xi−μj∥2≤∥xi−μl∥2,∀l≠j} C_j = \left\{ x_i \mid \| x_i - \mu_j \|^2 \leq \| x_i - \mu_l \|^2, \forall l \neq j \right\} Cj={xi∣∥xi−μj∥2≤∥xi−μl∥2,∀l=j}
合理性 :对固定的 μj\mu_jμj,目标函数 JJJ 是各样本距离平方的总和。为最小化 JJJ,每个 xix_ixi 必须属于能让其距离平方项最小的簇,因此上述分配是最优的。
2. 更新步骤(M步:更新簇中心)
固定簇分配 C1,...,CkC_1, ..., C_kC1,...,Ck,求解最优簇中心 μj\mu_jμj 使 JJJ 最小。
对 JJJ 关于 μj\mu_jμj 求导并令导数为0:
- 展开目标函数:J=∑j=1k∑xi∈Cj(xi−μj)T(xi−μj)J = \sum_{j=1}^k \sum_{x_i \in C_j} (x_i - \mu_j)^T (x_i - \mu_j)J=∑j=1k∑xi∈Cj(xi−μj)T(xi−μj)
- 展开平方项:(xi−μj)T(xi−μj)=∥xi∥2−2μjTxi+∥μj∥2(x_i - \mu_j)^T (x_i - \mu_j) = \| x_i \|^2 - 2\mu_j^T x_i + \| \mu_j \|^2(xi−μj)T(xi−μj)=∥xi∥2−2μjTxi+∥μj∥2
- 对 μj\mu_jμj 求偏导:∂J∂μj=∑xi∈Cj(−2xi+2μj)\frac{\partial J}{\partial \mu_j} = \sum_{x_i \in C_j} (-2x_i + 2\mu_j)∂μj∂J=∑xi∈Cj(−2xi+2μj)
- 令导数为0:∑xi∈Cj(−xi+μj)=0 ⟹ μj⋅∣Cj∣=∑xi∈Cjxi\sum_{x_i \in C_j} (-x_i + \mu_j) = 0 \implies \mu_j \cdot |C_j| = \sum_{x_i \in C_j} x_i∑xi∈Cj(−xi+μj)=0⟹μj⋅∣Cj∣=∑xi∈Cjxi
最终得最优簇中心为簇内样本的均值 :
μj=1∣Cj∣∑xi∈Cjxi \mu_j = \frac{1}{|C_j|} \sum_{x_i \in C_j} x_i μj=∣Cj∣1xi∈Cj∑xi
3. 收敛条件
重复"分配-更新"步骤,直到簇中心变化小于阈值(如 ∥μjnew−μjold∥2<ϵ\| \mu_j^{\text{new}} - \mu_j^{\text{old}} \|^2 < \epsilon∥μjnew−μjold∥2<ϵ)或迭代次数达到上限。
三、Python代码实现(手动实现)
python
import numpy as np
import matplotlib.pyplot as plt
class KMeans:
def __init__(self, n_clusters=2, max_iter=100, tol=1e-4):
self.n_clusters = n_clusters # 簇数量
self.max_iter = max_iter # 最大迭代次数
self.tol = tol # 收敛阈值
self.centers = None # 簇中心
self.labels = None # 样本标签
def fit(self, X):
n_samples, n_features = X.shape
# 1. 初始化簇中心(随机选择k个样本)
self.centers = X[np.random.choice(n_samples, self.n_clusters, replace=False)]
for _ in range(self.max_iter):
# 2. 分配步骤:计算每个样本到中心的距离,分配到最近簇
# 距离平方:(n_samples, n_clusters)
distances = np.sum((X[:, np.newaxis] - self.centers) **2, axis=2)
self.labels = np.argmin(distances, axis=1) # 每个样本的簇标签
# 3. 更新步骤:计算新簇中心(均值)
new_centers = np.array([X[self.labels == j].mean(axis=0) for j in range(self.n_clusters)])
# 4. 检查收敛
center_shift = np.sum(np.linalg.norm(new_centers - self.centers, axis=1)** 2)
if center_shift < self.tol:
break
self.centers = new_centers
# 测试代码
if __name__ == "__main__":
# 生成模拟数据
np.random.seed(42)
X1 = np.random.normal(loc=[2, 2], scale=0.5, size=(100, 2))
X2 = np.random.normal(loc=[5, 5], scale=0.5, size=(100, 2))
X3 = np.random.normal(loc=[8, 2], scale=0.5, size=(100, 2))
X = np.vstack([X1, X2, X3])
# 聚类
kmeans = KMeans(n_clusters=3)
kmeans.fit(X)
# 可视化
plt.scatter(X[:, 0], X[:, 1], c=kmeans.labels, cmap='viridis', alpha=0.7)
plt.scatter(kmeans.centers[:, 0], kmeans.centers[:, 1], c='red', s=200, marker='X', label='Centers')
plt.legend()
plt.show()
四、sklearn实现
sklearn的sklearn.cluster.KMeans
封装了高效实现(支持并行计算、多种初始化方式等)。
python
from sklearn.cluster import KMeans
import numpy as np
import matplotlib.pyplot as plt
# 生成模拟数据(同上)
np.random.seed(42)
X1 = np.random.normal(loc=[2, 2], scale=0.5, size=(100, 2))
X2 = np.random.normal(loc=[5, 5], scale=0.5, size=(100, 2))
X3 = np.random.normal(loc=[8, 2], scale=0.5, size=(100, 2))
X = np.vstack([X1, X2, X3])
# 初始化并训练模型
kmeans_sklearn = KMeans(n_clusters=3, random_state=42) # random_state确保结果可复现
kmeans_sklearn.fit(X)
# 结果
labels = kmeans_sklearn.labels_ # 聚类标签
centers = kmeans_sklearn.cluster_centers_ # 簇中心
inertia = kmeans_sklearn.inertia_ # 最终WCSS值
# 可视化
plt.scatter(X[:, 0], X[:, 1], c=labels, cmap='viridis', alpha=0.7)
plt.scatter(centers[:, 0], centers[:, 1], c='red', s=200, marker='X', label='Centers')
plt.legend()
plt.show()
总结
K-Means通过交替优化簇分配和簇中心,最小化簇内平方误差和,实现高效聚类。手动实现可理解核心逻辑,sklearn版本则适合工程应用(优化了初始化、空簇处理等细节)。