本文是英文原著翻译,并结合了其他文献的一个有益集成。中文在英文简述的后面。
We’ll follow the general machine learning workflow step-by-step 第二部分:
- Data cleaning and formatting 数据清洗和格式化
- Exploratory data analysis 探索性的数据分析
- Feature engineering and selection 字段的加工和选择
- Hide and filter Sensitive Feature 数据脱敏
- Compare several machine learning models on a performance metric 模型的评估和选择
- Perform hyperparameter tuning on the best model 用于模型优化的超参数调整
- Evaluate the best model on the testing set 评估测试集
- Interpret the model results 模型解释
- Draw conclusions and document work 记录工作和报告结果
Extrac from
在这篇文章里,我们会尝试用python实施和比较几个机器学习的模型,调试和调节hyperarameter,然后根据测试结果,做出模型的评估。。
整个项目的代码在Github里,关于这篇文章的第二个Notebook在这个链接里。随意用和修改。
模型的评估和选择(Model Evaluation and Selection)
提一下,我们的项目是supervised regression项目。使用纽约市建筑能源数据,我们希望开发一个可以预测建筑物能源星级的得分的模型。我们的重点是预测的准确性和模型的可解释性。
有大量的机器学习模型可供选择,决定从哪里开始可能是令人生畏的。虽然有一些图表试图向您展示使用哪种算法,但我更喜欢试用几种,看看哪种算法效果最好!机器学习仍然是一个主要由经验(实验)而不是理论结果驱动的领域,并且几乎不可能提前知道哪个模型将做得最好。
一般来说,最好从简单的,可解释的模型开始,如线性回归,如果性能不够,则转向更复杂但通常更准确的方法。下图显示了准确性与可解释性权衡的(非常不科学)版本:
我们将评估涵盖复杂性范围的五种不同模型:
- 线性回归 Linear Regression
- K-Nearest Neighbors Regression
- 随机森林回归 Random Forest Regression
- Gradient Boosted Regression
- 支持向量机回归 Support Vector Machine Regression
在这篇文章中,我们将重点介绍这些方法,而不是它们背后的理论。 对于有兴趣学习背景的人,我强烈推荐统计学习简介(可在线免费获得)或用Scikit-Learn和TensorFlow熟练掌握机器学习。 这两本教科书都很好地解释了理论,并分别展示了如何有效地使用R和Python中的方法。
输入缺失值 (Imputing Missing Values)
当我们清理数据时,我们丢弃了超过50%缺失值的列,但仍有不少缺失值的列。 机器学习模型不能处理任何缺失值,因此我们必须填写它们,这个过程称为插补。
首先,我们将读入所有数据并看看他们的维度(Dimension):
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)。 这意味着测试数据中的缺失值用训练数据中的相应中值填充。
(我们必须以这种方式进行估算,而不是对所有数据进行训练,以避免测试数据泄漏的问题,其中来自测试数据集的信息溢出到训练数据中)
# Create an imputer object with a median filling strategy
imputer = Imputer(strategy='median')
# Train on the training features
imputer.fit(train_features)
# Transform both training data and testing data
X = imputer.transform(train_features)
X_test = imputer.transform(test_features)
Missing values in training features: 0
Missing values in testing features: 0
所有的字段现在都具有真实的有限值,没有遗漏的例子。
特征缩放 (Feature Scaling)
缩放是指更改字段范围的过程。这是必要的,因为字段以不同的单位进行测量,因此涵盖不同的范围。考虑算法支持向量机和K-nearest neighbors等方法受到字段范围的显著影响,并且缩放允许他们在字段相同范围内学习。虽然线性回归和随机森林等方法实际上并不需要进行特征缩放,但在我们比较多个算法时,最佳做法仍然是采取这一步骤。
我们将通过将每个字段放在0和1之间的范围来缩放字段。这通过获取字段的每个值,减去特征的最小值,并除以最大值减去最小值(范围)来完成。这种特定方式的缩放通常称为规范化,另一种主要方式称为标准化。
虽然这个过程很容易手工实现,但我们可以使用Scikit-Learn中的MinMaxScaler对象来实现。此方法的代码与插补的代码相同,除了使用缩放器而不是imputer!同样,我们确保仅使用训练数据进行训练,然后转换所有数据。
# Create the scaler object with a range of 0-1
scaler = MinMaxScaler(feature_range=(0, 1))
# Fit on the training data
scaler.fit(X)
# Transform both the training and testing data
X = scaler.transform(X)
X_test = scaler.transform(X_test)
现在,每个字段的最小值为0,最大值为1.缺省值插补和字段缩放是几乎所有机器学习管道中都需要的两个步骤,因此了解它们的工作原理是个好主意!
在Scikit-Learn中实现机器学习模型(Implementing Machine Learning Models in 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
模型创建,培训和测试都是一条语句! 为了构建其他模型,我们使用相同的语法,只更改算法的名称。 结果如下:
为了正确看待这些数字,使用目标的中值计算的较低基线(naive baseline)有了显着的改进!
梯度增强回归量(MAE = 10.013)略微超过随机森林(10.014 MAE)。 这些结果并不完全公平,因为我们主要使用超参数的默认值。 特别是在支持向量机等模型中,性能高度依赖于这些参数设置。 尽管如此,根据这些结果,我们将选择梯度增强回归量用于模型优化。
用于模型优化的超参数调整(Hyperparameter Tuning for Model Optimization)
在机器学习中,在我们选择模型之后,我们可以通过调整模型超参数来优化它以解决我们的问题。
首先,什么是超参数?它们与参数有何不同?
- 模型超参数最好被认为是在训练之前由数据科学家设置的机器学习算法的参数。例如,随机森林(Random Forest)中的树木数量或K近邻算法(K-nearest neighbors)中使用的邻居数量。
- 模型参数是模型在训练期间学习的内容,例如线性回归中的权重。控制超参数通过改变模型中的欠拟合(underfit)和过度拟合(overfit)之间的平衡来影响模型性能。欠拟合(underfit)是指我们的模型不够复杂(它没有足够的自由度)来学习从字段到目标的映射。欠拟合 (underfit) 模型具有高偏差,我们可以通过使模型更复杂来纠正。
过度拟合(Overfit)是指我们的模型基本上覆盖训练数据。过拟合(overfit)模型具有高方差,high variance,我们可以通过正则化限制模型的复杂性来纠正。欠拟合(underfit)和过拟合 (overfit)模型都无法很好地通过测试数据。
选择正确的超参数的问题在于,每个机器学习问题的最优集合都是不同的!因此,找到最佳设置的唯一方法是在每个新数据集上尝试使用它们。幸运的是,Scikit-Learn有许多方法可以让我们有效地评估超参数。此外,Epistasis Lab的TPOT等项目正在尝试使用遗传编程等方法优化超参数搜索。在这个项目中,我们将坚持使用Scikit-Learn来做这件事,但仍然关注自动ML场景的更多工作!
随机搜索和交叉验证 (Random Search with Cross Validation)
我们将实现的特定超参数调整方法称为具有交叉验证的随机搜索:
随机搜索是指我们将用于选择超参数的技术。我们定义一个网格,然后随机抽样不同的组合,而不是网格搜索,也就是我们穷尽地尝试每一个组合。 (令人惊讶的是,随机搜索的表现几乎与网格搜索一样,运行时间却大幅缩短。)
交叉验证是我们用于评估所选超参数组合的技术。 我们使用K-Fold交叉验证,而不是将培训参数集拆分为单独的培训和验证集,以致减少我们可以使用的培训数据量。 这包括将训练数据划分为K个折叠,然后经历迭代过程,其中我们首先在折叠的K-1上训练然后评估第K折叠的性能。 我们重复这个过程K次,在K折交叉验证结束时,我们将每个K次迭代的平均误差作为最终性能度量。
K = 5的K-Fold交叉验证的想法如下所示:
使用交叉验证执行随机搜索的整个过程是:
- 设置一个超参数网格进行评估
- 随机抽样超参数的组合
- 使用所选组合创建模型
- 使用K折叠交叉验证评估模型
- 确定哪些超参数最有效
当然,我们实际上并没有手动执行此操作,而是让Scikit-Learn的RandomizedSearchCV
处理所有工作!
轻微转移:梯度提升方法 (Slight Diversion: Gradient Boosted Methods)
由于我们将使用Gradient Boosted回归模型,我(作者)应该至少给出一点背景知识!这个模型是一个集合方法,意味着它是由许多弱学习者构建的,也就是说是个体决策树。虽然随机森林等套袋算法并行训练弱学习者并让他们投票进行预测,但像Gradient Boosting这样的提升方法依次训练学习者,每个学习者“集中”前一个学生所犯的错误.
近年来,提升方法已经变得流行,并且经常赢得机器学习竞赛。梯度提升方法是一种特殊的实现,它使用梯度下降来通过顺序训练学习者对先前残差的影响来最小化成本函数。 Gradient Boosting的Scikit-Learn实现通常被认为效率低于其他库,例如XGBoost,但它对我们的小数据集来说效果不错,并且非常准确。
回到超参数调整 在Gradient Boosted回归程序中有很多超参数可以调整,您可以查看Scikit-Learn文档了解详细信息。 我们将优化以下超参数:
- loss:损失函数最小化
- n_estimators:要使用的弱学习者(决策树)的数量
- max_depth:每个决策树的最大深度
- min_samples_leaf:决策树的叶节点所需的最小示例数
- min_samples_split:拆分决策树节点所需的最小示例数
- max_features:用于拆分节点的最大功能数
我不确定是否有人真正了解所有这些相互作用的方式,找到最佳组合的唯一方法就是试用它们!
在下面的代码中,我们构建了一个超参数网格,创建了一个RandomizedSearchCV
对象,并使用超过25个不同超参数组合的4-Fold交叉验证执行超参数搜索:
# Loss function to be optimized
loss = ['ls', 'lad', 'huber']
# Number of trees used in the boosting process
n_estimators = [100, 500, 900, 1100, 1500]
# Maximum depth of each tree
max_depth = [2, 3, 5, 10, 15]
# Minimum number of samples per leaf
min_samples_leaf = [1, 2, 4, 6, 8]
# Minimum number of samples to split a node
min_samples_split = [2, 4, 6, 10]
# Maximum number of features to consider for making splits
max_features = ['auto', 'sqrt', 'log2', None]
# Define the grid of hyperparameters to search
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}
# Create the model to use for hyperparameter tuning
model = GradientBoostingRegressor(random_state = 42)
# Set up the random search with 4-fold cross validation
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
对象以找到最佳模型:
# Find the best combination of settings
random_cv.best_estimator_
GradientBoostingRegressor(loss='lad', max_depth=5,
max_features=None,
min_samples_leaf=6,
min_samples_split=6,
n_estimators=500)
然后,我们可以使用这些结果通过选择接近这些最佳值的网格参数来执行网格搜索。 但是,进一步调整不太可能显着改善我们的模型。 作为通用规则,正确的字段工程对模型性能的影响要比最广泛的超参数调整大得多。 这是应用于机器学习的递减收益定律:字段工程可以帮助您完成大部分工作,而超参数调整通常只能提供一个小的好处。
我们可以尝试的一个实验是改变估计量(决策树)的数量,同时保持其余的超参数稳定。 这直接让我们观察到这个特定设置的效果。 请参阅笔记本以了解实施情况,结果如下:
随着模型使用的随机森林树木数量的增加,训练和测试误差都会减少。 但是,训练误差比测试误差下降得更快,我们可以看到我们的模型过度拟合(overfit):它在训练数据上表现很好,但是在测试集上无法达到相同的性能。 我们总是期望测试集上的性能至少有所降低(毕竟,模型可以看到训练集的真实答案),但是显着的差距表明过度拟合(Overfit). 我们可以通过获取更多的训练数据或通过调节超参数降低模型的复杂性来解决过度拟合(overfit)问题。 在这种情况下,我们会将超参数保留在原来的位置,但我鼓励任何人尝试减少过度拟合(overfit)。 对于最终模型,我们将使用800个估算器,因为这导致交叉验证中的最小错误。 现在,是时候测试一下这个模型了!
评估测试集 (Evaluating on the Test Set)
作为负责任的机器学习工程师,我们确保不让我们的模型在任何培训点看到测试集。 因此,我们可以使用测试集性能作为我们的模型在现实世界中部署时的性能指标。
对测试集进行预测并计算性能相对简单。 在这里,我们将默认的Gradient Boosted Regressor的性能与调优模型进行比较:
# Make predictions on the test set using default and final model
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%可能是一个巨大的改进,但它是在一个重要的时间投资!
我们还可以计算在Jupyter笔记本中使用%timeit 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)。 残差几乎是正态分布,尽管我们看到一些大的负值,其中模型预测远低于真实值。 我们将在下一篇文章中深入研究解释模型的结果。
结论 (Conclusion)
在本文中,我们介绍了机器学习工作流程中的几个步骤:
- 填补缺失值和缩放特征
- 评估和比较几种机器学习模型
- 使用随机网格搜索和交叉验证进行超参数调整
- 评估测试集上的最佳模型
这项工作的结果显示,机器学习适用于使用可用数据预测建筑物能源的星级得分的任务。使用梯度增强回归算法,我们能够将测试集上的分数预测到真实值的9.1分之内。此外,我们看到超参数调整可以在投入的时间方面以显着的成本提高模型的性能。这是我们在开发机器学习解决方案时必须考虑的众多权衡之一。
在第三篇文章(此处提供)中,我们将查看我们创建的黑盒子,并尝试了解我们的模型如何进行预测。我们还将确定影响能源星级水平的得分的最重要因素。虽然我们知道我们的模型是准确的,但我们想知道为什么它会做出预测,以及它告诉我们的问题!
一如既往,我欢迎反馈和建设性的批评,可以通过我的Twitter账号联系我@koehrsen_will。
参考文献
- Hands-On Machine Learning with Scikit-Learn and Tensorflow (Jupyter Notebooks for this book are available online for free)!
- An Introduction to Statistical Learning
- Kaggle: The Home of Data Science and Machine Learning
- Datacamp: Good beginner tutorials for practicing data science coding
- Coursera: Free and paid courses in many subjects
- Udacity: Paid programming and data science courses