PCA 数学原理:协方差矩阵、特征值分解、主成分

说实话,很多讲 PCA 的教程一上来就扔公式。

我以前刚学的时候也懵。

但后来在项目中反复调优,才真正摸透这三个东西:协方差矩阵、特征值分解、主成分。

说白了,它们就是 PCA 的三个齿轮。理解清楚,后面用什么降维都不慌。

我的习惯:在讲数学之前,先看一眼数据长啥样。你想想看,100 维的基因表达数据,列比行还多,直接做协方差矩阵会奇异。嗯,这就是我踩过的坑。

1. 数据中心化 —— 起点

PCA 的第一步,不是算协方差,是去均值。

为什么要去?

因为协方差本质是衡量变量之间的“共同偏离”。如果均值都不对齐,你算出来的协方差会被整体偏移带偏。

import numpy as np

# 原始数据 X,形状 (n_samples, n_features)
X_centered = X - np.mean(X, axis=0)

就这么一行。但这一步决定了后面所有计算的有效性。

我在做一个用户行为聚类项目时,一开始没去均值,结果第一主成分几乎全是用户活跃度均值本身——废的。

2. 协方差矩阵 —— 变量关系的快照

中心化之后,我们就来算协方差矩阵。

公式长这样:

C = (1 / (n-1)) * X_centered.T @ X_centered

结果是一个 d × d 的对称矩阵(d 是特征数)。

它的对角线上是每个特征的方差,非对角线上是两两特征之间的协方差。

这里有个关键点:

  • 如果两个特征正相关,协方差 > 0
  • 负相关 < 0
  • 几乎无关 ≈ 0

你看,这个矩阵先把所有“特征之间的关系”浓缩成一个数字快照。PCA 后面的所有操作,都基于这个快照。

避坑指南:当特征量级差太多时(比如一列是 0~1,另一列是 10000~20000),协方差会被大数值特征主导。我建议先用标准化(Z-score)再做 PCA,尤其是做基因表达、图像像素这种量级差异大的数据。

3. 特征值分解 —— 找到“主方向”

拿到协方差矩阵 C 之后,我们要做特征值分解:

eigenvalues, eigenvectors = np.linalg.eig(C)

或者用更稳定的 SVD:

U, S, Vt = np.linalg.svd(X_centered, full_matrices=False)
eigenvalues = S**2 / (n-1)
eigenvectors = Vt.T

每个特征值对应一个特征向量。

特征值的大小,代表这个方向上的“数据方差”。

特征向量本身,就是一个新的坐标轴(即主成分方向)。

你想想看,方差越大,说明数据在这个方向上散得越开,信息量越大。我们保留最大的 k 个特征值对应的方向,就完成了降维。

概念含义在 PCA 中的作用
特征值 λ该方向上的方差大小决定主成分的“贡献率”
特征向量 v该方向的空间向量组成新坐标系的基
累计方差比前 k 个 λ 之和 / 总 λ 之和选多少个主成分的依据
我曾经踩过的坑:特征向量是单位向量,但方向有正负。如果你在 scikit-learn 里调 PCA,每次结果的正负可能不同,但本质一样。千万别因为符号不同就怀疑算错了。

4. 主成分 —— 降维后的新数据

最后一步:投影。

选 top-k 个特征向量组成矩阵 W(d × k)。

降维后的数据 Z = X_centered @ W。

k = 2  # 降到2维
top_k_eigenvectors = eigenvectors[:, :k]  # 取前k列
Z = X_centered @ top_k_eigenvectors

Z 就是新的特征空间,每一列是一个主成分,互不相关(正交)。

而且第一主成分的方差最大,第二主成分次之……

整体流程一图看

下面这张 SVG 把上面三步串起来了。

PCA 数学原理流程 原始数据 X X_centered 协方差矩阵 C 特征值 & 特征向量 选 top-k 主成分 降维数据 Z 累计方差比 前 k 个 λ 之和 / 总 λ 之和 决定保留多少信息 主成分贡献率 λ_i / 总 λ 越大代表该方向信息越多

小结

我个人经验是:把 PCA 的数学原理拆成去均值 → 算协方差 → 特征分解 → 选主成分 → 投影这五步,能让你真正理解它为什么能降维。

尤其注意协方差矩阵的质量。如果特征数远大于样本数,矩阵秩亏,特征值会有一堆零。这时候可以用随机 PCA 或核方法,但那是后话了。

嗯,先把基础打牢。项目中遇到数据行列不平衡时,你就能判断该不该用 PCA,还是先做特征选择。