降维:从正确的角度看见高维数据的结构 本节摘要:高维数据有结构,只是你得从正确的角度看。一个样本有 784 个特征(手写数字的像素、基因表达、用户行为),你无法可视化、无法画图、甚至无法想象——但其中大部分是冗余。一个手写的「7」不需要 784 个独立数字,几个就够(笔画角度、横线长度、倾斜度),其余是噪声。降维就是找出那个更小的曲面:把 784 维压到 2、10 或 50 维,同时保住关键结构。
本节摘要:高维数据有结构,只是你得从正确的角度看。一个样本有 784 个特征(手写数字的像素、基因表达、用户行为),你无法可视化、无法画图、甚至无法想象——但其中大部分是冗余。一个手写的「7」不需要 784 个独立数字,几个就够(笔画角度、横线长度、倾斜度),其余是噪声。降维就是找出那个更小的曲面:把 784 维压到 2、10 或 50 维,同时保住关键结构。本节先讲维度灾难(高维下距离失效、体积集中到角点、数据需求指数增长);然后从零实现 PCA——中心化、协方差矩阵、特征分解、按特征值排序、投影;用解释方差比与肘部法选主成分数;对比 t-SNE(保局部邻居、出版物级 2D 图)与 UMAP(更快、保全局结构);引入核 PCA(RBF 核分离同心圆这种非线性流形);最后用重建误差量化「压成了什么、丢了什么」,并把它接上异常检测。
对应原课程:Phase 01 · Lesson 10 ·
dimensionality-reduction(原英文phases/01-math-foundations/10-dimensionality-reduction/docs/en.md)。前置:第 1、2、3 节(线性代数)与第 6 节(概率)。
阅读完本节,你应当能够:
你有一个 784 特征的数据集,无法可视化、无法画图、甚至无法想象。但 784 个特征大多冗余,真正信息活在远更小的曲面上。降维找到这个曲面,把 784 维压到 2/10/50 维而保留关键结构。
高维空间违反直觉,三件事随维数增长而崩坏。
距离失效:高维下任意两随机点的距离都趋于同一值,最近邻搜索失效。
| 维数 | 平均距离比(随机点 max/min) |
|---|---|
| 2 | ~5.0 |
| 10 | ~1.8 |
| 100 | ~1.2 |
| 1000 | ~1.02 |
体积集中到角点:d 维单位超立方体有 2^d 个角。100 维下几乎所有体积都在角点,数据点散到边缘,模型在内部饿死。
数据需求指数增长:从 2D 升到 20D 要 10¹⁸ 倍数据才能保持同样密度——你永远没有足够数据。降维把数据密度拉回可用范围。
主成分分析(PCA)找数据变化最大的轴,旋转坐标系,使第一轴捕获最大方差、第二轴次之、依此类推。
1. 中心化数据 (每个特征减均值) 2. 算协方差 (特征如何协同变化) 3. 特征分解 (找主方向) 4. 按特征值排序 (最大方差优先) 5. 投影 (保留前 k 个特征向量,丢弃其余)
为什么特征分解? 协方差矩阵对称半正定,其特征向量是特征空间里正交方向,特征值告诉你每个方向捕获多少方差。最大特征值的特征向量指向最大方差方向。
每个主成分捕获总方差的一小部分:
| 成分 | 特征值 | 解释比 | 累计 |
|---|---|---|---|
| PC1 | 4.73 | 0.473 | 0.473 |
| PC2 | 2.51 | 0.251 | 0.724 |
| PC3 | 1.12 | 0.112 | 0.836 |
| PC4 | 0.89 | 0.089 | 0.925 |
累计解释方差达 0.95 时,这些成分就捕获了 95% 信息,其余多是噪声。
三种策略:
t-分布随机邻接嵌入(t-SNE)专为可视化设计,把高维数据映射到 2D/3D,同时保留「谁离谁近」。直觉:原空间按距离算点对的概率分布(近点概率高、远点概率低),再找一个 2D 排列使同一分布成立——784 维里的邻居在 2D 里仍是邻居。
性质:非线性(能展开 PCA 不能的复杂流形);随机性(不同运行布局不同);perplexity 控制考虑几个邻居(典型 5~50);输出里簇间距离无意义,只有簇本身有意义;大数据上慢(O(n²))。
UMAP 与 t-SNE 类似,但两点更优:更快(用近似最近邻图而非全 pairwise 距离);全局结构更好(簇的相对位置比 t-SNE 更有意义)。它在高维建加权图(「模糊拓扑表示」),再找尽量保住该图的低维布局。参数:n_neighbors(像 perplexity,大值保更多全局结构)、min_dist(输出里点挤得多紧,小值成更密簇)。
| 方法 | 用例 | 保留 | 速度 |
|---|---|---|---|
| PCA | 训练前预处理 | 全局方差 | 快(精确),百万样本可用 |
| PCA | 快速探索可视化 | 线性结构 | 快 |
| t-SNE | 出版级 2D 图 | 局部邻居 | 慢(<1 万样本理想) |
| UMAP | 大规模 2D 可视化 | 局部 + 部分全局 | 中(百万可处理) |
拇指法则:PCA 用于预处理与压缩;t-SNE 或 UMAP 用于 2D 结构可视化。
标准 PCA 找线性子空间(旋转坐标系、丢轴)。但若数据活在非线性流形上(2D 里的圆无法被任何直线分开),标准 PCA 无能为力。核 PCA 在核函数诱导的高维特征空间里做 PCA,而不显式算该空间坐标——这就是 SVM 背后的核技巧。
1. 算核矩阵 K,K_ij = k(xᵢ, xⱼ) 2. 在特征空间中心化核矩阵 3. 对中心化核矩阵特征分解 4. 前几个特征向量(乘 1/√特征值)即投影
| 核 | 公式 | 适用 |
|---|---|---|
| RBF(高斯) | exp(−γ·‖x−y‖²) | 多数非线性数据、光滑流形 |
| 多项式 | (x·y + c)^d | 多项式关系 |
| Sigmoid | tanh(α·x·y + c) | 类神经网络映射 |
经典例子:2D 同心圆(两环嵌套),标准 PCA 把两环投到同一直线(分类无用);RBF 核 PCA 把内外圈映到不同区域,线性可分。
降维好不好?压缩 784 到 50,丢了什么?量重建误差:
X_reduced = X @ W_k;X_hat = X_reduced @ W_kᵀ;mean((X − X_hat)²)。对 PCA,重建误差与解释方差有干净关系:
重建误差 = 未保留的特征值之和 总方差 = 全部特征值之和 丢失比例 = 丢弃特征值之和 / 全部特征值之和
画累计解释方差对成分数的曲线得「肘部」,拐点处即合适成分数。重建误差还可用于异常检测:重建误差高的样本是不拟合学得子空间的离群点——这是生产系统里 PCA 异常检测的基础。
完整源码见 phases/01-math-foundations/10-dimensionality-reduction/code/。
class PCA: def __init__(self, n_components): self.n_components = n_components def fit(self, X): self.mean = np.mean(X, axis=0) X_centered = X - self.mean cov_matrix = np.cov(X_centered, rowvar=False) eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix) # 协方差对称 → eigh sorted_idx = np.argsort(eigenvalues)[::-1] # 大到小 eigenvalues = eigenvalues[sorted_idx] eigenvectors = eigenvectors[:, sorted_idx] self.components = eigenvectors[:, :self.n_components].T self.eigenvalues = eigenvalues[:self.n_components] self.explained_variance_ratio_ = self.eigenvalues / np.sum(eigenvalues) return self def transform(self, X): return (X - self.mean) @ self.components.T def fit_transform(self, X): self.fit(X); return self.transform(X)
设计要点:协方差矩阵对称半正定,用
np.linalg.eigh(而非eig)更稳更快且保证实特征值;特征向量按[:, sorted_idx]重排列(列对应特征值)。
pca_50 = PCA(n_components=50); X_pca50 = pca_50.fit_transform(X_mnist) # 50 成分通常捕获 >80% 方差 from sklearn.manifold import TSNE from umap import UMAP X_tsne = TSNE(n_components=2, perplexity=30, random_state=42).fit_transform(X_mnist) X_umap = UMAP(n_components=2, n_neighbors=15, min_dist=0.1, random_state=42).fit_transform(X_mnist)
实战中直接用 scikit-learn:
from sklearn.decomposition import PCA pca = PCA(n_components=2) X_reduced = pca.fit_transform(X_mnist) print(pca.explained_variance_ratio_)
sklearn 的 PCA 用 SVD(而非协方差特征分解)实现,数值更稳、更快;但数学等价。你的从零版本与之最大差异应在 1e-10 量级。
outputs/skill-dimensionality-reduction.md:一份帮你为给定任务选对降维技术的技能说明。PCA 类是后续章节(第 3 章机器学习、第 5 章计算机视觉的特征压缩)的复用件。PCA 加 inverse_transform,用 10、50、200 成分重建 MNIST 数字,打印各自的重建误差(MSE)。make_classification 造 50 特征但只有 5 个有信息的数据集,套 PCA,看解释方差曲线是否正确识别「数据实质 5 维」。下一节,我们进入线性代数的「瑞士军刀」——奇异值分解(SVD):它统一了 PCA、矩阵求逆、推荐系统、潜在语义分析,是把任意矩阵拆成「旋转—缩放—旋转」三步的万能工具。