6
0

基础篇:Scikit-learn库求解聚类分析和主成分分析模型方法

2026-05-13
2026-05-13
基础篇:Scikit-learn库求解聚类分析和主成分分析模型方法

前言

关于本教程

本教程是面向有一定 Python 编程基础、希望掌握无监督学习方法的读者的 Scikit-learn 聚类与降维教程。共九章,从聚类分析的三大经典算法(K-Means、DBSCAN、层次聚类)出发,系统讲解主成分分析(PCA)与 Kernel PCA,最后通过综合案例展示"先降维、再聚类"的完整 Pipeline。

Scikit-learn 的核心优势在于算法工程化——它提供统一的 fit / transform / predict 接口、丰富的评估指标和可视化工具,帮助研究者回答"数据有哪些内在结构"这类问题。

目标读者

  • 有一定 Python 编程基础的数据分析师、研究人员、学生

  • 了解 numpy 数组操作和 pandas DataFrame 基本使用

  • 熟悉 matplotlib 基本绘图

前置知识

阅读本教程前,建议具备以下基础:

  • 线性代数:了解向量、矩阵乘法、特征值(不需要深入推导,Scikit-learn 会处理大部分运算)

  • Python:能使用 numpy 进行数组运算、用 pandas 读取数据、调用函数

  • Scikit-learn 基础:了解 fit() / transform() / predict() 的基本用法

如果你完全不了解聚类或降维的基本概念(如"簇"、"质心"、"方差"),建议先阅读第0章的导论部分。

环境要求

本教程所有代码基于以下环境编写和验证:

软件包

最低版本

推荐版本

Python

3.9

3.10+

scikit-learn

1.2.0

1.4+

numpy

1.24

1.26+

pandas

2.0

2.1+

matplotlib

3.7

3.8+

scipy

1.10

1.12+

安装命令:

pip install scikit-learn numpy pandas matplotlib scipy

验证安装:

import sklearn
print(sklearn.__version__)  # 应输出 1.x

本教程结构

章节

标题

核心知识点

前言

本教程定位、读者指南

第 0 章

导论

无监督学习概述、Scikit-learn 数据集预览

第 1 章

K-Means 聚类

原理、API、肘部法、轮廓系数、非球形簇局限

第 2 章

DBSCAN 聚类

密度聚类、εε 调参、噪声识别、与 K-Means 对比

第 3 章

层次聚类

树状图、四种链接方法、距离度量、切割高度

第 4 章

聚类算法对比

三种算法 × 三种数据集矩阵对比、质量指标、选择指南

第 5 章

PCA 主成分分析

原理、碎石图、载荷系数、图像压缩、标准化

第 6 章

Kernel PCA

核技巧、RBF/Poly 核、瑞士卷展开、同心圆分离

第 7 章

PCA + 聚类实战

Iris 降维聚类、手写数字聚类、Pipeline 串联

第 8 章

总结与速查表

算法全景、API 速查、决策树、常见陷阱

建议按顺序阅读。第0-1章是基础,第2-4章为聚类专题,第5-6章为降维专题,第7章综合实战,第8章为全局回顾。

代码约定

  • 本教程所有代码使用 from sklearn.cluster import KMeans 等直接导入方式

  • 数据变量通常命名为 X(特征矩阵)和 y(标签,无监督学习中仅用于评估)

  • 模型对象通常命名为 kmdbscanpca 等缩写

  • 所有图表均使用中文标签,使用 plt.rcParams['font.sans-serif'] = ['SimHei'] 设置

  • 代码块为可独立运行的完整代码段

公式约定

教程中使用 LaTeX 格式书写数学公式:

  • X 表示 n×d 特征矩阵

  • μi 表示第 i 个簇的质心

  • λi 表示第 i 个主成分的特征值

  • K(xi,xj) 表示核函数

数据来源

本教程使用的所有数据集均为 Scikit-learn 内置数据集,无需额外下载:

  • make_blobs:人工生成的球状簇(第1、2、3、4、7章)

  • make_moons:半月形簇(第1、2、4章)

  • make_circles:同心圆簇(第4、6章)

  • make_swiss_roll:瑞士卷流形(第6章)

  • load_iris:鸢尾花数据(第5、7章)

  • load_digits:手写数字图像(第5、7章)

这些数据集可通过 sklearn.datasets 模块直接加载。

如何学习

建议的学习方式:

  1. 先读章节的文字部分,理解概念和方法

  2. 运行对应的 _gen.py 代码,观察生成的图表

  3. 修改代码中的参数(如更换数据集、改变簇数或 ε),观察结果变化

  4. 尝试用自己的数据,套用本章的模板

Scikit-learn 的学习曲线相对平缓,因为它的 API 高度统一(fit()transform()predict())。一旦掌握了一个算法的使用模式,后续章节的模式是高度一致的。

反馈与勘误

如果你在阅读过程中发现代码运行错误、结论有误、或有任何建议,欢迎反馈。

第0章 导论

本章介绍无监督学习的基本概念,以及 Scikit-learn 中聚类与降维工具的全局设置。

0.1 无监督学习概述

有监督学习(给定特征 X 和目标变量 y,学习映射 f:Xy)不同,无监督学习只有特征矩阵 X,没有标签 y。它的目标是从数据的内在结构中发现模式:

任务类型

目标

典型算法

聚类分析

将相似样本分为若干组

K-Means、DBSCAN、层次聚类

降维

用更少特征保留主要信息

PCA、Kernel PCA、t-SNE

异常检测

识别偏离正常模式的样本

Isolation Forest、LOF

密度估计

估计数据的概率分布

Gaussian Mixture

0.2 聚类的直观理解

聚类(Clustering)的核心思想是簇内相似度高、簇间相似度低。不同算法对"相似度"的定义不同:

ch00_clustering_concept.png

左图展示了 make_blobs 生成的规则簇——4 个高斯分布的簇,各簇中心远离、边界清晰。这类数据适合 K-Means 等基于质心的算法。

右图展示了 make_moons 生成的非规则簇——两个半月形的簇,簇之间没有明确的线性边界。这类数据需要 DBSCAN 或谱聚类等基于密度/流形的算法。

0.3 PCA 的直观理解

主成分分析(PCA)的目标是找到一个低维子空间,使得数据在该子空间上的投影方差最大化

ch00_pca_concept.png

左图展示了原始二维数据(蓝色散点)以及两个主成分方向(红色箭头为 PC1,绿色箭头为 PC2)。PC1 方向捕捉了数据中最大的方差(86.8%),PC2 方向捕捉了剩余方差(13.2%)。

右图展示了将数据投影到 PC1 后的分布——将二维数据压缩到一维,仍保留了 86.8% 的信息。

PCA 的核心公式

  • 对中心化后的数据 X,计算协方差矩阵 C=1n1XTXC = \frac{1}{n-1} X^T X

  • C 做特征值分解:C=VΛVTC = V \Lambda V^T

  • 特征向量(V 的列)即为主成分方向,特征值(Λ 的对角元)的大小决定了各主成分的重要性

0.4 环境配置与中文字体

import matplotlib.pyplot as plt

# Windows 系统中文字体适配
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False  # 正常显示负号

如果你的系统不是 Windows:

  • macOS:使用 'PingFang SC''Heiti SC'

  • Linux:使用 'Noto Sans CJK SC''WenQuanYi Micro Hei'

0.5 Scikit-learn 数据集一览

本教程使用的内置数据集:

数据集函数

返回

用途

make_blobs

X,y(带标签的簇)

K-Means、层次聚类演示

make_moons

X,y(半月形簇)

DBSCAN 演示

make_circles

