通过调参来提升模型的泛化性能
网格搜索:尝试我们关心的参数的所有可能组合
一、简单网格搜索
在 2 个参数上使用 for 循环,对每种参数组合分别训练并评估一个分类器
# 简单的网格搜索实现
from sklearn.svm import SVC
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, random_state=0)
print("Size of training set: {} size of test set: {}".format(
X_train.shape[0], X_test.shape[0]))
best_score = 0
for gamma in [0.001, 0.01, 0.1, 1, 10, 100]:
for C in [0.001, 0.01, 0.1, 1, 10, 100]:
# 对每种参数组合都训练一个SVC
svm = SVC(gamma=gamma, C=C)
svm.fit(X_train, y_train)
# 在测试集上评估SVC
score = svm.score(X_test, y_test)
# 如果我们得到了更高的分数,则保存该分数和对应的参数 if score > best_score:
best_score = score
best_parameters = {'C': C, 'gamma': gamma}
print("Best score: {:.2f}".format(best_score))
print("Best parameters: {}".format(best_parameters))
Size of training set: 112 size of test set: 38
Best score: 0.97
Best parameters: {‘C’: 100, ‘gamma’: 0.001}
二、参数过拟合的风险与验证集
(1)对数据进行 3 折划分,分为训练集、验证集和测试集
mglearn.plots.plot_threefold_split()
(2)利用找到的参数设置重新构建一个模型同时在训练数据和验证数据上进行训练
from sklearn.svm import SVC
# 将数据划分为训练+验证集与测试集
X_trainval, X_test, y_trainval, y_test = train_test_split(
iris.data, iris.target, random_state=0)
# 将训练+验证集划分为训练集与验证集
X_train, X_valid, y_train, y_valid = train_test_split(
X_trainval, y_trainval, random_state=1)
print("Size of training set: {} size of validation set: {} size of test set:"
" {}\n".format(X_train.shape[0], X_valid.shape[0], X_test.shape[0]))
best_score = 0
for gamma in [0.001, 0.01, 0.1, 1, 10, 100]:
for C in [0.001, 0.01, 0.1, 1, 10, 100]:
# 对每种参数组合都训练一个SVC
svm = SVC(gamma=gamma, C=C)
svm.fit(X_train, y_train)
# 在验证集上评估SVC
score = svm.score(X_valid, y_valid)
# 如果我们得到了更高的分数,则保存该分数和对应的参数
if score > best_score:
best_score = score
best_parameters = {'C': C, 'gamma': gamma}
# 在训练+验证集上重新构建一个模型,并在测试集上进行评估
svm = SVC(**best_parameters)
svm.fit(X_trainval, y_trainval)
test_score = svm.score(X_test, y_test)
print("Best score on validation set: {:.2f}".format(best_score))
print("Best parameters: ", best_parameters)
print("Test set score with best parameters: {:.2f}".format(test_score))
Size of training set: 84 size of validation set: 28 size of test set: 38
Best score on validation set: 0.96
Best parameters: {‘C’: 10, ‘gamma’: 0.001}
Test set score with best parameters: 0.92
验证集上的最高分数是 96%,这比之前略低,可能是因为我们使用更少的数据来训练模型。
训练集、验证集和测试集之间的区别对于在实践中应用机器学习方法至关重要。任何根据测试集精度所做的选择都会将测试集的信息“泄漏”(leak)到模型中。因此,保留一个单独的测试集是很重要的,它仅用于最终评估。
带交叉验证的网格搜索
为了得到对泛化性能的更好估计,我们可以使用交叉验证来评估每种参数组合的性能,而不是仅将数据单次划分为训练集与验证集。
for gamma in [0.001, 0.01, 0.1, 1, 10, 100]:
for C in [0.001, 0.01, 0.1, 1, 10, 100]:
# 对于每种参数组合都训练一个SVC
svm = SVC(gamma=gamma, C=C)
# 执行交叉验证
scores = cross_val_score(svm, X_trainval, y_trainval, cv=5)
# 计算交叉验证平均精度
score = np.mean(scores)
# 如果我们得到了更高的分数,则保存该分数和对应的参数
if score > best_score:
best_score = score
best_parameters = {'C': C, 'gamma': gamma}
svm = SVC(**best_parameters)
svm.fit(X_trainval, y_trainval)
SVC(C=100, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape=‘ovr’, degree=3, gamma=0.01, kernel=‘rbf’,
max_iter=-1, probability=False, random_state=None, shrinking=True,
tol=0.001, verbose=False)
使用交叉验证的主要缺点就是训练所有这些模型所需花费的时间。
mglearn.plots.plot_cross_val_selection()
对于每种参数设置(图中仅显示了一部分),需要计算 5 个精度值,交叉验证的每次划分都要计算一个精度值。然后,对每种参数设置计算平均验证精度。最后,选择平均验证精度最高的参数,用圆圈标记。
划分数据、运行网格搜索并评估最终参数
mglearn.plots.plot_grid_search_overview()
如果 C 和 gamma 想要尝试的取值为 0.001、
0.01、0.1、1、10 和 100,可以将其转化为下面这个字典
param_grid = {'C': [0.001, 0.01, 0.1, 1, 10, 100],
'gamma': [0.001, 0.01, 0.1, 1, 10, 100]}
print("Parameter grid:\n{}".format(param_grid))
使用模型(SVC)、要搜索的参数网格(param_grid)与要使用的交叉验证策略(比如 5 折分层交叉验证)将 GridSearchCV 类实例化
from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC
grid_search = GridSearchCV(SVC(), param_grid, cv=5,
return_train_score=True)
将数据划分为训练集和测试集,以避免参数过拟合
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, random_state=0)
我们创建的 grid_search 对象的行为就像是一个分类器,我们可以对它调用标准的 fit、predict 和 score 方法
grid_search.fit(X_train, y_train)
拟合 GridSearchCV 对象不仅会搜索最佳参数,还会利用得到最佳交叉验证性能的参数在整个训练数据集上自动拟合一个新模型。
print("Test set score: {:.2f}".format(grid_search.score(X_test, y_test)))
我们找到的参数保存在 best_params_ 属性中,而交叉验证最佳精度(对于这种参数设置,不同划分的平均精度)保存在 best_score_ 中
print("Best parameters: {}".format(grid_search.best_params_))
print("Best cross-validation score: {:.2f}".format(grid_search.best_score_))
print("Best estimator:\n{}".format(grid_search.best_estimator_))
1. 分析交叉验证的结果
将交叉验证的结果可视化通常有助于理解模型泛化能力对所搜索参数的依赖关系。
import pandas as pd
results = pd.DataFrame(grid_search.cv_results_)
display(results.head())
results 中每一行对应一种特定的参数设置。对于每种参数设置,交叉验证所有划分的结果都被记录下来,所有划分的平均值和标准差也被记录下来。由于我们搜索的是一个二维参数网格(C 和 gamma),所以最适合用热图可视化(见图 5-8)。
scores = np.array(results.mean_test_score).reshape(6, 6)
mglearn.tools.heatmap(scores, xlabel='gamma', xticklabels=param_grid['gamma'],
ylabel='C', yticklabels=param_grid['C'], cmap="viridis")
热图中的每个点对应于运行一次交叉验证以及一种特定的参数设置。颜色表示交叉验证的精度:浅色表示高精度,深色表示低精度。
f
ig, axes = plt.subplots(1, 3, figsize=(13, 5))
param_grid_linear = {'C': np.linspace(1, 2, 6),
'gamma': np.linspace(1, 2, 6)}
param_grid_one_log = {'C': np.linspace(1, 2, 6),
'gamma': np.logspace(-3, 2, 6)}
param_grid_range = {'C': np.logspace(-3, 2, 6),
'gamma': np.logspace(-7, -2, 6)}
for param_grid, ax in zip([param_grid_linear, param_grid_one_log,
param_grid_range], axes):
grid_search = GridSearchCV(SVC(), param_grid, cv=5)
grid_search.fit(X_train, y_train)
scores = grid_search.cv_results_['mean_test_score'].reshape(6, 6)
# plot the mean cross-validation scores
scores_image = mglearn.tools.heatmap(
scores, xlabel='gamma', ylabel='C', xticklabels=param_grid['gamma'],
yticklabels=param_grid['C'], cmap="viridis", ax=ax)
plt.colorbar(scores_image, ax=axes.tolist())
第一张图没有显示任何变化,整个参数网格的颜色相同。
参数 C 和gamma 不正确的缩放以及不正确的范围造成
也可能是因为这个参数根本不重要
第二张图显示的是垂直条形模式。
表示只有 gamma 的设置对精度有影响
第三张图中 C 和 gamma 对应的精度都有变化。
由于最佳参数位于图像的边界,所以我们可以认为,在这个边界之外可能还有更好的取值,我们可能希望改变搜索范围以包含这一区域内的更多参数
2. 在非网格的空间中搜索
在某些情况下,尝试所有参数的所有可能组合(正如 GridSearchCV 所做的那样)并不是一个好主意
列表中的每个字典可扩展为一个独立的网格。包含内核与参数的网格搜索可能如下所示。
param_grid = {'C': [0.001, 0.01, 0.1, 1, 10, 100],
'gamma': [0.001, 0.01, 0.1, 1, 10, 100]}
scores = cross_val_score(GridSearchCV(SVC(), param_grid, cv=5),
iris.data, iris.target, cv=5)
print("Cross-validation scores: ", scores)
print("Mean cross-validation score: ", scores.mean())
List of grids:
[{‘kernel’: [‘rbf’], ‘C’: [0.001, 0.01, 0.1, 1, 10, 100],
‘gamma’: [0.001, 0.01, 0.1, 1, 10, 100]},
{‘kernel’: [‘linear’], ‘C’: [0.001, 0.01, 0.1, 1, 10, 100]}]
grid_search = GridSearchCV(SVC(), param_grid, cv=5)
grid_search.fit(X_train, y_train)
print("Best parameters: {}".format(grid_search.best_params_))
print("Best cross-validation score: {:.2f}".format(grid_search.best_score_))
Best parameters: {‘C’: 100, ‘kernel’: ‘rbf’, ‘gamma’: 0.01}
Best cross-validation score: 0.97
查看 cv_results_,如果 kernel 等于 ‘linear’,那么只有 C 是变化的
results = pd.DataFrame(grid_search.cv_results_)
# 我们给出的是转置后的表格,这样更适合页面显示:
display(results.T)
3. 使用不同的交叉验证策略进行网格搜索
与 cross_val_score 类似,GridSearchCV 对分类问题默认使用分层 k 折交叉验证,对回归问题默认使用 k 折交叉验证。
但是,你可以传入任何交叉验证分离器作为 GridSearchCV 的cv 参数
(1)嵌套交叉验证
调用 cross_val_score,并用 GridSearchCV的一个实例作为模型:
scores = cross_val_score(GridSearchCV(SVC(), param_grid, cv=5),
iris.data, iris.target, cv=5)
print("Cross-validation scores: ", scores)
print("Mean cross-validation score: ", scores.mean())
Cross-validation scores: [ 0.967 1. 0.967 0.967 1. ]
Mean cross-validation score: 0.98
嵌套交叉验证的结果可以总结为“SVC 在 iris 数据集上的交叉验证平均精度为 98%
def nested_cv(X, y, inner_cv, outer_cv, Classifier, parameter_grid):
outer_scores = []
# for each split of the data in the outer cross-validation
# (split method returns indices of training and test parts)
for training_samples, test_samples in outer_cv.split(X, y):
# find best parameter using inner cross-validation
best_parms = {}
best_score = -np.inf
# iterate over parameters
for parameters in parameter_grid:
# accumulate score over inner splits
cv_scores = []
# iterate over inner cross-validation
for inner_train, inner_test in inner_cv.split(
X[training_samples], y[training_samples]):
# build classifier given parameters and training data
clf = Classifier(**parameters)
clf.fit(X[inner_train], y[inner_train])
# evaluate on inner test set
score = clf.score(X[inner_test], y[inner_test])
cv_scores.append(score)
# compute mean score over inner folds
mean_score = np.mean(cv_scores)
if mean_score > best_score:
# if better than so far, remember parameters
best_score = mean_score
best_params = parameters
# build classifier on best parameters using outer training set
clf = Classifier(**best_params)
clf.fit(X[training_samples], y[training_samples])
# evaluate
outer_scores.append(clf.score(X[test_samples], y[test_samples]))
return np.array(outer_scores)
在 iris 数据集上运行这个函数
from sklearn.model_selection import ParameterGrid, StratifiedKFold scores = nested_cv(iris.data, iris.target, StratifiedKFold(5),
StratifiedKFold(5), SVC, ParameterGrid(param_grid)) print("Cross-validation scores: {}".format(scores))
Cross-validation scores: [ 0.967 1. 0.967 0.967 1. ]
(2)交叉验证与网格搜索并行
虽然在许多参数上运行网格搜索和在大型数据集上运行网格搜索的计算量可能很大,但这些计算都是并行的(parallel)。
scikit-learn 不允许并行操作的嵌套。如果你在模型(比如随机森林)中使用了 n_jobs 选项,那么就不能在 GridSearchCV 使用它来搜索这个模型。