【问题标题】:Inversing PCA transform with sklearn (with whiten=True)用 sklearn 反转 PCA 变换(使用 whiten=True)
【发布时间】:2014-06-08 21:36:18
【问题描述】:

通常 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

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

【问题讨论】:

    标签: python-2.7 scikit-learn pca


    【解决方案1】:

    这种行为可能很奇怪,但它仍然记录在相关函数的文档字符串中。

    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 learn 的代码(如果社区认为有用,可以正式完成),您正在寻找的解决方案是这样的(并且我现在将使用您的代码和变量名,请检查这是否适合您):

    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_ 中,可以用于反白。见 cmets。

    【讨论】:

    • 非常感谢,效果很好。我之前将 PCA 与 R 一起使用,无论组件是否被重新调整为具有单位方差,它都能反转转换,所以我很惊讶地发现 sklearn 无法这样做。
    • 我也很惊讶。它被明确记录的事实表明它背后有一个原因。如果没有,我认为应该更改。
    • 重新阅读您的评论,准确地说,白化不仅是将组件转换为具有单位方差(sklearn.preprocessing.StandardScaler 很容易做到这一点,也很容易进行逆变换)。这是高斯意义上的去相关:白化特征的协方差矩阵将是对角线
    • 顺便说一句,实际上不需要知道len(x) 参数(在计算inverse_transformed 时将其取消)或取singular_values 的平方根,这样可以:@987654348 @.
    • 更新:这实际上已得到修复,如果指定 (>= 0.16) 则对逆变换进行适当的去白化
    【解决方案2】:

    self.components_ 原本是受制于的特征向量

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

    要投影(sklearn 中的transform)到这些组件,PCA 减去它们的 self.mean_乘以 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 in sklearn)如下(从X得到Y

       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_whiten PCA 不受制于

    >>> np.allclose(self.components_.T, np.linalg.inv(self.components_))
    False
    

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

    所以,你需要修改sklearn.decomposition.pca

    1. 代码保留the reconstruction matrixself.components_whiten PCA

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

      所以我们可以指定the reconstruction matrix

      self.recons_ = V * S[:, np.newaxis] / np.sqrt(n_samples)
      
    2. inverse_transform被调用时,我们会将这个矩阵导出的结果返回为

      if self.whiten:
          return np.dot(X, self.recons_) + self.mean_
      

    就是这样。让我们测试一下。

    >>> p = decomposition.PCA(whiten=True)
    >>> p.fit(x)
    >>> np.allclose(p.inverse_transform(p.transform(a)), a)
    True
    

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

    【讨论】:

    • PCA 对象有一个关键字参数n_components。如果设置了此项,那么您的第一个条件将不会呈现 True(同样在 SVD 中,关键字 full_matrices=False 可能会导致非方阵)。您的解决方案似乎将代码添加到 scikit learn 包中。如果您有兴趣,您应该通过向github repo 提出拉取请求向社区提出建议。但是,可能需要进行一些更改,因为您的代码现在的方式是复制组件,这是低效的。删除后,这可能会很有趣。
    • 再次查看您的帖子,您可能应该编辑掉调用以使用inv 反转components_ 向量,因为它通常没有明确定义。伪逆 pinv 将在非正方形情况下工作,但我不确定结果是否理想。
    【解决方案3】:

    这个问题现在已经修复(现在的版本是 0.24.2)。但是如果你想手动inverse_transform,方法如下:

    pca = PCA()
    reduced = pca.fit_transform(x)
    x_original = np.dot(
        reduced / np.sqrt(n_samples-1) * pca.singular_values_,
        pca.components_
    ) + pca.mean_
    

    和现在的版本一样,whiten=True根据source code只影响fit的返回值:

    if self.whiten:
        # X_new = X * V / S * sqrt(n_samples) = U * sqrt(n_samples)
        U *= sqrt(X.shape[0] - 1)
    else:
        # X_new = X * V = U * S * Vt * V = U * S
        U *= S[:self.n_components_]
    

    从某种意义上说,文件是这样说的

    当为 True(默认为 False)时,components_ 向量乘以 n_samples 的平方根,然后除以奇异值,以确保具有单位分量方差的不相关输出。

    有点误导。无论whiten 参数设置为什么,components_ 都保持不变。

    【讨论】:

      猜你喜欢
      • 2019-04-03
      • 2014-04-03
      • 1970-01-01
      • 2018-12-20
      • 2021-03-24
      • 2021-02-02
      • 2013-12-06
      • 2020-12-29
      • 2021-10-15
      相关资源
      最近更新 更多