
交叉验证是机器学习深度学习中不可或缺的一项技术,但使用不当的情况却时常发生。
本文将重点指出两个常见的错误,并介绍一些有助于避免这些错误的概念:
掌握这些技术能让大家在构建机器学习(ML)模型时,避免一些低级错误。
先让我们回顾一下:交叉验证的目的是什么?
机器学习的基本思路是:在“训练”数据集上拟合模型,然后在单独留出的“测试”数据上评估其性能(这部分数据用于模拟模型在现实世界中的表现)。
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=1000, n_features=20, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
clf = LogisticRegression()
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
rocauc = roc_auc_score(y_test, y_pred)
然而,简单的训练-测试分割方法存在一个问题。
当你采用单一的训练-测试分割时,你的 X_test 和 y_test 分割可能无法代表模型在实际应用中将遇到的数据类型。
这会导致一个问题,即模型在测试分割上的性能可能无法可靠地反映其在生产环境中的性能。
解决这个问题的办法是使用交叉验证,具体步骤如下:
创建多个训练-测试分割。
在每个分割上分别训练和评估模型。
计算所有测试分割上的平均性能。
这样我们就能更可靠地估计模型在实际应用中的性能。(更专业的说法是,交叉验证有助于你“估计底层模型的泛化误差”。)
以下是简单评估与 k 折(5 折)交叉验证的直观对比:
如上图所示,交叉验证过程首先从创建不同的训练-测试分割开始。
在我们的例子中,我们使用的是5折交叉验证,因此会创建5个版本的训练和测试数据。
接下来对于每个分割,都在训练分割上训练模型,并在测试分割上计算其性能。
最后我们计算所有测试分割上的平均得分,这样就能更可靠地了解模型的能力,且这种了解受我们使用的特定分割策略影响几率很小。
Scikit-learn 的 cross_val_score 函数可以一行代码轻松实现这一过程:
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression()
scores = cross_val_score(clf, X, y, cv=5, metric=
'roc_auc')
那么……问题出在哪里呢?
交叉验证效果看起来很不错,不是吗?
但需要注意的是,使用像 k 折或留 p 交叉验证这样的简单交叉验证策略,往往不足以确保模型的可靠性。
在本文的下一部分,将介绍三个常见错误,以及应对这些错误所需的应对技术。
错误一:在调整超参数时未使用嵌套交叉验证
在调整模型超参数时,重要的是不要使用最终的测试集来反复评估不同的模型配置(因为这会导致一种微妙的泄漏问题)。
这是什么意思呢?假设你将数据分割成一个训练集和一个测试集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

