【精品系列】【机器学习实战】【实践项目一】区域房价中位数预测(微调模型)

5、微调模型

假设你现在有了一个有效模型的候选列表。现在你需要对它们进行微调。我们来看几个可行的方法。

5.1、网格搜索

一种微调的方式是以手动调整超参数,直到找到一组很好的超参数组合。这个过程非常枯燥乏味,你可以坚持不到足够的时间来探索出各种组合。
相反,你可以用Scikit-LearnGridSearchCV来替你进行探索。你所需要做的只是告诉它你要进行实验的超参数是什么,以及需要尝试的值,它将会使用交叉验证来评估超参数的所有可能组合。例如,下面这段代码搜索RandomForestRegressor的超参数数值的最佳组:

from sklearn.model_selection import GridSearchCV

param_grid = [
    {
     "n_estimators": [3, 10, 30], "max_features": [2, 4, 6, 8]},
    {
     "bootstrap": [False], "n_estimators": [3, 10], "max_features": [2, 3, 4]},
]
forest_reg = RandomForestRegressor()
grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
                           scoring="neg_mean_squared_error",
                           return_train_score=True)
grid_search.fit(housing_prepared, housing_labels)
GridSearchCV(cv=5, estimator=RandomForestRegressor(),
             param_grid=[{'max_features': [2, 4, 6, 8],
                          'n_estimators': [3, 10, 30]},
                         {'bootstrap': [False], 'max_features': [2, 3, 4],
                          'n_estimators': [3, 10]}],
             return_train_score=True, scoring='neg_mean_squared_error')

