自定义Sklearn变压器可单独工作,在管道中使用时会引发错误

Max*_*wer 6 python pipeline machine-learning pandas scikit-learn

我有一个简单的sklearn类,我想将其用作sklearn管道的一部分。此类仅需要一个pandas数据框X_DF和一个分类列名,并调用pd.get_dummies以返回该列已变为伪变量矩阵的数据框。

import pandas as pd
from sklearn.base import TransformerMixin, BaseEstimator

class dummy_var_encoder(TransformerMixin, BaseEstimator):
    '''Convert selected categorical column to (set of) dummy variables    
    '''


    def __init__(self, column_to_dummy='default_col_name'):
        self.column = column_to_dummy
        print self.column

    def fit(self, X_DF, y=None):
        return self 

    def transform(self, X_DF):
        ''' Update X_DF to have set of dummy-variables instead of orig column'''        

        # convert self-attribute to local var for ease of stepping through function
        column = self.column

        # add columns for new dummy vars, and drop original categorical column
        dummy_matrix = pd.get_dummies(X_DF[column], prefix=column)

        new_DF = pd.concat([X_DF[column], dummy_matrix], axis=1)

        return new_DF
Run Code Online (Sandbox Code Playgroud)

现在,单独使用此变压器进行拟合/转换,我得到了预期的输出。对于以下一些玩具数据:

from sklearn import datasets
# Load toy data 
iris = datasets.load_iris()
X = pd.DataFrame(iris.data, columns = iris.feature_names)
y = pd.Series(iris.target, name='y')

# Create Arbitrary categorical features
X['category_1'] = pd.cut(X['sepal length (cm)'], 
                         bins=3, 
                         labels=['small', 'medium', 'large'])

X['category_2'] = pd.cut(X['sepal width (cm)'], 
                         bins=3, 
                         labels=['small', 'medium', 'large'])
Run Code Online (Sandbox Code Playgroud)

...我的虚拟编码器产生正确的输出:

encoder = dummy_var_encoder(column_to_dummy = 'category_1')
encoder.fit(X)
encoder.transform(X).iloc[15:21,:]

category_1
   category_1  category_1_small  category_1_medium  category_1_large
15     medium                 0                  1                 0
16      small                 1                  0                 0
17      small                 1                  0                 0
18     medium                 0                  1                 0
19      small                 1                  0                 0
20      small                 1                  0                 0
Run Code Online (Sandbox Code Playgroud)

但是,当我从sklearn管道调用相同的变压器时,如下所示:

from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.model_selection import KFold, GridSearchCV

# Define Pipeline
clf = LogisticRegression(penalty='l1')
pipeline_steps = [('dummy_vars', dummy_var_encoder()),
                  ('clf', clf)
                  ]

pipeline = Pipeline(pipeline_steps)

# Define hyperparams try for dummy-encoder and classifier
# Fit 4 models - try dummying category_1 vs category_2, and using l1 vs l2 penalty in log-reg
param_grid = {'dummy_vars__column_to_dummy': ['category_1', 'category_2'],
              'clf__penalty': ['l1', 'l2']
                  }

# Define full model search process 
cv_model_search = GridSearchCV(pipeline, 
                               param_grid, 
                               scoring='accuracy', 
                               cv = KFold(),
                               refit=True,
                               verbose = 3) 
Run Code Online (Sandbox Code Playgroud)

一切顺利,直到我适应管道,这时我才从虚拟编码器收到错误消息:

cv_model_search.fit(X,y=y)
Run Code Online (Sandbox Code Playgroud)

在[101]中:cv_model_search.fit(X,y = y)4个候选者分别拟合3折,总共12个拟合

无无无无[CV] dummy_vars__column_to_dummy = category_1,clf__penalty = l1 .........

追溯(最近一次通话):

文件“”,第1行,位于cv_model_search.fit(X,y = y)中

文件“ /home/max/anaconda3/envs/remine/lib/python2.7/site-packages/sklearn/model_selection/_search.py​​”,第638行,适合cv.split(X,y,groups)))

在self.dispatch_one_batch(iterator)的调用中,文件“ /home/max/anaconda3/envs/remine/lib/python2.7/site-packages/sklearn/externals/joblib/parallel.py”第779行:

文件“ /home/max/anaconda3/envs/remine/lib/python2.7/site-packages/sklearn/externals/joblib/parallel.py”,行625,位于dispatch_one_batch self._dispatch(tasks)中

在_dispatch作业= self._backend.apply_async(批处理,回调= cb)

文件“ /home/max/anaconda3/envs/remine/lib/python2.7/site-packages/sklearn/externals/joblib/_parallel_backends.py”,行111,位于apply_async结果= InstantResult(func)中

初始 self.results = batch()文件的第332行,添加文件“ /home/max/anaconda3/envs/remine/lib/python2.7/site-packages/sklearn/externals/joblib/_parallel_backends.py”

文件“ /home/max/anaconda3/envs/remine/lib/python2.7/site-packages/sklearn/externals/joblib/parallel.py”,行131,在调用 返回[func(* args,** kwargs) self.items中的func,args,kwargs]