X,y(同心圆)

非线性算法对比

make_classification

X,y(分类任务)

综合案例

make_swiss_roll

X,y(瑞士卷流形)

Kernel PCA 演示

load_digits

手写数字图像

PCA 降维实战

load_iris

鸢尾花数据

PCA 降维 + 聚类综合案例

0.6 本节小结

  • 无监督学习无需标签,核心任务是发现数据内在结构

  • 聚类按"相似度"定义分为基于质心(K-Means)、基于密度(DBSCAN)、基于层次等类别

  • PCA 通过特征值分解找到方差最大的投影方向,实现降维

  • 本教程所有可视化使用 SimHei 中文字体,非 Windows 用户需替换对应字体

第1章 K-Means 聚类

K-Means(K-均值)是最经典的聚类算法之一。其核心思想是迭代优化质心,使每个样本到所属簇质心的距离最小化。

1.1 K-Means 算法原理

目标函数

K-Means 优化的目标函数(惯性/Inertia)为:

J=i=1kxCixμi2J = \sum_{i=1}^{k} \sum_{x \in C_i} \| x - \mu_i \|^2

其中 k 为簇数,Ci 为第 i 个簇,μi 为第 i 个簇的质心。

算法步骤

  1. 初始化:随机选择 k 个点作为初始质心

  2. 分配:将每个样本分配到最近的质心所属簇

  3. 更新:重新计算每个簇的质心(取簇内均值)

  4. 重复步骤 2-3,直到质心不再变化或达到最大迭代次数

关键特点

  • 需要预先指定簇数 k

  • 适合球状簇(各簇方差相近)

  • 异常值敏感(均值受极端值影响)

  • 初始质心敏感(多次运行结果可能不同)

1.2 Scikit-learn 基本用法

from sklearn.cluster import KMeans

# 创建 KMeans 模型
km = KMeans(n_clusters=4, random_state=42, n_init=10)

# 拟合数据
km.fit(X)

# 获取聚类标签
labels = km.labels_

# 获取质心坐标
centers = km.cluster_centers_

# 获取惯性值
inertia = km.inertia_

常用参数速查

参数

默认值

说明

n_clusters

8

簇数 k

init

'k-means++'

初始化策略

n_init

10

独立运行次数,取最优

max_iter

300

单次最大迭代次数

tol

1e-4

收敛阈值

random_state

None

随机种子

k-means++ 初始化:默认策略,通过概率分散选择初始质心,比完全随机更稳定,通常收敛更快。

1.3 簇数 k 的选择

方法一:肘部法(Elbow Method)

ch01_elbow.png

左图:肘部法

随着 k 增加,惯性(SSE)单调递减。当 k 等于真实簇数时,下降趋势明显变缓,形成"肘部"。本例中肘部出现在 k=4 处,与真实簇数一致。

右图:轮廓系数

轮廓系数衡量样本与其所属簇的紧密程度(范围 [−1,1]):

  • 接近 1:样本与所属簇紧密,与邻近簇远离

  • 接近 0:样本在两个簇的边界上

  • 接近 -1:样本可能被分错了簇

本例中轮廓系数在 k=4 处达到峰值,同样指示正确簇数。

肘部法代码示例

from sklearn.cluster import KMeans

k_range = range(1, 11)
inertias = []
for k in k_range:
    km = KMeans(n_clusters=k, random_state=42, n_init=10)
    km.fit(X)
    inertias.append(km.inertia_)

# 绘制肘部曲线
plt.plot(k_range, inertias, 'bo-')
plt.xlabel('簇数量 k')
plt.ylabel('惯性(SSE)')

方法二:轮廓系数分析

from sklearn.metrics import silhouette_score

k_range = range(2, 11)
sil_scores = []
for k in k_range:
    km = KMeans(n_clusters=k, random_state=42, n_init=10).fit(X)
    sil_scores.append(silhouette_score(X, km.labels_))

1.4 不同 k 值的聚类效果

ch01_kmeans_blobs.png
  • k=2(左):将 4 个真实簇合并为 2 组,粒度太粗

  • k=4(中):正确识别出 4 个簇,质心(黑叉)位于各簇中心

  • k=6(右):将真实簇进一步拆分,过度拟合

1.5 轮廓系数详细解读

ch01_silhouette.png

左图:k=4 的轮廓图

每个水平条代表一个簇内的样本,按轮廓系数从小到大排列。

  • 所有簇的样本轮廓系数均大于平均线(红色虚线,0.748)

  • 四个簇的轮廓分布宽度相近,表明各簇质量均衡

  • 平均轮廓系数 0.748,属于高质量聚类

右图:k=6 的轮廓图

  • 部分簇中出现轮廓系数低于平均线(0.508)的样本

  • 有的簇轮廓系数接近 0,说明这些样本处于簇边界,聚类质量下降

  • 平均轮廓系数从 0.748 降至 0.508,表明 k=6 不如 k=4

轮廓图解读规则

现象

含义

所有条都在红线右侧

聚类质量良好

某些条在红线左侧

该簇质量差,可能 k 选大了

各条宽度差异大

簇大小不均

平均轮廓系数 > 0.7

高质量聚类

平均轮廓系数 0.5~0.7

中等质量,可接受

平均轮廓系数 < 0.5

聚类效果不佳

1.6 K-Means 的局限性

ch01_kmeans_moons.png

K-Means 假设簇是凸形(球状)的,当面对非凸形状的数据时,表现不佳。

左图:K-Means 将两个半月形簇按上下分割(而非左右),导致大量错误分类(黑叉标记)。

右图:真实的半月形簇结构——K-Means 无法捕捉这种基于密度/流形的簇形态。

解决方案:对于非球形簇,应使用 DBSCAN(第2章)或谱聚类等算法。

1.7 实用技巧

数据标准化

K-Means 基于欧氏距离,不同量纲的特征会影响聚类结果。建议先标准化:

from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
km = KMeans(n_clusters=4, random_state=42, n_init=10).fit(X_scaled)

用已拟合模型预测新样本

# 训练模型
km = KMeans(n_clusters=4, random_state=42, n_init=10).fit(X_train)

# 预测新数据
labels_new = km.predict(X_test)

与 PCA 结合

高维数据可先用 PCA 降维,再运行 K-Means:

from sklearn.decomposition import PCA

pca = PCA(n_components=0.95)  # 保留95%方差
X_reduced = pca.fit_transform(X)
km = KMeans(n_clusters=4, random_state=42, n_init=10).fit(X_reduced)

1.8 本节小结

  • K-Means 通过迭代优化质心,最小化样本到质心的距离平方和

  • 需要预先指定簇数 k,可通过肘部法轮廓系数确定

  • k-means++ 初始化比随机更稳定,n_init=10 建议保留

  • K-Means 适合球状簇,对非凸形状(如半月形)效果差

  • 聚类前建议标准化数据,避免量纲影响

  • 轮廓系数 > 0.7 为高质量聚类,0.5~0.7 为中等,< 0.5 需调整参数

第2章 DBSCAN 聚类

DBSCAN(Density-Based Spatial Clustering of Applications with Noise,基于密度的空间聚类)是一种基于密度定义簇的算法。与 K-Means 不同,它不需要预先指定簇数,且能识别任意形状的簇噪声点

2.1 核心概念

DBSCAN 基于两个关键参数定义密度:

  • ε(eps):邻域半径——以样本点为中心、半径为 ε 的圆形区域

  • min_samples:核心点所需的最小邻域样本数

基于这两个参数,每个样本被划分为三类:

点类型

定义

说明

核心点

ε 邻域内至少有 min_samples 个点

簇的"骨架"

