You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于协方差矩阵与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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:26:13