在_fit_and_score estimator.fit(X_train,y_train,** fit_params)的第437行中,文件“ /home/max/anaconda3/envs/remine/lib/python2.7/site-packages/sklearn/model_selection/_validation.py”

文件“ /home/max/anaconda3/envs/remine/lib/python2.7/site-packages/sklearn/pipeline.py”,第257行,适合Xt,fit_params = self._fit(X,y,** fit_params )

_fit ** fit_params_steps [name]中的文件“ /home/max/anaconda3/envs/remine/lib/python2.7/site-packages/sklearn/pipeline.py”,第222行

文件“/home/max/anaconda3/envs/remine/lib/python2.7/site-packages/sklearn/externals/joblib/memory.py”,线路362,在调用 返回self.func(* ARGS,** kwargs )

_fit_transform_one res =转换器.fit_transform(X,y,** fit_params)中的文件“ /home/max/anaconda3/envs/remine/lib/python2.7/site-packages/sklearn/pipeline.py”,第589行

文件“ /home/max/anaconda3/envs/remine/lib/python2.7/site-packages/sklearn/base.py”,行521,在fit_transform中返回self.fit(X,y,** fit_params).transform (X)

转换dummy_matrix = pd.get_dummies(X_DF [column],prefix = column)中的文件“”,第21行

1964年在getitem 返回self中的文件“ /home/max/anaconda3/envs/remine/lib/python2.7/site-packages/pandas/core/frame.py”。_getitem_column(key )

文件“ /home/max/anaconda3/envs/remine/lib/python2.7/site-packages/pandas/core/frame.py”,第1971行,位于_getitem_column return self._get_item_cache(key)

文件“ /home/max/anaconda3/envs/remine/lib/python2.7/site-packages/pandas/core/generic.py”,行1645,在_get_item_cache值= self._data.get(item)中

在文件中获得“ /home/max/anaconda3/envs/remine/lib/python2.7/site-packages/pandas/core/internals.py”的行3599,并引发ValueError(“无法使用空键标记索引”)

ValueError:无法使用空键标记索引

Grr*_*Grr 4

跟踪准确地告诉您出了什么问题。学习诊断跟踪确实非常有价值,特别是当您从您可能没有完全理解的库继承时。

现在,我自己已经在 sklearn 中做了相当多的继承,我可以毫无疑问地告诉你,如果输入到你的或方法GridSearchCV中的数据类型不是 NumPy 数组,这会给你带来一些麻烦。正如 Vivek 在他的评论中提到的,传递给 fit 方法的 X 不再是 DataFrame。但让我们先看一下踪迹。fitfit_transform

ValueError:无法使用空键标记索引

虽然 Vivek 关于 NumPy 数组的说法是正确的,但这里还有另一个问题。您得到的实际错误是您的 fit 方法中的值为columnNone 。如果您查看encoder上面的对象,您会看到该__repr__方法输出以下内容:

dummy_var_encoder(column_to_dummy=None)
Run Code Online (Sandbox Code Playgroud)

使用时Pipeline,此参数被初始化并传递给GridSearchCV. 这种行为在交叉验证和搜索方法中也随处可见,并且具有与输入参数名称不同的属性会导致此类问题。解决这个问题将使您走上正确的道路。

修改该__init__方法将解决这个特定问题:

def __init__(self, column='default_col_name'):
    self.column = column
    print(self.column)
Run Code Online (Sandbox Code Playgroud)

然而,一旦你这样做了,Vivek 提到的问题就会浮现出来,你将不得不处理这个问题。这是我以前遇到过的事情,尽管不是专门针对 DataFrames 的。我在Use sklearn GridSearchCVon custom class中提出了一个解决方案,其 fit 方法需要 3 个参数。基本上,我创建了一个包装器,它以某种方式实现该__getitem__方法,使数据的外观和行为方式能够通过GridSearchCVPipeline和其他交叉验证方法中使用的验证方法。

编辑

我做了这些更改,看来您的问题来自验证方法check_array。虽然调用此方法dtype=pd.DataFrame可以工作,但线性模型调用此方法会dtype=np.float64引发错误。为了解决这个问题,而不是将原始数据与虚拟数据连接起来,您可以返回虚拟列并使用它们进行拟合。无论如何,这是应该完成的事情,因为您不想在您尝试拟合的模型中同时包含虚拟列和原始数据。你也可以考虑这个drop_first选择,但我偏离主题了。因此,像这样改变你的fit方法可以让整个过程按预期进行。

def transform(self, X_DF):
    ''' Update X_DF to have set of dummy-variables instead of orig column'''        

    # convert self-attribute to local var for ease of stepping through function
    column = self.column

    # add columns for new dummy vars, and drop original categorical column
    dummy_matrix = pd.get_dummies(X_DF[column], prefix=column)

    return dummy_matrix
Run Code Online (Sandbox Code Playgroud)

  • 原因一切都可以追溯到baseEstimator中的[_get_param_names](https://github.com/scikit-learn/scikit-learn/blob/ef5cb84a/sklearn/base.py#L186)。基本上,它不是获取实际属性,而是在 init 方法中查找参数名称并从那里开始。后来它在属性中查找这些名称,但显然找不到它们,因此该值设置为 None。 (3认同)