模型微调

假设你现在有了一个列表,列表里有几个有希望的模型。你现在需要对它们进行微调。让我们来看几种微调的方法。

网格搜索

微调的一种方法是手工调整超参数,直到找到一个好的超参数组合。这么做的话会非常冗长,你也可能没有时间探索多种组合。

你应该使用 Scikit-Learn 的GridSearchCV来做这项搜索工作。你所需要做的是告诉GridSearchCV要试验有哪些超参数,要试验什么值,GridSearchCV就能用交叉验证试验所有可能超参数值的组合。例如,下面的代码搜索了RandomForestRegressor超参数值的最佳组合:

  1. from sklearn.model_selection import GridSearchCV
  2. param_grid = [
  3. {'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
  4. {'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
  5. ]
  6. forest_reg = RandomForestRegressor()
  7. grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
  8. scoring='neg_mean_squared_error')
  9. grid_search.fit(housing_prepared, housing_labels)

当你不能确定超参数该有什么值,一个简单的方法是尝试连续的 10 的幂(如果想要一个粒度更小的搜寻,可以用更小的数,就像在这个例子中对超参数n_estimators做的)。

param_grid告诉 Scikit-Learn 首先评估所有的列在第一个dict中的n_estimatorsmax_features3 × 4 = 12种组合(不用担心这些超参数的含义,会在第 7 章中解释)。然后尝试第二个dict中超参数的2 × 3 = 6种组合,这次会将超参数bootstrap设为False而不是True(后者是该超参数的默认值)。

总之,网格搜索会探索12 + 6 = 18RandomForestRegressor的超参数组合,会训练每个模型五次(因为用的是五折交叉验证)。换句话说,训练总共有18 × 5 = 90轮!K 折将要花费大量时间,完成后,你就能获得参数的最佳组合,如下所示:

  1. >>> grid_search.best_params_
  2. {'max_features': 6, 'n_estimators': 30}

提示:因为 30 是n_estimators的最大值,你也应该估计更高的值,因为评估的分数可能会随n_estimators的增大而持续提升。

你还能直接得到最佳的估计器:

  1. >>> grid_search.best_estimator_
  2. RandomForestRegressor(bootstrap=True, criterion='mse', max_depth=None,
  3. max_features=6, max_leaf_nodes=None, min_samples_leaf=1,
  4. min_samples_split=2, min_weight_fraction_leaf=0.0,
  5. n_estimators=30, n_jobs=1, oob_score=False, random_state=None,
  6. verbose=0, warm_start=False)

注意:如果GridSearchCV是以(默认值)refit=True开始运行的,则一旦用交叉验证找到了最佳的估计器,就会在整个训练集上重新训练。这是一个好方法,因为用更多数据训练会提高性能。

当然,也可以得到评估得分:

  1. >>> cvres = grid_search.cv_results_
  2. ... for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
  3. ... print(np.sqrt(-mean_score), params)
  4. ...
  5. 64912.0351358 {'max_features': 2, 'n_estimators': 3}
  6. 55535.2786524 {'max_features': 2, 'n_estimators': 10}
  7. 52940.2696165 {'max_features': 2, 'n_estimators': 30}
  8. 60384.0908354 {'max_features': 4, 'n_estimators': 3}
  9. 52709.9199934 {'max_features': 4, 'n_estimators': 10}
  10. 50503.5985321 {'max_features': 4, 'n_estimators': 30}
  11. 59058.1153485 {'max_features': 6, 'n_estimators': 3}
  12. 52172.0292957 {'max_features': 6, 'n_estimators': 10}
  13. 49958.9555932 {'max_features': 6, 'n_estimators': 30}
  14. 59122.260006 {'max_features': 8, 'n_estimators': 3}
  15. 52441.5896087 {'max_features': 8, 'n_estimators': 10}
  16. 50041.4899416 {'max_features': 8, 'n_estimators': 30}
  17. 62371.1221202 {'bootstrap': False, 'max_features': 2, 'n_estimators': 3}
  18. 54572.2557534 {'bootstrap': False, 'max_features': 2, 'n_estimators': 10}
  19. 59634.0533132 {'bootstrap': False, 'max_features': 3, 'n_estimators': 3}
  20. 52456.0883904 {'bootstrap': False, 'max_features': 3, 'n_estimators': 10}
  21. 58825.665239 {'bootstrap': False, 'max_features': 4, 'n_estimators': 3}
  22. 52012.9945396 {'bootstrap': False, 'max_features': 4, 'n_estimators': 10}

在这个例子中,我们通过设定超参数max_features为 6,n_estimators为 30,得到了最佳方案。对这个组合,RMSE 的值是 49959,这比之前使用默认的超参数的值(52634)要稍微好一些。祝贺你,你成功地微调了最佳模型!

提示:不要忘记,你可以像超参数一样处理数据准备的步骤。例如,网格搜索可以自动判断是否添加一个你不确定的特征(比如,使用转换器CombinedAttributesAdder的超参数add_bedrooms_per_room)。它还能用相似的方法来自动找到处理异常值、缺失特征、特征选择等任务的最佳方法。

随机搜索

当探索相对较少的组合时,就像前面的例子,网格搜索还可以。但是当超参数的搜索空间很大时,最好使用RandomizedSearchCV。这个类的使用方法和类GridSearchCV很相似,但它不是尝试所有可能的组合,而是通过选择每个超参数的一个随机值的特定数量的随机组合。这个方法有两个优点:

  • 如果你让随机搜索运行,比如 1000 次,它会探索每个超参数的 1000 个不同的值(而不是像网格搜索那样,只搜索每个超参数的几个值)。

  • 你可以方便地通过设定搜索次数,控制超参数搜索的计算量。

集成方法

另一种微调系统的方法是将表现最好的模型组合起来。组合(集成)之后的性能通常要比单独的模型要好(就像随机森林要比单独的决策树要好),特别是当单独模型的误差类型不同时。我们会在第7章更深入地讲解这点。

分析最佳模型和它们的误差

通过分析最佳模型,常常可以获得对问题更深的了解。比如,RandomForestRegressor可以指出每个属性对于做出准确预测的相对重要性:

  1. >>> feature_importances = grid_search.best_estimator_.feature_importances_
  2. >>> feature_importances
  3. array([ 7.14156423e-02, 6.76139189e-02, 4.44260894e-02,
  4. 1.66308583e-02, 1.66076861e-02, 1.82402545e-02,
  5. 1.63458761e-02, 3.26497987e-01, 6.04365775e-02,
  6. 1.13055290e-01, 7.79324766e-02, 1.12166442e-02,
  7. 1.53344918e-01, 8.41308969e-05, 2.68483884e-03,
  8. 3.46681181e-03])

将重要性分数和属性名放到一起:

  1. >>> extra_attribs = ["rooms_per_hhold", "pop_per_hhold", "bedrooms_per_room"]
  2. >>> cat_one_hot_attribs = list(encoder.classes_)
  3. >>> attributes = num_attribs + extra_attribs + cat_one_hot_attribs
  4. >>> sorted(zip(feature_importances,attributes), reverse=True)
  5. [(0.32649798665134971, 'median_income'),
  6. (0.15334491760305854, 'INLAND'),
  7. (0.11305529021187399, 'pop_per_hhold'),
  8. (0.07793247662544775, 'bedrooms_per_room'),
  9. (0.071415642259275158, 'longitude'),
  10. (0.067613918945568688, 'latitude'),
  11. (0.060436577499703222, 'rooms_per_hhold'),
  12. (0.04442608939578685, 'housing_median_age'),
  13. (0.018240254462909437, 'population'),
  14. (0.01663085833886218, 'total_rooms'),
  15. (0.016607686091288865, 'total_bedrooms'),
  16. (0.016345876147580776, 'households'),
  17. (0.011216644219017424, '<1H OCEAN'),
  18. (0.0034668118081117387, 'NEAR OCEAN'),
  19. (0.0026848388432755429, 'NEAR BAY'),
  20. (8.4130896890070617e-05, 'ISLAND')]

有了这个信息,你就可以丢弃一些不那么重要的特征(比如,显然只要一个ocean_proximity的类型(INLAND)就够了,所以可以丢弃掉其它的)。

你还应该看一下系统犯的误差,搞清为什么会有些误差,以及如何改正问题(添加更多的特征,或相反,去掉没有什么信息的特征,清洗异常值等等)。

用测试集评估系统

调节完系统之后,你终于有了一个性能足够好的系统。现在就可以用测试集评估最后的模型了。这个过程没有什么特殊的:从测试集得到预测值和标签,运行full_pipeline转换数据(调用transform(),而不是fit_transform()!),再用测试集评估最终模型:

  1. final_model = grid_search.best_estimator_
  2. X_test = strat_test_set.drop("median_house_value", axis=1)
  3. y_test = strat_test_set["median_house_value"].copy()
  4. X_test_prepared = full_pipeline.transform(X_test)
  5. final_predictions = final_model.predict(X_test_prepared)
  6. final_mse = mean_squared_error(y_test, final_predictions)
  7. final_rmse = np.sqrt(final_mse) # => evaluates to 48,209.6

评估结果通常要比交叉验证的效果差一点,如果你之前做过很多超参数微调(因为你的系统在验证集上微调,得到了不错的性能,通常不会在未知的数据集上有同样好的效果)。这个例子不属于这种情况,但是当发生这种情况时,你一定要忍住不要调节超参数,使测试集的效果变好;这样的提升不能推广到新数据上。

然后就是项目的预上线阶段:你需要展示你的方案(重点说明学到了什么、做了什么、没做什么、做过什么假设、系统的限制是什么,等等),记录下所有事情,用漂亮的图表和容易记住的表达(比如,“收入中位数是房价最重要的预测量”)做一次精彩的展示。