在机器学习中,模型的选择和性能评估是一个重要环节。为了得到一个模型的泛化能力估计,通常使用交叉验证(CV)的方法。本文将探讨嵌套交叉验证与非嵌套交叉验证两种策略,并以鸢尾花数据集上的分类器为例进行比较。
非嵌套交叉验证可能会使模型偏向于数据集,因为它在选择参数时最大化了分数,从而得到过于乐观的结果。而嵌套交叉验证通过一系列的训练/验证/测试集分割来避免这个问题。在内部循环中,通过拟合每个训练集来近似最大化分数,并在验证集上直接最大化选择(超)参数。在外循环中,通过在多个数据集分割上平均测试集分数来估计泛化误差。
在下面的示例中,使用支持向量分类器和非线性核来构建一个通过网格搜索优化超参数的模型。通过比较非嵌套和嵌套交叉验证策略的分数差异来评估它们的性能。
以下是一个使用Python和scikit-learn库实现的示例代码。该代码首先加载鸢尾花数据集,然后设置参数网格,接着使用支持向量分类器进行模型训练和超参数优化。最后,比较非嵌套和嵌套交叉验证策略的性能。
import numpy as np
from matplotlib import pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import GridSearchCV, KFold, cross_val_score
from sklearn.svm import SVC
# 随机试验的次数
NUM_TRIALS = 30
# 加载数据集
iris = load_iris()
X_iris = iris.data
y_iris = iris.target
# 设置要优化的参数的可能值
p_grid = {
"C": [1, 10, 100],
"gamma": [0.01, 0.1]
}
# 使用支持向量分类器和"rbf"核
svm = SVC(kernel="rbf")
# 存储分数的数组
non_nested_scores = np.zeros(NUM_TRIALS)
nested_scores = np.zeros(NUM_TRIALS)
# 每次试验的循环
for i in range(NUM_TRIALS):
# 选择内部和外部循环的交叉验证技术
inner_cv = KFold(n_splits=4, shuffle=True, random_state=i)
outer_cv = KFold(n_splits=4, shuffle=True, random_state=i)
# 非嵌套参数搜索和评分
clf = GridSearchCV(estimator=svm, param_grid=p_grid, cv=outer_cv)
clf.fit(X_iris, y_iris)
non_nested_scores[i] = clf.best_score_
# 嵌套CV与参数优化
clf = GridSearchCV(estimator=svm, param_grid=p_grid, cv=inner_cv)
nested_score = cross_val_score(clf, X=X_iris, y=y_iris, cv=outer_cv)
nested_scores[i] = nested_score.mean()
score_difference = non_nested_scores - nested_scores
print("平均差异为 {:6f},标准差为 {:6f}.".format(score_difference.mean(), score_difference.std()))
# 绘制嵌套和非嵌套CV的分数图表
plt.figure()
plt.subplot(211)
non_nested_scores_line, = plt.plot(non_nested_scores, color="r")
nested_line, = plt.plot(nested_scores, color="b")
plt.ylabel("分数", fontsize="14")
plt.legend([non_nested_scores_line, nested_line], ["非嵌套CV", "嵌套CV"], bbox_to_anchor=(0, 0.4, 0.5, 0))
plt.title("鸢尾花数据集上的非嵌套与嵌套交叉验证", x=0.5, y=1.1, fontsize="15")
# 绘制差异的条形图
plt.subplot(212)
difference_plot = plt.bar(range(NUM_TRIALS), score_difference)
plt.xlabel("单独试验 #")
plt.legend([difference_plot], ["非嵌套CV - 嵌套CV分数"], bbox_to_anchor=(0, 1, 0.8, 0))
plt.ylabel("分数差异", fontsize="14")
plt.show()