缺失值处理
1) 一个简单的选项:删除包含缺失值的列
最简单的选择是删除包含缺失值的列。

除非被删除列中的大多数值缺失,否则这种方法会使模型丢失大量(潜在有用!)的信息。举一个极端的例子,考虑一个包含10,000行的数据集,其中一列缺少一个条目。这种方法会完全删除该列!
# 获取缺失值
cols_with_missing = [col for col in X_train.columns
if X_train[col].isnull().any()]
# 在训练集和验证集中删除同样的列
reduced_X_train = X_train.drop(cols_with_missing, axis=1)
reduced_X_valid = X_valid.drop(cols_with_missing, axis=1)
print("MAE from Approach 1 (Drop columns with missing values):")
print(score_dataset(reduced_X_train, reduced_X_valid, y_train, y_valid))2) 更好的选择:插补
Imputation 会用一些数字填补缺失值。例如,我们可以沿每一列填充均值。

在大多数情况下,插补的值不会完全正确,但它通常能比完全删除该列得到更准确的模型。
from sklearn.impute import SimpleImputer
# Imputation
my_imputer = SimpleImputer()
imputed_X_train = pd.DataFrame(my_imputer.fit_transform(X_train))
imputed_X_valid = pd.DataFrame(my_imputer.transform(X_valid))
# Imputation removed column names; put them back
imputed_X_train.columns = X_train.columns
imputed_X_valid.columns = X_valid.columns
print("MAE from Approach 2 (Imputation):")
print(score_dataset(imputed_X_train, imputed_X_valid, y_train, y_valid))3) 对插补的扩展
插补是标准方法,通常效果良好。然而,插补的值可能与实际值系统性地高于或低于(这些值未在数据集中收集)。或者,缺失值的行可能在其他方面具有独特性。在这种情况下,通过考虑哪些值原本缺失,您的模型可以做出更好的预测。

在这种方法中,我们像之前一样填充缺失值。此外,对于原始数据集中缺失条目的每一列,我们添加一个新列来显示填充条目的位置。在某些情况下,这将显著改善结果。在其他情况下,它根本不起作用。
# 复制一份儿副本防止填充的时候直接对原始数据进行修改
X_train_plus = X_train.copy()
X_valid_plus = X_valid.copy()
# 创建新列表示哪些列会被填充
for col in cols_with_missing:
X_train_plus[col + '_was_missing'] = X_train_plus[col].isnull()
X_valid_plus[col + '_was_missing'] = X_valid_plus[col].isnull()
# Imputation
my_imputer = SimpleImputer()
imputed_X_train_plus = pd.DataFrame(my_imputer.fit_transform(X_train_plus))
imputed_X_valid_plus = pd.DataFrame(my_imputer.transform(X_valid_plus))
# 填充的时候会移除列名,现在需要加回来
imputed_X_train_plus.columns = X_train_plus.columns
imputed_X_valid_plus.columns = X_valid_plus.columns
print("MAE from Approach 3 (An Extension to Imputation):")
print(score_dataset(imputed_X_train_plus, imputed_X_valid_plus, y_train, y_valid))分类变量
分类变量只取有限数量的值。
考虑一项调查,询问你多久吃一次早餐,并提供四个选项:“从不”、“很少”、“大多数日子”或“每天”。在这种情况下,数据是分类的,因为回答落在固定的类别中。
如果人们回答一项关于他们拥有的汽车品牌的调查,回答将落入“本田”、“丰田”和“福特”等类别中。在这种情况下,数据也是分类的。
如果你在不预先处理它们的情况下将这些变量输入大多数Python机器学习模型,你会得到错误。在这个教程中,我们将比较三种你可以用来准备你的分类数据的方法。
看一个数据集:
Type | Method | Regionname | Rooms | Distance | Postcode | Bedroom2 | Bathroom | Landsize | Lattitude | Longtitude | Propertycount | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 12167 | u | S | Southern Metropolitan | 1 | 5.0 | 3182.0 | 1.0 | 1.0 | 0.0 | -37.85984 | 144.9867 | 13240.0 |
| 6524 | h | SA | Western Metropolitan | 2 | 8.0 | 3016.0 | 2.0 | 2.0 | 193.0 | -37.85800 | 144.9005 | 6380.0 |
| 8413 | h | S | Western Metropolitan | 3 | 12.6 | 3020.0 | 3.0 | 1.0 | 555.0 | -37.79880 | 144.8220 | 3755.0 |
| 2919 | u | SP | Northern Metropolitan | 3 | 13.0 | 3046.0 | 3.0 | 1.0 | 265.0 | -37.70830 | 144.9158 | 8870.0 |
| 6043 | h | S | Western Metropolitan | 3 | 13.3 | 3020.0 | 3.0 | 1.0 | 673.0 | -37.76230 | 144.8272 | 4217.0 |
1)删除分类变量
处理分类变量的最简单方法是直接从数据集中删除它们。如果列中不包含有用信息,这种方法才会有效。
drop_X_train = X_train.select_dtypes(exclude=['object'])
drop_X_valid = X_valid.select_dtypes(exclude=['object'])
print("MAE from Approach 1 (Drop categorical variables):")
print(score_dataset(drop_X_train, drop_X_valid, y_train, y_valid))2) 序数编码
序数编码将每个唯一值分配给不同的整数。

