news 2026/9/10 4:08:34

交叉验证全解析:从K折到时间序列,彻底搞懂模型评估

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
交叉验证全解析:从K折到时间序列,彻底搞懂模型评估

你试过把数据切好一份训练集、一份测试集,然后对着那张测试集上的准确率自信满满地写进简历吗?我第一次做分类任务时就这么干过,而且栽得很惨——同一个模型,只换一下随机种子,测试集准确率从 0.83 掉到 0.71。那一刻我才意识到:真正的问题根本不是模型不够好,而是我的评估方法本身就测不准。交叉验证,就是专门治这个病的一套思路。

如果你正处于机器学习的入门阶段,或者正在准备期末复习(我看最近“机器学习期末复习”“机器学习 周志华 pdf”这类词搜得很多),又或者已经在实际项目里跑过几轮模型,但总觉得评估结果忽高忽低、不太可信——这篇文章应该能帮你把“交叉验证”这件事彻底搞明白。我会先讲它到底在解决什么问题,再对比各种变体的适用边界,然后给出可以直接抄的 Python 代码,最后聊几个我踩过的特别隐蔽的坑。

1. 为什么单一划分靠不住:交叉验证出现的真正原因

1.1 一个让我印象深刻的翻车现场

当时我在做一个二分类任务,数据集大概 5000 条,正负样本比例大约 3:7。我按老套路用train_test_split(X, y, test_size=0.2, random_state=42)切了一刀,把模型训练出来,测试集 AUC 跑到了 0.86。说实话,当时心里挺美的,觉得这个项目稳了。

后来为了复现别人的对比实验,我把random_state换成了 88。结果你猜怎么着?同样的模型、同样的数据、同样的超参数,AUC 掉到了 0.79。我当时第一反应是自己代码写错了,反复检查了好几遍,还在群里问了一圈。最后发现代码没有任何问题,问题就出在那一次划分上——不同的随机划分,让模型遇到了“运气好”的测试集和“运气差”的测试集。

这不是个例。你只要自己动手试一次,把random_state从 0 到 200 都跑一遍,画出测试集分数的分布,你会看到一条上下剧烈震荡的折线。换句话说,单次划分得到的分数,本质上是一次“抽样”,而抽样的均值是多少、方差有多大,你根本不知道。

1.2 单一划分的三大致命伤:方差、浪费和运气

单一训练/测试划分(Holdout)的问题,总结起来有三点:

第一,方差大。测试集只有那一个,模型评估结果对数据划分方式极其敏感。样本量越小,这种波动越明显。本来你的模型真实水平是 0.80,一次划分可能测出 0.86,另一次可能测出 0.74,全看运气。

第二,浪费数据。你切了 20% 出来做测试,这 20% 在训练阶段从未参与模型拟合。对小数据集来说,这简直是奢侈:训练数据本来就少,你还砍掉一大块。反过来,如果你把测试集比例切小了,测试分数又不可靠,因为验证样本太少、噪声太大。

第三,用一次测试结果做决策,本质上是在赌运气。模型选型、超参数调整,每一步都要看分数。如果这个分数本身不稳定,那么你根据它做的所有决策都可能跑偏。你可以把单次划分想象成一次考试只考一道题——这道题你恰好会,分数就高;恰好不会,分数就低。这显然不能反映你的真实水平。

1.3 交叉验证的核心思想:把考试多考几次

交叉验证的思路特别朴素:既然单次划分靠不住,那我就多做几次划分,让每一份数据都有机会当“考生”和“评委”,最后把多次结果平均起来。

以最常用的 K 折交叉验证为例:把训练数据随机洗牌后平均分成 K 份(比如 5 份),每次取其中 1 份做验证集,剩下 K-1 份做训练集,这样循环 K 次,得到 K 个模型和 K 个评估分数,最后取平均。这样一来,每个样本都被验证过一次,数据利用率大大提升,评估结果也不再依赖某一次划分的运气。

