机器学习实战:基于Scikit-Learn、Keras和TensorFlow(原书第3版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.7 微调模型

假设你现在有一份有前途的模型的候选名单。你现在需要微调它们。让我们看一下可以做到这一点的几种方法。

2.7.1 网格搜索

一种选择是手动调整超参数,直到找到超参数值的最佳组合。这将是一项非常乏味的工作,你可能没有时间来探索许多组合。

相反,你可以使用Scikit-Learn的GridSearchCV类来为你做搜索。你需要做的就是告诉它你希望实验哪些超参数以及要尝试哪些值,它会使用交叉验证来评估所有可能的超参数值的组合。例如,以下代码搜索RandomForestRegressor的超参数值的最佳组合:

请注意,你可以引用流水线中的任何估计器的任何超参数,即使该估计器嵌套在多个流水线和列转换器的深处。例如,当Scikit-Learn看到"preprocessing__geo__n_clusters"时,它会在双下划线处拆分此字符串,然后在流水线中查找名为"preprocessing"的估计器并找到预处理ColumnTransformer。接下来,它在此ColumnTransformer中查找名为"geo"的转换器,并找到我们在纬度和经度属性上使用的ClusterSimilarity转换器。然后它找到这个转换器的n_clusters超参数。同样,random_forest__max_features指的是max_features名为"random_forest"的估计器的超参数,当然是RandomForest模型(max_features超参数将在第7章解释)。

Scikit-Learn流水线中包装预处理步骤允许你调整预处理超参数以及模型超参数。这是一件好事,因为它们经常交互。例如,增加n_clusters可能也需要增加max_features。如果拟合流水线转换器的计算成本很高,你可以将流水线的memory超参数设置为缓存目录的路径:当你首次拟合流水线时,Scikit-Learn会将拟合的转换器保存到该目录中。如果你随后使用相同的超参数再次拟合流水线,Scikit-Learn将只加载缓存的转换器。

这个param_grid中有两个字典,所以GridSearchCV将首先评估第一个字典中指定的n_clustersmax_features超参数值的所有3×3=9种组合,然后它将尝试第二个字典中所有2×3=6种超参数值的组合。所以总共网格搜索将探索9+6=15种超参数值的组合,并且它将对每个组合训练流水线3次,因为我们使用的是3折交叉验证。这意味着总共会有15×3=45轮训练!这可能需要一段时间,但完成后你可以获得最佳的参数组合,如下所示:

在本例中,将n_clusters设置为15并将max_features设置为8能得到最佳模型。

由于15是为n_clusters评估的最大值,你可能应该尝试使用更高的值再次搜索;分数可能会继续提高。

你可以使用grid_search.best_estimator_来访问最佳估计器。如果GridSearchCV使用refit=True(默认设置)做初始化,那么一旦它使用交叉验证找到了最佳估计器,就会在整个训练集上重新训练它。这通常是个好主意,因为提供更多的数据可能会提高其性能。

评估分数可使用grid_search.cv_results_来获得。这是一个字典,但是如果你将它包装在一个DataFrame中,你会得到一个很好的列表,其中包含每个超参数组合和每个交叉验证拆分的所有测试分数,以及所有拆分的平均测试分数:

最佳模型的平均测试RMSE分数为44 042,这比你之前使用的默认超参数值(47 019)的分数要好。恭喜,你已成功地微调了最佳模型!

2.7.2 随机搜索

当你探索相对较少的组合时,网格搜索方法很好,就像在前面的示例中一样,但RandomizedSearchCV通常更可取,尤其是当超参数搜索空间很大时。这个类的使用方式与GridSearchCV类大致相同,但它不是尝试所有可能的组合,而是评估固定数量的组合,在每次迭代中为每个超参数选择一个随机值。这听起来可能令人惊讶,但这种方法有几个好处:

· 如果你的一些超参数是连续的(或离散的,但有许多可能的值),并且你让随机搜索运行1000次迭代,那么它会为每个超参数探索1000个不同的值,而网格搜索只会探索你列出的很少几个值。

· 假设一个超参数实际上并没有太大的区别,但你还不知道。如果它有10个可能的值并将其添加到网格搜索中,则训练时间将延长10倍。但是,如果将其添加到随机搜索中,则不会有任何区别。

· 如果有6个超参数需要探索,每个都有10个可能的值,那么网格搜索除了训练模型一百万次之外别无选择,而随机搜索总是可以运行你选择的任意次数的迭代。

对于每个超参数,你必须提供可能的值列表或概率分布:

Scikit-Learn也有HalvingRandomSearchCVHalvingGridSearchCV超参数搜索类。它们的目标是更有效地使用计算资源,来更快地训练或探索更大的超参数空间。它们的工作原理如下:在第一轮中,使用网格方法或随机方法生成许多超参数组合(称为“候选”)。然后像往常一样使用这些候选来训练使用交叉验证进行评估的模型。然而,训练使用的资源有限,这大大加快了第一轮的速度。默认情况下,“资源有限”意味着模型是在一小部分训练集上训练的。然而,其他限制也是有可能的,如果模型有超参数来设置它,则减少训练迭代次数。一旦对每个候选都进行了评估,只有最好的候选才能进入第二轮,在那里它们将获得更多的资源。经过几轮之后,将使用全部资源对最终候选进行评估。这可能会为你节省一些调整超参数的时间。

2.7.3 集成方法

微调系统的另一种方法是尝试性能最佳的模型组合。该组合(或“集成”)通常会比最好的单个模型表现更好——就像随机森林比它们所依赖的单棵决策树表现更好一样——尤其是当单个模型犯了不同类型的错误时。例如,你可以训练和微调k近邻模型,然后创建一个仅预测随机森林预测的均值和该模型的预测的集成模型。我们将在第7章中更详细地讨论这个主题。

2.7.4 分析最佳模型及其错误

通过检查最佳模型,你通常会得到对问题的深刻见解。例如,RandomForestRegressor可以表明每个属性对于做出准确预测的相对重要性:

让我们对这些重要性分数进行降序排序,并将它们显示在相应的属性名称旁边:

有了这些信息,你可能想尝试删除一些不太有用的特征(例如,显然只有一个ocean_proximity类别真正有用,因此你可以尝试删除其他类别)。

sklearn.feature_selection.SelectFromModel转换器可以自动为你删除最无用的特征:当你拟合它时,它会训练一个模型(通常是随机森林),查看它的feature_importances_属性,然后选择最有用的特征。然后,当你调用transform()时,它会丢弃其他特征。

你还应该查看系统产生的特定错误,然后尝试理解产生这些错误的原因以及可以解决问题的方法:添加额外的特征或删除无信息的特征、清洗异常值等。

现在也是确保你的模型不仅在平均水平上运行良好,而且在所有类别的地区(无论是农村还是城市、富裕地区还是贫困地区、北方还是南方、少数民族与否等)的好时机。为每个类别创建验证集的子集需要一些工作,但这很重要:如果你的模型在整个地区类别上表现不佳,那么在问题可能解决之前不应该部署它,或者至少不应该使用它对该类别做出预测,因为它可能弊大于利。

2.7.5 在测试集上评估系统

调整模型一段时间后,你最终拥有了一个性能足够好的系统。你已准备好在测试集上评估最终模型。这个过程没有什么特别的,只需从测试集中获取预测器和标签并运行final_model来转换数据并进行预测,然后评估这些预测:

在某些情况下,这样的泛化误差点估计不足以说服你启动模型:如果它只比当前生产环境的模型好0.1%怎么办?你可能想知道这个估计的精确度。为此,你可以使用scipy.stats.t.interval()来计算泛化误差的95%置信区间。你得到一个相当大的区间,从39 275到43 467,而你之前的点估计值41 424大致在它的中间:

如果你做了很多超参数调整,性能通常会比你使用交叉验证测量的性能稍差。这是因为你的系统最终会经过微调在验证数据集上表现良好,而在未知数据集上可能表现不佳。在这个例子中情况并非如此,因为测试RMSE低于验证RMSE,但是当它发生时,你必须抵制调整超参数来使数字在测试集上看起来不错的诱惑,这些改进不太可能泛化到新数据。

现在是项目启动前的阶段:你需要展示你的解决方案(突出展示你学到了什么、哪些有效、哪些无效、做出了哪些假设,以及你的系统的局限性)、记录所有内容,并创建漂亮的演示文稿与清晰的可视化和易于记忆的陈述(例如,“收入中位数是房价的首要预测指标”)。在这个加州的房屋示例中,系统的最终性能并不比专家的价格估计好多少,专家的价格估计通常要低30%,但启动它仍然可能是一个好主意,特别是如果这可以为专家节省一些时间,那么他们就可以从事更有趣、更有成效的任务。