为什么sklearn Pipeline调用transform()的次数比fit()多得多?

3

在阅读了很多关于pipeline.fit()操作的内容并且使用不同的verbose参数设置进行了检查后,我仍然困惑于我的管道为什么会多次访问某个步骤的transform方法。

下面是一个微不足道的示例pipeline,使用3折交叉验证的GridSearchCV进行fit,但只有一个超参数集合的param-grid。因此,我预期通过管道运行三次。正如预期的那样,step1和step2都调用了fit三次,但是每个步骤都调用了多次transform。这是为什么呢?下面是最小化的代码示例和日志输出。

# library imports
import pandas as pd
from sklearn import datasets
from sklearn.model_selection import KFold
from sklearn.linear_model import LogisticRegression
from sklearn.base import TransformerMixin, BaseEstimator
from sklearn.pipeline import Pipeline

# Load toy data
iris = datasets.load_iris()
X = pd.DataFrame(iris.data, columns = iris.feature_names)
y = pd.Series(iris.target, name='y')

# Define a couple trivial pipeline steps
class mult_everything_by(TransformerMixin, BaseEstimator):

    def __init__(self, multiplier=2):
        self.multiplier = multiplier

    def fit(self, X, y=None):
        print "Fitting step 1"
        return self

    def transform(self, X, y=None):
        print "Transforming step 1"
        return X* self.multiplier

class do_nothing(TransformerMixin, BaseEstimator):

    def __init__(self, meaningless_param = 'hello'):
        self.meaningless_param=meaningless_param


    def fit(self, X, y=None):
        print "Fitting step 2"
        return self

    def transform(self, X, y=None):
        print "Transforming step 2"
        return X

# Define the steps in our Pipeline
pipeline_steps = [('step1', mult_everything_by()),
                  ('step2', do_nothing()), 
                  ('classifier', LogisticRegression()),
                  ]

pipeline = Pipeline(pipeline_steps)

# To keep this example super minimal, this param grid only has one set
# of hyperparams, so we are only fitting one type of model
param_grid = {'step1__multiplier': [2],   #,3],
              'step2__meaningless_param': ['hello']   #, 'howdy', 'goodbye']
              }

# Define model-search process/object
# (fit one model, 3-fits due to 3-fold cross-validation)
cv_model_search = GridSearchCV(pipeline, 
                               param_grid, 
                               cv = KFold(3),
                               refit=False, 
                               verbose = 0) 

# Fit all (1) models defined in our model-search object
cv_model_search.fit(X,y)

输出:

Fitting step 1
Transforming step 1
Fitting step 2
Transforming step 2
Transforming step 1
Transforming step 2
Transforming step 1
Transforming step 2
Fitting step 1
Transforming step 1
Fitting step 2
Transforming step 2
Transforming step 1
Transforming step 2
Transforming step 1
Transforming step 2
Fitting step 1
Transforming step 1
Fitting step 2
Transforming step 2
Transforming step 1
Transforming step 2
Transforming step 1
Transforming step 2
1个回答

6

因为你使用了 cv = KFold(3)GridSearchCV,这将对你的模型进行交叉验证。下面是具体过程:

  1. 它将数据分为两部分:训练和测试。
  2. 对于训练,它将逐个拟合和转换管道的每个部分(不包括最后一个分类器)。这就是为什么你会看到fit step1, transform step1, fit step2, transform step2
  3. 它将在分类器上拟合转换后的数据(未在输出中打印)。
  4. 编辑 现在来到评分部分。在这里,我们不想重新拟合部分数据。我们将使用先前学习到的信息。因此,管道的每个部分只会调用transform()。这就是Transforming step 1, Transforming step 2的原因。

    它显示两次,因为在GridSearchCV中,默认行为是计算训练和测试数据的得分。这种行为由return_train_score控制。您可以设置return_train_score=False,然后只会看到一次。

  5. 这些转换后的测试数据将用于从分类器预测输出。(再次强调,不会在测试集上进行拟合,只有预测或转换)。

  6. 预测值将用于与实际值进行比较以评分模型。
  7. 步骤1-6将重复3次(KFold(3))
  8. 现在看一下您的参数:

    param_grid = {'step1__multiplier': [2], #,3], 'step2__meaningless_param': ['hello'] #, 'howdy', 'goodbye'] }

    展开后,只有单个组合,即:

    组合1:'step1__multiplier'=2,'step2__meaningless_param'='hello'

    如果您提供了更多选项,您已经注释了更多组合,则可能会出现更多组合,例如:

    组合1:'step1__multiplier'=2,'step2__meaningless_param'='hello'

    组合2:'step1__multiplier'=3,'step2__meaningless_param'='hello'

    组合3:'step1__multiplier'=2,'step2__meaningless_param'='howdy'

    等等..

  9. 步骤1-7将针对每个可能的组合重复。

  10. 在交叉验证的测试折叠上平均得分最高的组合将被选择最终使用完整数据(不分为训练和测试)来拟合模型。
  11. 但是您将refit=False。因此,模型将不会重新拟合。否则,您将看到一个输出:

    Fitting step 1 Transforming step 1 Fitting step 2 Transforming step 2

希望这能澄清问题。如有任何疑问请随时询问更多信息。

@MaxPower 啊,是的。我忽略了那个,抱歉。这是因为GridSearchCV还会记录训练数据上的分数。因此,它将再次基于训练数据对模型进行评分,因此需要重复的步骤,一次用于测试,一次用于训练。我很快就会更新答案,并提供一个示例。您可以查看GridSearchCV.cv_results_属性以检查它保留了哪些指标。 - Vivek Kumar
谢谢Vivek。通过return_train_score的解释,这就涵盖了所有打印的步骤。所以它是如何工作的。但是最后两个transform对于return_train_score真的必要吗?正如您在第2点中指出的那样,“对于训练...[我们有] fit step1transform step1fit step2transform step2”。然后管道“将转换后的数据拟合到分类器上”(您的3)。此时,我们已经拥有了生成train_score所需的一切:我们拟合的分类器将.predict()我们的train-preds。而且我们已经给出了train_y。那么为什么后面还需要额外的转换步骤呢? - Max Power
1
谢谢Vivek,我会去做的,不过可能要等到周末。是的,我也稍微了解了一下Pipeline的内存参数用于缓存转换器,但我不认为这解决了这个问题。虽然我可能误解了它。 - Max Power
1
问题已在此处提交:https://github.com/scikit-learn/scikit-learn/issues/10090 - Max Power
1
是的,我肯定不会与库开发人员就优先级进行争论。很高兴我现在至少知道每次发生步骤的原因。再次感谢您的答案和跟进,Vivek。 - Max Power
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接