你可能会问:“这不就是把同样的模型训练 K 遍吗?为什么训练出来的模型反而更可信?”这里关键不在于模型本身,而在于你拿到的是一个分数分布。通过 K 个分数的均值和标准差,你不仅知道模型大概什么水平,还能知道这个估计有多稳定。后面我会单独用一节讲这个“偏差-方差权衡”到底是怎么回事。

2. 主流变体怎么选:K折、留一、分层、分组与时间序列

2.1 K折交叉验证:最常用的默认选项

K 折交叉验证(K-Fold Cross-Validation)是整个交叉验证家族的基石。它的流程可以用一段话讲完:

  1. 把全部训练数据随机打乱;
  2. 平均切分成 K 份;
  3. 每一轮拿出第 i 份当验证集,合并其余 K-1 份当训练集;
  4. 记录模型在验证集上的得分;
  5. 重复 K 轮,计算平均分和标准差。

K 的取值直接决定“训练集有多大”和“验证集有多小”。K=5 时,每轮用 80% 数据训练、20% 数据验证;K=10 时,每轮用 90% 训练、10% 验证。K 越大,训练数据越充分,模型偏差越小,但同时每一轮验证集越小,验证分数本身的随机性也会变大,计算代价也随之增加。具体怎么权衡,我放到第 3 节细说。

实际使用中,我建议你把这个作为默认选项:

from sklearn.model_selection import KFold kf = KFold(n_splits=5, shuffle=True, random_state=42)

注意shuffle=True。如果数据本身带顺序(比如前 80% 都是类别 A,后 20% 都是类别 B),不洗牌直接切,很可能某一折里全是同一类别,训练和验证都会出问题。这一点很多人踩过坑,先记住:默认开 shuffle

2.2 留一法(LOOCV):小数据集的最优选择

留一法(Leave-One-Out Cross-Validation)是 K 折的极端情况——K 等于样本数 N。每次只留 1 个样本做验证,用剩下 N-1 个样本训练,重复 N 轮。

它的优点是:每一轮都几乎用到了全部数据来训练,对数据的利用率最高,而且评估结果几乎完全确定,没有随机划分带来的方差。缺点是:计算开销巨大,训练 N 次模型,如果样本量有 1 万条,就要训练 1 万个模型,而且当模型本身较慢时,这是灾难级的成本。

另外留一法还有一个隐蔽的问题:在模型性能评估上,经验上它的方差反而可能更大(是因为每次训练集之间高度重叠,导致 N 个测试结果之间高度相关)。所以在实际工程中,除非你的数据量特别小(比如少于几百条),否则我不建议把它作为首选。

2.3 分层K折:别忘了类别比例

如果你的数据存在类别不平衡(比如正样本只占 5%),普通 K 折切分后,某些折里可能一个正样本都分不到,验证时模型输出的指标就会剧烈波动,甚至直接报错(比如某折中只有一个类,AUC 无法计算)。

分层 K 折(StratifiedKFold)就是专门治这个问题的:在切分时,它会保持每个折里的类别比例与原始数据大致一致。换句话说,如果原始数据正负比是 1:9,那么每一折里,正负比也尽量保持在 1:9 左右。

from sklearn.model_selection import StratifiedKFold skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

我的习惯是:只要做的是分类任务,一律优先使用StratifiedKFold,哪怕数据看起来挺均匀也照用不误。原因很简单——它能让你少操心一种不确定性,尤其是在做网格搜索时,几百个超参数组合每种都要跑一遍交叉验证,某折因为类别缺失而中断,会让人非常头疼。

2.4 分组K折:防止数据泄漏的第一道防线

有一种泄漏不是靠“洗牌”能解决的,就是同一个来源的数据同时出现在训练集和验证集

