Scikit-Learn:在交叉验证期间避免数据泄漏

Posted

技术标签:

【中文标题】Scikit-Learn:在交叉验证期间避免数据泄漏【英文标题】:Scikit-Learn: Avoiding Data Leakage During Cross-Validation 【发布时间】:2018-07-07 00:54:12 【问题描述】:

我刚刚阅读了 k 折交叉验证,并意识到我在使用当前的预处理设置时无意中泄露了数据。

通常,我有一个训练和测试数据集。我对整个火车数据集进行了一堆数据插补和 one-hot 编码,然后运行 ​​k 折交叉验证。

出现泄漏是因为,如果我进行 5 折交叉验证,我将使用 80% 的训练数据进行训练,并在剩余的 20% 的训练数据上对其进行测试。

我真的应该根据 80% 的训练来估算 20%(而我之前使用的是 100% 的数据)。

1) 这是思考交叉验证的正确方法吗?

2) 我一直在查看sklearn.pipeline 中的Pipeline 类,它似乎对进行一堆转换然后最终将模型拟合到结果数据很有用。但是,我正在做很多事情,例如“用平均值估算 float64 列中的缺失数据”、“用模式估算所有其他数据”等。

这种插​​补没有明显的变换器。我将如何将此步骤添加到Pipeline?我可以自己创建BaseEstimator 的子类吗?

这里的任何指导都会很棒!

【问题讨论】:

是的,您应该在新类中扩展 BaseEstimator 和 Transformermixin 类,并在其中使用 Imputer 【参考方案1】:

1) 是的,您应该使用 80% 的训练数据来估算 20% 的测试数据。

2) 我写了a blog post 来回答你的第二个问题,但我会在这里包含核心部分。

使用sklearn.pipeline,您可以将单独的预处理规则应用于不同的特征类型(例如,数字、分类)。在下面的示例代码中,我在缩放数字特征之前估算了它们的中值。分类特征和布尔特征是用模式估算的——分类特征是 one-hot 编码的。

您可以在管道末端包含一个估计器,用于回归、分类等。

import numpy as np
from sklearn.pipeline import make_pipeline, FeatureUnion
from sklearn.preprocessing import OneHotEncoder, Imputer, StandardScaler

preprocess_pipeline = make_pipeline(
    FeatureUnion(transformer_list=[
        ("numeric_features", make_pipeline(
            TypeSelector(np.number),
            Imputer(strategy="median"),
            StandardScaler()
        )),
        ("categorical_features", make_pipeline(
            TypeSelector("category"),
            Imputer(strategy="most_frequent"),
            OneHotEncoder()
        )),
        ("boolean_features", make_pipeline(
            TypeSelector("bool"),
            Imputer(strategy="most_frequent")
        ))
    ])
)

管道的TypeSelector 部分假定对象X 是pandas DataFrame。使用TypeSelector.transform 选择具有给定数据类型的列子集。

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

class TypeSelector(BaseEstimator, TransformerMixin):
    def __init__(self, dtype):
        self.dtype = dtype

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

    def transform(self, X):
        assert isinstance(X, pd.DataFrame)
        return X.select_dtypes(include=[self.dtype])

【讨论】:

【参考方案2】:

我建议将 5 折交叉验证视为简单地将数据分成 5 个部分(或折叠)。您将其中的一个折叠起来用于测试,并将另外 4 个折叠起来用于您的训练集。我们再重复这个过程 4 次,直到每个折叠都有机会被测试。

为了使您的插补正常工作并且不受污染,您需要确定用于测试的 4 折的平均值,并使用它来插补训练集和测试集中的值。

我喜欢用StratifiedKFold 实现CV 拆分。这将确保您在折叠中的每个类都有相同数量的样本。

要回答您有关使用管道的问题,我想说您可能应该使用自定义的 Imputation 转换器对 BaseEstimator 进行子类化。在 CV 拆分的循环内部,您应该从训练集中计算平均值,然后将此平均值设置为转换器中的参数。然后你可以调用 fit 或 transform。

【讨论】:

以上是关于Scikit-Learn:在交叉验证期间避免数据泄漏的主要内容,如果未能解决你的问题,请参考以下文章

在交叉验证后对所有训练数据进行 scikit-learn 训练

scikit-learn 中纵向/面板数据的交叉验证

scikit-learn 中每个数据拆分的交叉验证指标

scikit-learn 的 LassoCV 的评分指标

为啥 scikit-learn SVM 分类器交叉验证这么慢?

Scikit-Learn 中的分层标记 K 折交叉验证