自定义 sklearn 管道变压器给出“pickle.PicklingError”

Posted

技术标签:

【中文标题】自定义 sklearn 管道变压器给出“pickle.PicklingError”【英文标题】:Custom sklearn pipeline transformer giving "pickle.PicklingError" 【发布时间】:2018-01-02 06:46:20 【问题描述】:

我正在尝试根据本教程的指导为 Python sklearn 管道创建一个自定义转换器:http://danielhnyk.cz/creating-your-own-estimator-scikit-learn/

现在我的自定义类/变压器看起来像这样:

class SelectBestPercFeats(BaseEstimator, TransformerMixin):
    def __init__(self, model=RandomForestRegressor(), percent=0.8,
                 random_state=52):
        self.model = model
        self.percent = percent
        self.random_state = random_state


    def fit(self, X, y, **fit_params):
        """
        Find features with best predictive power for the model, and
        have cumulative importance value less than self.percent
        """
        # Check parameters
        if not isinstance(self.percent, float):
            print("SelectBestPercFeats.percent is not a float, it should be...")
        elif not isinstance(self.random_state, int):
            print("SelectBestPercFeats.random_state is not a int, it should be...")

        # If checks are good proceed with fitting...
        else:
            try:
                self.model.fit(X, y)
            except:
                print("Error fitting model inside SelectBestPercFeats object")
                return self

            # Get feature importance
            try:
                feat_imp = list(self.model.feature_importances_)
                feat_imp_cum = pd.Series(feat_imp, index=X.columns) \
                    .sort_values(ascending=False).cumsum()

                # Get features whose cumulative importance is <= `percent`
                n_feats = len(feat_imp_cum[feat_imp_cum <= self.percent].index) + 1
                self.bestcolumns_ = list(feat_imp_cum.index)[:n_feats]
            except:
                print ("ERROR: SelectBestPercFeats can only be used with models with"\
                       " .feature_importances_ parameter")
        return self


    def transform(self, X, y=None, **fit_params):
        """
        Filter out only the important features (based on percent threshold)
        for the model supplied.

        :param X: Dataframe with features to be down selected
        """
        if self.bestcolumns_ is None:
            print("Must call fit function on SelectBestPercFeats object before transforming")
        else:
            return X[self.bestcolumns_]

我正在将这个类集成到这样的 sklearn 管道中:

# Define feature selection and model pipeline components
rf_simp = RandomForestRegressor(criterion='mse', n_jobs=-1,
                                n_estimators=600)
bestfeat = SelectBestPercFeats(rf_simp, feat_perc)
rf = RandomForestRegressor(n_jobs=-1,
                           criterion='mse',
                           n_estimators=200,
                           max_features=0.4,
                           )

# Build Pipeline
master_model = Pipeline([('feat_sel', bestfeat), ('rf', rf)])

# define GridSearchCV parameter space to search, 
#   only listing one parameter to simplify troubleshooting
param_grid = 
    'feat_select__percent': [0.8],


# Fit pipeline model
grid = GridSearchCV(master_model, cv=3, n_jobs=-1,
                    param_grid=param_grid)

# Search grid using CV, and get the best estimator
grid.fit(X_train, y_train)

每当我运行最后一行代码 (grid.fit(X_train, y_train)) 时,我都会收到以下“PicklingError”。谁能在我的代码中看到导致此问题的原因?

编辑:

或者,我的 Python 设置中是否有问题……我可能缺少一个包或类似的东西吗?我刚刚检查了我可以成功import pickle

Traceback(最近一次调用最后一次):文件“”,第 5 行,in 文件 "C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\site-packages\sklearn\model_selection_search.py​​", 第 945 行,合适 返回 self._fit(X, y, groups, ParameterGrid(self.param_grid)) 文件 "C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\site-packages\sklearn\model_selection_search.py​​", 第 564 行,在 _fit 对于 parameter_iterable 文件“C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\site-packages\sklearn\externals\joblib\parallel.py”中的参数, 第 768 行,在 调用 self.retrieve() 文件 "C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\site-packages\sklearn\externals\joblib\parallel.py", 第 719 行,在检索中 引发异常文件“C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\site-packages\sklearn\externals\joblib\parallel.py”, 第 682 行,在检索中 self._output.extend(job.get(timeout=self.timeout)) 文件 "C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\multiprocessing\pool.py", 第 608 行,在获取 提高 self._value 文件“C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\multiprocessing\pool.py”, 第 385 行,在 _handle_tasks 中 放置(任务)文件“C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\site-packages\sklearn\externals\joblib\pool.py”, 第 371 行,在发送中 CustomizablePickler(buffer, self._reducers).dump(obj) _pickle.PicklingError: Can't pickle : 属性查找 SelectBestPercFeats on builtins 失败

【问题讨论】:

或者,我的 Python 设置中是否有问题……我可能缺少一个包或类似的东西吗?我刚刚检查了我可以成功import pickle 我想我明白了。 pickle 包需要定义要在另一个模块中定义并导入的自定义类。所以我创建了另一个名为 transformation.py 的文件,然后像 from transformation import SelectBestPercFeats 一样将其导入。这解决了酸洗错误 还要确保您可以取消保存已保存的估算器并按预期工作。 @VivekKumar,感谢您的提醒。我检查了一下,一切都很好。但是,根据我的经验,情况并非总是如此,因此我很感激您的提醒。 【参考方案1】:

