自定义 sklearn 管道转换器提供 "pickle.PicklingError"

Custom sklearn pipeline transformer giving "pickle.PicklingError"

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

现在我的自定义 class/transformer 看起来像这样:

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_]

我正在将这个 Class 集成到这样的 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 (most recent call last): File "", line 5, in 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 File "C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\multiprocessing\pool.py", line 385, in _handle_tasks put(task) File "C:\Users\jjaaae\AppData\Local\Programs\Python\Python36\lib\site-packages\sklearn\externals\joblib\pool.py", line 371, in send CustomizablePickler(buffer, self._reducers).dump(obj) _pickle.PicklingError: Can't pickle : attribute lookup SelectBestPercFeats on builtins failed

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

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

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

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

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

首先,这是问题的定义及其解决方案,摘自 this source

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

这个问题只会在使用 Scikit-Learn 的某个时候浮出水面。这就是 no-return 的要点:您已经对整个生产流水线进行了编码,但是一旦对其进行训练并选择了最佳模型,您就会意识到您刚刚编码的内容无法序列化。

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

希望您能在业余时间开始编写自己的开源框架,因为在接下来的 100 个编码项目中您将遇到同样的情况,并且您的其他客户也会遇到同样的情况情况很快,这个 sh** 很关键。

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

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

每一步都负责自己保存,你应该为你的怪异对象定义一个或多个自定义保存器对象。储蓄者应该:

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

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

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

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

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

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

TL;DR: You can call the save() method on any pipeline in Neuraxle, and if some steps define a custom Saver, then the step will use that saver before using the default JoblibStepSaver.

非 picklable 管道的并行化

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

就我而言,我只需要重新启动检查变压器的 Ipython IDE。重新启动 IDE 和 re-running 代码后,它运行良好或开始给你一个更有意义的错误。