sklearn:有一个过滤样本的估计器

13 投票
2 回答
3906 浏览
提问于 2025-04-18 14:20

我正在尝试实现自己的数据填补器(Imputer)。在某些情况下,我想过滤掉一些我认为质量不高的训练样本。

但是,由于 transform 方法只返回 X 而不返回 y,而且 y 本身是一个 numpy 数组(据我所知,我不能直接在原地过滤它),而且当我使用 GridSearchCV 时,我的 transform 方法接收到的 yNone,所以我找不到办法去实现这个功能。

为了澄清一下:我完全知道如何过滤数组。我找不到将样本过滤应用到 y 向量的办法,无法与当前的 API 结合使用。

我真的想从 BaseEstimator 的实现中做到这一点,这样我就可以与 GridSearchCV 一起使用(它有一些参数)。我是不是漏掉了其他实现样本过滤的方法(不是通过 BaseEstimator,而是符合 GridSearchCV 的方式)?有没有什么方法可以绕过当前的 API?

2 个回答

5

scikit-learn的转换器API是用来改变数据特征的,比如特征的性质或者数量,但它不用于改变样本的数量。也就是说,任何会删除或添加样本的转换器,在目前的scikit-learn版本中,都不符合这个API的标准(未来如果觉得重要,可能会增加这样的功能)。

所以,从这个角度来看,你可能需要找到其他方法来处理标准的scikit-learn API。

16

我找到了解决方案,分为三个部分:

  1. 添加这一行 if idx == id(self.X):。这样可以确保只对训练集中的样本进行过滤。
  2. 重写 fit_transform 方法,以确保转换方法能接收到 y 而不是 None
  3. 重写 Pipeline,使得 transform 方法可以返回前面提到的 y

这里有一段示例代码来演示这个过程,我想这可能没有涵盖所有细节,但我认为它解决了与API相关的主要问题。

from sklearn.base import BaseEstimator
from mne.decoding.mixin import TransformerMixin
import numpy as np
from sklearn.pipeline import Pipeline
from sklearn.naive_bayes import GaussianNB
from sklearn import cross_validation
from sklearn.grid_search import GridSearchCV
from sklearn.externals import six

class SampleAndFeatureFilter(BaseEstimator, TransformerMixin):
    def __init__(self, perc = None):
        self.perc = perc

    def fit(self, X, y=None):
        self.X = X
        sum_per_feature = X.sum(0)
        sum_per_sample = X.sum(1)
        self.featurefilter = sum_per_feature >= np.percentile(sum_per_feature, self.perc)
        self.samplefilter  = sum_per_sample >= np.percentile(sum_per_sample, self.perc)
        return self

    def transform(self, X, y=None, copy=None):
        idx = id(X)
        X=X[:,self.featurefilter]
        if idx == id(self.X):
            X = X[self.samplefilter, :]
            if y is not None:
                y = y[self.samplefilter]
            return X, y
        return X

    def fit_transform(self, X, y=None, **fit_params):
        if y is None:
            return self.fit(X, **fit_params).transform(X)
        else:
            return self.fit(X, y, **fit_params).transform(X,y)

class PipelineWithSampleFiltering(Pipeline):
    def fit_transform(self, X, y=None, **fit_params):
        Xt, yt, fit_params = self._pre_transform(X, y, **fit_params)
        if hasattr(self.steps[-1][-1], 'fit_transform'):
            return self.steps[-1][-1].fit_transform(Xt, yt, **fit_params)
        else:
            return self.steps[-1][-1].fit(Xt, yt, **fit_params).transform(Xt)

    def fit(self, X, y=None, **fit_params):
        Xt, yt, fit_params = self._pre_transform(X, y, **fit_params)
        self.steps[-1][-1].fit(Xt, yt, **fit_params)
        return self

    def _pre_transform(self, X, y=None, **fit_params):
        fit_params_steps = dict((step, {}) for step, _ in self.steps)
        for pname, pval in six.iteritems(fit_params):
            step, param = pname.split('__', 1)
            fit_params_steps[step][param] = pval
        Xt = X
        yt = y
        for name, transform in self.steps[:-1]:
            if hasattr(transform, "fit_transform"):
                res = transform.fit_transform(Xt, yt, **fit_params_steps[name])
                if len(res) == 2:
                    Xt, yt = res
                else:
                    Xt = res
            else:
                Xt = transform.fit(Xt, y, **fit_params_steps[name]) \
                              .transform(Xt)
        return Xt, yt, fit_params_steps[self.steps[-1][0]]

if __name__ == '__main__':
    X = np.random.random((100,30))
    y = np.random.random_integers(0, 1, 100)
    pipe = PipelineWithSampleFiltering([('flt', SampleAndFeatureFilter()), ('cls', GaussianNB())])
    X_train, X_test, y_train, y_test = cross_validation.train_test_split(X, y, test_size = 0.3, random_state = 42)
    kfold = cross_validation.KFold(len(y_train), 10)
    clf = GridSearchCV(pipe, cv = kfold, param_grid = {'flt__perc':[10,20,30,40,50,60,70,80]}, n_jobs = 1)
    clf.fit(X_train, y_train)

撰写回答