这种方法假设类别存在排序关系:“Never”(0)<“Rarely”(1)<“Most days”(2)<“Every day”(3)。
在这个例子中,这种假设是有道理的,因为类别之间存在明确的等级。并非所有分类变量在值上都有清晰的排序,但我们将那些具有清晰排序的变量称为有序变量。对于基于树的模型(如决策树和随机森林),你可以预期有序编码能够很好地与有序变量配合使用。
from sklearn.preprocessing import OrdinalEncoder
# 创建副本以避免直接修改原始数据
label_X_train = X_train.copy()
label_X_valid = X_valid.copy()
# 对每一列可分类的数据应用序数编码器
ordinal_encoder = OrdinalEncoder()
label_X_train[object_cols] = ordinal_encoder.fit_transform(X_train[object_cols])
label_X_valid[object_cols] = ordinal_encoder.transform(X_valid[object_cols])
print("MAE from Approach 2 (Ordinal Encoding):")
print(score_dataset(label_X_train, label_X_valid, y_train, y_valid))3) 独热编码
One-hot encoding 会创建新的列来指示原始数据中每个可能值的存在(或不存在)。为了理解这一点,我们将通过一个例子来讲解。

在原始数据集中,“颜色”是一个具有三个类别的分类变量:“红色”、“黄色”和“绿色”。相应的独热编码为每个可能的值包含一列,并为原始数据集中的每一行包含一行。在原始值是“红色”的地方,我们在“红色”列中放入1;如果原始值是“黄色”,我们在“黄色”列中放入1,以此类推。
与序数编码不同,独热编码不假设类别之间的顺序。因此,当分类数据没有明确的顺序时(例如,“红色”既不比“黄色”更优也不比它更劣),你可以预期这种方法会特别有效。我们称没有内在排序的分类变量为名义变量。
如果分类变量取很多值(即,你通常不会用它来处理超过15个不同值的变量),独热编码通常表现不佳。
from sklearn.preprocessing import OneHotEncoder
# 对每一列可分类的数据应用One Hot Encoder
OH_encoder = OneHotEncoder(handle_unknown='ignore', sparse=False)
OH_cols_train = pd.DataFrame(OH_encoder.fit_transform(X_train[object_cols]))
OH_cols_valid = pd.DataFrame(OH_encoder.transform(X_valid[object_cols]))
# 序数编码后会删掉索引,放回去
OH_cols_train.index = X_train.index
OH_cols_valid.index = X_valid.index
# 扔掉原来的分类列,用独热编码后的列替代
num_X_train = X_train.drop(object_cols, axis=1)
num_X_valid = X_valid.drop(object_cols, axis=1)
# 将One Hot Encodeed后的列加入到扔掉分类列的数据中
OH_X_train = pd.concat([num_X_train, OH_cols_train], axis=1)
OH_X_valid = pd.concat([num_X_valid, OH_cols_valid], axis=1)
# 确保所有列都是字符串,防止类名类型冲突
OH_X_train.columns = OH_X_train.columns.astype(str)
OH_X_valid.columns = OH_X_valid.columns.astype(str)
print("MAE from Approach 3 (One-Hot Encoding):")
print(score_dataset(OH_X_train, OH_X_valid, y_train, y_valid))我们获取训练数据中所有分类变量的列表。我们通过检查每一列的数据类型(或dtype)来实现这一点。object dtype表示该列包含文本(理论上它可以是其他东西,但这对我们的目的来说不重要)。对于上面的数据集,包含文本的列表示分类变量。
# Get list of categorical variables
s = (X_train.dtypes == 'object')
object_cols = list(s[s].index)
print("Categorical variables:")
print(object_cols)管道 Pipelines
管道是一种简单的方法,可以保持您的数据预处理和建模代码的条理清晰。具体来说,管道将预处理和建模步骤捆绑在一起,以便您可以将整个捆绑包作为一个步骤来使用。
许多数据科学家在没有管道的情况下拼凑模型,但管道有一些重要的优势。这些包括:
更清晰的代码:在预处理每个步骤中考虑数据可能会很混乱。使用管道,您就不需要手动跟踪每个步骤中的训练和验证数据。
更少的错误:犯错误的机会更少,或者忘记预处理步骤。
更容易投入生产:将模型从原型转变为可大规模部署的东西可能非常困难。我们不会在这里讨论许多相关的问题,但管道可以帮助。
先看一下数据集:
Type | Method | Regionname | Rooms | Distance | Postcode | Bedroom2 | Bathroom | Car | Landsize | BuildingArea | YearBuilt | Lattitude | Longtitude | Propertycount | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 12167 | u | S | Southern Metropolitan | 1 | 5.0 | 3182.0 | 1.0 | 1.0 | 1.0 | 0.0 | NaN | 1940.0 | -37.85984 | 144.9867 | 13240.0 |
| 6524 | h | SA | Western Metropolitan | 2 | 8.0 | 3016.0 | 2.0 | 2.0 | 1.0 | 193.0 | NaN | NaN | -37.85800 | 144.9005 | 6380.0 |
| 8413 | h | S | Western Metropolitan | 3 | 12.6 | 3020.0 | 3.0 | 1.0 | 1.0 | 555.0 | NaN | NaN | -37.79880 | 144.8220 | 3755.0 |
| 2919 | u | SP | Northern Metropolitan | 3 | 13.0 | 3046.0 | 3.0 | 1.0 | 1.0 | 265.0 | NaN | 1995.0 | -37.70830 | 144.9158 | 8870.0 |
| 6043 | h | S | Western Metropolitan | 3 | 13.3 | 3020.0 | 3.0 | 1.0 | 2.0 | 673.0 | 673.0 | 1970.0 | -37.76230 | 144.8272 | 4217.0 |
第一步:定义预处理步骤
类似于工作流程如何将预处理和建模步骤捆绑在一起,我们使用 ColumnTransformer 类将不同的预处理步骤捆绑在一起。下面的代码:对数值数据进行缺失值填充,并填充缺失值并对分类数据应用独热编码。
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder
# 对数值数据进行预处理
numerical_transformer = SimpleImputer(strategy='constant')
# 对分类数据进行预处理
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
# 将上面的步骤绑定起来
preprocessor = ColumnTransformer(
transformers=[
('num', numerical_transformer, numerical_cols),
('cat', categorical_transformer, categorical_cols)
])第二步,定义模型
接下来,我们使用熟悉的 RandomForestRegressor 类定义一个随机森林模型。
from sklearn.ensemble import RandomForestRegressor
model = RandomForestRegressor(n_estimators=100, random_state=0)第三步:创建管道并评估
最后,我们使用 Pipeline 类来定义一个包含预处理和建模步骤的流水线。有几个重要的注意事项:
- 使用流水线,我们可以在一行代码中预处理训练数据并拟合模型。(相比之下,如果没有流水线,我们必须分别进行插补、独热编码和模型训练。如果我们要处理数值型和分类型变量,这会变得特别混乱!)
- 使用流水线,我们将未处理的特征
X_valid输入到predict()命令中,流水线会自动在生成预测前对特征进行预处理。(然而,如果没有流水线,我们必须记得在做出预测前预处理验证数据。
from sklearn.metrics import mean_absolute_error
# 绑定预处理和模型部分到管道
my_pipeline = Pipeline(steps=[('preprocessor', preprocessor),
('model', model)
])
# 对训练集数据进行拟合
my_pipeline.fit(X_train, y_train)
# 对验证集数据进行预测
preds = my_pipeline.predict(X_valid)
# 评估
score = mean_absolute_error(y_valid, preds)
print('MAE:', score)交叉验证
机器学习是一个迭代的过程。
你将面临关于使用哪些预测变量、使用什么类型的模型、向模型提供什么参数等选择。到目前为止,你通过使用验证集(或保留集)来衡量模型质量,以数据驱动的方式做出了这些选择。
但这种方法的缺点是什么。要理解这一点,想象一下你有一个包含 5000 行数据的数据集。你通常会保留大约 20%的数据作为验证数据集,即 1000 行。但这在确定模型分数时会留下一些随机性。也就是说,一个模型可能在一组 1000 行数据上表现良好,即使它在另一组 1000 行数据上可能不准确。
一般来说,验证集越大,模型质量评估中的随机性(即"噪声")就越少,评估结果就越可靠。不幸的是,我们只能通过从训练数据中删除行来获得较大的验证集,而较小的训练数据集意味着更差的模型!
什么是交叉验证?
在交叉验证中,我们使用数据的不同子集来运行建模过程,以获得多个模型质量的度量。
例如,我们可以先将数据分成 5 份,每份占完整数据集的 20%。在这种情况下,我们称将数据分成了 5 个“折”。

然后,我们对每个折运行一个实验:
- 在实验 1 中,我们使用第一个折作为验证(或保留)集,其余所有数据作为训练数据。这给我们提供了一个基于 20%保留集的模型质量度量。
- 在实验 2 中,我们保留第二折的数据(并使用除第二折以外的所有数据进行模型训练)。然后使用保留集来获得模型质量的第二次估计。
- 我们重复这个过程,每次使用一个折作为保留集。综合来看,100%的数据在某个时刻被用作保留集,最终我们得到一个基于数据集所有行的模型质量度量(即使我们不会同时使用所有行)。
什么时候应该使用交叉验证?
交叉验证能提供更准确的模型质量度量,这在你要做出大量建模决策时尤其重要。然而,它可能需要更长时间来运行,因为它估计多个模型(每个折一个模型)。
所以,考虑到这些权衡,什么时候应该使用每种方法呢?
- 对于小数据集,如果额外的计算负担不是大问题,你应该运行交叉验证。
- 对于较大的数据集,单个验证集就足够了。你的代码运行速度会更快,而且你可能有足够的数据,以至于没有必要将其中一些数据用于保留。
没有简单的阈值来定义什么是大数据集或小数据集。但如果你的模型运行时间不到几分钟,那么切换到交叉验证可能值得。
或者,你可以运行交叉验证,看看每个实验的分数是否相近。如果每个实验都得到相同的结果,那么一个单独的验证集可能就足够了。
接着用上一部分的数据吧,继续使用管道
from sklearn.ensemble import RandomForestRegressor
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
my_pipeline = Pipeline(steps=[('preprocessor', SimpleImputer()),
('model', RandomForestRegressor(n_estimators=50,
random_state=0))
])我们使用 scikit-learn 中的 cross_val_score() 函数获取交叉验证分数。我们通过 cv 参数设置折数。
from sklearn.model_selection import cross_val_score
# Multiply by -1 since sklearn calculates *negative* MAE
scores = -1 * cross_val_score(my_pipeline, X, y,
cv=5,
scoring='neg_mean_absolute_error')
print("MAE scores:\n", scores)scoring 参数用于选择报告的模型质量度量:在这种情况下,我们选择了负均绝对误差(MAE)。scikit-learn 的文档中列出了多个选项。
我们指定负 MAE 这一点有点令人惊讶。Scikit-learn 有一个惯例,即所有指标都是这样定义的:数值越高越好。在这里使用负数使它们能够与这一惯例保持一致,尽管几乎在其他地方都很少见到负 MAE。
我们通常需要一个单一的性能指标来比较不同的模型。因此,我们通过实验取平均值。
print("Average MAE score (across experiments):")
print(scores.mean())XGBoost
我们将随机森林方法称为一种"集成方法"。根据定义,集成方法结合了多个模型的预测(例如,在随机森林的情况下,是多个树)。
接下来,我们将学习另一种集成方法,称为梯度提升。
梯度提升
梯度提升是一种通过循环迭代将模型添加到集成中的方法。它首先使用单个模型初始化集成,该模型的预测可能相当简单。(即使其预测非常不准确,后续添加到集成中的模型将解决这些错误。)然后,我们开始循环:
- 首先,我们使用当前集成模型为数据集中的每个观测值生成预测。为了进行预测,我们将集成中所有模型的预测结果相加。
- 这些预测结果用于计算损失函数(例如常见的均方误差MSE)。
- 然后,我们使用损失函数来拟合一个新的模型,该模型将被添加到集成中。具体来说,我们确定模型参数,以便将这个新模型添加到集成中能够降低损失。(旁注:在“梯度提升”中的“梯度”指的是我们将使用梯度下降法在损失函数上确定这个新模型的参数。)
- 最后,我们将新模型添加到集成模型中
- 重复上述步骤

XGBoost 代表极端梯度提升,它是梯度提升的一种实现,并具有几个针对性能和速度的额外特性。(Scikit-learn 有另一种梯度提升版本,但 XGBoost 有一些技术优势。)
from xgboost import XGBRegressor
my_model = XGBRegressor()
my_model.fit(X_train, y_train)我们还进行预测并评估模型。
from sklearn.metrics import mean_absolute_error
predictions = my_model.predict(X_valid)
print("Mean Absolute Error: " + str(mean_absolute_error(predictions, y_valid)))参数调优
XGBoost 有几个参数可以显著影响准确性和训练速度。你应该首先理解的参数是:
n_estimators 指定要经过上述建模周期的次数。它等于我们包含在集成中的模型数量。
- 值太低会导致欠拟合,这会导致在训练数据和测试数据上都不准确的预测。
- 过高值会导致过拟合,这会导致在训练数据上准确预测,但在测试数据上不准确预测(这是我们关心的)。
典型值范围在 100-1000 之间,尽管这很大程度上取决于下面讨论的 learning_rate 参数。
这是设置集成中模型数量的代码:
my_model = XGBRegressor(n_estimators=500)
my_model.fit(X_train, y_train)early_stopping_rounds 提供了一种自动找到 n_estimators 理想值的方法。早停机制会在验证分数停止提升时使模型停止迭代,即使我们还没有达到 n_estimators 的硬停点。设置一个较高的 n_estimators 值,然后使用 early_stopping_rounds 找到最佳的停止迭代时间,这是一个明智的选择。
由于随机性有时会导致一轮验证分数没有提升,你需要指定一个数字,表示在停止之前允许连续多少轮分数下降。设置 early_stopping_rounds=5 是一个合理的选择。在这种情况下,我们会在连续 5 轮验证分数下降后停止。
使用 early_stopping_rounds 时,还需要为计算验证分数预留一些数据——这是通过设置 eval_set 参数来完成的。
我们可以修改上面的示例以包含提前停止:
my_model = XGBRegressor(n_estimators=500)
my_model.fit(X_train, y_train,
early_stopping_rounds=5,
eval_set=[(X_valid, y_valid)],
verbose=False)如果你后来想用所有数据拟合模型,将 n_estimators 设置为在早停时找到的最佳值。
我们不必简单地将每个组件模型的预测值相加来获取预测值,而是在相加之前,可以将每个模型的预测值乘以一个小的数(称为学习率)。
这意味着我们添加到集成中的每一棵树对我们的帮助都减少了。因此,我们可以设置更高的 n_estimators 值而不至于过拟合。如果我们使用早停,合适的树的数量将自动确定。
一般来说,较小的学习率和较大的估计器数量会产生更精确的 XGBoost 模型,但它也会因为需要进行更多次迭代而使模型训练时间更长。默认情况下,XGBoost 设置 learning_rate=0.1 。
修改上述示例以改变学习率得到以下代码:
my_model = XGBRegressor(n_estimators=1000, learning_rate=0.05)
my_model.fit(X_train, y_train,
early_stopping_rounds=5,
eval_set=[(X_valid, y_valid)],
verbose=False)在运行时间是需要考虑的大数据集中,你可以使用并行处理来更快地构建模型。通常将参数 n_jobs 设置为你的机器上的核心数。在小数据集中,这不会有所帮助。
生成的模型不会更好,因此针对拟合时间进行微优化通常只是分散注意力。但在大数据集中,如果你原本在 fit 命令上需要花费很长时间等待,这会很有用。
my_model = XGBRegressor(n_estimators=1000, learning_rate=0.05, n_jobs=4)
my_model.fit(X_train, y_train,
early_stopping_rounds=5,
eval_set=[(X_valid, y_valid)],
verbose=False)数据泄露
数据泄漏(或泄漏)发生在你的训练数据包含有关目标的信息,但在模型用于预测时,类似的数据将不会可用。这会导致在训练集(甚至可能是验证数据)上表现出高性能,但模型在生产环境中表现不佳。
换句话说,泄漏会导致模型看起来准确,直到你开始使用模型做决策,然后模型会变得非常不准确。
泄漏主要有两种类型:目标泄漏和训练-测试污染。
目标泄露
目标泄漏是指你的预测模型包含了在预测时不可用的数据。重要的是要从数据变得可用的时间或时间顺序的角度来考虑目标泄漏,而不仅仅是某个特征是否有助于做出良好的预测。
举一个例子会更有帮助。假设你想预测谁会患上肺炎。你原始数据的前几行看起来像这样:
| got_pneumonia | age 年龄 | weight 体重 | male 男性 | took_antibiotic_medicine 服用抗生素 | ... |
|---|---|---|---|---|---|
| False | 65 | 100 | False | False | ... |
| False | 72 | 130 | True | False | ... |
| True | 58 | 100 | False | True | ... |
人们患上肺炎后会服用抗生素药物以恢复健康。原始数据显示这些列之间存在强烈的关联,但在确定 got_pneumonia 的值后, took_antibiotic_medicine 的值经常被更改。这是目标泄露。
模型会看到任何值为 False 的 took_antibiotic_medicine 的人都没有肺炎。由于验证数据与训练数据来自同一来源,该模式将在验证中重复出现,模型将获得很高的验证(或交叉验证)分数。
但是,当模型随后在现实世界中部署时,它将非常不准确,因为当我们需要预测他们未来的健康状况时,即将患肺炎的患者还没有收到抗生素。
为了防止这种类型的数据泄露,任何在目标值实现后更新(或创建)的变量都应该被排除。

训练测试污染
当你不小心区分训练数据与验证数据时,会发生另一种类型的泄露。
回想一下,验证的目的是衡量模型在它之前未曾考虑过的数据上的表现。如果验证数据影响了预处理行为,你可能会以微妙的方式破坏这个过程。这有时被称为训练-测试污染。
例如,假设你在调用 train_test_split() 之前运行预处理(比如拟合缺失值填充器)。结果会怎样?你的模型可能会获得良好的验证分数,让你对其充满信心,但在部署用于做决策时表现很差。
毕竟,你将验证或测试数据中的信息融入了你的预测方式,所以它可能在这个特定数据上表现良好,即使它无法泛化到新数据。当进行更复杂的特征工程时,这个问题会变得更加微妙(也更危险)。
如果你的验证基于简单的训练-测试拆分,请将验证数据排除在任何类型的拟合之外,包括预处理步骤的拟合。如果你使用 scikit-learn 的管道,这样做会更简单。在使用交叉验证时,将预处理步骤放在管道内部更是至关重要!
我们将使用一个关于信用卡申请的数据集,并跳过基本的数据设置代码。最终结果是,每个信用卡申请的信息都存储在一个 DataFrame X 中。我们将用它来预测哪些申请被接受了,结果存储在一个 Series y 中。
| 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
# 没有预处理,其实这里可以不需要管道的
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,否则为 0reports: 主要负面报告的数量age: 年龄 n 年加上十二分之一年income: 年收入(除以 10,000)share: 月信用卡支出与年收入之比expenditure: 平均每月信用卡支出owner: 1 如果拥有住房,0 如果租房selfempl: 1 如果是自雇,0 如果不是dependents: 1 加上抚养人数months: 在当前地址居住的月数majorcards: 持有主要信用卡的数量active: 活跃的信用卡账户数量
有几个变量看起来很可疑。例如, expenditure 是指这张卡上的支出还是申请前使用的卡上的支出?
此时,基本的数据比较会非常有帮助:
如上所示,所有未收到卡的人没有支出,而收到卡的人中只有 2%没有支出。我们的模型看起来具有高准确度并不令人意外。但这似乎也是一个目标泄漏的案例,其中支出可能指的是他们申请的卡的支出。
由于 share 部分由 expenditure 决定,因此也应该被排除。变量 active 和 majorcards 稍微不太明确,但从描述来看,它们似乎存在问题。在大多数情况下,如果你无法追踪到创建数据的人以获取更多信息,那么安全起见更好。
我们将按照以下方式运行一个无目标泄漏的模型:
# 扔到有泄露嫌疑的特征
potential_leaks = ['expenditure', 'share', 'active', 'majorcards']
X2 = X.drop(potential_leaks, axis=1)
# 评估
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%的准确率,而漏数据模型可能会比这差得多(尽管它在交叉验证中的得分更高)。
文章评论