【问题标题】:Does sklearn pipeline() feed both X and y to the following steps?sklearn pipeline() 是否将 X 和 y 都提供给以下步骤?
【发布时间】:2021-08-29 12:48:23
【问题描述】:

所以我尝试在分类器训练之前在管道中进行异常值去除和监督特征选择。为此,我必须创建自定义转换器以输入管道。我发现的所有示例都将y=None 作为transform() 方法的参数,但是,由于我需要更改y(即从y 中删除异常值),我需要能够访问它。这是我的自定义转换器,用于去除异常值。

from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.neighbors import LocalOutlierFactor
from sklearn.preprocessing import StandardScaler

class OutlierExtractor1(BaseEstimator, TransformerMixin):
    def __init__(self):
        self.threshold = 2
        self.isInlier = None

    def transform(self, X, y):
        ind = [False if i == -1 else True for i in self.isInlier]
        return (X.loc[ind,:], y.loc[ind])

    def fit(self, X, y):
        X2 = np.asarray(X)
        y2 = np.asarray(y)
        scaler = StandardScaler()
        norm = scaler.fit_transform(X2)
        normalized_X = pd.DataFrame(norm, columns=X.columns)
        lcf = LocalOutlierFactor(metric = 'euclidean')
        self.isInlier = list(lcf.fit_predict(normalized_X))
        return self

这是我使用所述转换器的管道:

from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import fbeta_score, make_scorer

space = {'rf__max_depth': [9, 11, 12, 14],
         'rf__n_estimators': [80, 90, 100]}

pipe = Pipeline([('outliers', OutlierExtractor1()),
                 ('rf', RandomForestClassifier(criterion = 'entropy',
                                               min_samples_split = 4,
                                               min_samples_leaf = 2,
                                               min_impurity_decrease = 0.01,
                                               random_state=0))])

ftwo_scorer = make_scorer(fbeta_score, beta=2)
ftwo_scorer = make_scorer(fbeta_score, beta=2)
search = GridSearchCV(pipe, param_grid = space, scoring = ftwo_scorer, cv = 4, return_train_score = True, verbose = 1)
search.fit(X = downsampled, y = target)
pd.DataFrame(search.cv_results_)

我收到此错误。

TypeError                                 Traceback (most recent call last)
<ipython-input-34-d10a6e74d8e8> in <module>
     20 ftwo_scorer = make_scorer(fbeta_score, beta=2)
     21 search = GridSearchCV(pipe, param_grid = space, scoring = ftwo_scorer, cv = 4, return_train_score = True, verbose = 1)
---> 22 search.fit(X = downsampled, y = target)
     23 pd.DataFrame(search.cv_results_)

~\AppData\Roaming\Python\Python37\site-packages\sklearn\utils\validation.py in inner_f(*args, **kwargs)
     70                           FutureWarning)
     71         kwargs.update({k: arg for k, arg in zip(sig.parameters, args)})
---> 72         return f(**kwargs)
     73     return inner_f
     74 

~\AppData\Roaming\Python\Python37\site-packages\sklearn\model_selection\_search.py in fit(self, X, y, groups, **fit_params)
    763             refit_start_time = time.time()
    764             if y is not None:
--> 765                 self.best_estimator_.fit(X, y, **fit_params)
    766             else:
    767                 self.best_estimator_.fit(X, **fit_params)

