使用sklearn反转PCA变换(使用whiten = True)

时间:2014-04-23 20:37:14

标签: python-2.7 scikit-learn pca

通常PCA变换很容易被反转:

import numpy as np
from sklearn import decomposition

x = np.zeros((500, 10))
x[:, :5] = random.rand(500, 5)
x[:, 5:] = x[:, :5] # so that using PCA would make sense

p = decomposition.PCA()
p.fit(x)

a = x[5, :]

print p.inverse_transform(p.transform(a)) - a  # this yields small numbers (about 10**-16)

现在,如果我们尝试添加whiten = True参数,结果将完全不同:

p = decomposition.PCA(whiten=True)
p.fit(x)

a = x[5, :]

print p.inverse_transform(p.transform(a)) - a  # now yields numbers about 10**15

所以,由于我没有找到任何其他方法可以解决这个问题,我觉得如何才能获得原始值?或者它甚至可能吗?非常感谢您的帮助。

2 个答案:

答案 0 :(得分:14)

这种行为无疑是可能很奇怪的,但它仍记录在相关功能的文档字符串中。

PCA的类文档字符串说明了以下关于whiten

的内容
whiten : bool, optional
    When True (False by default) the `components_` vectors are divided
    by n_samples times singular values to ensure uncorrelated outputs
    with unit component-wise variances.

    Whitening will remove some information from the transformed signal
    (the relative variance scales of the components) but can sometime
    improve the predictive accuracy of the downstream estimators by
    making there data respect some hard-wired assumptions.

PCA.inverse_transform的代码和文档字符串说:

def inverse_transform(self, X):
    """Transform data back to its original space, i.e.,
    return an input X_original whose transform would be X

    Parameters
    ----------
    X : array-like, shape (n_samples, n_components)
        New data, where n_samples is the number of samples
        and n_components is the number of components.

    Returns
    -------
    X_original array-like, shape (n_samples, n_features)

    Notes
    -----
    If whitening is enabled, inverse_transform does not compute the
    exact inverse operation as transform.
    """
    return np.dot(X, self.components_) + self.mean_

现在看看函数whiten=True中的PCA._fit会发生什么:

    if self.whiten:
        self.components_ = V / S[:, np.newaxis] * np.sqrt(n_samples)
    else:
        self.components_ = V

其中S是奇异值,V是奇异向量。根据定义,白化会对频谱进行调整,基本上将协方差矩阵的所有特征值设置为1

为了最终回答您的问题:sklearn.decomposition的PCA对象不允许从白化矩阵重建原始数据,因为居中的奇异值数据/协方差矩阵的特征值在函数PCA._fit之后被垃圾收集。

然而,如果您手动获得奇异值S,则可以将它们相乘并返回原始数据。

试试这个

import numpy as np
rng = np.random.RandomState(42)

n_samples_train, n_features = 40, 10
n_samples_test = 20
X_train = rng.randn(n_samples_train, n_features)
X_test = rng.randn(n_samples_test, n_features)

from sklearn.decomposition import PCA
pca = PCA(whiten=True)

pca.fit(X_train)

X_train_mean = X_train.mean(0)
X_train_centered = X_train - X_train_mean
U, S, VT = np.linalg.svd(X_train_centered, full_matrices=False)
components = VT / S[:, np.newaxis] * np.sqrt(n_samples_train)

from numpy.testing import assert_array_almost_equal
# These assertions will raise an error if the arrays aren't equal
assert_array_almost_equal(components, pca.components_)  # we have successfully 
                                                        # calculated whitened components

transformed = pca.transform(X_test)
inverse_transformed = transformed.dot(S[:, np.newaxis] ** 2 * pca.components_ /
                                            n_samples_train) + X_train_mean

assert_array_almost_equal(inverse_transformed, X_test)  # We have equality

从创建inverse_transformed的行中可以看到,如果将奇异值乘以组件,则可以返回原始空间。

事实上,奇异值S实际上隐藏在组件的范数中,因此无需计算PCA旁边的SVD。使用上面的定义可以看到

S_recalculated = 1. / np.sqrt((pca.components_ ** 2).sum(axis=1) / n_samples_train)
assert_array_almost_equal(S, S_recalculated)

结论:通过获取居中数据矩阵的奇异值,我们可以撤消白化并转换回原始空间。但是,此功能未在PCA对象中本机实现。

补救措施无需修改scikit的代码(如果社区认为有用,可以正式完成),您正在寻找的解决方案就是这个(和我现在将使用您的代码和变量名称,请检查这是否适用于您):

transformed_a = p.transform(a)
singular_values = 1. / np.sqrt((p.components_ ** 2).sum(axis=1) / len(x))
inverse_transformed = np.dot(transformed_a, singular_values[:, np.newaxis] ** 2 *
                                          p.components_ / len(x)) + p.mean_)

(恕我直言,任何估算器的inverse_transform函数都应尽可能接近原始数据。在这种情况下,明确存储奇异值也不会花费太多,所以这个功能实际应该是添加到sklearn。)

编辑中心矩阵的奇异值不是最初想到的垃圾收集。事实上,它们存储在pca.explained_variance_中,可以用来解决问题。见评论。

答案 1 :(得分:4)

self.components_最初是Eignenvectors,受

影响
>>> np.allclose(self.components_.T, np.linalg.inv(self.components_))
True

要将(transformsklearn投射到这些组件,PCA 减去他们的self.mean_倍增 {{1}作为

self.components_

其中 Y = np.dot(X - self.mean_, self.components_.T) => Y = (X - mean) * V.T # rewritten for simple notation 是样本,X是训练样本的平均值,mean是主要成分。

然后重建V中的inverse_transform)如下(从sklearn获取Y

X

问题是 Y = (X - mean) * V.T => Y*inv(V.T) = X - mean => Y*V = X - mean # inv(V.T) = V => X = Y*V + mean => Xrec = np.dot(X, self.components_) + self.mean_ self.components_ PCA不受

的影响
whiten

您可以从@eickenberg的代码中找出为什么的原因。

因此,您需要修改>>> np.allclose(self.components_.T, np.linalg.inv(self.components_)) False

  1. 代码保留sklearn.decomposition.pcathe reconstruction matrix的{​​{1}}是

    self.components_

    因此我们可以将whiten PCA指定为

    self.components_ = V / S[:, np.newaxis] * np.sqrt(n_samples)
    
  2. 调用the reconstruction matrix时,我们将返回此矩阵派生的结果

    self.recons_ = V * S[:, np.newaxis] / np.sqrt(n_samples)
    
  3. 就是这样。我们来试试吧。

    inverse_transform

    抱歉我的英文。请改进这篇文章我不确定这些表达是否正确。