【问题标题】:Why does PCA result change drastically with a small change in the input?为什么 PCA 结果会随着输入的微小变化而发生巨大变化?
【发布时间】:2021-04-21 22:42:21
【问题描述】:

我正在使用 PCA 将 Nx3 阵列缩减为 Nx2 阵列。这主要是因为 PCA 变换(Nx2 矩阵)对于在原始 Nx3 数组上执行的旋转或平移是不变的。我们以以下为例。

import numpy as np
from sklearn.decomposition import PCA
a = np.array([[0.5  , 0.5  , 0.5  ],
              [0.332, 0.456, 0.751],
              [0.224, 0.349, 0.349],
              [0.112, 0.314, 0.427]])
pca = PCA(n_components=2, svd_solver='full', random_state=10)
print(pca.fit_transform(a))

以下是输出。请注意,由于平移不变性,我们得到相同的输出,print(pca.fit_transform(a-L))L 是任意数字。与旋转相同。

[[ 0.16752654  0.15593431]
 [ 0.20568992 -0.14688601]
 [-0.16899598  0.06364857]
 [-0.20422047 -0.07269687]]

现在,我对数组 a 进行非常小的扰动 (~1%) 并执行 PCA。

a_p = np.array([[0.51 , 0.53 , 0.52 ],
       [0.322, 0.452, 0.741],
       [0.217, 0.342, 0.339],
       [0.116, 0.31 , 0.417]])
pca = PCA(n_components=2, svd_solver='full', random_state=10)
print(pca.fit_transform(a_p))

结果如下。这与原始数组的 PCA 有很大的不同。

 [[-0.2056024 , -0.14346977]
 [-0.18563578  0.15627932]
 [ 0.17974942 -0.07001969]
 [ 0.21148876  0.05721014]]

我预计扰动数组的 PCA 变换与原始数组的 PCA 变换非常相似,但百分比变化很大。为什么是这样?有什么方法可以让我得到一个非常相似的 PCA 转换,用于稍微扰动/摇晃的数组?

我知道我可以通过在第二种情况下仅执行变换操作来获得类似的 PCA(例如pca.transform(a_p)),但是,在这种情况下,我失去了旋转和平移不变性 w.r.t。 a_p.

这个问题最初与晶体学有关,我的要求是 PCA(或其他)变换不应显着改变为输入的微小变化,并且它应该对输入的旋转和变换保持不变。任何人都可以解释上述内容或向我建议一种符合我目的的替代方法吗?

【问题讨论】:

  • "1% perturbation" 听起来像是一个很小的变化,但请注意,它通过打破第 0 行 [0.5 , 0.5 , 0.5] 和第 2 行 [0.224, 0.349, 0.349] 到 [0.217, 0.342, 0.339]。

标签: python pca dimensionality-reduction


【解决方案1】:

你得到的向量是以符号移位的为主成分。

请参阅以下代码。我刚刚获取了pca1pca2 的2 个PCA 实例来访问它们的components_ 属性:


import numpy as np
from sklearn.decomposition import PCA
a = np.array([[0.5  , 0.5  , 0.5  ],
              [0.332, 0.456, 0.751],
              [0.224, 0.349, 0.349],
              [0.112, 0.314, 0.427]])
pca1 = PCA(n_components=2, svd_solver='full', random_state=10)
print(pca1.fit_transform(a))

a_p = np.array([[0.51 , 0.53 , 0.52 ],
       [0.322, 0.452, 0.741],
       [0.217, 0.342, 0.339],
       [0.116, 0.31 , 0.417]])
pca2 = PCA(n_components=2, svd_solver='full', random_state=10)
print(pca2.fit_transform(a_p))


pca1.components_
array([[ 0.64935364,  0.38718276,  0.65454515],
       [ 0.63947417,  0.18783695, -0.74551329]])

pca2.components_
array([[-0.65743254, -0.42817638, -0.62003826],
       [-0.59052329, -0.21834821,  0.77692104]])

如您所见,PC 指向相似的方向,但您得到了相反的标志。

例如,看到pca1 的 PC1 是 [ 0.64935364, 0.38718276, 0.65454515],而 pca2 的 PC1 是 [-0.65743254, -0.42817638, -0.62003826]。忽略符号,每个坐标之间的差异都比较小……根据我的计算,大约在 2%、10% 和 5% 左右。

这符合你的直觉,即“它们应该相对接近”。

这里的关键见解是向量[-0.65743254, -0.42817638, -0.62003826] 和向量[0.65743254, 0.42817638, 0.62003826] 在空间中的同一条线上,但只是“指向”不同的方向。因此,对于 PCA 来说,两者都是同样有效的主成分。

我不知道有什么方法可以强制 sklearn 生成指向同一象限的向量。

这解释了您的点之间的大部分距离,这是一个“标志”距离。由于您引入的差异,其余的解释。

一种快速的解决方案是切换a_p 的 PCA 转换结果的符号。

“符号问题”的一个积极方面是,实际上您可以切换嵌入值的符号而不会丢失信息。

所以你会做这样的事情:


t1 = pca1.fit_transform(a)
t2 = pca2.fit_transform(a_p)


t2 = -t2 # Change signs

t1
array([[ 0.16752654,  0.15593431],
       [ 0.20568992, -0.14688601],
       [-0.16899598,  0.06364857],
       [-0.20422047, -0.07269687]])

t2
array([[ 0.2056024 ,  0.14346977],
       [ 0.18563578, -0.15627932],
       [-0.17974942,  0.07001969],
       [-0.21148876, -0.05721014]])

t1t2 与您最初建议的直觉大致相似,而且是正确的。

【讨论】:

  • 是的。这是有道理的。虽然,我需要 PC 的标志保持一致(无需手动检查)才能使其有用。我似乎找不到为 PCA 提供一致标志的 python 包。如果我找到一个,我会回到这里。
猜你喜欢
  • 2018-05-19
  • 2012-09-22
  • 2018-09-15
  • 2020-02-20
  • 2021-05-05
  • 1970-01-01
  • 2021-08-22
  • 2018-05-01
  • 1970-01-01
相关资源
最近更新 更多