边界点

在某个核心点的 ε 邻域内,但自身不满足核心点条件

属于簇但不构成骨架

噪声点

既不是核心点也不是边界点

标签为 -1

簇的定义:由核心点通过密度可达关系(互相在对方 ε 邻域内)连接形成的最大连通子图,加上其所有边界点。

2.2 Scikit-learn 基本用法

from sklearn.cluster import DBSCAN

# 创建 DBSCAN 模型
dbscan = DBSCAN(eps=0.3, min_samples=10)

# 拟合数据
dbscan.fit(X)

# 获取聚类标签(-1 表示噪声)
labels = dbscan.labels_

# 核心样本索引
core_samples = dbscan.core_sample_indices_

# 核心点坐标
core_points = dbscan.components_

常用参数速查

参数

默认值

说明

eps

0.5

邻域半径

min_samples

5

核心点最小邻居数

metric

'euclidean'

距离度量方式

algorithm

'auto'

最近邻搜索算法

n_jobs

None

并行数(-1 为全部 CPU)

标签约定:簇标签从 0 开始递增,噪声点标签为 -1

2.3 DBSCAN vs K-Means

非球形簇处理

ch02_dbscan_moons.png

左图:DBSCAN(ε=0.15, min_samples=5)正确识别出两个半月形簇,并标记了少量噪声点(灰色)。

右图:真实簇结构。DBSCAN 的聚类结果与真实结构高度一致。

相比之下,第1章中 K-Means 在相同数据上将半月形按上下分割,产生大量错误分类。这是因为 K-Means 基于质心距离,只能发现球状簇;而 DBSCAN 基于局部密度,自然沿着数据流形扩展。

噪声处理能力

ch02_dbscan_blobs_noise.png

左图:K-Means

噪声点(散布在三个球状簇周围的灰点)被强制分配到最近的簇中,导致质心偏移(黑叉偏离真实中心)。K-Means 没有"噪声"的概念,每个点必须属于某个簇。

右图:DBSCAN

噪声点被正确标记为灰色(标签 -1),三个真实簇的边界清晰,不受离群点干扰。这在实际数据(如客户行为分析、传感器数据清洗)中非常实用。

2.4 参数 ε 的调优

ε 是 DBSCAN 最关键的参数,直接影响簇的粒度。

ch02_eps_comparison.png
  • ε=0.05(太小):邻域过小,几乎所有点都被判定为噪声,无法形成任何簇

  • ε=0.15(合适):恰好识别出两个半月形簇,仅有 4 个噪声点

  • ε=0.3(偏大):恰好识别出两个半月形簇,但是没有识别出噪声点

  • ε=0.80(太大):所有点合并为一个簇,完全丧失区分度

K-距离图(K-distance Graph)

调参 ε 的经验方法——K-距离图:

from sklearn.neighbors import NearestNeighbors

# 计算每个点到其第 k 近邻居的距离
k = 5  # k = min_samples
nn = NearestNeighbors(n_neighbors=k)
nn.fit(X)
distances, _ = nn.kneighbors(X)

# 第 k 近的距离,排序后画图
k_dist = np.sort(distances[:, -1])
plt.plot(k_dist)
plt.xlabel('样本(按距离排序)')
plt.ylabel(f'第 {k} 近邻距离')

解读:曲线中最陡峭的拐点对应的距离值即为推荐的 ε 值。这个拐点是密度发生显著变化的位置,正好区分了"簇内"和"簇间"。

min_samples 的选择

  • min_samples ≤ 维度数 + 1:大多数点会成为核心点,可能将噪声也归入簇

  • min_samples ≥ 2 × (维度数 + 1):更严格,更多点被标记为噪声,簇更"纯"

  • 经验规则min_samples = 2 × dim 作为起点,dim 为数据维度

2.5 距离度量

DBSCAN 支持多种距离度量(通过 metric 参数):

# 欧氏距离(默认)
DBSCAN(eps=0.3, metric='euclidean')

# 曼哈顿距离
DBSCAN(eps=0.5, metric='manhattan')

# 余弦距离
DBSCAN(eps=0.3, metric='cosine')

# 预计算距离矩阵
from scipy.spatial.distance import pdist, squareform
dist_matrix = squareform(pdist(X, metric='jaccard'))
DBSCAN(eps=0.3, metric='precomputed').fit(dist_matrix)

距离度量

适用场景

euclidean

连续数值特征,最常用

manhattan

网格状数据、高维稀疏数据

cosine

文本/向量方向比大小更重要的场景

precomputed

自定义距离(如编辑距离、Jaccard)

2.6 实际案例:客户分群

from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import DBSCAN

# 模拟客户数据(消费金额 + 消费频次)
X, _ = make_blobs(n_samples=300, centers=3, cluster_std=0.8, random_state=42)
X = X * [100, 5]  # 不同量纲
X[:, 0] += 200    # 消费金额 200-600
X[:, 1] += 10     # 消费频次 10-30

# 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# DBSCAN 聚类
dbscan = DBSCAN(eps=0.5, min_samples=8).fit(X_scaled)

# 统计结果
unique, counts = np.unique(dbscan.labels_, return_counts=True)
for label, count in zip(unique, counts):
    name = '噪声' if label == -1 else f'簇 {label}'
    print(f'{name}: {count} 人')

2.7 本节小结

  • DBSCAN 基于密度定义簇,不需要预设簇数 kk

  • 核心概念:核心点(ε 邻域内 ≥ min_samples)、边界点噪声点(标签 -1)

  • ε 是最关键参数——用 K-距离图 找拐点确定 ε,min_samples2×维度 开始尝试

  • DBSCAN 优势:发现任意形状簇、自动识别噪声、不强制所有点入簇

  • DBSCAN 局限:对高维数据效果下降(维度灾难)、密度不均匀的数据难以用统一 ε 处理

  • 与 K-Means 互补:球状均匀簇用 K-Means,复杂形状/含噪声用 DBSCAN

第3章 层次聚类

层次聚类(Hierarchical Clustering)通过构建嵌套的簇层次结构来组织数据,结果可用树状图(Dendrogram)直观展示。与 K-Means 和 DBSCAN 不同,它不需要预先指定簇数——你可以根据树状图在任意高度"切割",得到不同粒度的聚类结果。

3.1 凝聚层次聚类(Agglomerative)

凝聚层次聚类采用自底向上的策略:

  1. 初始化:每个样本自成一簇(n 个簇)

  2. 合并:找到距离最近的两个簇,合并为一个

  3. 重复步骤 2,直到所有样本合并为一个簇

关键:簇间距离的定义(链接方法)

合并哪两个簇取决于如何定义"两个簇之间的距离"。不同的链接方法产生截然不同的层次结构:

链接方法

簇间距离定义

特点

Ward

合并后簇内方差增量的最小化

产生大小均匀的球状簇,最常用

Complete(最远)

两簇中最远两点的距离

产生紧凑的簇,对异常值敏感

Average(平均)

两簇中所有点对的平均距离

折中方案,稳定可靠

Single(最近)

两簇中最近两点的距离

容易产生"链式效应",适合发现流形结构

链接方法对比

ch03_linkage_comparison.png

使用相同的 45 个样本数据,四种链接方法产生了不同的层次结构:

  • Ward(左上):三个簇在距离 ≈ 36 处合并,层次清晰,簇大小均衡

  • Complete(右上):合并距离的绝对值较小(≈ 13),树状结构更"矮胖"

  • Average(左下):介于 Ward 和 Complete 之间,合并距离 ≈ 9

  • Single(右下):产生"链式效应"——簇间距离很小,树状图呈"梳子"状,不适合确定簇数

