从零实现的PCA与scikit-learn输出存在方向差异的原因及解决方法
1 sklearn PCA与手写实现的核心差异
导致输出不一致的原因主要有3个:
- 特征向量的排序逻辑错误
你当前手写实现里只对特征值做了降序排序计算解释方差,但是numpy.linalg.eig返回的特征值默认不是按大小排序的,你直接取eig_vecs_[:, :self.n_components]并没有对应调整特征向量的顺序,和sklearn严格按特征值降序取主成分的逻辑不一致,会导致主成分顺序错乱、数值差异。 - 特征向量的符号处理差异
特征向量乘以任意正负号都不改变其对方差的解释能力,属于PCA的合理歧义。numpy.linalg.eig返回的特征向量符号没有统一规则,而sklearn的PCA内部会对特征向量做符号归一:确保每个主成分的特征向量中绝对值最大的元素为正,避免符号随机翻转。 - 底层计算逻辑不同
sklearn的PCA没有采用协方差矩阵特征分解的方案,而是直接对中心化后的数据做奇异值分解(SVD),数值稳定性远高于先算协方差矩阵再做特征分解的方案,两者的数值计算误差有差异,会带来结果的微小数值区别。
2 对齐两者输出的修复方案
按以下步骤修改你的PCA类即可对齐sklearn的输出:
- 同步排序特征值与对应特征向量:计算得到特征值、特征向量后,按特征值降序同时重排两者,而不是仅排序特征值。
- 统一特征向量符号:对每个特征向量,判断其绝对值最大的元素的符号,如果为负则将整个特征向量乘以-1,和sklearn的处理逻辑对齐。
- 显式对输入数据做中心化,和sklearn的预处理逻辑完全对齐。
修改后的fit_transform核心代码参考:
def fit_transform(self, X : np.ndarray): if not self.n_components: self.n_components = min(X.shape) # 显式中心化,和sklearn逻辑一致 X_centered = X - np.mean(X, axis=0) self.covariance_ = np.cov(X_centered.T) # 计算特征值、特征向量 self.eig_vals_, self.eig_vecs_ = np.linalg.eig(self.covariance_) # 按特征值降序同步排序特征值和特征向量 sorted_idx = np.argsort(self.eig_vals_)[::-1] self.eig_vals_ = self.eig_vals_[sorted_idx] self.eig_vecs_ = self.eig_vecs_[:, sorted_idx] # 统一特征向量符号:每个主成分最大绝对值元素为正 for i in range(self.eig_vecs_.shape[1]): max_abs_idx = np.argmax(np.abs(self.eig_vecs_[:, i])) if self.eig_vecs_[max_abs_idx, i] < 0: self.eig_vecs_[:, i] *= -1 # 计算解释方差 _tot_eig_vals = sum(self.eig_vals_) self.explained_variance_ = np.array([(i / _tot_eig_vals) * 100 for i in self.eig_vals_]) self.cum_explained_variance_ = np.cumsum(self.explained_variance_) self.W_ = self.eig_vecs_[:, :self.n_components] return X_centered.dot(self.W_)
修改后再测试鸢尾花数据集,输出和sklearn PCA的结果就会几乎完全一致,散点图也不会出现方向翻转的问题。
内容的提问来源于stack exchange,提问作者Mr. Hobo
相关产品推荐
相关产品推荐

