如何拆分数据集以进行交叉验证、学习曲线和最终评估?

机器算法验证 机器学习 交叉验证 Python scikit-学习
2022-01-29 01:00:20

什么是拆分数据集的合适策略?

我要求对以下方法提供反馈(不是关于test_sizeor之类的单个参数n_iter,而是如果我使用了X, y, X_train, y_train,X_testy_test适当的以及顺序是否有意义):

从 scikit-learn 文档扩展这个例子)

1.加载数据集

from sklearn.datasets import load_digits
digits = load_digits()
X, y = digits.data, digits.target

2. 分成训练集和测试集(例如,80/20)

from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

3. 选择估算器

from sklearn.svm import SVC
estimator = SVC(kernel='linear')

4.选择交叉验证迭代器

from sklearn.cross_validation import ShuffleSplit
cv = ShuffleSplit(X_train.shape[0], n_iter=10, test_size=0.2, random_state=0)

5. 调整超参数

在训练集上应用交叉验证迭代器

from sklearn.grid_search import GridSearchCV
import numpy as np
gammas = np.logspace(-6, -1, 10)
classifier = GridSearchCV(estimator=estimator, cv=cv, param_grid=dict(gamma=gammas))
classifier.fit(X_train, y_train)

6. 使用学习曲线调试算法

X_train被随机分为训练集和测试集 10 次 ( n_iter=10)。训练分数曲线上的每个点是模型在前i个训练示例上训练和评估的 10 个分数的平均值。交叉验证分数曲线上的每个点都是 10 个分数的平均值,其中模型在前i个训练示例上训练并在测试集的所有示例上进行评估。

from sklearn.learning_curve import learning_curve
title = 'Learning Curves (SVM, linear kernel, $\gamma=%.6f$)' %classifier.best_estimator_.gamma
estimator = SVC(kernel='linear', gamma=classifier.best_estimator_.gamma)
plot_learning_curve(estimator, title, X_train, y_train, cv=cv)
plt.show()

学习曲线

plot_learning_curve()可以在 scikit-learn (0.15-git) 的当前开发版本中找到。

7. 对测试集的最终评估

classifier.score(X_test, y_test)

7a。使用嵌套交叉验证测试模型选择中的过度拟合(使用整个数据集)

from sklearn.cross_validation import cross_val_score
cross_val_score(classifier, X, y)

附加问题: 用嵌套交叉验证替换第 7 步是否有意义?还是应该将嵌套 cv 视为对第 7 步的补充

(代码似乎与 scikit-learn 中的 k-fold 交叉验证一起使用,但不适用于 shuffle & split。因此cv需要在上面进行更改以使代码正常工作)

8. 在整个数据集上训练最终模型

classifier.fit(X, y)

编辑:我现在同意 cbeleites 的观点,即步骤 7a 在这个序列中没有多大意义。所以我不会采用那个。

1个回答

我不确定你想在步骤 7a 中做什么。根据我现在的理解,这对我来说没有意义。

以下是我对您的描述的理解:在步骤 7 中,您希望将保留性能与包含步骤 4-6 的交叉验证的结果进行比较。(所以是的,这将是一个嵌套设置)。

我认为这种比较没有多大意义的要点是:

  • 这种比较无法检测到我在实践中遇到的过度乐观验证结果的两个主要来源:

    • 训练数据和测试数据之间的数据泄漏(依赖性)是由分层(也称为集群)数据结构引起的,并且在拆分中没有考虑到。在我的领域,我们通常有多个(有时是数千个)读数(= 数据矩阵中的行)同一患者或实验的生物学重复。这些不是独立的,因此需要在患者级别进行验证拆分。但是,发生这样的数据泄漏,您将在保留集的拆分和交叉验证拆分中都有它。坚持会像交叉验证一样有乐观的偏见。

    • 对整个数据矩阵进行的数据预处理,其中每行的计算不是独立的,而是许多/所有行用于计算预处理参数。典型的例子是“实际”分类之前的 PCA 投影。
      同样,这会影响您的保留和外部交叉验证,因此您无法检测到它。

    对于我使用的数据,这两个错误很容易导致错误分类的比例被低估一个数量级!

  • 如果您仅限于测试用例类型的性能计算分数,则模型比较需要非常大量的测试用例或真实性能的巨大差异。比较具有无限训练数据的 2 个分类器可能是进一步阅读的良好开端。

但是,比较“最佳”模型的内部交叉验证声称的模型质量和外部交叉验证或保留验证确实有意义:如果差异很大,那么您的网格搜索优化是否有效是值得怀疑的(您可能有由于性能度量的高方差而导致的撇去方差)。这种比较更容易,因为如果您的内部估计与另一个相比非常好,您可以发现问题 - 如果不是,您无需担心您的优化。但无论如何,如果您对性能的外部 (7) 测量是诚实可靠的,那么您至少对所获得的模型有一个有用的估计,无论它是否是最优的。

恕我直言,测量学习曲线是一个不同的问题。我可能会单独处理,我认为您需要更清楚地定义您需要学习曲线的内容(您需要给定问题、数据和分类方法数据集的学习曲线还是学习曲线对于给定问题、数据和分类方法的这个数据集),以及一堆进一步的决策(例如,如何处理模型复杂度作为训练样本大小的函数?重新优化,使用固定的超参数,决定根据训练集大小修复超参数的功能?)

