4. 主成分分析(PCA)原理
主成分分析,简称 PCA。这名字听着挺唬人,其实核心思想就一句话:用更少的维度,保留最多的信息。
我刚开始接触高维数据时,也被那几十上百个特征搞得头晕。后来发现,很多特征之间是相互关联的,你变我也变,信息冗余得很厉害。PCA 就是找到这些特征背后真正的「主心骨」。
4.1 核心思想:降维与最大化方差
说白了,PCA 做了两件事:
- 降维:把高维空间的数据投影到一个低维子空间上。
- 最大化方差:投影的时候,要选一个方向,让投影后的数据点尽可能分散开。嗯,这里要注意了,为什么选最大方差?因为方差越大,数据在该方向上的区分度就越高,信息损失就越少。
举个例子。想象一个数据集只有两个特征:身高和鞋码。你会发现,它们基本是正相关的。这两个特征在二维空间里画出来,大致是一条斜线。PCA 要做的事,就是把这条斜线拎出来,作为新的坐标轴。然后,把所有数据点投影到这条线上。原来你需要两个数字(身高、鞋码)描述一个人,现在只需要一个数字(在这条线上的位置)就够了。这就是降维。
我的一点经验:在遇到高相关特征时,PCA 的效果特别棒。比如金融时序里的不同期限的利率,或者图像里相邻的像素点。我习惯先用热力图看看特征的相关性,如果看到一大片红色,基本可以确定 PCA 能帮你省下不少功夫。
4.2 数学原理:协方差矩阵与特征分解
PCA 怎么找到那个「主心骨」方向呢?靠的是协方差矩阵和特征值分解。
你想想看,协方差矩阵描述的是特征之间的相关性。对角线上的元素是每个特征的方差,非对角线上的元素是特征间的协方差。我们想要一个方向,使得投影后的数据方差最大。这个方向,其实就是协方差矩阵的特征向量。
具体步骤是这样的:
- 数据中心化:把每个特征的均值归零。这是为了消除量纲影响,让协方差矩阵的计算更纯粹。
- 计算协方差矩阵:得到特征之间的相关性矩阵。
- 计算特征值和特征向量:特征向量指示了新坐标轴的方向,特征值表示了该方向上投影数据的方差大小。
- 选择主成分:把特征值从大到小排序,取最大的
k个特征值对应的特征向量,组成投影矩阵。 - 数据投影:用这个投影矩阵把原始数据降维。
我刚开始学的时候,总觉得特征向量和特征值很抽象。后来我把它理解成「找方向」和「算重要程度」。特征值越大,那个方向上的信息就越多,就越不能丢掉。
4.3 核心步骤:从数据到主成分
为了让你更直观地理解,我画了一个流程图,描述了 PCA 从原始数据到最终主成分的全过程。
4.4 主成分的选择:我们用几个?
特征值分解之后,我们得到了 p 个主成分,每个都有对应的特征值。选多少个呢?
我个人习惯看累积方差贡献率。把特征值按从大到小排好,然后算一下前 k 个特征值之和,占总特征值之和的比例。这个比例就代表了你保留了原始数据多少信息。通常我们会选一个阈值,比如 85% 或 95%,然后选达到这个阈值的最小的 k。
举个例子:
| 主成分 | 特征值 | 方差贡献率 | 累积方差贡献率 |
|---|---|---|---|
| PC1 | 3.5 | 70.0% | 70.0% |
| PC2 | 0.8 | 16.0% | 86.0% |
| PC3 | 0.4 | 8.0% | 94.0% |
| PC4 | 0.2 | 4.0% | 98.0% |
| PC5 | 0.1 | 2.0% | 100.0% |
你看上面的表格,如果我们把阈值设在 85%,那么选前 2 个主成分就够了(累积贡献 86%)。如果想保留 95% 的信息,那就得选前 3 个(累积贡献 94%)。
避坑指南:千万别为了追求高保留率而选太多的主成分。那样就失去了降维的意义。我一般会在 80%-90% 之间选一个数,然后看看可视化效果,再决定要不要调整。
4.5 用 Python 快速实现
理论说完了,看看代码。Scikit-learn 已经把 PCA 封装好了,用起来非常舒服。
import numpy as np
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
# 1. 准备高维数据
# 假设你有100个样本,每个样本有50个特征
X = np.random.randn(100, 50)
# 2. 标准化(非常重要的步骤!)
# 如果不标准化,量纲大的特征会主导结果
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 3. 创建PCA对象,设置保留95%方差
pca = PCA(n_components=0.95)
# 4. 拟合数据
X_pca = pca.fit_transform(X_scaled)
# 5. 查看结果
print("原始数据形状:", X.shape)
print("降维后数据形状:", X_pca.shape)
print("保留的主成分个数:", pca.n_components_)
print("各主成分方差贡献率:", pca.explained_variance_ratio_)
print("累积方差贡献率:", np.cumsum(pca.explained_variance_ratio_))
输出结果会告诉你,原来 50 维的数据被压缩到比如 10 维,还能保留 95% 的信息。这效果,你想想看,是不是很强大?
警告:PCA 假设数据之间的关系是线性的。如果数据是流形结构(比如瑞士卷),PCA 的效果会很差。在这种情况下,你应该考虑 t-SNE 或 UMAP 这类非线性降维方法。我以前在一个基因表达数据上栽过这个跟头,数据量很大,但 PCA 降维后分类效果反而变差了,就是因为数据是非线性结构。
4.6 PCA 的局限性
好处说完了,我也得提提它的不足。没有一个工具是万能的。
- 线性假设:就像刚才说的,它对非线性数据无能为力。
- 可解释性差:降维后得到的主成分是原始特征的线性组合。想想看,新特征「0.3*身高 + 0.4*体重 + 0.3*年龄」到底代表什么?很难说清楚。
- 对异常值敏感:PCA 会努力让所有数据的投影方差最大,如果一个离群点离得很远,它会严重影响主成分的方向。
这些就是 PCA 的核心原理了。记住,它就是一个工具,用在合适的地方才能发挥威力。下次遇到高维数据无从下手时,不妨先试试它,看看能不能帮你把复杂的结构一目了然地展现出来。