举个例子:你有一批用户行为数据,同一个用户贡献了 50 条样本。普通 K 折随机划分时,这个用户的 50 条样本很可能被拆得到处都是——其中 40 条进了训练集,另外 10 条进了验证集。模型训练时已经见识过这个用户的习惯,验证时自然猜得特别准,分数虚高。但在实际线上环境里,新用户的数据是模型没见过的,你会被这个虚高的分数骗得很惨。

分组 K 折(GroupKFold)的做法是:保证同一个组的样本只能同时出现在训练集或验证集里。你只要把用户 ID 这样的分组标识传入,它就帮你完成这个隔离。

from sklearn.model_selection import GroupKFold gkf = GroupKFold(n_splits=5) for train_idx, val_idx in gkf.split(X, y, groups=user_ids): pass

这是我在真实项目里吃过亏后养成的习惯:只要样本之间存在层级结构(用户、设备、患者、店铺),分组划分就是底线。尤其在做风控、推荐、医疗相关任务时,忽略这一层,你的评估分数连参考价值都没有。

2.5 时间序列数据的交叉验证:绝对不能shuffle

时间序列数据(股票价格、日活预测、传感器数据)有一个特点:未来是未知的,训练数据的顺序本身携带信息。如果按照时间顺序采集的数据,你却用随机打乱的 K 折或者时间上错位的切分来训练和验证,相当于让模型偷看了未来,评估结果会严重高估模型的真实表现。

处理时间序列的标准做法是使用TimeSeriesSplit,它保证训练集的时刻永远在验证集之前:

from sklearn.model_selection import TimeSeriesSplit tscv = TimeSeriesSplit(n_splits=5) for train_idx, val_idx in tscv.split(X): pass

第一次划分用前 20% 训练、后面 20% 验证;第二次用前 40% 训练、再后面 20% 验证;以此类推。这种“不断滚动”的方式更贴近真实世界中模型被部署后的表现,尤其适合做“用历史预测未来”的任务。

变体适用场景核心注意点
K-Fold一般任务默认记得 shuffle=True
StratifiedKFold分类任务,尤其类别不平衡保持每折类别比例
LOOCV数据量极少计算昂贵,结果稳定
GroupKFold数据存在分组/层级结构防止同组数据跨折泄漏
TimeSeriesSplit时间序列预测训练集必须在验证集之前

3. 折数选择背后的偏差-方差博弈与计算代价

3.1 K=5还是K=10:为什么大家默认用这两个数

你一定好奇过:为什么教程里都爱用 K=5 或 K=10,而不是 K=3 或 K=20?

这背后其实是一组偏差和方差的权衡,也是一个非常经典的“统计学直觉”问题。我用通俗的语言拆一遍。

先说偏差。K 越小,每一轮参与训练的样本越少。K=3 时,每轮只有 2/3 的数据参与训练,模型能用的信息变少,拟合出来的模型质量会打折扣。也就是说,K 越小,评估分数你越有可能低估模型在完整数据上的真实表现——因为你评估的是一个“喂不饱”的模型,而不是一个“吃满全部数据”的模型。

但如果 K 越来越大(比如 K=N,也就是留一法)呢?每一轮训练集都用了 N-1 条数据,几乎喂满了,偏差自然很小。可问题出在方差上:每一轮的训练集之间高度重合,只差了 1 个样本,所以 K 个验证分数之间并不是独立的,而是高度相关的。高度相关意味着什么?意味着这 K 个分数的平均值,并不比单次评估“包含更多信息”,甚至由于验证集太小(只有 1 个样本),每次验证分数本身噪声巨大。

3.2 一个通俗类比:平时测验与期末考试

把交叉验证看作一次大考的“平时分”方案。

K 很小(比如 2 折),相当于只用一半课程内容去考“平时测验”,然后拿这个成绩预测期末成绩——当然不靠谱,因为题目范围太窄。K 很大(比如 N 折),相当于每一道题做一次考试,单题得分要么 100 要么 0,随机性太大,平均下来也未必能反映真实能力。

