自定义的sklearn管道转换器出现“pickle.PicklingError”错误

10
我将为您翻译有关IT技术的内容,以下是需要翻译的文本:

我正在尝试根据这篇教程http://danielhnyk.cz/creating-your-own-estimator-scikit-learn/创建一个自定义转换器,用于Python sklearn流水线。

目前我的自定义类/转换器如下:

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设置有问题……可能我缺少一个包或类似的东西吗?我刚刚检查了一下,可以成功导入pickle 追踪(Traceback)信息如下:
``` Traceback (most recent call last): File "", line 5, in grid.fit(X_train, y_train) File "C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\site-packages\sklearn\model_selection\_search.py", line 945, in fit return self._fit(X, y, groups, ParameterGrid(self.param_grid)) File "C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\site-packages\sklearn\model_selection\_search.py", line 564, in _fit for parameters in parameter_iterable File "C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\site-packages\sklearn\externals\joblib\parallel.py", line 768, in __call__ self.retrieve() File "C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\site-packages\sklearn\externals\joblib\parallel.py", line 719, in retrieve raise exception File "C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\site-packages\sklearn\externals\joblib\parallel.py", line 682, in retrieve self._output.extend(job.get(timeout=self.timeout)) File "C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\multiprocessing\pool.py", line 608, in get raise self._value _pickle.PicklingError: Can't pickle : attribute lookup SelectBestPercFeats on builtins failed ```

或者,我的Python设置有问题吗?我可能缺少某个包或类似的东西吗?我刚刚检查了一下,我可以成功地导入pickle。 - Jed
我想我弄清楚了。pickle包需要在另一个模块中定义自定义类的定义并进行导入。因此,我创建了另一个名为_transformation.py_的文件,然后像这样导入它from transformation import SelectBestPercFeats。这解决了pickling错误。 - Jed
还要确保您可以对保存的估计器进行反序列化并按预期工作。 - Vivek Kumar
@VivekKumar,谢谢你提醒我。我检查了一下,所有的反序列化都没问题。然而,根据我的经验,情况并非总是如此,所以我很感激你的提醒。 - Jed
4个回答

13

pickle 包需要自定义类在另一个模块中被定义,然后再被引入。因此,创建另一个 Python 包文件(例如transformation.py),然后像这样导入它:from transformation import SelectBestPercFeats。这将解决 pickling 错误。


2
非常感谢您,因为我的自定义类在同一文件中,我花了4个小时来修复这个问题。 - DrGeneral
这在Python 3中对我不起作用,我快要疯了。请有人帮帮我哈哈!我有一个包,我把我的自定义预处理器放到另一个文件中,并在文件夹中创建了一个__init__.py,并尝试了所有可能的导入处理器到加载转储的预处理器的文件中的组合。但是包仍然无法识别它。 - eonurk
@eonurk,我建议您开始一个新的问题,并引用此问题,并说明这个问题没有解决您的问题。 - Jed
我通过在初始化文件中明确定义路径来解决了这个问题。您可以在此处找到该存储库:https://github.com/eonurk/sinkaf。另外,我在sinkaf.ipynb文件中说明了如何保存模型。谢谢。 - eonurk
@Jed Jupyter怎么样? - Ofir
@Ofir,一般来说这个解决方案也适用于Jupyter。你只需在Jupyter笔记本旁边的目录中创建自定义类文件(最简单的方法是在相同的目录中),然后将该文件导入到Jupyter笔记本中即可。 - Jed

2
当你编写自己的转换器时,如果这个转换器包含无法序列化的代码,则如果尝试序列化整个流水线,整个流水线都无法序列化。
不仅如此,您需要进行这种序列化才能并行化您的任务,比如使用n_jobs=-1,以使用多个线程。
scikit-learn的一个坏处是每个对象都应该有其保存者。幸运的是,有一个解决方案。要么使您的对象可序列化(因此删除从外部库导入的东西),要么只有一个作业(没有线程),要么使您的对象具有保存程序,以将对象保存为序列化。这里将探讨第二种解决方案。
首先,以下是一个问题的定义及其解决方案,摘自this source
这个问题只会在使用Scikit-Learn一段时间后才会浮出水面。这是无法回头的关键时刻:你已经编写了整个生产流程,但一旦你训练好并选择了最佳模型,你意识到你刚刚编写的内容无法序列化。

这意味着一旦训练完成,你的流程无法保存到磁盘,因为其中一个步骤从另一种编写在其他语言中的奇怪的Python库中导入东西和/或使用GPU资源。你的代码看起来很奇怪,你开始惊慌失措,因为这是一整年的研究开发成果。

希望你足够好心,在一边编写自己的开源框架,因为你将在接下来的100个编码项目中遇到同样的情况,而且你还有其他客户也会很快陷入同样的困境,这是至关重要的。

好吧,正是出于共同的需求,Neuraxle应运而生。

解决方案:在每个步骤中使用Saver链

每个步骤都负责保存自己,并且你应该为你的奇怪对象定义一个或多个自定义的saver对象。Saver应该:

  1. 使用Saver保存重要的步骤(请参见:Saver)。
  2. 从步骤中删除它(使其可串行化)。 步骤现在已被Saver剥离。
  3. 然后,默认的JoblibStepSaver将执行(在链中),保存所有剩余的剥离对象并从您代码的RAM中删除该对象。 这意味着您可以在最终默认的JoblibStepSaver之前拥有许多部分存储器。

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

  1. 将其所有子步骤保存在与管道序列化的子文件夹相关的子文件夹中。
  2. 从管道对象中删除它们,除了它们的名称以便在加载时找到它们。 现在,管道已被剥离。
  3. 让默认的Saver保存剥离的管道。

你不想写脏代码。 不要违反Demeter法则,他们说。 在我看来,这是编程中最重要(也最容易被忽视)的法则之一。 谷歌它,我敢打赌。 违反此法律是您代码库中大多数恶的根源。

我得出结论,避免违反这里的法律的最简洁方法是拥有一系列的Savers。如果某个对象无法与joblib进行序列化,则每个对象都负责具有特殊的Savers。这样当事情出错时,您就可以选择为导致错误的对象创建自己的序列化器,这样在保存时间时就不需要打破封装来手动挖掘对象了,从而违反Demeter定律。

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

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

你的非可拾取管道的并行处理

所以您已经使用Neuraxle完成了上述工作。很好。现在使用Neuraxle的AutoML和随机搜索等类,它们应该具有适当的抽象来使用保存器进行并行化,以序列化事物。为了实现并行化,需要将事物序列化以将代码发送到其他Python进程中。

0

我曾经遇到过同样的问题,但在我的情况下,问题是使用函数转换器时pickle有时难以序列化函数。对我来说解决方案是改用dill,尽管它稍微慢一些。


-1
在我的情况下,我只需要重新启动我检查变压器的IPython IDE。重新启动IDE并重新运行代码后,它要么运行良好,要么开始给出更有意义的错误。

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