~\AppData\Roaming\Python\Python37\site-packages\sklearn\pipeline.py in fit(self, X, y, **fit_params)
    328         """
    329         fit_params_steps = self._check_fit_params(**fit_params)
--> 330         Xt = self._fit(X, y, **fit_params_steps)
    331         with _print_elapsed_time('Pipeline',
    332                                  self._log_message(len(self.steps) - 1)):

~\AppData\Roaming\Python\Python37\site-packages\sklearn\pipeline.py in _fit(self, X, y, **fit_params_steps)
    294                 message_clsname='Pipeline',
    295                 message=self._log_message(step_idx),
--> 296                 **fit_params_steps[name])
    297             # Replace the transformer of the step with the fitted
    298             # transformer. This is necessary when loading the transformer

~\AppData\Roaming\Python\Python37\site-packages\joblib\memory.py in __call__(self, *args, **kwargs)
    350 
    351     def __call__(self, *args, **kwargs):
--> 352         return self.func(*args, **kwargs)
    353 
    354     def call_and_shelve(self, *args, **kwargs):

~\AppData\Roaming\Python\Python37\site-packages\sklearn\pipeline.py in _fit_transform_one(transformer, X, y, weight, message_clsname, message, **fit_params)
    738     with _print_elapsed_time(message_clsname, message):
    739         if hasattr(transformer, 'fit_transform'):
--> 740             res = transformer.fit_transform(X, y, **fit_params)
    741         else:
    742             res = transformer.fit(X, y, **fit_params).transform(X)

~\AppData\Roaming\Python\Python37\site-packages\sklearn\base.py in fit_transform(self, X, y, **fit_params)
    691         else:
    692             # fit method of arity 2 (supervised transformation)
--> 693             return self.fit(X, y, **fit_params).transform(X)
    694 
    695 

TypeError: transform() missing 1 required positional argument: 'y'

如果我设置y=None,错误就会消失,但是y 不会改变!看起来管道功能仅将X 提供给预处理步骤。有人可以帮忙吗?

编辑

pipeline() 函数源代码将 X 和 y 提供给每个步骤的 fit() 方法,但是它只将 X 提供给 transform() 方法,因此无法更改 y。

我的解决方案是在管道之外进行异常值提取,因此在交叉验证之外进行,这很糟糕。

【问题讨论】:

标签: python scikit-learn


【解决方案1】:

关于在训练/测试中检测异常值的一件事,请记住,您使用的是较小的子集,因此它可能不太准确。如果目的只是排除,您可以在将其传递到管道之前执行此操作。

如果您确实需要这样做,那么在拟合范围内进行异常值检测会更有意义。下面是a comment by jnothman in github后面的代码修改:

from sklearn.base import BaseEstimator, ClassifierMixin, clone
from sklearn.neighbors import LocalOutlierFactor
from sklearn.ensemble import RandomForestClassifier

class WithoutOutliersClassifier(BaseEstimator, ClassifierMixin):
    def __init__(self, outlier_detector, classifier):
        self.outlier_detector = outlier_detector
        self.classifier = classifier

    def fit(self, X, y):
        self.outlier_detector_ = clone(self.outlier_detector)
        mask = self.outlier_detector_.fit_predict(X, y) == 1
        self.classifier_ = clone(self.classifier).fit(X[mask], y[mask])
        return self

    def predict(self, X):
        return self.classifier_.predict(X)

我们可以测试一下

import numpy as np
np.random.seed(111)
x = np.random.normal(0,1,(200,3))
y = np.random.binomial(1,0.5,200)

我们预计有 4 个异常值:

(LocalOutlierFactor(metric='euclidean').fit_predict(x) == 1).sum()
4

我设置oob_score = True 以表明分类器是在我们期望的子集上训练的:

rf = WithoutOutliersClassifier(LocalOutlierFactor(metric='euclidean'),
RandomForestClassifier(oob_score=True))
rf.fit(x,y)
rf.classifier_.oob_decision_function_.shape
 (196, 2)

现在将其放入管道中,注意参数名称的变化:

from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV

space = {'rf__classifier__max_depth': [3,4],
'rf__classifier__n_estimators' : [50,100]}

pipe = Pipeline([('scale', StandardScaler()),
                 ('rf', rf)])

search = GridSearchCV(pipe, param_grid = space)
search.fit(X = x, y = y)

【讨论】:

  • 谢谢!我有个疑问。我使用 QDA 作为分类器。 WithoutOutliersClassifier.fit 中克隆了self.classifier 的行引发了错误raise RuntimeError('Cannot clone object %s, as the constructor ' RuntimeError: Cannot clone object QuadraticDiscriminantAnalysis(priors=[0.5, 0.5]), as the constructor either does not set or modifies parameter priors。所以我刚刚删除了分类器的克隆,现在它似乎工作正常。这是危险的还是会导致不正确的行为?
  • @Álvaro,当我使用 scikit-learn.org/stable/modules/generated/… 尝试时,我没有收到您的错误,所以我认为这与您使用上述代码的方式有关
  • scikit-learn.org/stable/modules/generated/…关于克隆,所以你应该能够创建一个不合适的对象。我也可以看到这是一个完全不同的问题
猜你喜欢
  • 1970-01-01
  • 2020-12-14
  • 1970-01-01
  • 1970-01-01
  • 2019-06-17
  • 1970-01-01
  • 1970-01-01
  • 2013-03-29
  • 2013-10-16
相关资源
最近更新 更多