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

Posted

技术标签:

【中文标题】为啥 sklearn Pipeline 调用 transform() 的次数比 fit() 多这么多?【英文标题】:Why does sklearn Pipeline call transform() so many more times than fit()?为什么 sklearn Pipeline 调用 transform() 的次数比 fit() 多这么多? 【发布时间】:2018-04-14 05:59:15 【问题描述】:

经过大量阅读和检查不同verbose参数设置下的pipeline.fit()操作后,我仍然很困惑为什么我的管道会多次访问某个步骤的transform方法。

下面是一个简单的示例pipelinefitGridSearchCV,使用 3 折交叉验证,但参数网格只有一组超参数。所以我预计管道中有三个运行。正如预期的那样,step1step2 都调用了三次 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】:

因为您已将GridSearchCVcv = KFold(3) 一起使用,这将对您的模型进行交叉验证。以下是发生的事情:

    它将数据分成两部分:训练和测试。 对于train,它将拟合和转换管道的每个部分(不包括最后一个,它是分类器)。这就是您看到fit step1, transform step1, fit step2, transform step2 的原因。 它将适合分类器上的转换数据(未打印在您的输出中。

    已编辑 现在是评分部分。在这里,我们不想再次重新安装零件。我们将使用之前拟合过程中学到的信息。所以管道的每一部分只会调用transform()。这就是Transforming step 1, Transforming step 2的原因。

    它显示了两次,因为在 GridSearchCV 中,默认行为是计算训练和测试数据的分数。此行为由return_train_score 产生。您可以设置return_train_score=False 并且只会看到它们一次。

    转换后的测试数据将用于预测分类器的输出。 (同样,没有拟合测试,只有预测或转换)。

    预测值将用于与实际值进行比较以对模型进行评分。 步骤 1-6 将重复 3 次 (KFold(3))

    现在看看你的参数:

    param_grid = 'step1__multiplier': [2], #,3], 'step2__meaningless_param': ['hello'] #, '你好', '再见']

    当展开时,它变成一个单一的组合,即:

    组合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'

    等等..

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

    将选择在交叉验证的测试折叠中给出最高平均分数的组合,以最终用完整的数据拟合模型(不分为训练和测试)。

    但是你保留了refit=False。所以模型不会再次拟合。否则你会看到更多的输出

    装配步骤 1 转换步骤 1 安装步骤 2 转换步骤 2

希望这可以解决这个问题。随时询问更多信息。

【讨论】:

您好 Vivek,非常感谢您的回答。我仍然不太明白它如何解释我的日志输出。您的第 2 点解释了我如何到达fit step1, transform step1, fit step2, transform step2。到目前为止,我和你在一起。您的第 4 点非常有帮助,我没有想到这一点,所以这解释了下一个 Transforming step 1, Transforming step 2。但在我们回到第二个 KFold 之前,我们还有更多 Transforming step 1, Transforming step 2 需要解释——我的日志在第 9 行重新开始,第二个实例为 Fitting step 1。我们如何解释日志输出中的第 7 行和第 8 行? @MaxPower 啊,是的。我忽略了,对不起。那是因为 GridSearchCV 也会记录训练数据的分数。因此它将再次根据训练数据对模型进行评分,因此重复步骤,一次用于测试,一次用于训练。我将在短时间内通过一个示例更新答案。您可以查看GridSearchCV.cv_results_ 属性以检查它保留了哪些指标。 谢谢 Vivek,会的,不过可能要到本周末。是的,我还阅读了Pipeline 用于缓存转换器的内存参数,但我认为这不能解决这个问题。不过我可能会误解它。 在此处提交的问题:github.com/scikit-learn/scikit-learn/issues/10090 是的,我当然不会与图书馆开发人员争论优先级。很高兴我至少知道为什么现在每次都会发生这些步骤。再次感谢您的回答并跟进 vivek

以上是关于为啥 sklearn Pipeline 调用 transform() 的次数比 fit() 多这么多?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 sklearn.pipeline 转换新数据

sklearn.pipeline.Pileline

使用 sklearn Pipeline 和 MultiOutputRegressor 访问属性

sklearn中pipeline的用法和FeatureUnion

是否可以安装 sklearn 管道的单独部分?

从 sklearn.pipeline.Pipeline 获取转换器结果