【问题标题】:How do I make GridSeachCV work with a custom transformer in my pipeline?如何让 GridSeachCV 在我的管道中使用自定义转换器?
【发布时间】:2015-09-08 10:34:51
【问题描述】:

如果我排除我的自定义转换器,GridSearchCV 运行良好,但会出错。 这是一个假数据集:

import pandas
import numpy
from sklearn_pandas import DataFrameMapper
from sklearn_pandas import cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.grid_search import GridSearchCV
from sklearn.base import TransformerMixin
from sklearn.preprocessing import LabelBinarizer
from sklearn.ensemble import RandomForestClassifier
import sklearn_pandas
from sklearn.preprocessing import MinMaxScaler

df = pandas.DataFrame({"Letter":["a","b","c","d","a","b","c","d","a","b","c","d","a","b","c","d"],
                       "Number":[1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4], 
                       "Label":["G","G","B","B","G","G","B","B","G","G","B","B","G","G","B","B"]})

class MyTransformer(TransformerMixin):

    def transform(self, x, **transform_args):
        x["Number"] = x["Number"].apply(lambda row: row*2)
        return x

    def fit(self, x, y=None, **fit_args):
        return self

x_train = df
y_train = x_train.pop("Label")    

mapper = DataFrameMapper([
    ("Number", MinMaxScaler()),
    ("Letter", LabelBinarizer()),
    ])

pipe = Pipeline([
    ("custom", MyTransformer()),
    ("mapper", mapper),
    ("classifier", RandomForestClassifier()),
    ])


param_grid = {"classifier__min_samples_split":[10,20], "classifier__n_estimators":[2,3,4]}

model_grid = sklearn_pandas.GridSearchCV(pipe, param_grid, verbose=2, scoring="accuracy")

model_grid.fit(x_train, y_train)

错误是

list indices must be integers, not str

当我的管道中有自定义转换器时,如何使 GridSearchCV 工作?

【问题讨论】:

  • 您可以添加您的import 语句,以便我可以运行您的代码吗?
  • +1 表示之前的评论。没有时间查看无法重现错误的代码。可能有必要从BaseEstimatorTransformerMixin 继承,但我的错误似乎并不指向那个方向。完整的回溯也很好......
  • 我得到一个不同的错误:AttributeError: 'MyTransformer' object has no attribute 'get_params'
  • 是的,您需要将 BaseEstimator 添加到您的估算器的超类列表中。
  • 我没有解决方案,但看起来问题出在非数字功能上。当您从df 中取出它时,它工作正常。问题可能是,当特征都是数字时,DataFrame 可以强制转换为 Numpy array,但是当存在非数字特征时,它会被强制转换为 list。如果您将print(type(x)) 放入MyTransformer.transform,您可以看到这一点

标签: python pandas scikit-learn


【解决方案1】:

短版:pandas 和 scikit-learn 的交叉验证方法不喜欢这样说话(在我的版本中,0.15);这可以通过将 scikit-learn 更新为 0.16/stable 或 0.17/dev 来解决。

GridSearchCV 类验证数据并将其转换为数组(以便它可以正确执行 CV 拆分)。所以你不能在内置的交叉验证循环中使用 Pandas DataFrame 功能。

如果你想做这种事情,你将不得不制作自己的不做验证的交叉验证例程。

编辑:这是我对 scikit-learn 的交叉验证例程的体验。这就是 sklearn-pandas 提供 cross_val_score 的原因。但是,据我所知, GridSearchCV 并不是 sklearn-pandas 专门的;您对它的导入意外地导入了默认的 sklearn 版本。因此,您可能必须使用 ParameterGrid 和 sklearn-pandas 的 cross_val_score 实现自己的网格搜索。

【讨论】:

  • 这不是真的。 Pipeline 中需要的只是任何具有 fittransform 方法的对象,该方法返回一个数组并采用正确的位置参数(数组 x,一维可选数组 y
  • @ssdecontrol 谢谢,你说的很对。我忘记了导致问题的是交叉验证,而不是 Pipeline 的。
  • Andreus 我也从来没有遇到过这个问题。
  • 抱歉,我知道我在这方面听起来有点生硬,@ssdecontrol。我添加了一个编辑,希望能解释我的经历。您的链接似乎没有引用与交叉验证相关的任何内容;我错过了什么吗?
  • 我使用的是最新版本,但它对我来说仍然坏掉了。我认为不同之处在于我的数据中没有任何非数字特征。这可能会影响任何内部强制过程。
【解决方案2】:

我知道这个答案来得太晚了,但我在 sklearn 和 BaseSearchCV 派生类中遇到了同样的行为。问题实际上似乎源于 sklearn cross_validation 模块中的 _PartitionIterator 类,因为它假设管道中每个 TransformerMixin 类发出的所有内容都将是类似数组的,因此它会生成索引切片用于以类似数组的方式索引传入的X args。这是__iter__ 方法:

def __iter__(self):
    ind = np.arange(self.n)
    for test_index in self._iter_test_masks():
        train_index = np.logical_not(test_index)
        train_index = ind[train_index]
        test_index = ind[test_index]
        yield train_index, test_index 

BaseSearchCV 网格搜索元类调用cross_validation 的_fit_and_score,它使用了一种称为safe_split 的方法。这是相关的行:

X_subset = [X[idx] for idx in indices]

如果 X 是您从 transform 函数发出的 pandas 数据帧,这绝对会产生意想不到的结果。

我发现有两种方法可以解决这个问题:

  1. 确保从你的转换器返回一个数组:

    return x.as_matrix()
    
  2. 这是一个黑客。如果转换器管道要求下一个转换器的输入是 DataFrame,就像我的情况一样,您可以编写一个实用程序脚本,该脚本与 sklearn grid_search 模块基本相同,但包含一些调用的巧妙验证方法BaseSearchCV 类的_fit 方法:

    def _validate_X(X):
        """Returns X if X isn't a pandas frame, otherwise 
        the underlying matrix in the frame. """
        return X if not isinstance(X, pd.DataFrame) else X.as_matrix()
    
    def _validate_y(y):
        """Returns y if y isn't a series, otherwise the array"""
        if y is None:
            return y
    
        # if it's a series
        elif isinstance(y, pd.Series):
            return np.array(y.tolist())
    
        # if it's a dataframe:
        elif isinstance(y, pd.DataFrame):
            # check it's X dims
            if y.shape[1] > 1:
                raise ValueError('matrix provided as y')
            return y[y.columns[0]].tolist()
    
        # bail and let the sklearn function handle validation
        return y
    

例如,here's my "custom grid_search module"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-03-08
    • 2020-06-17
    • 2019-11-20
    • 2018-11-04
    • 2020-10-25
    • 2020-06-24
    • 2017-12-24
    • 1970-01-01
    相关资源
    最近更新 更多