K-Means 聚类实战:Python 手写数字识别与 3 维可视化
·
K-Means 聚类实战:Python 手写数字识别与 3 维可视化
当我们需要从无标签数据中发现隐藏结构时,K-Means 聚类算法就像一位善于发现群体共性的侦探。今天我们将用 MNIST 手写数字数据集,展示如何从原始像素到三维空间中的清晰分类。这个实战项目不仅能帮你理解聚类算法的核心思想,还能掌握数据降维与可视化的关键技巧。
1. 环境准备与数据加载
在开始之前,确保你的 Python 环境已安装以下关键库:
import numpy as np
from sklearn.datasets import fetch_openml
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from sklearn.metrics import silhouette_score
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline
加载 MNIST 数据集并做基础预处理:
mnist = fetch_openml('mnist_784', version=1, as_frame=False)
X = mnist.data / 255.0 # 像素值归一化
y = mnist.target.astype(int)
print(f"数据集形状: {X.shape}") # (70000, 784)
print(f"标签示例: {y[:10]}") # [5 0 4 ... 4 5 6]
注意:MNIST 每张图片是 28x28 像素,展平后成为 784 维向量。归一化到 [0,1] 区间有助于提升聚类效果。
2. K-Means 模型训练与 K 值选择
确定最佳聚类数量是 K-Means 成功的关键。我们使用肘部法则和轮廓系数双重验证:
# 肘部法则计算不同K值的惯性
inertias = []
silhouettes = []
K_range = range(5, 15)
for k in K_range:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
kmeans.fit(X)
inertias.append(kmeans.inertia_)
# 轮廓系数评估(抽样计算避免内存问题)
sample_idx = np.random.choice(len(X), 5000, replace=False)
silhouette = silhouette_score(X[sample_idx], kmeans.predict(X[sample_idx]))
silhouettes.append(silhouette)
# 绘制双指标曲线
plt.figure(figsize=(12,4))
plt.subplot(121)
plt.plot(K_range, inertias, 'bo-')
plt.xlabel('K值'), plt.ylabel('惯性值')
plt.title('肘部法则')
plt.subplot(122)
plt.plot(K_range, silhouettes, 'r*-')
plt.xlabel('K值'), plt.ylabel('轮廓系数')
plt.title('轮廓分析')
plt.tight_layout()
典型输出会显示在 K=10 附近出现拐点(对应 10 个数字类别),同时轮廓系数在 K=10 时达到峰值。确定 K=10 后训练最终模型:
optimal_k = 10
kmeans = KMeans(n_clusters=optimal_k, random_state=42, n_init=10)
cluster_labels = kmeans.fit_predict(X)
3. 高维数据可视化技巧
784 维数据难以直接观察,我们使用 PCA 进行降维:
pca = PCA(n_components=3)
X_3d = pca.fit_transform(X)
# 创建3D散点图
fig = plt.figure(figsize=(10,8))
ax = fig.add_subplot(111, projection='3d')
scatter = ax.scatter(X_3d[:,0], X_3d[:,1], X_3d[:,2],
c=cluster_labels, cmap='tab10',
alpha=0.5, s=5)
# 添加颜色条和图例
plt.colorbar(scatter, ax=ax, label='聚类标签')
ax.set_xlabel('PCA 1'), ax.set_ylabel('PCA 2'), ax.set_zlabel('PCA 3')
plt.title('MNIST 数据的3D聚类可视化')
为了更直观理解聚类效果,我们可以查看每个簇的中心图像:
fig, axes = plt.subplots(2, 5, figsize=(12,5))
centers = kmeans.cluster_centers_
for i, ax in enumerate(axes.flat):
ax.imshow(centers[i].reshape(28,28), cmap='gray')
ax.set_title(f'簇 {i} 中心')
ax.axis('off')
4. 聚类结果评估与调优
评估聚类质量需要综合多个指标:
| 评估指标 | 计算公式 | 理想值 |
|---|---|---|
| 惯性 (Inertia) | Σ(样本到其簇中心的距离平方) | 越小越好 |
| 轮廓系数 | (b-a)/max(a,b) (a:簇内距离, b:最近簇距离) | [-1,1] 接近1最佳 |
| 纯度 (Purity) | Σ(每个簇中最多数类别的样本数)/总样本数 | 接近1最佳 |
计算这些指标:
from sklearn.metrics import confusion_matrix
# 计算纯度
conf_mat = confusion_matrix(y, cluster_labels)
purity = np.sum(np.amax(conf_mat, axis=0)) / len(y)
print(f"惯性值: {kmeans.inertia_:.2f}")
print(f"轮廓系数: {silhouette_score(X, cluster_labels, sample_size=5000):.3f}")
print(f"聚类纯度: {purity:.3f}")
如果发现某些数字被错误聚类(如4和9混淆),可以尝试以下优化策略:
- 特征工程 :使用方向梯度直方图(HOG)替代原始像素
- 算法改进 :采用K-Means++初始化或MiniBatch K-Means
- 后处理 :合并相似簇或拆分异质簇
5. 完整项目集成与扩展
将整个流程封装为可复用的Jupyter notebook,添加以下实用功能:
def plot_cluster_samples(X, labels, n_samples=5):
"""展示每个簇的典型样本"""
unique_labels = np.unique(labels)
plt.figure(figsize=(12,8))
for i, label in enumerate(unique_labels):
samples = X[labels == label][:n_samples]
for j, sample in enumerate(samples):
plt.subplot(len(unique_labels), n_samples, i*n_samples + j + 1)
plt.imshow(sample.reshape(28,28), cmap='gray')
plt.axis('off')
if j == 0:
plt.ylabel(f'簇 {label}')
# 调用示例
plot_cluster_samples(X, cluster_labels)
对于想要进一步探索的开发者,可以考虑以下扩展方向:
- 结合t-SNE进行非线性降维可视化
- 实现二分K-Means算法解决局部最优问题
- 将聚类结果作为特征输入到监督学习模型
这个项目展示了如何将理论算法转化为实际解决方案。当你在三维空间中看到那些逐渐分离的数字簇时,就能直观感受到无监督学习发现数据内在结构的魔力。
更多推荐



所有评论(0)