pickle 包需要在另一个模块中定义自定义类然后导入。所以,创建另一个 python 包文件(例如transformation.py),然后像这样from transformation import SelectBestPercFeats 导入它。这将解决酸洗错误。

【讨论】:

非常感谢,我花了 4 个小时试图解决这个问题,因为我的自定义类在同一个文件中。 这在 Python 3 中对我不起作用,我快要发疯了。求大神帮帮我哈哈!我有一个包,我将自定义预处理器放入另一个文件中,并在文件夹中创建了一个__init__.py,并尝试了将处理器导入到我加载转储预处理器的文件的所有可能组合。包仍然无法识别它。 @eonurk,我建议在引用这个问题的情况下开始一个新问题,并说这个问题没有解决你的问题。 我解决了它在初始化文件中明确定义路径。你可以在这里找到回购:github.com/eonurk/sinkaf。另外,我解释了如何将模型保存在 sinkaf.ipynb 文件中。干杯。【参考方案2】:

当您编写自己的转换器时,如果此转换器包含无法序列化的代码,那么如果您尝试对其进行序列化,整个管道将无法序列化。

不仅如此,您还需要这样的序列化才能并行化您的事物,例如您所说的 n_jobs=-1 可见,以使用多个线程。

scikit-learn 的一个坏处是每个对象都应该有它的保护程序。希望有一个解决方案。它要么使您的对象可序列化(并因此删除您从外部库导入的内容),要么只有 1 个作业(无线程),或者使您的对象具有保存对象以对其进行序列化的保护程序。这里将探讨第二种解决方案。

首先,这是一个问题的定义及其解决方案,取自this source:

问题:您无法使用 Joblib 无法“按原样”序列化的步骤来并行化或保存管道

这个问题只会在使用 Scikit-Learn 的某些时候出现。这就是不归路:您已经编写了整个生产流水线,但是一旦您对其进行了训练并选择了最佳模型,您就会意识到您刚刚编写的代码无法序列化。

这意味着一旦经过训练,您的管道就无法保存到磁盘,因为它的一个步骤会从以另一种语言编码的怪异 python 库导入内容和/或使用 GPU 资源。你的代码闻起来很奇怪,你开始为一整年的研究开发感到恐慌。

希望您能够很好地开始编写自己的开源框架,因为您将在接下来的 100 个编码项目中遇到同样的情况,而且您很快就会有其他客户处于同样的情况,而这个 sh** 很关键。

嗯,创建 Neuraxle 是出于共同的需求。

解决方案:在每个步骤中使用一系列 Savers

每个步骤都负责保存自己,您应该为您的怪异对象定义一个或多个自定义保存器对象。保护程序应该:

    使用 Saver 保存步骤中的重要内容(请参阅:Saver) 从步骤中删除它(使其可序列化)。该步骤现在已被 Saver 剥离。 然后,默认 JoblibStepSaver 将通过保存剥离对象的所有剩余部分并从代码的 RAM 中删除该对象,在该点之后执行(链式)。这意味着您可以在最终默认 JoblibStepSaver 之前拥有许多部分保存程序。

例如,管道将在调用 save() 方法时执行以下操作,因为它有自己的 TruncableJoblibStepSaver :

    将相关子文件夹中的所有子步骤保存到管道的序列化子文件夹中 从管道对象中删除它们,除了它们的名称,以便稍后在加载时找到它们。管道现已剥离。 让默认保护程序保存剥离的管道。

您不想编写脏代码。他们说,不要违反得墨忒耳法则。在我看来,这是最重要(但也很容易被忽视)的编程法则之一。谷歌一下,我敢。违反这条法律是代码库中最邪恶的根源。

我得出的结论是,在这里不违反这条法律的最巧妙方法是拥有一连串的 Savers。如果它不能用 joblib 序列化,它会让每个对象负责拥有特殊的保存器。整洁的。因此,当事情中断时,您可以选择为中断的对象创建自己的序列化程序,这样您就无需在保存时打破封装以手动挖掘对象,这将违反 Demeter 定律.

请注意,保存器也需要能够在加载保存时重新加载对象。我们已经写了TensorFlow Neuraxle saver。

TL;DR:您可以在 Neuraxle 中的任何管道上调用 save() 方法,如果某些步骤定义了自定义 Saver,则该步骤将在使用默认 JoblibStepSaver 之前使用该 saver。

非picklable管道的并行化

所以您已经使用 Neuraxle 完成了上述操作。整洁的。现在将 Neuraxle 的类用于 AutoML 和随机搜索等。他们应该有适当的并行化抽象,使用保存器来序列化事物。必须对事物进行序列化才能将您的代码发送到其他 python 进程以进行并行化。

【讨论】:

【参考方案3】:

我遇到了同样的问题,但在我的情况下,问题在于使用函数转换器,pickle 有时在序列化函数时遇到困难。我的解决方案是改用dill,虽然它有点慢。

【讨论】:

以上是关于自定义 sklearn 管道变压器给出“pickle.PicklingError”的主要内容,如果未能解决你的问题,请参考以下文章

Sklearn Pipeline:将参数传递给自定义变压器?

sklearn ColumnTransformer:变压器中的重复列

有没有办法在 sklearn 管道中链接 pd.cut FunctionTransformer?

使用自定义函数在 sklearn 中创建管道?

mlflow 如何使用自定义转换器保存 sklearn 管道?

用于自定义操作的 sklearn 管道