k-means

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版本则适合工程应用(优化了初始化、空簇处理等细节)。

相关推荐
shepherd1111 分钟前
一文带你掌握 LLM、Token、Context、Prompt、RAG、MCP、Skill、Agent 等 AI 核心概念
人工智能·后端·ai编程
小林ixn18 分钟前
MCP 保姆级入门指南:AI 的“万能充电口”到底怎么玩?
人工智能
转转技术团队2 小时前
没有测试的核心代码,怎么交给 AI 重构
人工智能
爱读源码的大都督3 小时前
Claude Code源码分析(三):为什么系统提示词中需要有tools呢?
前端·人工智能·后端
半个落月4 小时前
LLM如何预测下一个Token?一文拆解Transformer核心流程
人工智能
触底反弹4 小时前
🔥 2026 年爆火的 Harness Engineering 到底是什么?从原理到实战一文讲透
javascript·人工智能·程序员
user4465117917914 小时前
源码深读 XAgent:6 个 Agent 怎么分工?工具失败不崩、死循环怎么防?
人工智能
魏祖潇4 小时前
SDD 完整指南——Spec 端打底、Story 端交付、留白区
人工智能·后端
常丛丛4 小时前
5.9 式输出:实时查看 LangGraph Agent 思考过程
人工智能