第3章 PCA实现步骤:数据中心化、协方差计算、特征向量选择
要说PCA怎么落地,我先拆解成三步。这三步我做了十几年降维,几乎每次都用。说白了,就是一套线性代数的标准操作。
3.1 数据中心化
第一步,把数据拉回原点。为什么要做这一步?我见过很多新手直接拿原始数据算协方差,结果主成分完全跑偏。原因很简单——如果变量的量纲不同,高均值的变量会主导协方差矩阵。
中心化的公式就一句话:
X_centered = X - np.mean(X, axis=0)
每一个特征减去它自己的均值。这样处理后,每个特征的均值变成0。我记得有一次做用户行为数据,年龄和消费金额差了上百倍,中心化后PCA才能正确抓到结构。
X - mean广播计算,numpy非常高效。
还有一点要注意:如果你做训练/测试集,中心化用的均值必须从训练集算,然后应用到测试集上。绝对不能拿测试集的均值去中心化测试集。嗯,这个坑我踩过。
3.2 协方差矩阵计算
中心化之后,算协方差矩阵。它衡量的是特征之间的共变关系。
公式长这样:
C = (1 / (n-1)) * X_centered.T @ X_centered
其中n是样本数。为什么除以n-1?因为样本协方差用无偏估计。你平时用np.cov(X_centered.T)也是一回事。
协方差矩阵是个对称阵,大小是d×d(d是特征数)。对角线上是每个特征的方差,非对角线上是两两特征的协方差。如果某个非对角线绝对值很大,说明这两个特征冗余,可以压缩。
我建议你实际操作时打印一下协方差矩阵的热力图。看到一堆高亮格子,就知道有哪些特征需要处理。
3.3 特征值和特征向量
有了协方差矩阵,接下来就是线性代数的核心操作——特征分解。
eigenvalues, eigenvectors = np.linalg.eig(C)
# 或者用更稳定的: np.linalg.eigh(C) 针对对称矩阵
特征值代表对应特征向量方向上的方差大小。特征向量就是我们要找的“主成分方向”。
为什么特征值大的方向就更重要?我换个说法——你把协方差矩阵想象成一个椭球,特征向量的方向是椭球的轴方向,特征值就是轴的长度。最长的轴就是数据变化最剧烈的方向。
3.4 特征向量选择与降维
特征值按从大到小排序,取前k个最大的特征值对应的特征向量。这k个向量构成投影矩阵W。
投影这一步很简单:
X_pca = X_centered @ W # W的列是选出的特征向量
怎么选k?通常看累计方差贡献率。我习惯画一个“碎石图”,找拐点。
累计方差贡献率计算:
cumsum = np.cumsum(sorted_eigenvalues) / np.sum(eigenvalues)
k = np.argmax(cumsum >= 0.95) + 1 # 取95%方差
实际工作中,k不一定取到95%,有时候80%就够了,看你对信息保留的要求。
3.5 完整流程代码示例
下面是我常用的PCA实现(不用sklearn):
import numpy as np
def pca_manual(X, n_components):
# 1. 中心化
mean = np.mean(X, axis=0)
X_centered = X - mean
# 2. 协方差矩阵
cov = np.cov(X_centered, rowvar=False)
# 3. 特征分解
eigenvalues, eigenvectors = np.linalg.eigh(cov)
# 4. 按特征值降序排列
idx = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[idx]
eigenvectors = eigenvectors[:, idx]
# 5. 选前k个
W = eigenvectors[:, :n_components]
# 6. 投影
X_pca = X_centered @ W
return X_pca, eigenvalues, eigenvectors
这段代码可以跑在任意二维以上数据上。你试试就知道了。
3.6 PCA vs SVD的关系
你可能会问,为什么很多实现用SVD而不是特征分解?其实SVD更数值稳定,特别是当样本数小于特征数时。
SVD的公式:
U, S, Vt = np.linalg.svd(X_centered, full_matrices=False)
# 主成分得分 = U @ np.diag(S)
# 加载向量 = Vt.T
SVD直接对数据矩阵分解,不需要先算协方差矩阵。而且SVD的奇异值平方等于特征值乘以(n-1)。所以两者本质等价。
我个人习惯用SVD来做PCA,尤其在数据量大时。
3.7 核心步骤总结
最后把整个流程画成一张图,方便你记:
这张图的核心逻辑就是:数据→中心化→协方差→特征分解→选主成分→投影。每一步都有数学基础,但实际操作也就几行代码。
3.8 常见问题与避坑
| 问题 | 原因 | 解决办法 |
|---|---|---|
| 主成分方向不稳定 | 特征向量符号不唯一 | 不用纠结,只关心方向,不关心符号 |
| 降维后数据失去可解释性 | 主成分是原始特征的线性组合 | 检查加载向量,找贡献大的原始特征 |
| 方差解释率低 | 数据本身线性相关性弱 | 考虑t-SNE或UMAP等非线性方法 |
| 内存溢出 | 特征数太多导致协方差矩阵过大 | 用SVD分解,或先做特征选择 |
sklearn.decomposition.PCA(svd_solver='randomized')。