PCA 原理简介:核心思想、几何解释与数学推导

主成分分析(PCA),说白了就是降维。你不是搞 ADMET 批次效应检测嘛,手上少则几十个分子描述符,多则几百个,维度一高,距离度量失效、聚类乱成一团,批次效应肉眼根本看不出来。PCA 就是帮你抓住数据里“最值钱”的那些方向,丢掉冗余和噪声。

我个人习惯把 PCA 理解成三个层次:它想干什么(核心思想)、它干成了什么样子(几何解释)、它怎么算出来的(数学推导)。咱们一个层次一个层次拆开聊。

核心思想:方差最大与重构误差最小

你想想看,一堆点在空间中散布,哪个方向最“重要”?

PCA 给的答案是:方向上的投影方差越大,这个方向越重要。因为方差大意味着数据在这个方向上分得开、信息量足。相反,沿着某个方向投影后点都挤在一起,那这个方向基本是噪音,丢了也不心疼。

换个角度,PCA 也在找一组新坐标轴,使得把原数据投影到这些轴上后,重建回去的误差最小。这两个角度是等价的——我当年读论文时花了半天才想通这件事。

  • 目标1:找到投影方差最大的方向(第一主成分)
  • 目标2:在垂直方向上继续找方差最大的方向(第二主成分)
  • 结果:前 k 个主成分保留了数据最大的信息量
一点经验:在 ADMET 批次效应检测里,我经常只用前 2~3 个主成分就能把批次分开。如果前三个主成分都分不开,那说明批次效应很弱或者数据本身就没啥信号——这时候就该回头检查实验设计,别硬算。

几何解释:旋转坐标系,看到数据的骨架

几何上,PCA 就是做了一次刚性旋转和缩放。原始坐标轴(比如 logP、分子量、TPSA)通常互相关联,PCA 把它们旋转成一个新坐标系,新坐标轴(主成分)彼此垂直,并且按照方差大小排序。

我经常跟团队的人说:
“你别把 PCA 当成黑盒,它就是帮你找到一个能看清数据‘骨架’的视角而已。”

比如下面这个二维例子:

PCA几何解释:二维数据的主成分方向 原始X 原始Y PC1 PC2 蓝色散点:批次A 绿色散点:批次B 红色散点:批次C

图中三个批次在原始坐标下混在一起,但沿 PC1 方向(红色箭头)明显分成了三层。PC2 方向方差小,说明批次内差异主要是随机波动——这就是 PCA 直观的“分离力”。

数学推导概要:协方差矩阵与特征值分解

好了,理论怎么落地?数学上分 4 步,我一个个说。

  1. 中心化:每个变量减去自己的均值,让数据中心回到原点。这一步不做的话,第一主成分会偏向数据重心,而方差信息会被偏移干扰。
  2. 算协方差矩阵:对于 p 维数据,得到一个 p×p 的对称矩阵,对角线上是每个变量的方差,非对角线上是变量间的协方差(线性相关程度)。
  3. 特征值分解:对协方差矩阵解特征方程,得到 p 个特征值和对应的特征向量。特征值越大,说明对应特征向量方向上的方差越大。
  4. 排序与投影:按特征值从大到小排序,取前 k 个特征向量组成投影矩阵 W,然后用 XW 得到降维后的数据(主成分得分)。

我这里写一段最简的 Python 实现,帮你看看数学在代码里长什么样:

import numpy as np

def pca_numpy(X, n_components):
    # X: (n_samples, n_features)
    # 1. 中心化
    X_centered = X - X.mean(axis=0)
    # 2. 协方差矩阵
    cov = np.cov(X_centered, rowvar=False)
    # 3. 特征值分解
    eigenvalues, eigenvectors = np.linalg.eigh(cov)
    # 特征值升序,取最后n_components个(即最大的)
    idx = np.argsort(eigenvalues)[::-1][:n_components]
    components = eigenvectors[:, idx]
    # 4. 投影
    return X_centered @ components, eigenvalues[idx]

# 示例:10个样本,4个描述符
X_example = np.random.randn(10, 4)
scores, evals = pca_numpy(X_example, 2)
print("主成分得分形状:", scores.shape)
print("前两个特征值:", evals)
我曾经踩过的坑:别直接用 np.linalg.eig 而不用 eigh。协方差矩阵是对称的,eigh 对实对称矩阵优化过,数值稳定性好得多。有次我用 eig,特征向量符号乱跳,导致批次聚类时前后跑出相反的分组颜色,排查了俩小时才找到原因。

方差解释率与主成分选择

拿到特征值以后,怎么判断取几个主成分够用?

标准做法是计算方差解释比例:

explained_variance_ratio = eigenvalues / eigenvalues.sum()
cumulative = np.cumsum(explained_variance_ratio)

通常建议取累计方差解释率 > 80% 或 > 90% 时的主成分个数。我自己的习惯是:先跑一次完整 PCA,看碎石图(scree plot),如果前两个主成分已经能解释 70% 以上,那检测批次效应时基本够用;如果连 50% 都不到,说明数据噪音很大,先做变量筛选再说。

主成分序号特征值方差解释率累计方差解释率
12.8547.5%47.5%
21.3222.0%69.5%
30.9415.7%85.2%
40.538.8%94.0%
50.366.0%100.0%

你想想看,上表里前两个主成分解释了近 70% 的方差。用在批次效应检测上,PC1 vs PC2 的散点图大概率能看出批次分组。第三主成分还有 15%,要不要加进来?我的建议是先用二维图试,分不开再加第三个轴,不要一上来就把所有主成分都扔进去“碰运气”。

说实话,PCA 的数学推导看着吓人(又是特征值又是投影矩阵),但实际应用时你完全不用手算。关键是要理解:它在干什么?结果怎么解读?我自己带新人时,会让他们先对着二维模拟数据手算一遍 PCA——不用代码,只用计算器和坐标纸——感受一下“方差最大方向”到底长什么样。转一圈回来,再用 sklearn.decomposition.PCA 就顺手多了。

PCA 在批次效应检测中的特殊角色

前面讲的是普适原理。回到咱们课程的主题——ADMET 批次效应检测——PCA 之所以成为标配,是因为它无监督:不需要事先知道样本属于哪个批次,只凭数据自身的分布就能发现异常分组。

如果有批次效应,那么前几个主成分的散点图上,不同批次的样本会自然形成分离的簇。反过来,如果批次标签跟主成分得分毫无相关性,说明数据本身的系统偏差很小,可以放心往下做模型训练。

嗯,这里要注意一点:PCA 只能告诉你“有差异”,但分辨不出哪些差异是批次造成的、哪些是真实的生物学/化学差异。要区分这个,得用更高级的方法(比如 ComBat、Harmony),我们后面章节会讲。

以上就是 PCA 的核心思想、几何直觉和数学底子。下一段实操里,咱们就要把这些原理用到真实的 ADMET 数据上,亲手跑一遍、画一张 PCA 图,看看批次效应到底长什么样。