Kaggle课程 — 机器学习进阶 Intermediate Machine Learning
)
本文翻译自kaggle官方网站https://www.kaggle.com/learn/intermediate-machine-learning,仅供参考。
1.简介
假如你有机器学习的背景知识并且也愿意学习如何快速地改进你的模型质量,你来对地方了。在这门微课程中,通过学习如下知识你将加速你的机器学习专长:
- 处理实际中经常遇到的数据问题(缺失值、变量分类)
- 设计流水线来改进你的机器学习模型代码
- 使用先进的技术来验证模型(交叉验证)
- 创建被广泛使用来赢取Kaggle竞赛的顶尖模型(
XGBoost
) - 避免普遍并且重要的数据科学错误(泄漏)
一路上,你将通过完成练习来巩固你的知识。练习中使用的数据来自于kaggle房屋价格竞赛,你将使用79个不同的解释变量(比如屋顶类型、卫生间数量等)来预测房屋价格。你将通过提交预测结果来观察你在排行榜的排名情况。
1.1 先决条件
假如你之前已经创建一个模型,并且熟悉模型评估、欠拟合和过度拟合、随机森林等概念,那么你就做好了学习该课程的准备。
如果你刚开始学习机器学习,请先学习机器学习入门课程,入门课程包含了学习进阶课程的各项准备。
2.缺失值
在本节中,你将学习三种方法来处理缺失值。然后你将比较这三种方法用于现实数据的效率。
2.1 简介
有许多方式导致数据以缺失值结束。比如:
- 一间2居室房屋未包含有3居室房屋的价值
- 调查对象可能选择不共享他的收入情况
如果你尝试使用含缺失值的数据来建模,许多机器学习库(包括scikit-learn
)将发生错误,因此你需要选择如下一些策略。
2.2 三种方法
1)简单方法:删除含有缺失值的列
最简单的方法之一就是删掉含有缺失值的列。
除非删掉的列具有大量有用的信息,否则你都可以这么做。一个极端的例子,考虑一个含有10000行的数据集,然而一个重要的列只要有一项缺失,该方法将删除整列。
2)较好的方法:Imputation
Imputation
方法是用一些数字来填充缺失值。例如,我们可以使用每一列的平均值来填充。
大多数情况下imputed values
不一定完全正确,但是通常来说这样做比删掉缺失值更加准确。
3)Imputation
方法扩展
Imputation
方法是基本的方法,并且通常来说运行良好。然而,imputed values
可能系统性地比实际值大或小。或者某些情况下有缺失值的行是唯一的。在这种情况下,通过考虑哪些值是自然缺失的将使你的模型有更好的预测结果。
在这种方法下,我们像之前一样填充缺失值。并且,额外的,对于原始数据集中含有缺失项的每一列,我们添加一个新的列来表示新增填充项的位置。
在一些情况下,这样做将改进预测结果。在某些情况下,也有可能没有什么用。
2.3 一个例子
在本例中,我能将使用Melbourne Housing dataset
。我们的模型将使用房间数量、土地面积等信息来预测房屋价格。
我们将不关心如何加载数据。你可以试想你已经完成该步骤并且有训练数据和验证数据X_train, X_valid, y_train, y_valid
。
import pandas as pd
from sklearn.model_selection import train_test_split
# Load the data
data = pd.read_csv('../input/melbourne-housing-snapshot/melb_data.csv')
# Select target
y = data.Price
# To keep things simple, we'll use only numerical predictors
melb_predictors = data.drop(['Price'], axis=1)
X = melb_predictors.select_dtypes(exclude=['object'])
# Divide data into training and validation subsets
X_train, X_valid, y_train, y_valid = train_test_split(X, y, train_size=0.8, test_size=0.2,
random_state=0)
2.3.1 定义函数来测量每种方法
我们定义一个函数score_dataset()
来比较不同方法处理缺失值。通过该函数报告一个随机森林模型的MAE
。
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error
# Function for comparing different approaches
def score_dataset(X_train, X_valid, y_train, y_valid):
model = RandomForestRegressor(n_estimators=10, random_state=0)
model.fit(X_train, y_train)
preds = model.predict(X_valid)
return mean_absolute_error(y_valid, preds)
2.3.2 方法1得分(删除缺失值)
# Get names of columns with missing values
cols_with_missing = [col for col in X_train.columns
if X_train[col].isnull().any()]
# Drop columns in training and validation data
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))
MAE from Approach 1 (Drop columns with missing values):183550.22137772635
2.3.3 方法2得分(Imputation
)
接下来,我们使用SimpleImputer
将缺失的值替换为每一列的平均值。
尽管填充平均值很简单,但通常执行得很好(但这因数据集而异)。虽然统计学家已经尝试了更复杂的方法来确定输入值(例如回归输入),但一旦你将结果插入复杂的机器学习模型,这些复杂的策略通常不会带来额外的好处。
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))
MAE from Approach 2 (Imputation):178166.46269899711
2.3.4 方法3得分(Imputation
方法扩展)
# Make copy to avoid changing original data (when imputing)
X_train_plus = X_train.copy()
X_valid_plus = X_valid.copy()
# Make new columns indicating what will be imputed
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))
# Imputation removed column names; put them back
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))
MAE from Approach 3 (An Extension to Imputation):178927.503183954
综上所述,方法3的表现比方法2略差。
如上,为什么方法2比方法1表现好?
训练数据有10864行和12列,其中有3列含有缺失值。对于每一列,不超过一半的项缺失。因此,删掉列的同时也丢失了大量有用信息,所以imputation
方法表现更好。
# Shape of training data (num_rows, num_columns)
print(X_train.shape)
# Number of missing values in each column of training data
missing_val_count_by_column = (X_train.isnull().sum())
print(missing_val_count_by_column[missing_val_count_by_column > 0])
(10864, 12)
Car 49
BuildingArea 5156
YearBuilt 4307
dtype: int64
2.4 小结
通常,比起直接删除含有缺失值的列,填充缺失值获得更好的结果。
3.变量分类
3.1 简介
一个分类变量只有有限数量的值。
- 考虑一个你经常吃早餐吗的调查,并提供4个选项:从不、很少、大部分时候、每天。在这种情况下,这类数据做了分类,因为答案落在固定的分类里面。
- 假如人们回答关于他们拥有什么品牌的汽车,这种情况下回答将落在如下分类:本田、丰田、福特等。
如果你尝试将这类变量插入python
中的大多数机器学习模型并没有事先做预处理,你将遇到错误。
3.2 三种方法
3.2.1 删除分类变量
处理分类变量最简单的方法就是从数据集中删除分类变量。这种方法只有当该列不含有用信息时候能够运行良好。
3.2.2标签编码Label Encoding
Label Encoding
方法是给每一个特殊的值赋予不同的整数。
这种方法假设分类遵从一种次序,如“从不(0)<很少(1)<大部分时候(2)<每天(3)”。
在本例中这种假设是有意义的,因为该分类的确存在一种排序。并不是所有的分类变量都有一个明确的次序,但是我们将有顺序的变量称作序数变量ordinal variables
。对于三种基础模型(如决策树和随机森林),你可以期待标签编码方法能够像序数变量那样表现很好。
3.2.3 独热编码 One-hot Encoding
独热编码方法生成新的列指向原始数据集中每个可能存在或不存在的值。我们通过一个例子来帮助理解。
在原始数据集中,Color
是一个分类变量,有三种分类:“Red”,“Yellow”和“Green”。正确的独热编码包括含有每一个可能的值的列,和每一个对应原始数据的行。当原始值是“Red”,我们将1放到“Red”列,当原始值是“Yellow”,我们将1放到“Yellow”列,以此类推。
与标签编码相比,独热编码不用假设分类的排序。因此,当分类变量没有清楚的次序时,独热编码方法会变现的更好(例如,红色既没有比黄色多也没有比黄色少)。我们将分类变量没有内在次序称作名义变量nominal variables
。
假如分类变量有大量的值,独热编码通常变现不是很好。(也就是说,通常情况下,当分类变量超过15个不同的值,你不会使用该方法。)
3.3 举例
如之前的教程,我们使用Melbourne Housing
数据集。
我们不关心加载数据的步骤。试想你已经完成该步骤并有如下训练数据和验证数据:X_train, X_valid, y_train, and y_valid
。
我们先通过head()
方法查看一下训练数据的前几行,如下:
X_train.head()
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
接下来,我们获取训练数据中所有分类变量的列表。
我们通过查看每一列的数据类型dtype
来获取。object
类型表明该列含有文本。对于该数据集,列中含有文本表明其为分类变量。
# Get list of categorical variables
s = (X_train.dtypes == 'object')
object_cols = list(s[s].index)
print("Categorical variables:")
print(object_cols)
Categorical variables:
['Type', 'Method', 'Regionname']
3.3.1 定义函数来评价每一种方法
我们定义一个函数score_dataset()
来比较处理分类变量的三种不同方法。这个函数报告一个随机森林模型的平均绝对误差MAE
。通常来说,我们想要MAE
越低越好。
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error
# Function for comparing different approaches
def score_dataset(X_train, X_valid, y_train, y_valid):
model = RandomForestRegressor(n_estimators=100, random_state=0)
model.fit(X_train, y_train)
preds = model.predict(X_valid)
return mean_absolute_error(y_valid, preds)
3.3.2 方法1得分(删除分类变量)
我们通过select_dtypes()
方法来删除object
列。
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))
MAE from Approach 1 (Drop categorical variables):
175703.48185157913
3.3.3 方法2得分(标签编码)
scikit-learn
有一个LabelEncoder
类能够用来做标签编码。我们遍历分类变量并且对每一列运用标签编码。
from sklearn.preprocessing import LabelEncoder
# Make copy to avoid changing original data
label_X_train = X_train.copy()
label_X_valid = X_valid.copy()
# Apply label encoder to each column with categorical data
label_encoder = LabelEncoder()
for col in object_cols:
label_X_train[col] = label_encoder.fit_transform(X_train[col])
label_X_valid[col] = label_encoder.transform(X_valid[col])
print("MAE from Approach 2 (Label Encoding):")
print(score_dataset(label_X_train, label_X_valid, y_train, y_valid))
MAE from Approach 2 (Label Encoding):
165936.40548390493
在上面的代码模块中,对于每一列,我们随机给每一个值赋予一个不同的整数。这是一种普遍的方法,比传统的标签简单。然而,我们能够期待一种表现的提升,假如我们提供更好的信息标签给所有的序列变量。
3.3.4 方法3得分(独热编码)
我们从scikit-learn
中调用OneHotEncoder
类来进行独热编码。这里有大量的参数可以用来改变其表现。
- 当验证数据包含的类不在训练数据中体现的时候,我们设置
handle_unknown='ignore'
来避免错误。 - 设置
sparse=False
来确保被编码的列返回为一个numpy array
(而不是一个sparse matrix
)
为了调用编码器,我们仅需提供我们想要编码的分类变量的列。例如,为了编码训练数据,我们提供X_train[object_cols]
。(下面代码单元中的object_cols
是包含分类数据的列名列表,因此X_train[object_cols]
包含训练集中的所有分类数据。)
from sklearn.preprocessing import OneHotEncoder
# Apply one-hot encoder to each column with categorical data
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]))
# One-hot encoding removed index; put it back
OH_cols_train.index = X_train.index
OH_cols_valid.index = X_valid.index
# Remove categorical columns (will replace with one-hot encoding)
num_X_train = X_train.drop(object_cols, axis=1)
num_X_valid = X_valid.drop(object_cols, axis=1)
# Add one-hot encoded columns to numerical features
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)
print("MAE from Approach 3 (One-Hot Encoding):")
print(score_dataset(OH_X_train, OH_X_valid, y_train, y_valid))
MAE from Approach 3 (One-Hot Encoding):
166089.4893009678
哪一种方法最优呢?
在本例中,方法1表现最差,由于其有最高的MAE
。对于其余两种方法,由于返回的MAE
得分如此接近,并没有比其余一方有任何更好的表现。
通常地,方法3将典型的表现最好,方法1表现最差,但是基于具体的问题有所不同。
3.4 小结
世界中充满了分类数据。假如你知道如何使用这类数据类型,你将变成更有效率的数据科学家。
4.流水线 Pipelines
4.1 简介
Pipelines
流水线是一种预处理数据及效率建模的简单方法。具体来说,一个流水线将预处理和建模步骤捆绑在一起,这样你就能够像使用单个步骤一样使用整个捆绑。
许多数据科学家将没有流水线的模型拼凑在一起,但流水线有一些重要的好处。包括:
- 更干净的代码:考虑到在预处理数据的每一步可能会变得混乱。有了流水线,你就不需要在每一步手动跟踪训练和验证数据。
2.bug
更少:错误应用步骤或忘记预处理步骤的机会更少。 - 更容易产品化:将模型从原型转换成可大规模部署的东西是非常困难的。我们不会在这里讨论更多相关的问题,但是流水线会有所帮助。
- 有更多验证模型的选择:您将在下一节教程中看到一个示例,它涵盖了交叉验证。
4.2 举例
如之前的教程,我们使用Melbourne Housing
数据集。
我们不关心加载数据的步骤。试想你已经完成该步骤并有如下训练数据和验证数据:X_train, X_valid, y_train, and y_valid
。
我们先通过head()
方法查看一下训练数据的前几行,如下:
X_train.head()
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:定义预处理步骤
类似于流水线如何将预处理和建模步骤捆绑,我们使用ColumnTransformer
类来捆绑不同的预处理步骤。代码如下:
- 将缺失值赋予数值
- 对分类变量运用独热编码
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder
# Preprocessing for numerical data
numerical_transformer = SimpleImputer(strategy='constant')
# Preprocessing for categorical data
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
# Bundle preprocessing for numerical and categorical data
preprocessor = ColumnTransformer(
transformers=[
('num', numerical_transformer, numerical_cols),
('cat', categorical_transformer, categorical_cols)
])
步骤2:定义模型
接下来,我们定义一个随机森林模型。
from sklearn.ensemble import RandomForestRegressor
model = RandomForestRegressor(n_estimators=100, random_state=0)
步骤3:创建和评估流水线
最后,我们使用pipeline
类来定义一个流水线将预处理与建模步骤捆绑。这里有一些重要的内容要注意:
- 通过流水线,我们使用一行代码预处理训练数据并且拟合模型。(与之相反,没有流水线,我们不得不通过不同的步骤处理缺失值,独热编码,模型训练等。同时处理缺失值和分类变量很容易混乱)
- 通过流水线,我们将
X_valid
中未处理的特征提供给predict()
命令,流水线在生成预测之前自动预处理这些特征。(但是,如果没有流水线,我们必须记住在做出预测之前预处理验证数据。)
from sklearn.metrics import mean_absolute_error
# Bundle preprocessing and modeling code in a pipeline
my_pipeline = Pipeline(steps=[('preprocessor', preprocessor),
('model', model)
])
# Preprocessing of training data, fit model
my_pipeline.fit(X_train, y_train)
# Preprocessing of validation data, get predictions
preds = my_pipeline.predict(X_valid)
# Evaluate the model
score = mean_absolute_error(y_valid, preds)
print('MAE:', score)
MAE: 160679.18917034855
4.3 小结
流水线对于清理机器学习代码和避免错误很有价值,并且对于具有复杂数据预处理的工作流程特别有用。
5.交叉验证
5.1 简介
机器学习是一个迭代的过程。
你将面临选择使用什么预测变量,使用什么类型的模型,为这些模型提供什么参数,等等。到目前为止,您已经用数据驱动的方式做出了这些选择,通过使用验证(或集中)数据来测量模型质量。
但这种方法也有一些缺点。要看到这一点,假设有一个5000行数据集。通常将20%的数据作为验证数据,即1000行。但这在决定模型分数时留下了一些随机的机会。也就是说,一个模型可能在某个1000行的上表现良好,即使它在不同的1000行上表现不准确。
在极端情况下,您可以想象在验证集中只有1行数据。如果您比较不同的模型,哪一个对单个数据点做出最好的预测将主要取决于运气!
一般来说,验证数据集越大,我们的模型质量测量中的随机性(又叫“噪声”)就越少,也就越可靠。不幸的是,我们只能通过从我们的训练数据中移除行来得到一个大的验证集,而更小的训练数据集意味着更糟糕的模型!
5.2 什么是交叉验证
在交叉验证中,我们在数据的不同子集上运行我们的建模过程,以获得模型质量的多种度量。
例如,我们可以首先将数据分为5个部分,每个部分占整个数据集的20%。在本例中,我们将数据分解为5个“折叠”folds
。
然后,我们对每一个数据折叠进行实验:
- 在实验1中,我们使用第一个折叠作为验证(或坚守
holdout
)集,其他所有数据作为训练数据。这给了我们一个模型质量的度量,基于20%的坚守集合holdout set
。 - 在实验2中,我们从第二个折叠中取出数据作为坚守集(并使用除第二个折叠以外的所有数据来训练模型)。然后,用坚守集对模型质量进行第二次评估。
- 我们重复这个过程,使用每一个折叠作为坚守集。在把上述过程合并一起,100%的数据被用作验证数据,在某种程度上,我们最终得到的模型质量,是基于所有行的数据集(即使我们不同时使用所有行)。
5.3 什么时候使用交叉验证
交叉验证为模型质量提供了更准确的度量,如果您要做出许多建模决策,这一点尤其重要。然而,它可能需要更长的时间来运行,因为它估计了多个模型(每个折叠一个模型)。
那么,考虑权衡取舍,什么时候应该使用这种方法呢?
- 对于小型数据集,额外的计算负担不是什么大问题,你应该运行交叉验证。
- 对于更大的数据集,单个验证集就足够了。你的代码将运行得更快,并且你可能有足够的数据,不需要为重复使用其中的一些数据作为坚守集。
对于大数据集和小数据集,并没有一个简单的阈值。但是,如果你的模型需要几分钟或更短的时间来运行,那么可能需要切换到交叉验证。
或者,你可以进行交叉验证,看看每个实验的得分是否接近。如果每个实验都得到相同的结果,那么一个验证集可能就足够了。
5.4 举例
使用之前小节相同的数据。我们加载输入数据X
和输出数据y
。
然后,我们定义一个流水线,使用imputer
处理缺失值和随机森林模型来做预测。
虽然可以在没有流水线的情况下进行交叉验证,但这相当困难!使用流水线将使代码更直观。
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)
MAE scores:
[301628.7893587 303164.4782723 287298.331666 236061.84754543 260383.45111427]
通过scoring
参数选择一个模型质量的度量来反馈:在本例中,我们选择负平均绝对误差negative mean absolute error
。
我们指定负MAE
是有点令人惊讶的。Scikit-learn
有一个惯例,定义所有指标数字越大越好。在这里使用负MAE
可以使他们与惯例保持一致,尽管负数在其他地方几乎闻所未闻。
我们通常需要一个模型质量的单一度量来比较不同的模型。所以我们取每个实验的平均值。
print("Average MAE score (across experiments):")
print(scores.mean())
Average MAE score (across experiments):277707.3795913405
5.5 小结
使用交叉验证可以更好地度量模型质量,还可以清理代码:请注意,我们不再需要跟踪单独的训练集和验证集。所以,特别是对于小型数据集,这是一个很好的改进!
6.极端梯度提升 XGBoost
在本节教程中,你将学习如何使用梯度提升gradient boosting
来创建和优化模型。该方法主导了许多kaggle
竞赛并且在各种各样的数据集上得到最先进的结果。
6.1 简介
该教程的大多数时候,你都使用随机森林方法来做预测,比用一个单一决策树获得了更好的结果。
我们称随机森林方法是一种组合方法ensemble method
。如其定义,组合方法结合了不同模型的预测。
接下来,我们学习另外一种组合方法叫做梯度提升。
6.2 梯度提升 Gradient Boosting
Gradient boosting
梯度提升是一种循环迭代将模型添加到组合中的方法。
先通过一个模型初始化组合,其预测结果为最原始的。(即使其预测结果非常不准确,但是后续加入组合的更多内容会逐渐消除错误)。
因此,我们开始这个循环:
- 首先,我们使用当前组合来为数据集中的每个观测结果生成预测。为了作出预测,我们将所有模型的预测添加到组合中。
- 这些预测用于计算损失函数(例如,均方误差
mean squared errir
等等)。 - 然后,我们使用损失函数拟合一个新的模型,该模型将被添加到组合中。具体来说,我们确定模型参数,以便将这个新模型添加到组合能够减少损失。(注意:“梯度提升”中的“梯度”是指我们将在损失函数上使用梯度下降来确定新模型中的参数。)
- 最后,我们将新模型添加到集成中,并且…
- …重复!
6.3 举例
我们通过加载训练数据和验证数据开始X_train, X_valid, y_train, and y_valid
.
import pandas as pd
from sklearn.model_selection import train_test_split
# Read the data
data = pd.read_csv('../input/melbourne-housing-snapshot/melb_data.csv')
# Select subset of predictors
cols_to_use = ['Rooms', 'Distance', 'Landsize', 'BuildingArea', 'YearBuilt']
X = data[cols_to_use]
# Select target
y = data.Price
# Separate data into training and validation sets
X_train, X_valid, y_train, y_valid = train_test_split(X, y)
在本例中,你将使用XGBoost
库。XGBoost
是指extreme gradient boosting
极度梯度提升,这是梯度提升的一种补充,使用一些附加特征来提升模型表现和速度。(在scikit-learn
中有另外一个版本的梯度提升,但是XGBoost
有一些技术上的优势。)
在接下来的代码模块中,我们导入scikit-learn
的XGBoost
的API
(xgboost.XGBRegressor
)。这允许我们创建和拟合模型,正如我们即将在scikit-learn
中所做的一样。你将在输出结果中看到,XGBRegressor
类有许多可调参数,很快你将学习到。
from xgboost import XGBRegressor
my_model = XGBRegressor()
my_model.fit(X_train, y_train)
XGBRegressor(base_score=0.5, booster='gbtree', colsample_bylevel=1,
colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,
importance_type='gain', interaction_constraints='',
learning_rate=0.300000012, max_delta_step=0, max_depth=6,
min_child_weight=1, missing=nan, monotone_constraints='()',
n_estimators=100, n_jobs=0, num_parallel_tree=1, random_state=0,
reg_alpha=0, reg_lambda=1, scale_pos_weight=1, subsample=1,
tree_method='exact', validate_parameters=1, verbosity=None)
我们同样的做预测和评估模型:
from sklearn.metrics import mean_absolute_error
predictions = my_model.predict(X_valid)
print("Mean Absolute Error: " + str(mean_absolute_error(predictions, y_valid)))
Mean Absolute Error: 239960.14714193667
6.4 参数调节
XGBoost
有许多参数可以极度影响模型准确度和训练速度。第一个你需要理解的参数是:
n_estimators
n_estimators
是指你遍历模型循环的次数。这等同于我们打包到整体里面的模型数量。
- 过低的值导致欠拟合,同样导致在训练数据和测试数据上预测不准确
- 过高的值导致过度拟合,同样导致在训练数据上预测准确,但是在测试数据上预测不准确(这是我们所关心的)。
典型的取值范围从100-1000,尽管这样依赖于大量接下来将讨论的learning_rate
参数。
my_model = XGBRegressor(n_estimators=500)
my_model.fit(X_train, y_train)
XGBRegressor(base_score=0.5, booster='gbtree', colsample_bylevel=1,
colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,
importance_type='gain', interaction_constraints='',
learning_rate=0.300000012, max_delta_step=0, max_depth=6,
min_child_weight=1, missing=nan, monotone_constraints='()',
n_estimators=500, n_jobs=0, num_parallel_tree=1, random_state=0,
reg_alpha=0, reg_lambda=1, scale_pos_weight=1, subsample=1,
tree_method='exact', validate_parameters=1, verbosity=None)
early_stopping_rounds
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)
XGBRegressor(base_score=0.5, booster='gbtree', colsample_bylevel=1,
colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,
importance_type='gain', interaction_constraints='',
learning_rate=0.300000012, max_delta_step=0, max_depth=6,
min_child_weight=1, missing=nan, monotone_constraints='()',
n_estimators=500, n_jobs=0, num_parallel_tree=1, random_state=0,
reg_alpha=0, reg_lambda=1, scale_pos_weight=1, subsample=1,
tree_method='exact', validate_parameters=1, verbosity=None)
如果你希望在此之后用所有数据拟合模型,可以将n_estimators
设置为在运行提前停止时发现的最优值。
learning_rate
我们不是通过简单地将每个组合模型的预测相加来获得预测结果,而是将每个模型的预测乘以一个小数字(称为学习速率learning_rate
),然后再将它们加入预测结果。
这意味着我们在整体中增加的每一棵树对我们的帮助都更少。因此,我们可以为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)
XGBRegressor(base_score=0.5, booster='gbtree', colsample_bylevel=1,
colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,
importance_type='gain', interaction_constraints='',
learning_rate=0.05, max_delta_step=0, max_depth=6,
min_child_weight=1, missing=nan, monotone_constraints='()',
n_estimators=1000, n_jobs=0, num_parallel_tree=1, random_state=0,
reg_alpha=0, reg_lambda=1, scale_pos_weight=1, subsample=1,
tree_method='exact', validate_parameters=1, verbosity=None)
n_jobs
在大型数据集上需要考虑运行时间,你可以使用并行更快地构建模型。通常将参数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)
XGBRegressor(base_score=0.5, booster='gbtree', colsample_bylevel=1,
colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,
importance_type='gain', interaction_constraints='',
learning_rate=0.05, max_delta_step=0, max_depth=6,
min_child_weight=1, missing=nan, monotone_constraints='()',
n_estimators=1000, n_jobs=4, num_parallel_tree=1, random_state=0,
reg_alpha=0, reg_lambda=1, scale_pos_weight=1, subsample=1,
tree_method='exact', validate_parameters=1, verbosity=None)
6.5 小结
XGBoost
是处理标准表格数据的领先软件库(比如你存储在Pandas DataFrames
中的数据类型,而不是图像和视频等更奇特的数据类型)。通过仔细的参数调节,你可以训练出高度精确的模型。
7. 数据泄漏
在本教程中,您将了解什么是数据泄漏data leakage
以及如何防止它。如果你不知道如何预防,泄漏就会频繁出现,它会以微妙而危险的方式破坏你的模型。因此,这是数据科学实践中最重要的概念之一。
7.1 简介
当训练数据包含目标信息时,数据泄漏(或泄漏)就会发生,所以当模型用于预测时类似的数据将变得无效。这将导致模型在训练数据中(甚至验证数据)的表现很好,但在生产中表现不佳。
换句话说,泄漏会导致模型看起来是准确的,直到您开始使用模型做出决策,然后模型就会变得非常不准确。
泄漏主要有两种类型:Target leakage
和train-test contamination
。
目标泄漏Target leakage
当你的预测包含无效数据时,就会发生目标泄漏。重要的是,要根据数据可用的时间或时间顺序来考虑目标泄漏,而不仅仅是一个特性是否有助于做出良好的预测。
举个例子会很有帮助。想象一下,你想预测谁会得肺炎。原始数据的前几行是这样的:
【】
为了康复,人们在患肺炎后服用抗生素药物。原始数据显示了这些列之间的强烈关系,但是在确定got_pneumonia
的值之后,took_antibiotic
tic_medicine
经常被更改。这是目标泄漏。
该模型将看到,任何值为taking
抗生素_medicine的人都没有患肺炎。由于验证数据和训练数据来自同一个来源,模式会在验证中重复自己,模型将有很好的验证(或交叉验证)分数。
但是这个模型在实际应用时是非常不准确的,因为当我们需要预测他们未来的健康状况时,即使是肺炎患者也还没有使用抗生素。
为了防止这种类型的数据泄漏,在实现了目标值之后更新(或创建)的任何变量都应该排除在外。
Train-Test Contamination
如果不注意区分训练数据和验证数据,就会发生另一种类型的泄漏。
回想一下,验证是对模型如何处理之前没有考虑到的数据的一种度量。如果验证数据影响预处理行为,则可以以微妙的方式破坏这个过程。这有时被称为列车测试污染。
例如,假设您在调用train_test_split()
之前运行预处理(如为缺失的值拟合输入器)。最终的结果吗?您的模型可能会得到很好的验证分数,使您对它有很大的信心,但是当您部署它来做决策时,它的性能很差。
毕竟,您将来自验证或测试数据的数据合并到您如何进行预测中,所以即使不能概括为新数据,也可以在特定数据上做得很好。当您进行更复杂的特性工程时,这个问题会变得更加微妙(也更加危险)。
如果您的验证是基于一个简单的训练测试分割,请将验证数据从任何类型的拟合中排除,包括预处理步骤的拟合。如果使用scikit-learn管道,这将更容易。在使用交叉验证时,在管道内进行预处理甚至更为关键!
7.2 举例
在本例中,您将学习一种检测和移除目标泄漏的方法。
我们将使用关于信用卡应用程序的数据集,并跳过基本的数据设置代码。最终的结果是,关于每个信用卡应用程序的信息存储在一个数据框架x中。我们将使用它来预测在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
【】
由于这是一个小数据集,我们将使用交叉验证来确保模型质量的准确度量。
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.978776
根据经验,您会发现很少有模型在98%的时间内是准确的。这种情况确实发生过,但并不常见,因此我们应该更仔细地检查数据,以确定是否有目标泄漏。
以下是数据的摘要,你也可以在data选项卡下找到:
- card: 1 if credit card application accepted, 0 if not reports: Number
of major derogatory reports age: Age n years plus twelfths of a year
income: Yearly income (divided by 10,000) share: Ratio of monthly
credit card expenditure to yearly income expenditure: Average monthly
credit card expenditure owner: 1 if owns home, 0 if rents selfempl: 1
if self-employed, 0 if not dependents: 1 + number of dependents
months: Months living at current address majorcards: Number of major
credit cards held active: Number of active credit accounts
有几个变量看起来很可疑。例如,“支出”是指这张卡的支出还是指申请前使用的卡的支出?
在这一点上,基本的数据比较是非常有用的:
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%没有消费。我们的模型似乎有很高的准确性,这并不奇怪。但这似乎也是一个目标泄漏的情况,支出可能意味着他们申请的卡上的支出。
由于份额部分由支出决定,因此也应排除在外。变量active和majorcards不太清楚,但从描述来看,它们听起来令人担忧。在大多数情况下,如果您无法追踪创建数据的人以了解更多信息,那么安全总比后悔好。
我们将运行一个没有目标泄漏的模型如下:
# 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.828650
这个精度相当低,这可能令人失望。然而,在新应用程序上使用时,我们可以预期它在80%的情况下是正确的,而泄漏的模型可能会做得更糟(尽管它在交叉验证中的明显得分更高)。
7.3 小结
在许多数据科学应用中,数据泄漏可能是一个价值数百万美元的错误。仔细分离训练数据和验证数据可以防止训练测试污染,管道可以帮助实现这种分离。同样,谨慎、常识和数据探索的组合可以帮助识别目标泄漏。