你首先使用默认超参数初始化模型,在训练集上训练它,并在测试集上评估它:
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score
clf = RandomForestClassifier()
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print(roc_auc_score(y_test, y_pred))
AUC 值为 0.73
假设在查看这些结果后,我们决定尝试一个稍有不同的超参数配置,即将 n_estimators 设置为 500,你在训练集上重新训练(新)模型,并再次在测试集上评估它:
clf = RandomForestClassifier(n_estimators=500)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print(roc_auc_score(y_test, y_pred))
在看到这些结果后,我们又选择了第三个超参数配置,在训练集上重新训练,并在测试集上评估:
clf = RandomForestClassifier(n_estimators=1000)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print(roc_auc_score(y_test, y_pred))
现在AUC 值达到了0.75,但真的是这样吗?从表面上看,我们的模型似乎得到了改进。
但大家看出问题了吗?
我们使用了测试集上的得分来指导超参数的选择,这是一种微妙的泄漏问题,因为我们所选择的超参数是针对特定测试集优化的,但我们并不知道这些超参数是否也是最适合现实世界中泛化性能的超参数。
我们间接地将测试集的一些信息“泄漏”给了模型;这些信息在生产环境中是无法获得的,因此不应该被使用。
拥有验证集和留出的测试集有助于防止这种情况:
对于每个超参数配置,你在训练集上训练模型,在验证集上评估它。
一旦找到最优超参数,就可以使用这些超参数训练一个模型,并在最终的“留出”测试分割上评估它(模型之前从未见过这个测试集),这有助于确保训练过程的完整性,并防止任何泄漏问题。
另外我们精心打磨了一套基于数据与模型方法的 AI科研入门学习方案(已经迭代第6次),对于人工智能来说,任何专业,要处理的都只是实验数据,所以我们根据实验数据将课程分为了三种方向的针对性课程,包含时序、影像、AI+实验室,我们会根据你的数据类型来帮助你选择合适的实验室,根据规划好的路线学习 只需3-5个月左右(很多同学通过学习已经发表了 sci 一区及以下、和同等级别的会议论文)学习形式为直播+录播,
多位老师为你的论文保驾护航。
大家感兴趣可以直接添加小助手微信:ai0808q通过后回复“咨询”既可。
第6期课程大纲
嵌套交叉验证的重要性
到目前为止,这还相对简单——机器学习基础入门!
但接下来,事情开始变得有趣了。
我们面临的问题是,所选择的特定训练-验证分割可能会影响我们选择的超参数。
为了防止这种情况,我们可以使用交叉验证来寻找最优超参数:
虽然这比简单的训练-验证-测试分割策略要好,但仍然不是最理想的,因为紫色的测试分割可能无法代表我们在实际应用中将遇到的数据。
现在我们又回到了简单(非交叉验证)评估方法中遇到的问题。
所以我们可能需要使用嵌套交叉验证,其中一个交叉验证循环用于选择超参数,另一个用于评估模型:
以下是 Scikit-learn 代码示例:
param_grid = {
'pca__n_components': [2, 5, 10],
'classifier__n_estimators': [50, 100],
'classifier__max_depth': [None, 10, 20],
}
grid_search = GridSearchCV(RandomForestClassifier(), param_grid, cv=5)
outer_cv = KFold(n_splits=5)
nested_score = cross_val_score(grid_search, X, y, cv=outer_cv)
print("Nested CV Score: ", nested_score.mean())
嵌套交叉验证是非常复杂的内容,但对于想深耕数据科学的人来说,掌握这项技术是非常有必要的。
错误二:时间序列数据分割不当
时间序列数据需要特殊处理。
时间序列数据的一个显著特征是它们具有自相关性——即时间序列与其滞后版本之间存在线性关系。(更专业的说法是,相邻时间点的观测值往往相似。)
这是一个问题,因为如果你的训练数据集中包含比测试数据集更晚的记录,那么你就允许模型“窥探”到生产环境中无法获得的有用信息。
我们不希望模型使用未来的信息来学习,我们希望它使用过去的信息来学习趋势。
所以在进行交叉验证时,我们必须使用特殊的分割策略,以下是我们目标的一个快速可视化:
首先我们定义留出的测试集(在上图中,测试集跨越了从 2024 年第 9 周到 2024 年第 13 周的四周时间)。
这是我们将用于估计模型在实际应用中性能的最终“跨时间”分割。
接下来,我们创建 k 个交叉验证分割(即创建 k 个版本的训练集和验证集)。每个训练集从 2023 年第 1 周开始,一直持续到验证集分割前一周。
Scikit-learn 提供了一个方便的 TimeSeriesSplit 类来帮助你实现这一点:
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
scores = []
for i, (train_index, val_index) in enumerate(tscv.split(X)):
train_X = X_train.loc[train_index]
train_y = y_train.loc[train_index]
val_X = X_train.loc[val_index]
val_y = y_train.loc[val_index]
clf = RandomForestClassifier()
clf.fit(train_X, train_y)
y_pred = clf.predict(val_X)
scores.append(roc_auc_score(val_y, y_pred))
print(np.mean(scores))
(需要注意的是,这里没有包含嵌套交叉验证——只有一个留出的测试分割,包含四周的数据。这主要是为了简洁起见,也是为了说明我们并不总是需要使用工具箱中的所有工具。合适的交叉验证策略是取决于具体情况来定的。)
另外我们精心打磨了一套基于数据与模型方法的 AI科研入门学习方案(已经迭代第6次),对于人工智能来说,任何专业,要处理的都只是实验数据,所以我们根据实验数据将课程分为了三种方向的针对性课程,包含时序、影像、AI+实验室,我们会根据你的数据类型来帮助你选择合适的实验室,根据规划好的路线学习 只需3-5个月左右(很多同学通过学习已经发表了 sci 一区及以下、和同等级别的会议论文)学习形式为
直播+录播,多位老师为你的论文保驾护航。
大家感兴趣可以直接添加小助手微信:ai0808q通过后回复“咨询”既可。
第6期课程大纲
大家想自学的我还给大家准备了一些机器学习、深度学习、神经网络资料大家可以看看以下文章(文章中提到的资料都打包好了,都可以直接添加小助手获取)
大家觉得这篇文章有帮助的话记得分享给你的死党、闺蜜、同学、朋友、老师、敌蜜!