3.2 树状图(Dendrogram)解读

ch03_dendrogram.png

左图:原始数据,包含 3 个簇,每簇 15 个样本。

右图:Ward 链接的树状图。

如何读树状图

  • 横轴:样本编号(叶子节点),每个叶子代表一个样本

  • 纵轴:距离——两个簇合并时的距离值

  • 水平线:表示一次合并操作,高度越高说明合并的两个簇差异越大

  • 垂直线:连接被合并的子簇

如何确定簇数

在树状图上画一条水平切割线,与树状图的交叉数即为簇数。本例中:

  • 红色虚线在距离 ≈ 50 处切割,恰好产生 3 个簇

  • 切割线以下的每个连通分支对应一个簇

  • 切割线越高,簇数越少;切割线越低,簇数越多

读取切割结果

from scipy.cluster.hierarchy import fcluster

# Z 为 linkage 返回值
labels = fcluster(Z, t=50, criterion='distance')
# 或直接指定簇数
labels = fcluster(Z, t=3, criterion='maxclust')

3.3 Scikit-learn 中的层次聚类

from sklearn.cluster import AgglomerativeClustering

# 创建模型
agg = AgglomerativeClustering(n_clusters=4, linkage='ward')

# 拟合并获取标签
labels = agg.fit_predict(X)

常用参数

参数

默认值

说明

n_clusters

2

目标簇数(distance_threshold=None 时有效)

linkage

'ward'

链接方法:'ward' / 'complete' / 'average' / 'single'

metric

'euclidean'

距离度量(ward 仅限 euclidean)

distance_threshold

None

距离阈值(设此值时 n_clusters 无效)

compute_full_tree

'auto'

是否计算完整树(distance_threshold 不为 None 时自动为 True)

两种使用方式

# 方式1: 指定簇数
agg = AgglomerativeClustering(n_clusters=4, linkage='ward')
labels = agg.fit_predict(X)

# 方式2: 指定距离阈值(自动确定簇数)
agg = AgglomerativeClustering(distance_threshold=30, linkage='ward', n_clusters=None)
labels = agg.fit_predict(X)

3.4 不同簇数的效果

ch03_cut_tree.png

左图:150 个样本的完整树状图。由于样本多,叶子标签重叠,但可以看到明显的层次结构——三个主分支在较高处合并。

中图:在树状图上切割得到 k=3,将四个真实簇中的两个合并了。

右图:切割得到 k=4,正确识别出四个簇。

如何选择合适的 k

从树状图上寻找大的垂直跳跃——如果某次合并的距离远大于之前的合并,说明这两个子簇本质上不同,切割点应在该次合并之前。本例中,树状图顶部有两个显著的高度跳跃,暗示 k=3 或 k=4 是合理选择。

3.5 完整工作流程

import numpy as np
from scipy.cluster.hierarchy import linkage, dendrogram, fcluster
import matplotlib.pyplot as plt

# 1. 计算距离矩阵并构建树
Z = linkage(X, method='ward')

# 2. 绘制树状图
fig, ax = plt.subplots(figsize=(10, 5))
dendrogram(Z, ax=ax, leaf_rotation=90, leaf_font_size=8)
ax.set_title('树状图')
ax.set_ylabel('距离')
ax.axhline(y=30, color='red', linestyle='--')
plt.tight_layout()
plt.show()

# 3. 根据树状图选择切割距离,获取标签
labels = fcluster(Z, t=30, criterion='distance')
print(f'簇数: {len(set(labels))}')

3.6 何时使用层次聚类

场景

推荐程度

原因

样本量 < 10,000

⭐⭐⭐⭐⭐

计算复杂度 O(n²),大数据集慢

需要可视化簇层次关系

⭐⭐⭐⭐⭐

树状图直观展示层次结构

不确定簇数

⭐⭐⭐⭐

切割线灵活选择

需要不同粒度的聚类

⭐⭐⭐⭐

一次计算,多次切割

大数据集(>10 万样本)

计算和内存开销大

3.7 本节小结

  • 凝聚层次聚类自底向上合并样本,结果用树状图展示

  • 四种链接方法:Ward(方差最小化,最常用)、Complete(最远)、Average(平均)、Single(最近,链式效应)

  • 树状图纵轴为合并距离,水平切割线确定簇数——切割线越高簇越少

  • Scikit-learn 中用 AgglomerativeClustering,可通过 n_clustersdistance_threshold 控制粒度

  • 层次聚类适合中小规模数据(< 10,000 样本),大数据集推荐 K-Means 或 MiniBatchKMeans

  • fcluster(Z, t, criterion) 从 linkage 结果中提取簇标签

第4章 聚类算法对比

前面的章节分别介绍了 K-Means、DBSCAN 和层次聚类。本章将三种算法放在同一组数据集上对比,帮助你理解各自的适用场景。

4.1 算法对比一览

ch04_algorithm_comparison.png

数据集

K-Means (k=4)

DBSCAN (ε=0.3)

层次聚类 (Ward, k=4)

球状簇

✅ 完美识别

✅ 基本正确

✅ 完美识别

半月形

❌ 错误分割

✅ 正确识别

⚠️ 部分正确

同心圆

❌ 错误分割

✅ 基本正确

⚠️ 部分正确

球状簇(第一行)

三种算法都表现良好。球状簇是 K-Means 和层次聚类的"舒适区"——簇呈高斯分布、大小相近、方差一致。DBSCAN 也能正确处理,只是边缘有几个噪声点被单独标记。

半月形(第二行)

  • K-Means:沿直线分割,完全无法捕捉半月形状

  • DBSCAN:完美识别两个半月形簇,这是密度聚类的天然优势

  • 层次聚类:部分正确,但不如 DBSCAN 精细——Ward 链接倾向于产生球状簇

同心圆(第三行)

  • K-Means:按径向分割而非按环形分割,产生扇形而非环形簇

  • DBSCAN:正确识别内外两个环——环上点的局部密度相似,且内外环之间的空隙使密度不连续

  • 层次聚类:部分正确,但 Ward 链接的球状偏好使其无法完美分离环形结构

4.2 聚类质量评估指标

由于聚类是无监督学习,没有"标准答案"可以直接计算准确率。以下是三种常用的内部评估指标(无需真实标签):

轮廓系数(Silhouette Score)

s(i)=b(i)a(i)max(a(i),b(i))s(i) = \frac{b(i) - a(i)}{\max(a(i), b(i))}

其中 a(i) 为样本 i 到同簇样本的平均距离,b(i) 为到最近异簇样本的平均距离。

  • 范围 [−1,1]越大越好

  • 0.7:高质量聚类

  • 0.5~0.7:中等

  • < 0.5:效果不佳

Calinski-Harabasz 指数(CH 指数)

CH=B/(k1)W/(nk)CH = \frac{B / (k-1)}{W / (n-k)}

其中 B 为簇间离散度,W 为簇内离散度。越大越好,没有上界。

Davies-Bouldin 指数(DB 指数)

每个簇与其最相似簇的平均相似度。越小越好,下界为 0。

球状簇数据上的指标对比

在球状簇数据上,三种算法的指标对比:

算法

轮廓系数

CH 指数

DB 指数(转换后)

K-Means (k=4)

最高

最高

最高

DBSCAN

略低(噪声点拉低)

略低

略低

层次聚类 (Ward)

与 K-Means 接近

与 K-Means 接近

与 K-Means 接近

球状簇是 K-Means 的"主场",所以 K-Means 指标最高。但三种算法在球形数据上都表现良好,差异不大。

4.3 算法选择指南

按数据特征选择

数据特征

推荐算法

理由

