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

时间:2017-07-26 19:09:57

标签: python scikit-learn customization pickle pipeline

我正在尝试根据本教程的指导为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:\用户\ jjaaae \应用程序数据\本地\程序\ Python的\ Python36 \ LIB \站点包\ sklearn \ model_selection_search.py​​”   第945行,合适       return self._fit(X,y,groups,ParameterGrid(self.param_grid))文件   “C:\用户\ jjaaae \应用程序数据\本地\程序\ Python的\ Python36 \ LIB \站点包\ 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中       put(任务)文件“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:无法pickle:内置函数上的属性查找SelectBestPercFeats失败

3 个答案:

答案 0 :(得分:6)

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

答案 1 :(得分:0)

我遇到了同样的问题,但就我而言,问题是使用函数转换器,其中pickle有时难以序列化函数。对我来说,解决方案是改用dill,尽管它要慢一些。

答案 2 :(得分:0)

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

不仅如此,还需要进行序列化以使您的事物并行化(例如,您在n_jobs=-1中可见)使用多个线程。

使用scikit-learn的一件坏事是每个对象都应该有其保护程序。希望有一个解决方案。它要么使您的对象可序列化(从而从外部库中删除导入的内容),要么仅执行一项工作(无线程),要么使您的对象具有一个保护程序,该保护程序将保存对象以对其进行序列化。这里将探讨第二种解决方案。

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

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

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

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

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

好吧,这是出于创建Neuraxle的共同需求。

解决方案:在每个步骤中使用一系列保护程序

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

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

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

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

您不想做肮脏的代码。他们说,不要违反得墨meter耳定律。在我看来,这是最重要(且容易被忽视)的编程法则之一。 Google,我敢。违反此法则是代码库中最邪恶的根源。

我得出的结论是,在这里不违反该法律的最简洁方法是拥有一连串的储户。如果无法通过joblib进行序列化,它将使每个对象都有特殊的保护程序。整齐。因此,只要发生故障,您就可以选择为损坏的对象创建自己的序列化程序,这样,您就无需在保存时中断封装即可手动挖掘对象,这将打破Demeter的定律。

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

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

不可插拔管道的并行化

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