基于协方差矩阵与SVD的Python PCA结果不一致问题排查
两种PCA实现结果不一致的原因与修正方案
你遇到的问题其实是PCA实现中几个容易忽略的细节导致的,我帮你一步步拆解:
1. 最核心的问题:数据未中心化
PCA的本质是对中心化后的数据(即每个变量减去其均值)进行分析,但你的两种实现没有统一处理这一步:
- 协方差矩阵方法:
np.cov(XT, rowvar=False)会自动计算中心化后的协方差(因为协方差的定义就是基于均值偏差的),所以这部分其实隐式做了中心化。 - SVD方法:你直接对原始数据
XT做了SVD,完全没做中心化,这就导致两种方法的输入数据根本不一样,结果自然无法匹配。
2. SVD与协方差矩阵PCA的对应关系(中心化后)
当数据中心化后,我们来看两者的正确对应方式:
假设中心化后的数据为X_centered = XT - XT.mean(axis=0),那么:
- 协方差矩阵:
cov = np.cov(X_centered, rowvar=False),其特征值eVals和特征向量eVecs满足cov @ eVecs[:,i] = eVals[i] * eVecs[:,i]。 - SVD方式:对
X_centered做SVD得到u, s, vh = np.linalg.svd(X_centered, full_matrices=False),此时:vh的行就是协方差矩阵的特征向量(注意是行,不是列),即vh[i,:]对应eVecs[:,i](可能符号相反,这是正常的,因为特征向量的方向不唯一)。- 奇异值
s和特征值eVals的关系是:eVals = (s ** 2) / (XT.shape[0] - 1)(因为协方差矩阵除以了n-1做无偏估计)。
3. 修正后的代码对比
我们把两种方法都加上中心化,再调整SVD的投影计算,就能得到一致的结果:
修正后的SVD方法
import numpy as np # 数据中心化 X_centered = XT - XT.mean(axis=0) # 对中心化后的数据做SVD u, s, vh = np.linalg.svd(X_centered, full_matrices=False) # 投影到第一主成分:vh的第一行是第一主成分的方向向量 proj_0 = np.dot(X_centered, vh[0,:])
修正后的协方差矩阵方法
# 数据中心化(其实np.cov会隐式做,但这里显式写出来更清晰) X_centered = XT - XT.mean(axis=0) cov = np.cov(X_centered, rowvar=False) eVals, eVecs = np.linalg.eigh(cov) # 按降序排序(注意eigh默认返回升序的特征值) idx = np.argsort(eVals)[::-1] eVals = eVals[idx] eVecs = eVecs[:, idx] # 投影到第一主成分 PCA_0 = np.dot(X_centered, eVecs[:, 0])
现在你再运行这两段代码,proj_0和PCA_0的结果要么完全一致,要么符号完全相反(这是因为特征向量的方向可以取反,不影响PCA的效果),这都是正常的。
额外说明:为什么你的原始SVD代码得到的结果不对?
你原始的SVD代码用了np.dot(XT, vh)[:,0],这里的vh是原始数据的右奇异向量,不是中心化后数据的,而且vh的每一行是原始数据的右奇异向量,你用XT乘以vh其实是把数据投影到所有右奇异向量上,但因为没中心化,这个投影和PCA的主成分投影完全不是一回事。
另外,np.linalg.eigh返回的特征值默认是升序排列的,你用np.argsort(np.abs(eVals))[::-1]其实没必要,直接对eVals升序后反转更准确,因为特征值本身是实数且非负(协方差矩阵是半正定的),不需要取绝对值。
内容的提问来源于stack exchange,提问作者refle
相关产品推荐
相关产品推荐