当你不知道超参数应该赋什么值时,一个简单的方法是尝试10次连续幂次方(如果你想要得到更细粒度的搜索,可以使用更小的数,参考这个示例中n_estimators超参数。

这个param_grid告诉Scikit-Learn,首先评估第一个dict中的n_estimatorsmax_features的所有 3×4=12 种超参数组合;接着,尝试第二个dict中超参数的所有 1×2×3=6 种组合,但这次超参数bootstrap需要设置为False而不是True

总而言之,网格探索将探索RandomForestRegressor超参数值的 12+6=18 种组合,并对每个模型进行5次训练(cv=5)。换句话,总共完成 18×5=90 次训练。

但是训练完成后你就可以获得最佳的参数组合:

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

你可以直接得到最好的估算器:

grid_search.best_estimator_
RandomForestRegressor(max_features=6, n_estimators=30)

如果GridSearchCV被初始化为refit=True(默认值),那么一旦通过交叉验证找到最佳估算器,它将在整个训练集上重新训练。这通常是个好方法,因为提供更多的数据很可能提升其性能。

当然还有评估分数:

cvres = grid_search.cv_results_
for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
    print(np.sqrt(-mean_score), params)
64219.37103342171 {'max_features': 2, 'n_estimators': 3}
55757.44574868692 {'max_features': 2, 'n_estimators': 10}
52912.46028198916 {'max_features': 2, 'n_estimators': 30}
60369.36943060073 {'max_features': 4, 'n_estimators': 3}
53342.610401252685 {'max_features': 4, 'n_estimators': 10}
50755.23490862702 {'max_features': 4, 'n_estimators': 30}
59396.83436658384 {'max_features': 6, 'n_estimators': 3}
52375.46588717245 {'max_features': 6, 'n_estimators': 10}
50133.101632717895 {'max_features': 6, 'n_estimators': 30}
58851.03261455543 {'max_features': 8, 'n_estimators': 3}
52154.38996091269 {'max_features': 8, 'n_estimators': 10}
50142.71940679718 {'max_features': 8, 'n_estimators': 30}
63061.98058118926 {'bootstrap': False, 'max_features': 2, 'n_estimators': 3}
54457.63242342584 {'bootstrap': False, 'max_features': 2, 'n_estimators': 10}
59490.18437223276 {'bootstrap': False, 'max_features': 3, 'n_estimators': 3}
52951.47441756218 {'bootstrap': False, 'max_features': 3, 'n_estimators': 10}
59440.60460822187 {'bootstrap': False, 'max_features': 4, 'n_estimators': 3}
51717.31614272946 {'bootstrap': False, 'max_features': 4, 'n_estimators': 10}

在本例中,我们得到的最佳解决方案是将超参数max_features设置为6,n_estimators设置为30。这个组合的RMSE分数为50133,略优于之前默认参数的分数50287

5.2、随机搜索

如果探索的组合数量太少(例如上一个案例),那么网格搜索是一个不错的方法。但是当超参数的搜索范围比较大时,通过会优先选择使用RandomizedSearchCV。这个类用起来与GridSearchCV类大致相同,但是它不会尝试所有可能的组合,而是在每次迭代中为每个超参数选择一个随机值,然后对一定数量的随机组合进行评估。这种方法有两个显著好处:

  1. 如果运行随机搜索1000个迭代,那么将会探索每个超参数的1000个不同的值。
  2. 通过简单的设置迭代次数,可以更好地控制要分配给超参数搜索的计算预算。
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint

param_distribs = {
     
    'n_estimators': randint(low=1, high=200),
    'max_features': randint(low=1, high=8),
}

forest_reg = RandomForestRegressor()
rnd_search = RandomizedSearchCV(forest_reg, param_distributions=param_distribs,
                                n_iter=10, cv=5, scoring='neg_mean_squared_error')
rnd_search.fit(housing_prepared, housing_labels)
RandomizedSearchCV(cv=5, estimator=RandomForestRegressor(),
                   param_distributions={'max_features': ,
                                        'n_estimators': },
                   scoring='neg_mean_squared_error')
cvres = rnd_search.cv_results_
for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
    print(np.sqrt(-mean_score), params)
49182.98287156724 {'max_features': 6, 'n_estimators': 142}
49256.64243951438 {'max_features': 7, 'n_estimators': 157}
49705.21035567195 {'max_features': 7, 'n_estimators': 59}
68431.80112151649 {'max_features': 1, 'n_estimators': 3}
49547.2056799527 {'max_features': 4, 'n_estimators': 144}
51481.190769293426 {'max_features': 5, 'n_estimators': 13}
59848.891702369874 {'max_features': 1, 'n_estimators': 9}
49482.15331762217 {'max_features': 4, 'n_estimators': 188}
50134.64676419512 {'max_features': 3, 'n_estimators': 162}
55151.747332409956 {'max_features': 1, 'n_estimators': 47}

5.3、集成方法

还有一种微调系统的方法是将表现最优的模型组合起来。组合(或“集成”)方法通过最佳的单一模型更好(就像随机森林比任何单个的决策树模型更好一样),特别是当单一模型会产生不同类型误差时更是如此。

5.4、分析最佳模型及其误差

通过检查最佳模型,你总是可以得到一些好的洞见。例如在进行准确预测时,RandomForestRegressor可以指出每个属性的相对重要程度:

feature_importances = grid_search.best_estimator_.feature_importances_
feature_importances
array([8.20349643e-02, 7.08313931e-02, 4.31025707e-02, 1.82135239e-02,
       1.66778827e-02, 1.83580953e-02, 1.58181207e-02, 2.93820957e-01,
       5.79152902e-02, 1.08181525e-01, 1.01867756e-01, 1.83167523e-02,
       1.43188327e-01, 4.00992699e-05, 4.73585356e-03, 6.89688957e-03])

将这些重要性分数显示在对应的属性名称旁边:

extra_attribs = ["rooms_per_hhold", "pop_per_hhold", "bedrooms_per_room"]
#cat_encoder = cat_pipeline.named_steps["cat_encoder"] # 旧版本
cat_encoder = full_pipeline.named_transformers_["cat"]
cat_one_hot_attribs = list(cat_encoder.categories_[0])
attributes = num_attribs + extra_attribs + cat_one_hot_attribs
sorted(zip(feature_importances, attributes), reverse=True)
[(0.2938209569026082, 'median_income'),
 (0.14318832667800896, 'INLAND'),
 (0.10818152506358511, 'pop_per_hhold'),
 (0.10186775584763018, 'bedrooms_per_room'),
 (0.08203496427830204, 'longitude'),
 (0.07083139305733996, 'latitude'),
 (0.057915290153382135, 'rooms_per_hhold'),
 (0.043102570688951285, 'housing_median_age'),
 (0.01835809530124593, 'population'),
 (0.018316752295000915, '<1H OCEAN'),
 (0.01821352394132179, 'total_rooms'),
 (0.0166778826835988, 'total_bedrooms'),
 (0.015818120710775083, 'households'),
 (0.006896889571023202, 'NEAR OCEAN'),
 (0.0047358535572835985, 'NEAR BAY'),
 (4.009926994286412e-05, 'ISLAND')]

5.5、通过测试集评估系统

通过一段时间的训练,你终于有了一个表现足够优秀的系统。现在是用测试集评估最终模型的时候了。这个过程没有什么特别的,只需要从测试集中获取预测器和标签,运行full_pipline来转换数据(调用transform()而不是fit_transform()),然后在测试集上评估最终模型:

final_model = rnd_search.best_estimator_

X_test = strat_test_set.drop("median_house_value", axis=1)
y_test = strat_test_set["median_house_value"].copy()

X_test_prepared = full_pipeline.transform(X_test)
final_predictions = final_model.predict(X_test_prepared)

final_mse = mean_squared_error(y_test, final_predictions)
final_rmse = np.sqrt(final_mse)
final_rmse
46979.86059266281

如果想知道这个估计的精确度。为此,你可以使用scipy.stats.t.interval()计算泛化误差的95%置信区间:

from scipy import stats

confidence = 0.95
squared_errors = (final_predictions - y_test) ** 2
np.sqrt(stats.t.interval(confidence, len(squared_errors) - 1,
                         loc=squared_errors.mean(),
                         scale=stats.sem(squared_errors)))
array([45009.06645486, 48871.24450507])

如果之前进行过大量的超参数调整,这时的评估结果通常会略逊色与之前使用的交叉验证时的表现结果(因为通过不断调整,系统在验证数据上终于表现良好,在未知数据集上可能达不到那么好的效果)。这时不要再继续调整超参数,因为这些改进在泛化到新数据集时又会变成无用功。

在本案例中,系统的最终性能可能并不比专家估算的效果更好,通过会下降20%左右,但是依然可以为专家腾出大量时间,投入到其他任务上。

你可能感兴趣的:(机器学习,python,人工智能)