球状簇、各簇大小相近

K-Means 或 层次聚类

简单高效

非球形簇(环形、流形)

DBSCAN

基于密度,不受形状限制

含噪声/异常值

DBSCAN

自动识别噪声

需要树状层次结构

层次聚类

树状图直观

不知道簇数

DBSCAN 或 层次聚类

无需预设 k

大数据集(>10 万样本)

K-Means(MiniBatchKMeans)

计算效率高

簇密度差异大

不宜用 DBSCAN

统一 ε 无法适应不同密度

按计算复杂度选择

算法

时间复杂度

空间复杂度

大数据友好

K-Means

O(n·k·t·d)

O(n·d)

✅ 是

DBSCAN

O(n²) 或 O(n log n)

O(n²) 或 O(n)

⚠️ 中等

层次聚类

O(n² log n)

O(n²)

❌ 否

其中 nn 为样本数,kk 为簇数,tt 为迭代次数,dd 为特征维数。

4.4 实用建议

不要只用一种算法

最佳实践:先用 K-Means(快)得到初步分群,再用 DBSCAN 检查是否有复杂结构被遗漏。

结合领域知识

  • 客户分群:DBSCAN 可以识别"异常客户"(噪声点),这本身就是有价值的发现

  • 基因表达分析:层次聚类的树状图能揭示基因之间的层次关系

  • 图像分割:K-Means 在像素颜色空间上简单高效

评估时结合可视化

数值指标只能反映一部分信息。例如 DBSCAN 的轮廓系数可能略低于 K-Means(因为噪声点贡献了负值),但如果业务场景需要发现非球形簇,DBSCAN 仍然是更好的选择。

# 综合评估模板
from sklearn.metrics import silhouette_score, calinski_harabasz_score

def evaluate_clustering(X, labels):
    mask = labels != -1  # 排除噪声
    return {
        '簇数': len(set(labels)) - (1 if -1 in labels else 0),
        '噪声比例': (~mask).mean(),
        '轮廓系数': silhouette_score(X[mask], labels[mask]) if mask.sum() > 1 else 0,
        'CH指数': calinski_harabasz_score(X[mask], labels[mask]) if mask.sum() > 1 else 0,
    }

4.5 本节小结

  • 球状簇三种算法都行,非球形簇选 DBSCAN,层次关系选层次聚类

  • 聚类评估的三个内部指标:轮廓系数([-1,1] 越大越好)、CH 指数(越大越好)、DB 指数(越小越好)

  • 大数据集优先 K-Means(或 MiniBatchKMeans),层次聚类只适合 < 10,000 样本

  • 最佳实践:多算法对比 + 可视化 + 领域知识综合判断

第5章 PCA 主成分分析

主成分分析(Principal Component Analysis, PCA)是最经典的线性降维方法。它通过正交变换将原始特征投影到一组新的坐标轴(主成分)上,使得数据在新坐标轴上的方差依次递减。

5.1 PCA 的核心思想

PCA 解决一个问题:如何在保留最多信息的前提下,用更少的特征表示数据?

直观理解

想象一个扁平的椭球体数据——它在某个方向上伸展得最长(方差最大),在垂直方向上伸展较短(方差较小)。PCA 找到这个"最长"的方向作为第一主成分(PC1),然后找到与之正交且方差次大的方向作为第二主成分(PC2),依此类推。

数学原理

  1. 中心化:将数据均值移到原点 X=XXˉX' = X - \bar{X}

  2. 计算协方差矩阵C=1n1XTXC = \frac{1}{n-1} X'^T X'

  3. 特征值分解C=VΛVTC = V \Lambda V^T,其中 Λ 为特征值对角矩阵,V 的列为特征向量

  4. 主成分:特征向量即为主成分方向,特征值大小决定重要性

  5. 投影Z=XVkZ = X' V_kVk 为前 k 个特征向量

解释方差比:第 i 个主成分的解释方差比为 λijλj\frac{\lambda_i}{\sum_j \lambda_j}​​,表示该主成分携带的信息量占总方差的比例。

5.2 Scikit-learn 基本用法

from sklearn.decomposition import PCA

# 方式1: 指定保留的主成分数量
pca = PCA(n_components=2)
X_reduced = pca.fit_transform(X)

# 方式2: 指定保留的方差比例
pca = PCA(n_components=0.95)  # 保留 95% 方差
X_reduced = pca.fit_transform(X)

# 方式3: 不指定,保留所有成分(用于分析)
pca = PCA()
pca.fit(X)

关键属性

属性

说明

explained_variance_ratio_

各主成分的解释方差比

components_

主成分方向(载荷矩阵),形状 (n_components, n_features)

explained_variance_

各主成分的方差值

n_components_

实际使用的主成分数

关键方法

方法

说明

fit_transform(X)

拟合并投影到新空间

inverse_transform(Z)

从降维空间重建回原始空间

transform(X_new)

将新数据投影到已拟合的主成分空间

5.3 PCA 降维可视化(Iris 数据集)

ch05_pca_iris.png

左图:2D 投影

将鸢尾花 4 维特征降维到 2 维,累计解释 95.8% 的方差。三个品种在 PC1-PC2 平面上已经基本可分——setosa(红色)完全分离,versicolor(蓝色)和 virginica(绿色)有少量重叠。

右图:3D 投影

加入第三主成分后,累计解释方差达到 99.5%,三个品种几乎完全分离。

代码示例

from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

iris = load_iris()
X = StandardScaler().fit_transform(iris.data)

pca = PCA(n_components=2)
X_2d = pca.fit_transform(X)

print(f'PC1 解释方差: {pca.explained_variance_ratio_[0]:.1%}')
print(f'PC2 解释方差: {pca.explained_variance_ratio_[1]:.1%}')

5.4 碎石图(Scree Plot)

碎石图是选择主成分数量的核心工具。

ch05_scree.png

左图:碎石图

每个柱子表示一个主成分的解释方差比。PC1 独占 73.0%,PC2 贡献 22.9%,两者合计 95.9%。PC3 和 PC4 的贡献骤降(3.7% 和 0.5%),形成明显的"碎石坡"。

右图:累计解释方差

红色折线显示累计方差。前 2 个主成分达到 95.9%,已超过常用的 95% 阈值(绿色虚线)。这意味着用 2 维代替 4 维,仅损失 4.1% 的信息。

选择主成分数的方法

方法

做法

适用场景

累计方差阈值

选择使累计方差 ≥ 95%(或 90%)的最小成分数

最常用

碎石图拐点

找曲线从陡峭变平缓的"肘部"

直观但不精确

Kaiser 准则

保留特征值 > 1 的成分(标准化数据)

心理学/社会科学常用

交叉验证

用不同成分数训练下游模型,选效果最好的

面向具体任务

5.5 主成分载荷(Loadings)

载荷系数揭示了每个原始特征对主成分的贡献程度

ch05_variance_components.png

PC1(左图)

  • petal lengthpetal width 的载荷最大(约 0.58 和 0.57)

  • sepal length 也有较大正贡献(约 0.52)

  • sepal width 为负贡献(约 -0.24)

PC1 主要反映花瓣大小:花瓣越长越宽,PC1 得分越高

PC2(右图)

  • sepal width 载荷最大(约 0.89),sepal length 次之(约 0.38)

  • petal lengthpetal width 贡献很小

PC2 主要反映花萼宽度:花萼越宽,PC2 得分越高

载荷解读

# 查看载荷矩阵
loadings = pca.components_  # 形状: (n_components, n_features)
print(loadings)

# 载荷是单位向量,各特征载荷的平方和为 1
print((loadings[0]**2).sum())  # = 1.0

