在scikit-learn中仅对特征子集使用PCA的管道

3 投票
2 回答
2742 浏览
提问于 2025-04-18 09:09

我有一组特征想要建模,其中有一个特征是一个在100个不同点上采样的直方图。也就是说,这个直方图特征实际上包含了100个不同的特征。我想通过对直方图特征进行主成分分析(PCA)来减少建模问题的维度,但我不想把其他特征也包括在PCA中,以便保持模型的可解释性。

理想情况下,我想建立一个管道,使用PCA来转换直方图特征,然后用支持向量分类器(SVC)来进行拟合,最后将这个管道输入到GridSearchCV中,以确定SVC的超参数。在这种设置中,有没有办法让PCA只转换我的特征子集(即直方图的各个区间)呢?最简单的方法是修改PCA对象,让它接受一个特征掩码,但我更希望能使用现有的功能。

编辑

在实现了@eickenberg的答案后,我意识到我还想要一个新的PCA类的逆转换方法。这个方法可以用来重新创建最初的特征集,并保持列的原始顺序。下面提供了这个方法,供其他感兴趣的人参考:

def inverse_transform(self, X):
    if self.mask is not None:
        # Inverse transform appropriate data
        inv_mask = np.arange(len(X[0])) >= sum(~self.mask)
        inv_transformed = self.pca.inverse_transform(X[:, inv_mask])

        # Place inverse transformed columns back in their original order
        inv_transformed_reorder = np.zeros([len(X), len(self.mask)])
        inv_transformed_reorder[:, self.mask] = inv_transformed
        inv_transformed_reorder[:, ~self.mask] = X[:, ~inv_mask]
        return inv_transformed_reorder
    else:
        return self.pca.inverse_transform(X)

2 个回答

0

你可以使用 ColumnTransformer:

https://scikit-learn.org/stable/modules/generated/sklearn.compose.ColumnTransformer.html

pca_transformer = ColumnTransformer([('pca', PCA(), pca_columns)], remainder="passthrough")
pipe = Pipeline(steps=[('pca_transformer', pca_transformer), ('logistic', logistic)])
5

这件事在使用scikit-learn时,不能直接做到。为了充分利用PipelineGridSearchCV的所有功能,你可以考虑创建一个叫MaskedPCA的对象,这个对象要从sklearn.base.BaseEstimator继承,并且需要实现fittransform这两个方法。在这个对象里,你应该对被遮罩的特征使用一个PCA对象。遮罩应该在创建这个对象的时候传入。

from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.decomposition import PCA

class MaskedPCA(BaseEstimator, TransformerMixin):

    def __init__(self, n_components=2, mask=None):  
        # mask should contain selected cols. Suppose it is boolean to avoid code overhead
        self.n_components = n_components
        self.mask = mask

    def fit(self, X):
        self.pca = PCA(n_components=self.n_components)
        mask = self.mask
        mask = self.mask if self.mask is not None else slice(None)
        self.pca.fit(X[:, mask])
        return self

    def transform(self, X):
        mask = self.mask if self.mask is not None else slice(None)
        pca_transformed = self.pca.transform(X[:, mask])
        if self.mask is not None:
            remaining_cols = X[:, ~mask]
            return np.hstack([remaining_cols, pca_transformed])
        else:
            return pca_transformed

你可以在一些生成的数据上进行测试

import numpy as np
X = np.random.randn(100, 20)
mask = np.arange(20) > 4

mpca = MaskedPCA(n_components=2, mask=mask)

transformed = mpca.fit(X).transform(X)

# check whether first five columns are equal
from numpy.testing import assert_array_equal
assert_array_equal(X[:, :5], transformed[:, :5])

注意现在transformed的列数是(~mask).sum + mpca.n_components == 7

撰写回答