K=5 或 10 是经验上比较平衡的区间:训练集比例足够高(80% 或 90%),偏差不算大;每折验证集的样本量足够多(20% 或 10%),单折分数不算太离谱。所以绝大多数教材和工具默认值都落在这个范围。如果你数据量特别大(比如百万级),K=5 就可以了,因为 20% 验证集已经是一个很大的量级;如果你数据量中等(几千条),K=10 会更好,因为验证集更大、分数更稳定。

3.3 重复K折与嵌套交叉验证:什么时候需要更复杂

有时候单次 K 折的平均分仍然有波动。比如你换了random_state,平均分在 0.78 和 0.80 之间跳。这时候可以引入“重复 K 折”(RepeatedKFold):把 K 折交叉验证重复做 M 次,每次随机打乱顺序不同,最后把 M×K 个分数全部平均。

from sklearn.model_selection import RepeatedKFold rkf = RepeatedKFold(n_splits=5, n_repeats=3, random_state=42)

这相当于把考试重复考了好几轮,每轮题目组合不同,最后取平均分。结果会更稳定,代价是训练次数翻倍。我一般只在下面两种场景用:一是数据量不大但评估结果抖动明显;二是要拿最终评估分数给老板汇报,需要更可信的区间。

比重复 K 折更进阶的是“嵌套交叉验证”(Nested CV)。它要解决的是另一个问题:当你在训练集上做超参搜索时,你已经用光了数据的“信息”,如果再用同一份数据做模型评估,分数会偏乐观。嵌套交叉验证的做法是,外层循环负责评估模型,内层循环负责调参,每一折外层验证所用的模型,都是由内层交叉验证独立选出来的,杜绝了信息泄漏。这种方法相对重、耗时大,但如果你想严谨地报告模型泛化能力,它是金标准。

4. Python实操:从手写循环到sklearn封装

4.1 先手写一个K折交叉验证,理解底层逻辑

虽然 sklearn 已经封装好了,但我还是建议你至少手写一遍流程,能帮助你真正理解发生了什么。这套逻辑在你去面试时也会成为你的加分项。