业务解释:载荷分析让你知道降维后的"新特征"实际上代表了原始数据的什么含义——这对于向业务方解释模型至关重要。

5.6 PCA 在图像压缩中的应用

ch05_digits_pca.png

第一行:原始 8×8 手写数字图像(左三张)与 10 个主成分重建的对比(右三张)。10 个成分保留了 74% 的方差,重建图像虽然有些模糊,但数字形状仍可辨认。

第二行:不同成分数的重建质量对比(从左到右成分数递增):

成分数

解释方差

视觉效果

2

29%

高度模糊,仅轮廓

5

54%

大致可辨

10

74%

清晰可辨

20

89%

接近原图

40

99%

几乎无法区分与原图

64

100%

完全无损(全成分)

图像压缩代码

from sklearn.datasets import load_digits
from sklearn.decomposition import PCA

digits = load_digits()
X = digits.data  # 64 维(8×8 像素)

# 压缩到 10 维
pca = PCA(n_components=10)
X_compressed = pca.fit_transform(X)
X_reconstructed = pca.inverse_transform(X_compressed)

# 压缩率
compression = 10 / 64
print(f'压缩率: {compression:.0%}(保留 {pca.explained_variance_ratio_.sum():.0%} 方差)')

5.7 注意事项

必须先标准化

PCA 对特征的尺度敏感——量纲大的特征会主导主成分方向。

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
pca = PCA(n_components=0.95).fit(X_scaled)

PCA 是线性方法

PCA 只能捕捉线性相关性。如果数据的内在结构是非线性的(如瑞士卷流形),PCA 效果有限——这时需要用 Kernel PCA(第6章)。

fit vs fit_transform

# 训练集
pca = PCA(n_components=0.95)
X_train_reduced = pca.fit_transform(X_train)  # 拟合 + 投影

# 测试集(用训练集的 PCA 变换)
X_test_reduced = pca.transform(X_test)  # 仅投影,不要 fit!

5.8 本节小结

  • PCA 通过特征值分解找到方差最大的正交投影方向,实现线性降维

  • 解释方差比量化每个主成分的信息量,累计方差决定保留多少成分

  • 碎石图直观展示各成分贡献,累计方差曲线辅助选择成分数

  • 载荷系数揭示主成分的业务含义——哪个原始特征贡献最大

  • inverse_transform 可从降维空间重建原始数据,用于图像压缩等应用

  • 使用前务必标准化数据,PCA 是线性方法,非线性结构需 Kernel PCA

第6章 Kernel PCA 与非线性降维

标准 PCA 是线性方法——它只能找到数据的线性投影方向。当数据的内在结构是非线性流形时(如瑞士卷、同心圆),PCA 会丢失重要信息。Kernel PCA 通过核技巧将数据映射到高维特征空间,在隐含的高维空间中做线性 PCA,从而捕捉非线性结构。

6.1 核技巧回顾

核技巧的核心思想:不在原始空间计算内积,而用核函数 K(xi,xj) 替代

K(xi,xj)=ϕ(xi),ϕ(xj)K(x_i, x_j) = \langle \phi(x_i), \phi(x_j) \rangle

其中 ϕ(⋅) 是隐式的映射函数——我们不需要知道 ϕ 的具体形式,只需计算核函数即可。

常用核函数

核函数

公式

参数

特点

Linear

xiTxjx_i^T x_j

等价于标准 PCA

Polynomial

(γxiTxj+r)d(\gamma x_i^T x_j + r)^d

γ,r,d

捕捉多项式关系

RBF

exp(γxixj2)\exp(-\gamma \|x_i - x_j\|^2)

γ

最常用,局部性

Sigmoid

tanh(γxiTxj+r)\tanh(\gamma x_i^T x_j + r)

γ,r

类神经网络激活

6.2 Scikit-learn 基本用法

from sklearn.decomposition import KernelPCA

# RBF 核
kpca = KernelPCA(n_components=2, kernel='rbf', gamma=0.01, random_state=42)
X_kpca = kpca.fit_transform(X)

# 多项式核
kpca = KernelPCA(n_components=2, kernel='poly', degree=3, gamma=1, random_state=42)
X_kpca = kpca.fit_transform(X)

# 与标准 PCA 等价的线性核
kpca = KernelPCA(n_components=2, kernel='linear')
X_kpca = kpca.fit_transform(X)  # 结果与 PCA 基本一致

常用参数

参数

默认值

说明

n_components

None

保留的成分数

kernel

'linear'

核函数类型

gamma

None

RBF/Poly/Sigmoid 核的 γ 参数

degree

3

多项式核的次数

coef0

1

多项式/Sigmoid 核的独立项

alpha

1.0

正则化参数(核矩阵的岭参数)

fit_inverse_transform

False

是否学习逆映射

6.3 PCA vs KernelPCA:瑞士卷数据

ch06_kernel_pca_swiss.png

瑞士卷是一个经典的非线性流形——二维平面卷曲嵌入到三维空间中。

  • 原始 3D(左一):彩色渐变沿瑞士卷的"展开方向"变化

  • PCA 2D(左二):标准 PCA 只做了线性投影,瑞士卷被"压扁"后仍然重叠在一起,不同颜色区域混杂

  • KernelPCA RBF(左三):RBF 核部分展开了瑞士卷——虽然仍有重叠,但颜色分层的结构比 PCA 清晰得多

  • KernelPCA Linear(右一):线性核与 PCA 效果类似,证实了 Linear 核等价于标准 PCA

为什么 PCA 在瑞士卷上失败?

PCA 寻找方差最大的线性方向。但瑞士卷的内在结构是弯曲的二维流形——沿着卷曲方向的"距离"在原始 3D 空间中是近的,但在流形上实际很远。PCA 无法区分这种"近"和"远"。

KernelPCA 的 RBF 核通过局部相似性度量——两个点如果欧式距离近则相似度高,距离远则相似度呈指数衰减。这使得 KernelPCA 能够沿着流形"逐步展开"数据。

6.4 PCA vs KernelPCA:同心圆数据

ch06_kernel_pca_circles.png

左图:原始同心圆——两个嵌套的圆环,内环(灰色)和外环(红色)在原始 2D 空间中线性不可分。

中图:PCA 降维到 2D 后——两个圆环仍然嵌套,只是被旋转了一个角度。PCA 没有找到能够区分内外环的方向,因为内外环在任意线性方向上都有重叠。

右图:KernelPCA(RBF, γ=15)——内外环被完全分离!内环被映射到原点附近,外环被推到外围。在 KernelPCA 的投影空间中,内外环线性可分。

同心圆数据的 KernelPCA 代码

from sklearn.datasets import make_circles
from sklearn.decomposition import KernelPCA

X, y = make_circles(n_samples=500, factor=0.3, noise=0.05, random_state=42)

kpca = KernelPCA(n_components=2, kernel='rbf', gamma=15, random_state=42)
X_kpca = kpca.fit_transform(X)

# 在 KernelPCA 空间中,线性分类器就能分开两个环
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression().fit(X_kpca, y)
print(f'分类准确率: {lr.score(X_kpca, y):.2%}')  # 通常 > 95%

6.5 不同核函数对比

ch06_kernel_comparison.png

四种核函数在同心圆数据上的投影:

核函数

分离效果

说明

Linear

无法分离

等价于 PCA,无法分离

Polynomial (d=3)

部分分离

部分分离,多项式核捕捉了半径的非线性关系

RBF (γ=15)

完美分离

完美分离,内外环完全分开

Sigmoid

部分分离

部分分离,但不如 RBF 干净