(我的数据通常很少有独立的案例来获得足够精确的学习曲线测量以在实践中使用它 - 但如果你的 1200 行实际上是独立的,你可能会更好)


更新:scikit-learn 示例有什么“错误”?

首先,这里的嵌套交叉验证没有错。嵌套验证对于数据驱动的优化至关重要,而交叉验证是一种非常强大的方法(特别是在迭代/重复的情况下)。

然后,是否有任何问题取决于您的观点:只要您进行诚实的嵌套验证(保持外部测试数据严格独立),外部验证就是对“最佳”模型性能的适当衡量。没有错。

但是对于 SVM 的超参数调整,这些比例型性能度量的网格搜索可能而且确实会出错。基本上它们意味着您可能(可能?)不能依赖优化。尽管如此,只要您的外部拆分正确完成,即使模型不是最好的,您也可以诚实地估计您获得的模型的性能。

我将尝试给出直观的解释,为什么优化可能会遇到麻烦:

  • 从数学/统计学上讲,比例的问题在于,由于有限的测试样本大小 $n$(也取决于模型的真实性能 $p$),测量的比例 $\hat p$ 会受到巨大的变化: $Var (\hat p) = \frac{p (1 - p)}{n}$p^ are subject to a huge variance due to finite test sample size n (depending also on the true performance of the model, p):
    Var(p^)=p(1p)n

    您需要大量的案例(至少与我通常可以拥有的案例数量相比)才能达到估计召回率所需的精度(偏差/方差感),精度(机器学习性能感)。这当然也适用于您根据这些比例计算的比率。查看二项式比例的置信区间。它们大得惊人!通常大于超参数网格的真正性能改进。从统计学上讲,网格搜索是一个巨大的多重比较问题:您评估的网格点越多,发现某些超参数组合的风险就越高,这些组合对于您正在评估的训练/测试拆分意外地看起来非常好。这就是我所说的略读方差。

  • 直观地,考虑一个超参数的假设变化,这会慢慢导致模型恶化:一个测试用例向决策边界移动。直到案例越过边界并且在错误的一侧,“硬”比例性能测量才检测到这一点。然而,然后,他们立即为超参数的无限小变化分配一个完全错误。
    为了进行数值优化,您需要表现良好的性能度量。这意味着:比例型性能测量的跳跃(不可连续微分)部分以及除了跳跃之外没有检测到实际发生的变化的事实都不适用于优化。
    正确的评分规则以特别适合优化的方式定义。当预测概率与每个案例属于相关类别的真实概率相匹配时,它们具有全局最大值。

  • 对于 SVM,您还有一个额外的问题,即不仅性能度量,而且模型也会以这种跳跃的方式做出反应:超参数的微小变化不会改变任何东西。只有当超参数变化到足以导致某些情况不再是支持向量或成为支持向量时,模型才会发生变化。同样,这样的模型很难优化。

文学:


更新二:略读方差

在模型比较方面你能负担得起的显然取决于独立案例的数量。让我们在这里对略读方差的风险进行一些快速而肮脏的模拟:

scikit.learn说他们有 1797 个在digits数据中。

  • 假设比较了 100 个模型,例如 2 个参数的 10 美元 × 10 美元网格。10×10 grid for 2 parameters.
  • 假设两个参数(范围)根本不影响模型,
  • 即,所有模型都具有相同的真实性能,例如 97%(digits数据集的典型性能)。

  • 运行 $10^4$ 的“测试这些模型”模拟,样本大小 =数据集中1797 行104 simulations of "testing these models" with sample size = 1797 rows in the digits

    p.true = 0.97 # hypothetical true performance for all models
    n.models = 100 # 10 x 10 grid
    
    n.rows = 1797 # rows in scikit digits data
    
    sim.test <- replicate (expr= rbinom (n= nmodels, size= n.rows, prob= p.true), 
                           n = 1e4)
    sim.test <- colMaxs (sim.test) # take best model
    
    hist (sim.test / n.rows, 
          breaks = (round (p.true * n.rows) : n.rows) / n.rows + 1 / 2 / n.rows, 
          col = "black", main = 'Distribution max. observed performance',
          xlab = "max. observed performance", ylab = "n runs")
    abline (v = p.outer, col = "red")
    

这是观察到的最佳性能的分布:

略读方差模拟

红线标志着我们所有假设模型的真实表现。平均而言,我们仅观察到 100 个比较模型中看似最好的模型的真实错误率只有 2/3(对于模拟,我们知道它们都以 97% 的正确预测执行相同)。

这个模拟显然非常简化:

  • 除了测试样本量方差之外,至少还有由于模型不稳定性引起的方差,所以我们在这里低估了方差
  • 影响模型复杂性的调整参数通常会覆盖模型不稳定并因此具有高方差的参数集。
  • 对于示例中的 UCI 数字,原始数据库具有 ca。44人写的11000位数字。如果数据是根据写的人进行聚类的呢?(也就是说,如果你知道某人写的 8 是否更容易识别,比如 3?)那么有效样本量可能低至 44。
  • 调整模型超参数可能会导致模型之间的相关性(事实上,从数值优化的角度来看,这将被认为表现良好)。很难预测其影响(我怀疑如果不考虑分类器的实际类型,这是不可能的)。

然而,一般而言,独立测试用例数量少和比较模型数量多都会增加偏差。此外,Cawley 和 Talbot 的论文给出了经验观察到的行为。