import numpy as np from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score def manual_kfold_cv(X, y, k=5, random_state=42): rng = np.random.RandomState(random_state) idx = rng.permutation(len(X)) fold_sizes = np.full(k, len(X) // k, dtype=int) fold_sizes[: len(X) % k] += 1 # 把余数分配给前几折 scores = [] current = 0 for fold_size in fold_sizes: val_idx = idx[current:current + fold_size] train_idx = np.concatenate([idx[:current], idx[current + fold_size:]]) current += fold_size model = LogisticRegression(max_iter=1000) model.fit(X[train_idx], y[train_idx]) y_pred = model.predict(X[val_idx]) scores.append(accuracy_score(y[val_idx], y_pred)) return np.mean(scores), np.std(scores)

这段代码有几个细节值得注意:

  • 先用rng.permutation全局打乱索引,保证不按原顺序切分;
  • len(X) % k != 0时,把多出来的样本分给前几折,保证每个样本都恰好出现在一处验证集中;
  • 每一折都重新创建并训练一个模型,K 折交叉验证不是只训一个模型,而是 K 个模型。

你跑一遍这个函数,会看见每次输出的平均分和标准差。标准差越大,说明模型在不同数据子集上的表现越不稳定。

4.2 sklearn一行代码:cross_val_score的使用

实际项目里我们当然不用手写,直接用 sklearn 的封装更高效。最常用的是cross_val_score,一行代码返回 K 个分数:

from sklearn.model_selection import cross_val_score from sklearn.ensemble import RandomForestClassifier model = RandomForestClassifier(n_estimators=100, random_state=42) scores = cross_val_score(model, X, y, cv=5, scoring='f1_macro') print(f"平均F1: {scores.mean():.4f} ± {scores.std():.4f}")

这里cv=5可以传整数,也可以直接传一个KFoldStratifiedKFold实例。scoring用来指定评估指标,分类任务常用'accuracy''f1''roc_auc',回归任务常用'neg_mean_squared_error''r2'。注意 sklearn 的负号约定:这些打分函数遵循“越大越好”的原则,所以均方误差会被取成负数。

除了cross_val_score,还有几个高频 API 也值得掌握:

  • cross_validate:允许一次返回多个指标(比如同时看准确率和 F1),还能返回拟合时间、训练集分数,适合详细诊断;
  • cross_val_predict:不是评估分数,而是把模型每次在验证集上的预测结果拼起来,返回“每个样本被验证时的预测结果”,常用于绘制混淆矩阵或做模型集成。

4.3 最容易犯的预处理器偷看数据错误:Pipeline的重要性

这是我见过初学者最容易踩的暗坑之一。先看你有没有写过这样的代码:

from sklearn.preprocessing import StandardScaler from sklearn.model_selection import cross_val_score scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 在交叉验证之前就 fit 了 scores = cross_val_score(model, X_scaled, y, cv=5)

这段代码是错的,而且错得很隐蔽。原因是:StandardScaler在全量数据上做了fit,计算出了全局均值和方差,而交叉验证中的每一折验证集其实已经通过“全局标准化”偷看到了训练集之外的数据信息。这会轻微降低评估结果的可靠性,在某些特征工程里(比如用全量数据做特征选择后再交叉验证),泄漏带来的偏差会被急剧放大。

正确的做法是把预处理器和数据放进同一个 Pipeline,让预处理器在每一折内只使用“当前折的训练数据”进行 fit:

from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC pipe = Pipeline([ ('scaler', StandardScaler()), ('svm', SVC(kernel='rbf')) ]) scores = cross_val_score(pipe, X, y, cv=5, scoring='accuracy')

这样每一折内部都会先做一次独立的标准化 fit-transform,不掺入验证集信息。这个原则不仅适用于标准化,也适用于 PCA 降维、缺失值填充、特征选择等所有包含统计量的预处理步骤。记住一句话:任何“看过全量数据”的步骤,都必须放进 CV 循环内部,否则你就是在自欺欺人。

5. 交叉验证在超参调优中的正确打开方式

5.1 网格搜索与随机搜索中的交叉验证

交叉验证在日常项目里最频繁的使用场景,就是配合超参数搜索。具体逻辑是:给一组候选超参数,每一种组合都用 K 折交叉验证跑一遍,取 K 折平均分作为该组参数的得分,最后选出得分最高的一组。

sklearn 的GridSearchCV把它封装得非常方便:

from sklearn.model_selection import GridSearchCV from sklearn.ensemble import GradientBoostingClassifier param_grid = { 'n_estimators': [50, 100], 'max_depth': [3, 5, 7], 'learning_rate': [0.01, 0.1] } gs = GridSearchCV( GradientBoostingClassifier(random_state=42), param_grid, cv=5, scoring='f1', n_jobs=-1 ) gs.fit(X, y) print(gs.best_params_) print(gs.best_score_)

这里cv=5的含义就是:对每一种超参数组合,把训练数据切成 5 份做 5 折交叉验证,最后选平均分最高的组合。

当超参数组合很多、网格搜索太慢时,可以用RandomizedSearchCV做随机搜索:从参数分布中随机抽取固定次数的组合来试,而不是穷举所有组合。实测下来在高维超参空间中,随机搜索往往比网格搜索更快找到不错的参数区,因为它不会在无效维度上浪费太多计算。

5.2 一个容易忽略的问题:交叉验证的结论只在训练集上有效

这里必须强调一个非常重要但经常被忽略的边界:交叉验证的全部过程,都必须只在训练集上完成。你的最终测试集——也就是那份你和模型从未谋面的数据——必须一直被锁在保险箱里,直到所有调参、选型都结束后才能拿出来用一次。

很多人容易把GridSearchCV.best_score_直接当成模型的最终泛化精度去汇报。严格来说,这个分数已经通过“反复尝试大量超参数”这个过程被优化过了,它在某种程度上是“看到了候选模型集合”之后的一个乐观估计。虽然交叉验证比单次划分对此问题缓解不少,但测试集仍然是唯一的“干净”评估手段。

我的工作流是:

  1. 拿到数据先划分出一份独立测试集,通常 20% 左右,深度冻结;
  2. 在剩余 80% 的训练集上做交叉验证和超参调优;
  3. 选定最终模型后,在冻结的测试集上跑一次,用这个分数作为对外汇报的泛化估计。

这样虽然测试集只评估了一次,但它的结果是值得信任的,因为它从未参与过任何决策过程。

5.3 工程环境中的落地:以 SQL Server 机器学习服务为例

最近“sql server 2019机器学习服务器组件下载”这个关键词热度不低,评论区里也有朋友在问:交叉验证这种纯统计工具,能不能在企业级数据库环境里直接用。答案是可以的。

SQL Server 2019 内置的机器学习服务允许在 T-SQL 中通过sp_execute_external_script调用 R 或 Python 脚本,你可以在脚本里直接使用 sklearn 的交叉验证函数,然后把评估结果以表格形式返回给 SQL Server。

EXEC sp_execute_external_script @language = N'Python', @script = N' import pandas as pd from sklearn.model_selection import cross_val_score from sklearn.ensemble import RandomForestClassifier df = InputDataSet X = df.drop("label", axis=1) y = df["label"] model = RandomForestClassifier(n_estimators=100, random_state=42) scores = cross_val_score(model, X, y, cv=5, scoring="roc_auc") OutputDataSet = pd.DataFrame({"fold": range(1, 6), "score": scores}) ', @input_data_1 = N'SELECT * FROM your_table' WITH RESULT SETS ((fold INT, score FLOAT));

这样做的优势是:数据不用导出到 Python 环境,直接在数据库内部完成训练与验证,适合数据敏感、不方便外传的场景。如果你所在的公司数据管控严格,这是一个非常实用的思路。

6. 误用交叉验证的典型翻车现场与避坑清单

6.1 翻车现场一:先做特征选择再做交叉验证

这类翻车是交叉验证误用中最常见、也最容易让人“死不瞑目”的一种。

看这段“看似合理”的代码:

from sklearn.feature_selection import SelectKBest, f_classif selector = SelectKBest(f_classif, k=50) X_selected = selector.fit_transform(X, y) # 在全量数据上选特征 scores = cross_val_score(model, X_selected, y, cv=5)

问题出在:特征选择这一步已经用全量数据(包括每一折验证集)计算了 F 值和 p 值。你在每一折里用到的特征,本质上是通过偷看验证集标签选出来的。模型在验证集上得分会虚高,而且越是特征数量大、噪声多的数据集,虚高得越离谱。

正确的做法是把特征选择放进 Pipeline:

pipe = Pipeline([ ('select', SelectKBest(f_classif, k=50)), ('model', RandomForestClassifier(random_state=42)) ]) scores = cross_val_score(pipe, X, y, cv=5)

我见过一个极端的例子:某高维稀疏数据集上,先做特征选择再交叉验证,AUC 报出 0.92;把特征选择放进 Pipeline 之后,真实分数直接掉到 0.72。差距之大足以让你怀疑人生。所以我的经验是:所有基于数据统计进行的预处理或特征工程,都必须在每一次交叉验证的折内部重新 fit。

6.2 翻车现场二:把测试集混进交叉验证里调参

还有一个很普遍的操作失误:数据明明已经划分好了训练集和测试集,却在调参时把训练集和测试集合并放在一起,直接GridSearchCV.fit(X_all, y_all)

这种做法等于你让模型在调参阶段就“认识”了测试集的所有样本。调出来的参数虽然在交叉验证分数上很好看,但测试时表现往往很差,因为你已经用测试集的信息对超参数进行了隐式优化。更隐蔽的是,有人会用一个循环反复在测试集上评估模型、再微调超参数,多来几轮之后,测试集严格意义上已经变成了训练集的一部分。

正确的流程前面讲过了:调参只在训练集上做,测试集只在最后一次评估时使用。这个原则再怎么强调都不为过。

6.3 翻车现场三:样本量太小时仍然硬上K折

当样本量不足时,K 折交叉验证也会给出不靠谱的结论。比如你一共只有 100 条样本,做 10 折交叉验证,每个验证集只有 10 个样本,那么每一折的 AUC 或 F1 只是一个基于 10 个样本的极端估计,方差极大。你可能会看到五折分数是 0.90,另外五折直接崩到 0.55,平均出来一个完全没意义的值。

小样本场景下我更推荐:

  • 用较少的折数,比如 3 折或 4 折,让每折验证集有 25~33 条样本;
  • 或者用重复 K 折,跑多次后取平均,至少能把随机因素磨平一部分;
  • 如果数据量再少(比如几十条),可能需要考虑留一法,或直接采用领域知识做规则模型,不要对机器学习抱有太高期待。

另一个经验是,小样本时别只看平均分,更要关注每一折的分数分布。如果某几折出现极端值,往往说明数据本身存在很严重的分布不均匀,这时先回去检查数据分层、清洗和特征表达,而不是闷头调参。

6.4 避坑清单汇总

我把自己这些年踩过的坑整理成一张表,你可以当做 checklist 收藏:

误区典型表现正确做法
有效抽样错误预处理器在全量数据上 fit所有预处理放进 Pipeline
信息泄漏特征选择先于交叉验证特征选择在每一折内部完成
测试集污染调参时混入测试集测试集只评估一次
类别比例失衡普通 K 折切分不平衡数据使用 StratifiedKFold
分组泄漏同组样本跨折出现使用 GroupKFold
顺序破坏时间序列随机 shuffle使用 TimeSeriesSplit
小样本硬上100 条数据做 10 折减小折数或重复 K 折
忽略随机因子不设 random_state固定种子保证可复现

6.5 最后分享一个小技巧

如果你在做分类任务时发现每折分数波动很大,先别急着换模型。试一下把StratifiedKFoldshuffle=True打开,并设置一个固定random_state;如果波动依然很大,再去检查有没有泄漏。大多数情况下,忽高忽低的分数根本不是模型不够好,而是评估流程在偷偷作弊或者偷工减料。

交叉验证本质上是给模型评估这件事加了一层“多次测量取平均”的保险。它不复杂,但细节极多。把上面这些变体和坑都消化掉,你会发现模型评估的结果不再像股票走势图一样忽上忽下,而是稳定、可信、敢写进简历的数字。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 4:05:44

475与AMS Trex手操器全面对比:从操作逻辑到现场维护的换代之选

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 4:05:40

GE获取算子属性API文档

GetAllAttrNamesAndTypes 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型内存占用。 GE 提供对 PyTorch、T…

作者头像 李华
网站建设 2026/9/10 4:04:22

CANN/GE UDF工作空间示例

目录结构 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型内存占用。 GE 提供对 PyTorch、TensorFlow 前端的…

作者头像 李华
网站建设 2026/9/10 4:04:17

27B小模型凭MCP工具调用击败大模型?本地AI部署与实战解析

先说结论:这个结果一点都不离谱。前几天信通院MCP专项测评的榜单在圈子里传得挺快,StartLux这个主打本地部署的智能体方案,靠着一套27B参数的开源模型底座,在MCP工具调用能力上硬是压过了不少几百B参数的云端巨无霸,综…

作者头像 李华
网站建设 2026/9/10 4:03:06

农村人城市化:从身份标签到生存技能的全面升级

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华