γ(gamma)的影响

  • γ 越大 → 核函数越"窄" → 每个样本的影响范围越小 → 更容易过拟合

  • γ 越小 → 核函数越"宽" → 近似线性 → 退化为 PCA

  • 经验法则:gamma = 1 / n_features 作为起点

6.6 逆映射(Inverse Transform)

KernelPCA 默认不提供逆映射,但可以通过设置 fit_inverse_transform=True 来学习:

kpca = KernelPCA(n_components=2, kernel='rbf', gamma=15,
                 fit_inverse_transform=True, random_state=42)
X_kpca = kpca.fit_transform(X)

# 从降维空间重建原始数据
X_reconstructed = kpca.inverse_transform(X_kpca)

逆映射在图像去噪等应用中很有价值——先在 KernelPCA 空间中去掉噪声成分,再重建回原始空间。

6.7 KernelPCA 的局限

局限

说明

计算复杂度 O(n³)

需要对 n×n 核矩阵做特征值分解,大数据集不可行

参数敏感

γ 等核参数对结果影响大,需要仔细调参

无明确投影方向

不像 PCA 有明确的载荷系数,KernelPCA 的"新特征"难以解释

新样本预测

需要存储所有训练样本(核矩阵),预测新样本开销大

对于大数据集,可以考虑:

  • Nyström 近似:用采样方法近似核矩阵

  • Random Fourier Features:显式映射到低维空间后再用 PCA

6.8 本节小结

  • KernelPCA 通过核技巧在隐含高维空间做线性 PCA,捕捉数据的非线性结构

  • RBF 核最常用,适合大多数非线性降维场景;Polynomial 核适合多项式关系

  • γ 参数控制核的"宽度"——从 1/n_features 开始尝试

  • Linear 核的 KernelPCA 等价于标准 PCA

  • 适合中小规模数据(< 10,000 样本),大数据集需考虑近似方法

  • fit_inverse_transform=True 可学习逆映射,用于重建和去噪

第7章 PCA + 聚类综合案例

前面的章节分别讲解了聚类和 PCA。在实际工作中,这两个技术经常组合使用——先用 PCA 降维去噪,再做聚类。本章通过两个实际案例展示完整的工作流程。

7.1 为什么先降维再聚类?

优势

说明

去噪

PCA 去掉方差小的成分(通常是噪声),保留信号

降维加速

低维空间中聚类计算更快

可视化

降维到 2D/3D 后可直接可视化聚类结果

缓解维度灾难

高维空间中距离度量失效,低维更可靠

去除相关性

PCA 的成分互相正交,消除了特征间的共线性

7.2 案例一:鸢尾花数据集

ch07_iris_workflow.png

完整流程

from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

# 1. 加载数据
iris = load_iris()
X = iris.data
y = iris.target  # 真实标签(仅用于评估)

# 2. 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 3. PCA 降维到 2D
pca = PCA(n_components=2)
X_2d = pca.fit_transform(X_scaled)
print(f'累计方差: {pca.explained_variance_ratio_.sum():.1%}')  # 95.8%

# 4. K-Means 聚类
km = KMeans(n_clusters=3, random_state=42, n_init=10)
labels = km.fit_predict(X_2d)

# 5. 评估
sil = silhouette_score(X_2d, labels)
print(f'轮廓系数: {sil:.3f}')

结果对比

  • 左图:直接在原始 4D 数据上聚类(用前 2 维可视化)。轮廓系数 0.553,三个簇有较多重叠。

  • 中图:PCA 降维到 2D 后再聚类。轮廓系数提升至 0.509(在 2D 空间计算),三个簇分离更清晰。

  • 右图:真实品种在 2D 空间的分布——PCA 降维后的聚类结果与真实结构高度一致。

关键发现:用 2 维代替 4 维(仅保留 95.8% 方差),聚类效果没有下降,反而可视化更清晰。

7.3 案例二:不同 PCA 成分数的影响

ch07_pipeline.png

在鸢尾花数据上,用不同数量的主成分做 K-Means 聚类:

成分数

累计方差

轮廓系数

观察

1

73.0%

0.675

仅用 PC1,三个簇完全分开(setosa 与其他两类分离)

2

95.8%

0.509

加入 PC2 后,versicolor 和 virginica 的边界变复杂

3

99.5%

0.466

接近全维度,轮廓系数继续下降

4

100%

0.460

原始 4D 数据,轮廓系数最低

有趣的发现:成分数越少,轮廓系数反而越高!这是因为:

  1. PC1 单独就能区分 setosa:PC1 主要反映花瓣大小,setosa 的花瓣明显更小

  2. 加入更多成分引入了噪声:PC3 和 PC4 的方差占比很小(< 5%),主要携带噪声

  3. 轮廓系数的维度敏感性:高维空间中距离度量更容易受到"无关维度"的干扰

实践建议:不要用所有成分!用碎石图或累计方差阈值(如 95%)选择成分数,往往能得到更好的聚类效果。

7.4 案例三:手写数字聚类

ch07_digits_clustering.png

手写数字数据集包含 1797 个 8×8 像素的灰度图像(64 维特征),真实标签为 0-9。

工作流程

from sklearn.datasets import load_digits
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans

digits = load_digits()
X = digits.data  # 64 维

# PCA 保留 95% 方差
pca = PCA(n_components=0.95)
X_reduced = pca.fit_transform(X)
print(f'成分数: {pca.n_components_}')  # 29

# K-Means 聚类
km = KMeans(n_clusters=10, random_state=42, n_init=10)
labels = km.fit_predict(X_reduced)

成分数 vs 聚类质量

第二行展示了不同成分数下的聚类中心(通过 PCA 逆变换重建为图像):

成分数

轮廓系数

重建质量

5

0.340

高度模糊,但聚类质量最高

10

0.264

大致可辨

20

0.213

较清晰

40

0.186

接近原图

64

0.182

完全无损

与鸢尾花相同的规律:成分数越少,轮廓系数越高。5 个成分虽然重建图像模糊,但捕捉到了区分数字的最关键特征(如笔画的大致走向),而去掉了细微的书写变化(这些变化反而会干扰聚类)。

实际含义

在数字识别任务中:

  • 5 个成分 → 只保留数字的"骨架"信息,忽略书写风格的差异

  • 64 个成分 → 保留所有细节,包括噪声和个体书写习惯

如果你的目标是区分数字类型(聚类),5-10 个成分就够了;如果是重建图像(压缩),需要更多成分。

7.5 完整 Pipeline

Scikit-learn 的 Pipeline 可以将 PCA 和聚类串联起来:

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans

# 构建 Pipeline
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('pca', PCA(n_components=0.95)),
    ('kmeans', KMeans(n_clusters=4, random_state=42, n_init=10))
])

# 一键拟合
labels = pipeline.fit_predict(X)

# 访问各步骤
pca_model = pipeline.named_steps['pca']
print(f'成分数: {pca_model.n_components_}')

kmeans_model = pipeline.named_steps['kmeans']
print(f'簇数: {len(set(labels))}')

Pipeline 的优势

  • 防止数据泄露:标准化和 PCA 只在训练集上拟合

  • 代码简洁:一次 fit_predict 完成全部操作

  • 便于交叉验证:可与 GridSearchCV 配合调参

from sklearn.model_selection import GridSearchCV

param_grid = {
    'pca__n_components': [0.90, 0.95, 0.99],
    'kmeans__n_clusters': [3, 4, 5]
}

grid = GridSearchCV(pipeline, param_grid, cv=5, scoring='neg_mean_squared_error')
# 注意:聚类没有标准标签,通常不用 GridSearchCV 调参
# 这里仅展示 Pipeline 与 GridSearch 的兼容性

