带有 SkLearn 管道的 GridSearch 无法正常工作

Posted

技术标签:

【中文标题】带有 SkLearn 管道的 GridSearch 无法正常工作【英文标题】:GridSearch with SkLearn Pipeline not working properly 【发布时间】:2018-12-11 03:52:08 【问题描述】:

我为列名以数字开头的功能子集实现了自定义 PCA,在 PCA 之后,将它们与其余功能连接起来。然后在网格搜索中实现一个 GBRT 模型作为 sklearn 管道。管道本身运行良好,但使用 GridSearch,每次发出错误时似乎都会获取数据子集。自定义 PCA 为:

class PartialPCA(BaseEstimator, TransformerMixin):

def __init__(self, n_components=0.9995, svd_solver='full', mask=None):
    # mask should contain selected cols. Suppose it is boolean to avoid code overhead
    self.n_components = n_components
    self.svd_solver = svd_solver
    self.mask = mask

def fit(self, X, y=None):
    print(X.shape)
    print(type(X))
    X.to_csv('InitialX.csv')
    print(X.isnull().values.any())
    X.reset_index(inplace=True, drop=True)
    self.remaining_cols = X[[i for i in X.columns if i[0].isdigit() is False]].copy()
    self.pca = PCA(n_components=self.n_components, svd_solver=self.svd_solver)
    mask = self.mask if self.mask is not None else slice(None)
    self.pca.fit(X[mask])
    return self

def transform(self, X, y=None):
    mask = self.mask if self.mask is not None else slice(None)
    pca_transformed = self.pca.transform(X[mask])
    if self.mask is not None:
        print(pca_transformed.shape)
        col_no = pca_transformed.shape[1]
        pca_transformed = pd.DataFrame(data=pca_transformed, columns=range(1, col_no + 1))
        X = pd.concat(objs=(self.remaining_cols, pca_transformed), axis=1)
        X.to_csv('X.csv')
        print(X.isnull().values.any())
        print(pca_transformed.isnull().values.any())
        print(self.remaining_cols.isnull().values.any())
        return X
    else:
        return pca_transformed

然后它被调用

mask = [i for i in trainPredTrans.columns if i[0].isdigit() is True]
pca = PartialPCA(n_components=0.9995, svd_solver='full', mask=mask)
print(pca)
gbrt = GradientBoostingRegressor(n_estimators=100, random_state=10)
pipe = Pipeline(steps=[('pca', pca), ('gbrt', gbrt)])
estimator = model_selection.GridSearchCV(pipe,param_grid=[dict(pca__svd_solver=['auto','full','arpack']),
                                               dict(gbrt__learning_rate=[0.1,  0.2,  0.3, 0.4,  0.5],
                                                                    gbrt__loss=["ls", "lad", "huber", "quantile"],
                                                                    gbrt__max_depth=[3, 4, 5],
                                                                    gbrt__min_samples_split=[2, 3, 4])])
print(estimator)
trainPredTrans.to_csv('trainPredTrans.csv')
estimator.fit(trainPredTrans, trainTarget.values.ravel())

输入火车数据框的形状为 (1198, 1248) 但在函数内部,当我打印 X.shape 时,它​​是 (798, 1248) 并且在拟合后它变成 (798, 97) 并且似乎再次迭代并且给出一个错误,说输入具有由于连接两个不同大小的数据帧而发生的 nan 值(但应该具有相同的大小)。 我花了很多时间,但无法弄清楚这个问题以及为什么它似乎在没有 gridsearch 的情况下工作。似乎 Gridsearch 正在使用 gbrt 参数来迭代 pca,这不应该发生

【问题讨论】:

【参考方案1】:

那是因为训练和测试数据的长度。 GridSearcCV 将根据 cv 参数将数据拆分为训练和测试。所以训练数据的长度会更多,保存到self.remaining_cols当测试数据去transform()时,你尝试将原始self.remaining_cols附加到新数据中,因此新数据有更多样本附加Nans 以匹配长度。

要解决这个问题,我建议您将 self.remaining_cols 逻辑移动到 transform() 而不是 fit()。像这样的:

...
...
def fit(self, X, y=None):
    X.reset_index(inplace=True, drop=True)
    self.pca = PCA(n_components=self.n_components, svd_solver=self.svd_solver)
    mask = self.mask if self.mask is not None else slice(None)
    self.pca.fit(X[mask])
    return self

def transform(self, X, y=None):
    mask = self.mask if self.mask is not None else slice(None)
    X.reset_index(inplace=True, drop=True)
    pca_transformed = self.pca.transform(X[mask])
    if self.mask is not None:
        col_no = pca_transformed.shape[1]
        pca_transformed = pd.DataFrame(data=pca_transformed, columns=range(1, col_no + 1))
        self.remaining_cols = X[[i for i in X.columns if i[0].isdigit() is False]].copy()
        X = pd.concat(objs=(self.remaining_cols, pca_transformed), axis=1)
        return X
    else:
        return pca_transformed

此外,要执行此类仅选择列子集进行某些处理的事情,我建议您查看 FeatureUnion 和 ItemSelector,如本示例中所述:

http://scikit-learn.org/stable/auto_examples/hetero_feature_union.html

注意:我观察到您将参数空间定义为两个字典。您不应该将字典列表发送到 GridSearchCV 会使它们排他性。这意味着它们将单独计算,而不是相互结合。

【讨论】:

感谢您指出这一点。您忘记将您提到的更改放在代码中,但我明白您所说的,现在它工作得很好。我选择用数据框而不是数组来做 pca,因为我想要一个动态 pca 代码,它只选择以数字开头的列而不是数组中的固定位置

以上是关于带有 SkLearn 管道的 GridSearch 无法正常工作的主要内容,如果未能解决你的问题,请参考以下文章

sklearn.pipeline.Pileline

ValueError:使用 GridSearch 参数时估计器 CountVectorizer 的参数模型无效

带有 ColumnTransformer 的 SKLearn 管道:'numpy.ndarray' 对象没有属性'lower'

具有 PredefinedSplit 评分的 Sklearn GridSearch 与独立分类器不匹配

使用带有 RBM 和 MLP Sklearn 的管道

带有 fit_transfrom 或预测对象而不是拟合对象的 sklearn 管道