组装解决问题所需的所有机器学习文件可能是一项艰巨的任务。在本系列文章中,我们将逐步实现使用真实数据集的机器学习工作流程,以了解各种技术如何结合在一起。
在第一篇文章中,我们清理和构建了数据,执行了探索性数据分析,开发了一套在我们的模型中使用的功能,并建立了一个我们可以衡量性能的基线。在本文中,我们将介绍如何在Python中实现和比较多个机器学习模型,执行超参数调整以优化最佳模型,以及评估测试集上的最终模型。
这个项目的完整代码在GitHub上,与本文对应的第二个jupyter notebook就在这里。您可以以任何方式随意使用,共享和修改代码!
作为提醒,我们正在开展监督回归任务:使用纽约市建筑能源数据,我们希望开发一个可以预测建筑物能源之星得分的模型。我们的重点是预测的准确性和模型的可解释性。
有大量的机器学习模型可供选择,决定从哪里开始可能是令人生畏的。虽然有一些图表试图向您展示使用哪种算法,但我更喜欢试用几种,看哪哪种效果最好!机器学习仍然是一个主要由经验(实验)而不是理论结果驱动的领域,并且几乎不可能提前知道哪个模型将做得最好。
一般来说,最好从简单的,可解释的模型开始,如线性回归,如果性能不够,则转向更复杂但通常更准确的方法。下图显示了准确性与可解释性权衡的(高度不科学)版本:
解释性与准确性(来源)
我们将评估涵盖复杂性范围的五种不同模型:
在这篇文章中,我们将专注于实现这些方法而不是它们背后的理论。对于有兴趣学习背景的人,我强烈推荐使用Scikit-Learn和TensorFlow 进行统计学习简介(可在线免费获得)或动手机器学习。这两本教科书都很好地解释了理论,并分别展示了如何有效地使用R和Python中的方法。
输入缺失值
虽然我们在清理数据时丢弃了超过50%缺失值的列,但仍然有相当多的缺失观察结果。机器学习模型不能处理任何缺席值,因此我们必须填写它们,这个过程称为插补。
首先,我们将读入所有数据并提醒自己它的样子:
import pandas as pd
import numpy as np
# Read in data into dataframes
train_features = pd.read_csv('data/training_features.csv')
test_features = pd.read_csv('data/testing_features.csv')
train_labels = pd.read_csv('data/training_labels.csv')
test_labels = pd.read_csv('data/testing_labels.csv')
Training Feature Size: (6622, 64)
Testing Feature Size: (2839, 64)
Training Labels Size: (6622, 1)
Testing Labels Size: (2839, 1)
每个值NaN
代表缺失的观察。虽然有很多方法可以填补缺失的数据,但我们将采用一种相对简单的方法,即中位数估算。这将使用列的中值替换列中的所有缺失值。
在下面的代码中,我们创建一个Scikit-Learn Imputer
对象,其策略设置为中位数。然后,我们在训练数据(使用imputer.fit
)上训练此对象,并使用它来填充训练和测试数据中的缺失值(使用imputer.transform
)。这意味着测试数据中的缺失值用训练数据中的相应中值填充。
(我们必须以这种方式进行估算,而不是对所有数据进行训练,以避免测试数据泄漏的问题,其中来自测试数据集的信息溢出到训练数据中。)
# 创建一个具有中位填充策略的
imputer = Imputer(strategy='median')
# Train on the training features
imputer.fit(train_features)
# 转换训练数据和测试数据
X = imputer.transform(train_features)
X_test = imputer.transform(test_features)
Missing values in training features: 0
Missing values in testing features: 0
所有功能现在都具有真实的有限值,没有遗漏的例子。
缩放是指更改要素范围的一般过程。这是必要的,因为要素以不同的单位进行测量,因此涵盖不同的范围。考虑到观测之间的距离测量的支持向量机和K近邻等方法受到特征范围的显着影响,并且缩放允许他们学习。虽然线性回归和随机森林等方法实际上并不需要进行特征缩放,但在我们比较多个算法时,最佳做法仍然是采取这一步骤。
我们将通过将每个特征放在0和1之间的范围来缩放特征。这可以通过获取特征的每个值,减去特征的最小值,然后除以最大值减去最小值(范围)来完成。这种特定版本的缩放通常称为规范化,另一种主要版本称为标准化。
虽然这个过程很容易手工实现,但我们可以使用MinMaxScaler
Scikit-Learn中的对象来实现。此方法的代码与插补的代码相同,除了使用缩放器而不是imputer!同样,我们确保仅使用训练数据进行训练,然后转换所有数据。
# 创建范围为0-1
scaler = MinMaxScaler(feature_range=(0, 1))
# 适合训练数据
scaler.fit(X)
# 转换训练和测试数据
X = scaler.transform(X)
X_test = scaler.transform(X_test)
现在,每个功能的最小值都为0,最大值为1.缺少值插补和特征缩放是几乎所有机器学习管道中都需要的两个步骤,因此了解它们的工作原理是个好主意!
在Scikit-Learn中实现机器学习模型
在我们花费所有工作清理和格式化数据之后,实际创建,训练和预测模型相对简单。我们将使用Python中的Scikit-Learn库,它具有出色的文档和一致的模型构建语法。一旦您知道如何在Scikit-Learn中制作一个模型,您就可以快速实现各种算法。
我们可以用Gradient Boosting Regressor 说明模型创建,训练(使用.fit
)和测试(使用 .predict
)的一个例子 :
from sklearn.ensemble import GradientBoostingRegressor
# Create the model
gradient_boosted = GradientBoostingRegressor()
# Fit the model on the training data
gradient_boosted.fit(X, y)
# Make predictions on the test data
predictions = gradient_boosted.predict(X_test)
# Evaluate the model
mae = np.mean(abs(predictions - y_test))
print('Gradient Boosted Performance on the test set: MAE = %0.4f' % mae)
Gradient Boosted Performance on the test set: MAE = 10.0132
模型创建,培训和测试都是一条线!为了构建其他模型,我们使用相同的语法,只更改算法的名称。结果如下:
为了正确看待这些数字,使用目标中值计算的幼稚基线为24.5。很明显,机器学习适用于我们的问题,因为它比基线有了显着的改进!
的梯度升压回归(MAE = 10.013)略微击败了随机森林(10.014 MAE)。这些结果并不完全公平,因为我们主要使用超参数的默认值。特别是在支持向量机等模型中,性能高度依赖于这些设置。尽管如此,根据这些结果,我们将选择梯度增强回归量用于模型优化。
在机器学习中,在我们选择模型之后,我们可以通过调整模型超参数来针对我们的问题对其进行优化。
首先,什么是超参数?它们与参数有何不同?
控制超参数会通过改变模型中欠拟合和过度拟合之间的平衡来影响模型性能。欠拟合是指我们的模型不够复杂(它没有足够的自由度)来学习从特征到目标的映射。欠配合模型具有高偏差,我们可以通过使模型更复杂来纠正。
过度拟合是指我们的模型基本上记忆训练数据。过拟合模型具有高方差,我们可以通过正则化来限制模型的复杂性来纠正。欠装和过装模型都无法很好地概括为测试数据。
选择正确的超参数的问题在于,每个机器学习问题的最优设置都是不同的!因此,找到最佳设置的唯一方法是在每个新数据集上尝试使用它们。幸运的是,Scikit-Learn有许多方法可以让我们有效地评估超参数。此外,Epistasis Lab的TPOT等项目正在尝试使用遗传编程等方法优化超参数搜索。在这个项目中,我们将坚持使用Scikit-Learn这样做,但是仍然关注自动ML场景的更多工作!
我们将实现的特定超参数调整方法称为具有交叉验证的随机搜索:
K = 5的K-Fold交叉验证的想法如下所示:
K = 5的K折交叉验证(来源)
使用交叉验证执行随机搜索的整个过程是:
当然,我们实际上并没有手动执行此操作,而是让Scikit-Learn RandomizedSearchCV
处理所有工作!
由于我们将使用Gradient Boosted回归模型,我应该至少给出一些背景知识!这个模型是一个集合方法,意味着它是由许多弱学习者构建的,在这种情况下是个体决策树。虽然随机森林等套袋算法并行训练弱学习者,并让他们投票进行预测,但像Gradient Boosting这样的提升方法依次训练学习者,每个学习者“集中”前一个学生所犯的错误。 。
近年来,提升方法已经变得流行,并且经常赢得机器学习竞赛。在梯度推进的方法是使用梯度下降通过对前两者的残差顺序训练学习者以最小的成本函数一个特定的实现。Gradient Boosting的Scikit-Learn实现通常被认为效率低于其他库,例如XGBoost ,但它对我们的小数据集来说效果很好,而且非常准确。
在Gradient Boosted Regressor中有许多超参数可供调整,您可以查看Scikit-Learn文档了解详细信息。我们将优化以下超参数:
loss
:最小化的损失函数n_estimators
:要使用的弱学习者(决策树)的数量max_depth
:每个决策树的最大深度min_samples_leaf
:决策树的叶节点所需的最小示例数min_samples_split
:拆分决策树节点所需的最小示例数max_features
:用于拆分节点的最大功能数我不确定是否有人真正理解所有这些相互作用的方式,找到最佳组合的唯一方法就是尝试它们!
在下面的代码中,我们构建了一个超参数网格,创建了一个RandomizedSearchCV
对象,并使用超过25个不同超参数组合的4倍交叉验证执行超参数搜索:
# 优化损失功能
loss = ['ls', 'lad', 'huber']
# 提升过程中使用的树数
n_estimators = [100, 500, 900, 1100, 1500]
# 每棵树的最大深度
max_depth = [2, 3, 5, 10, 15]
# 每片叶子的最小样本数
min_samples_leaf = [1, 2, 4, 6, 8]
# 拆分节点的最小样本数
min_samples_split = [2, 4, 6, 10]
# 进行拆分时要考虑的最大功能数
max_features = ['auto', 'sqrt', 'log2', None]
# 定义要进行搜索的超参数网格
hyperparameter_grid = {'loss': loss,
'n_estimators': n_estimators,
'max_depth': max_depth,
'min_samples_leaf': min_samples_leaf,
'min_samples_split': min_samples_split,
'max_features': max_features}
# 创建用于超参数调整的模型
model = GradientBoostingRegressor(random_state = 42)
# 设置4次交叉验证的随机搜索
random_cv = RandomizedSearchCV(estimator=model,
param_distributions=hyperparameter_grid,
cv=4, n_iter=25,
scoring = 'neg_mean_absolute_error',
n_jobs = -1, verbose = 1,
return_train_score = True,
random_state=42)
# Fit on the training data
random_cv.fit(X, y)
执行搜索后,我们可以检查RandomizedSearchCV
对象以找到最佳模型:
# 找到设置的最佳组合
random_cv.best_estimator_
GradientBoostingRegressor(loss ='lad',max_depth = 5,
max_features = None,
min_samples_leaf = 6,
min_samples_split = 6,
n_estimators = 500)
然后,我们可以使用这些结果通过选择接近这些最佳值的网格参数来执行网格搜索。但是,进一步调整不太可能显着改善我们的模型。作为一般规则,正确的特征工程对模型性能的影响要比最广泛的超参数调整大得多。这是应用于机器学习的递减收益定律:特征工程可以帮助您完成大部分工作,而超参数调整通常只能提供一个小的好处。
我们可以尝试的一个实验是改变估计量(决策树)的数量,同时保持其余的超参数稳定。这直接让我们观察到这个特定设置的效果。请参阅笔记本以了解实施情况,但结果如下:
随着模型使用的树木数量的增加,训练和测试误差都会减少。但是,训练误差比测试误差下降得快得多,我们可以看到我们的模型过度拟合:它在训练数据上表现很好,但是在测试集上却无法达到相同的性能。
我们总是期望测试集上的性能至少有所降低(毕竟,模型可以看到训练集的真实答案),但是显着的差距表明过度拟合。我们可以通过获取更多培训数据来解决过度拟合问题,或通过hyer参数降低模型的复杂性。在这种情况下,我们会将超参数留在原来的位置,但我鼓励任何人尝试减少过度拟合。
对于最终模型,我们将使用800个估算器,因为这导致交叉验证中的最小错误。现在,是时候测试一下这个模型了!
作为负责任的机器学习工程师,我们确保不让我们的模型在任何培训点看到测试集。因此,我们可以使用测试集性能作为我们的模型在现实世界中部署时的性能指标。
对测试集进行预测并计算性能相对简单。在这里,我们将默认的Gradient Boosted Regressor的性能与调优模型进行比较:
# 使用默认值和最终模型对测试集进行预测
default_pred = default_model.predict(X_test)
final_pred = final_model.predict(X_test)
Default model performance on the test set: MAE = 10.0118.
Final model performance on the test set: MAE = 9.0446.
超参数调整将模型的准确度提高了约10%。根据使用情况,10%可能是一个巨大的改进,但它是在一个重要的时间投资!
我们还可以计算%timeit
在Jupyter笔记本中使用magic命令训练两个模型所需的时间。首先是默认模型:
%%timeit -n 1 -r 5
default_model.fit(X, y)
1.09 s ± 153 ms per loop (mean ± std. dev. of 5 runs, 1 loop each)
1秒似乎很合理。最终调整的模型不是那么快:
%%timeit -n 1 -r 5
final_model.fit(X, y)
12.1 s ± 1.33 s per loop (mean ± std. dev. of 5 runs, 1 loop each)
这展示了机器学习的一个基本方面:它始终是一种权衡的游戏。我们必须始终保持准确性与可解释性,偏差与方差,准确性与运行时间等的平衡。正确的混合将最终取决于问题。在我们的例子中,相对而言,运行时间增加了12倍,但从绝对意义上说,并不是那么重要。
一旦我们得到最终预测,我们就可以对它们进行调查,看看它们是否表现出任何明显的偏差。左边是预测值和实际值的密度图,右边是残差的直方图:
预测值和实际值的密度图(左)和残差直方图(右)
模型预测似乎遵循实际值的分布,尽管密度峰值更接近训练集的中值(66)而不是密度的真实峰值(接近100)。残差几乎是正态分布,尽管我们看到一些大的负值,其中模型预测远低于真实值。我们将在下一篇文章中深入研究解释模型的结果。
在本文中,我们介绍了机器学习工作流程中的几个步骤:
这项工作的结果向我们表明,机器学习适用于使用可用数据预测建筑物能源之星得分的任务。使用梯度增强回归量,我们能够将测试集上的分数预测到真实值的9.1分之内。此外,我们发现超参数调整可以在投入的时间方面以显着的成本提高模型的性能。这是我们在开发机器学习解决方案时必须考虑的众多权衡之一。
在第三篇文章(此处提供)中,我们将查看我们创建的黑盒子,并尝试了解我们的模型如何进行预测。我们还将确定影响能源之星得分的最大因素。虽然我们知道我们的模型是准确的,但我们想知道为什么它会做出预测,以及它告诉我们这个问题!
想看第一部分点击这里。
一如既往,我欢迎反馈和建设性的批评,可以在Twitter @koehrsen_will上联系。
原文:https://towardsdatascience.com/a-complete-machine-learning-project-walk-through-in-python-part-two-300f1f8147e2