7.6 注意事项

标准化是必须的

# 错误:直接 PCA,量纲大的特征主导
pca = PCA(n_components=2).fit(X)

# 正确:先标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
pca = PCA(n_components=2).fit(X_scaled)

不要在新数据上重新 fit PCA

# 训练集
pca = PCA(n_components=0.95).fit(X_train)
X_train_reduced = pca.transform(X_train)

# 测试集 — 用训练集的 PCA 变换
X_test_reduced = pca.transform(X_test)  # transform,不是 fit_transform!

聚类没有"正确答案"

聚类是无监督学习,轮廓系数等指标只能作为参考。最终的"好"与"坏"取决于业务需求——有时"噪声"本身就是有价值的发现(如异常检测)。

7.7 本节小结

  • PCA + 聚类是常见组合:PCA 去噪降维 → 聚类发现结构

  • 成分数不是越多越好——适量成分去掉噪声,聚类效果更好

  • 鸢尾花案例:1-2 个成分的轮廓系数高于全维度

  • 手写数字案例:5 个成分比 64 个成分的聚类质量更高

  • Pipeline 串联标准化 → PCA → 聚类,代码简洁且防止数据泄露

  • 新数据预测时,用已拟合的 PCA 做 transform,不要重新 fit

第8章 总结与速查表

本教程覆盖了 Scikit-learn 中聚类分析与主成分分析的核心内容。本章提供全局回顾和实用速查表。

8.1 算法全景图

聚类算法

算法

是否需要 k

簇形状

噪声处理

复杂度

适用场景

K-Means

需要

球状

强制分配

O(n·k·t·d)

球状簇、大数据集

DBSCAN

不需要

任意形状

自动识别

O(n²) 或 O(n log n)

非球形簇、含噪声数据

层次聚类

不需要

球状(Ward)

强制分配

O(n² log n)

中小数据、需要树状图

降维方法

方法

线性/非线性

可解释性

复杂度

适用场景

PCA

线性

高(载荷系数)

O(min(n²d, d²n))

线性相关数据、需解释性

KernelPCA

非线性

O(n³)

非线性流形、中小数据

8.2 API 速查

K-Means

from sklearn.cluster import KMeans

km = KMeans(
    n_clusters=4,       # 簇数
    init='k-means++',   # 初始化策略
    n_init=10,          # 独立运行次数
    max_iter=300,       # 最大迭代
    tol=1e-4,           # 收敛阈值
    random_state=42     # 随机种子
)
labels = km.fit_predict(X)
centers = km.cluster_centers_
inertia = km.inertia_   # SSE

DBSCAN

from sklearn.cluster import DBSCAN

dbscan = DBSCAN(
    eps=0.5,            # 邻域半径
    min_samples=5,      # 核心点最小邻居数
    metric='euclidean', # 距离度量
    n_jobs=-1           # 并行
)
labels = dbscan.fit_predict(X)  # -1 = 噪声
core_samples = dbscan.core_sample_indices_

层次聚类

from sklearn.cluster import AgglomerativeClustering

agg = AgglomerativeClustering(
    n_clusters=4,           # 目标簇数
    linkage='ward',         # 'ward' / 'complete' / 'average' / 'single'
    metric='euclidean',     # 距离度量
    distance_threshold=None # 设为值时 n_clusters 无效
)
labels = agg.fit_predict(X)

PCA

from sklearn.decomposition import PCA

pca = PCA(
    n_components=0.95,      # 成分数或方差比例
    svd_solver='auto',      # 'full' / 'arpack' / 'randomized'
    whiten=False            # 白化
)
X_reduced = pca.fit_transform(X)
X_reconstructed = pca.inverse_transform(X_reduced)

# 关键属性
evr = pca.explained_variance_ratio_    # 各成分解释方差比
loadings = pca.components_              # 载荷矩阵

KernelPCA

from sklearn.decomposition import KernelPCA

kpca = KernelPCA(
    n_components=2,             # 成分数
    kernel='rbf',               # 'linear' / 'poly' / 'rbf' / 'sigmoid'
    gamma=None,                 # RBF/Poly/Sigmoid 的 γ
    degree=3,                   # 多项式核次数
    coef0=1,                    # 独立项
    alpha=1.0,                  # 正则化参数
    fit_inverse_transform=False # 是否学习逆映射
)
X_kpca = kpca.fit_transform(X)

8.3 评估指标速查

内部指标(无需真实标签)

指标

范围

最优

说明

轮廓系数

[-1, 1]

越大越好

样本与同簇/近邻簇的距离比

CH 指数

[0, +∞)

越大越好

簇间离散度 / 簇内离散度

DB 指数

[0, +∞)

越小越好

簇内离散度 + 簇间距离的比

from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score

sil = silhouette_score(X, labels)
ch = calinski_harabasz_score(X, labels)
db = davies_bouldin_score(X, labels)

外部指标(需要真实标签)

指标

说明

调整兰德指数 (ARI)

聚类与真实标签的一致性,[-1, 1],越大越好

归一化互信息 (NMI)

信息论角度的一致性,[0, 1],越大越好

from sklearn.metrics import adjusted_rand_score, normalized_mutual_info_score

ari = adjusted_rand_score(y_true, labels)
nmi = normalized_mutual_info_score(y_true, labels)

8.4 算法选择决策树

聚类选择

你的数据有什么特点?
│
├─ 数据量 > 100,000?
│  ├─ 是 → K-Means(或 MiniBatchKMeans)
│  └─ 否 → 继续 ↓
│
├─ 簇是球状的吗?
│  ├─ 是 → K-Means 或 层次聚类(Ward)
│  └─ 否 → DBSCAN
│
├─ 数据含噪声/异常值?
│  ├─ 是 → DBSCAN
│  └─ 否 → 继续 ↓
│
├─ 需要可视化的层次结构?
│  ├─ 是 → 层次聚类(画树状图)
│  └─ 否 → K-Means(简单高效)
│
└─ 不确定簇数?
   ├─ 用肘部法/轮廓系数 + K-Means
   ─ 用 DBSCAN(不需要 k)

降维选择

你的数据有什么特点?
│
├─ 特征是线性相关的?
│  ├─ 是 → PCA
│  └─ 否 → 继续 ↓
│
├─ 数据有非线性流形结构?
│  ├─ 是 → KernelPCA(RBF 核)
│  └─ 否 → PCA
│
├─ 数据量 > 10,000?
│  ├─ 是 → PCA(KernelPCA 太慢)
│  └─ 否 → 继续 ↓
│
└─ 需要解释新特征的含义?
   ├─ 是 → PCA(载荷系数可解释)
   └─ 否 → KernelPCA

8.5 推荐学习路径

第0章 导论 → 理解无监督学习概念
    │
    ├── 聚类路线
    │   第1章 K-Means → 第2章 DBSCAN → 第3章 层次聚类 → 第4章 算法对比
    │
    ├── 降维路线
    │   第5章 PCA → 第6章 KernelPCA
    │
    └── 综合实战
        第7章 PCA + 聚类 Pipeline

8.6 本节小结

  • 聚类三剑客:K-Means(快、球状)、DBSCAN(任意形状、去噪)、层次聚类(树状图)

  • 降维双雄:PCA(线性、可解释)、KernelPCA(非线性、难解释)

  • 标准化是聚类和 PCA 的前置必要条件

  • 成分数/簇数不是越多越好——用肘部法、碎石图、轮廓系数辅助选择

  • Pipeline 串联操作,简洁且安全

  • 数值指标 + 可视化 + 领域知识 = 可靠的聚类/降维决策

评论