在本教程中,您将了解什么是数据泄漏以及如何预防它。如果您不知道如何预防,泄漏就会频繁发生,并会以微妙而危险的方式破坏您的模型。因此,这是实践数据科学家最重要的概念之一。

Introduction

数据泄漏(或泄漏)是指你的训练数据包含目标信息,但在使用模型进行预测时无法获得类似的数据。这会导致模型在训练集(甚至验证集)上表现出色,但在生产环境中表现不佳。

换句话说,泄漏会导致模型看起来很准确,直到你开始用它做决策,然后模型就会变得非常不准确。

泄漏主要有两种类型:目标泄漏训练-测试污染

目标泄漏

目标泄漏是指你的预测器包含在你进行预测时无法获得的数据。重要的是要从数据可用的时间或时间顺序来考虑目标泄漏,而不仅仅是某个特征是否有助于做出正确的预测。

举个例子会有所帮助。假设你想预测谁会患肺炎。你的原始数据的前几行如下所示:

got_pneumonia age weight male took_antibiotic_medicine
False 65 100 False False
False 72 130 True False
True 58 100 False True

人们在患肺炎后会服用抗生素药物来康复。原始数据显示这些列之间存在很强的相关性,但“took_antibiotic_medicine”在“got_pneumonia”的值确定后经常发生变化。这就是目标泄漏。

该模型会认为“took_antibiotic_medicine”值为“False”的任何人都没有患肺炎。由于验证数据与训练数据来自同一来源,因此该模式将在验证中重复出现,并且该模型将获得很高的验证(或交叉验证)分数。

但是,该模型随后在现实世界中部署时会非常不准确,因为即使是那些即将患肺炎的患者,在我们需要预测他们未来健康状况时,他们也还没有接受抗生素治疗。

为了防止这种类型的数据泄漏,任何在目标值实现后更新(或创建)的变量都应排除在外。

tut7_leakydata

Train-Test Contamination

当您没有仔细区分训练数据和验证数据时,就会发生另一种类型的泄漏。

回想一下,验证旨在衡量模型在之前未考虑过的数据上的表现。如果验证数据影响了预处理行为,您可能会以微妙的方式破坏此过程。这有时被称为训练-测试污染

例如,假设您在调用 train_test_split() 之前运行了预处理(例如,使用插补器拟合缺失值)。最终结果如何?您的模型可能获得了良好的验证分数,让您对其充满信心,但在部署它进行决策时,其表现却很差。

毕竟,您将验证数据或测试数据中的数据融入了您的预测过程中,因此即使它无法推广到新数据,也可能在特定数据上表现良好。当您进行更复杂的特征工程时,这个问题会变得更加微妙(也更加危险)。

如果您的验证基于简单的训练-测试拆分,请将验证数据排除在任何类型的拟合之外,包括预处理步骤的拟合。如果您使用 scikit-learn 流程,这会更容易。使用交叉验证时,在流程内部进行预处理就显得尤为重要!

Example

在本例中,您将学习一种检测和消除目标泄漏的方法。

我们将使用一个关于信用卡申请的数据集,并跳过基本的数据设置代码。最终结果是,每个信用卡申请的信息都存储在 DataFrame“X”中。我们将使用它来预测 Series“y”中哪些申请被接受。

import pandas as pd

# Read the data
data = pd.read_csv('../input/aer-credit-card-data/AER_credit_card_data.csv',
true_values = ['yes'], false_values = ['no'])

# Select target
y = data.card

# Select predictors
X = data.drop(['card'], axis=1)

print("Number of rows in the dataset:", X.shape[0])
X.head()
Number of rows in the dataset: 1319
reports age income share expenditure owner selfemp dependents months majorcards active
0 0 37.66667 4.5200 0.033270 124.983300 True False 3 54 1 12
1 0 33.25000 2.4200 0.005217 9.854167 False False 3 34 1 13
2 0 33.66667 4.5000 0.004156 15.000000 True False 4 58 1 5
3 0 30.50000 2.5400 0.065214 137.869200 False False 0 25 1 7
4 0 32.16667 9.7867 0.067051 546.503300 True False 2 64 1 5

由于这是一个小数据集,我们将使用交叉验证来确保模型质量的准确测量。

from sklearn.pipeline import make_pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score

# Since there is no preprocessing, we don't need a pipeline (used anyway as best practice!)
my_pipeline = make_pipeline(RandomForestClassifier(n_estimators=100))
cv_scores = cross_val_score(my_pipeline, X, y,
cv=5,
scoring='accuracy')

print("Cross-validation accuracy: %f" % cv_scores.mean())
Cross-validation accuracy: 0.981052

凭借经验,你会发现,准确率高达 98% 的模型非常罕见。这种情况确实存在,但并不常见,因此我们应该更仔细地检查数据,避免目标数据泄露。

以下是数据摘要,您也可以在数据选项卡下找到:

  • **card**:信用卡申请被接受,则为 1;未被接受,则为 0
  • **reports**:重大不良报告数量
  • **age**:年龄(n 岁加 1/12 岁)
  • **income**:年收入(除以 10,000)
  • **share**:每月信用卡支出占年收入的比例
  • **expenditure**:每月平均信用卡支出
  • **owner**:房屋自有,则为 1;租房,则为 0
  • **selfempl**:个体经营者,则为 1;非个体经营者,则为 0
  • **dependents**:1 + 受抚养人数量
  • **months**:在当前地址居住的月份
  • **majorcards**:持有的主要信用卡数量
  • **active**:活跃信用账户数量

一些变量看起来可疑。例如,“支出”是指这张卡的支出,还是申请前使用的卡的支出?

此时,基本数据比较会非常有帮助:

expenditures_cardholders = X.expenditure[y]
expenditures_noncardholders = X.expenditure[~y]

print('Fraction of those who did not receive a card and had no expenditures: %.2f' \
%((expenditures_noncardholders == 0).mean()))
print('Fraction of those who received a card and had no expenditures: %.2f' \
%(( expenditures_cardholders == 0).mean()))
Fraction of those who did not receive a card and had no expenditures: 1.00
Fraction of those who received a card and had no expenditures: 0.02

如上所示,所有未收到信用卡的人都没有支出,而收到信用卡的人中只有 2% 没有支出。我们的模型似乎具有较高的准确率,这并不奇怪。但这似乎也存在目标泄漏的情况,其中支出可能指的是他们申请的信用卡上的支出

由于**share部分由expenditure决定,因此也应将其排除。变量activemajorcards**不太明确,但从描述来看,它们听起来令人担忧。在大多数情况下,如果您无法找到创建数据的人来了解更多信息,最好谨慎行事。

我们将运行一个没有目标泄漏的模型,如下所示:

# Drop leaky predictors from dataset
potential_leaks = ['expenditure', 'share', 'active', 'majorcards']
X2 = X.drop(potential_leaks, axis=1)

# Evaluate the model with leaky predictors removed
cv_scores = cross_val_score(my_pipeline, X2, y,
cv=5,
scoring='accuracy')

print("Cross-val accuracy: %f" % cv_scores.mean())
Cross-val accuracy: 0.830919

这个准确率相当低,可能令人失望。然而,我们可以预期它在新应用上的准确率大约为 80%,而有泄漏的模型的表现可能会更差(尽管它在交叉验证中得分更高)。

Conclusion

在许多数据科学应用中,数据泄露可能造成数百万美元的损失。仔细分离训练数据和验证数据可以防止训练测试数据污染,而管道可以帮助实现这种分离。同样,谨慎、常识和数据探索的结合可以帮助识别目标泄露。