训集F1 0.89,测试集0.58:狂调正则化没救,补完监督学习基础我从数据层面止血

发布时间:2026/8/30 22:41:12
训集F1 0.89,测试集0.58:狂调正则化没救,补完监督学习基础我从数据层面止血 训集F1 0.89,测试集0.58:狂调正则化没救,补完监督学习基础我从数据层面止血发版前一天的压测下午,运维把测试集上的指标报告甩到群聊里。我盯着那个数字愣了半分钟:F1只有0.58,而训练集的F1稳稳趴在0.89。这是个医疗图像分类项目,过拟合已经严重到能直接给产品判死刑。我的第一反应是翻出模型代码,给卷积层后面加了一层Dropout,又把L2惩罚系数翻了一倍。重新训练三个小时,测试集F1--涨了0.01。那天下班前我坐在工位上反复盯着混淆矩阵,发现有一个类别几乎全部被预测成了背景。可这批数据明明在训练集里表现很好。我这才意识到,问题压根不在模型复杂度上,而是我一开始搭管道时就没处理好数据的分布和多样性。就是在那个晚上,我点开了机器学习基础课程,想从监督学习的源头把问题掰开揉碎。这门课不讲花哨的模型架构,而是把重心放在了数据预处理、特征工程和管道搭建上,恰恰补上了我最缺失的那一环。如果当时没有沿着机器学习管道的思路重新审视整个实验流程,我根本想不到数据泄漏、采样偏差这些看似基础的问题会直接把模型推下悬崖。为什么正则化没救?过拟合的根不在模型我把Dropout率从0.3一路调到0.7,又加了一层BatchNorm,测试集F1的变化曲线活像心电图。当时我跟组长汇报说“模型容量太大,需要更狠的正则化”。组长反问了一句:“你确定训练集和测试集是同分布的?”我回去仔细检查了数据划分脚本,发现一个致命错误:我们的图像数据是按采集批次存放在不同文件夹里的,而我随机划分时用的是全局打乱,结果训练集吃到了测试集同一个病人的连续断层扫描切片。这种数据泄漏让监督学习的评估完全失效--它本质上等同于把答案提前塞给了模型。后来在机器学习基础课程里我看到了专门讲数据划分陷阱的章节,它用真实的案例解释了为什么随机打乱不足以解决所有问题,比如时间序列数据需要按时间窗口切割,医学影像数据要按病人ID分割,否则模型在线上会把相邻切片当作弊信号。这个对数据预处理的系统梳理让我彻底明白了:盲目调参而不排查数据隐患,等于在沙子上盖楼。现在回想起来,如果早点接触AWS基础知识里对管道各环节的强调,我绝不会犯这种低级错误。混淆矩阵里的密码:样本失衡的放大效应在调正则化的过程中,我顺手打印出每个类别的精确率与召回率,这才发现第三个类别(某种罕见病变)的召回率直接为零。而这个类别在训练集里一共只有124张切片,占全部样本的1.4%。模型为了降低整体交叉熵,干脆学会了“永远不预测这类病变”,因为猜错的代价远低于猜对。监督学习场景下,样本不平衡的杀伤力比预想的大得多。而机器学习入门教程往往只用平衡的鸢尾花数据集做演示,根本不会让你意识到真实医疗数据中0.5%的正样本率意味着什么。我在那个时候尝试了给少样本类别加权重,把类别权重从1:1:1调成了1:1:30,结果模型开始在大量正常切片上疯狂误报,F1反而更低了。后来跟着机器学习管道的理念把数据增强和重采样组合起来用,情况才开始好转。课程里那个关于过采样SMOTE和图像随机增强结合的例子,让我第一次理解了“合成数据不能完全替代真实分布”的原理。机器学习基础确实不像某些高级课程那样一上来就讲Transformer,但它把监督学习中这些容易忽视的边角问题掰得极细,这正是我当时最需要的。数据增强:不只是一个transform函数我最开始的数据增强策略非常粗暴:随机旋转±15度、水平翻转、亮度微调。代码大概长这样:import torchvision.transforms as transforms train_transform transforms.Compose([ transforms.RandomRotation(15), transforms.RandomHorizontalFlip(0.5), transforms.ColorJitter(brightness0.1, contrast0.1), transforms.Resize((224, 224)), transforms.ToTensor() ]) # 当时以为这样就能让模型学到旋转不变性 # 但完全没考虑不同类别对旋转的敏感度差异病灶类别的图像本身就不具备对称性,过度的旋转让一部分恶性病变被翻转成了类似良性结节的形态,等于在训练时人为制造了噪声标签。学完机器学习基础中关于特征工程和数据增广的原则之后我才明白,数据增强要根据任务特性做定制化调整,而不是套一个官方的随机增强就万事大吉。我把增强策略改成了这样:# 针对病灶类别做了更保守的增强 custom_aug transforms.Compose([ transforms.RandomAffine(degrees5, translate(0.02, 0.02)), # 微小平移 transforms.RandomHorizontalFlip(0.3), # 降低翻转概率 transforms.RandomAdjustSharpness(sharpness_factor1.5, p0.3), transforms.Resize((224, 224)), transforms.ToTensor() ]) # 减少大角度旋转,改用微小位移和锐度调整 # 从数据层面尊重医学影像的空间约束这个修改让测试集F1从0.58提到了0.64,虽然还不够,但至少证明了方向是对的。而且在这个过程中,我顺带纠正了一个之前的认知偏差:监督学习的泛化能力不是靠模型容量硬撑出来的,而是靠数据给模型提供的“真实世界样本分布”来保证的。AWS机器学习相关的培训资料里多次强调这一点,但以前我以为自己懂了,直到踩坑才发现根本没懂到位。交叉验证的坑:5折不等于安全第一次数据泄漏之后,我决定用交叉验证来评估模型,以为这样就能万无一失。代码当时这么写的:from sklearn.model_selection import cross_val_score, KFold kf KFold(n_splits5, shuffleTrue, random_state42) scores cross_val_score(model, X, y, cvkf, scoringf1_macro) print(f交叉验证F1均值: {scores.mean():.3f}, 标准差: {scores.std():.3f}) # 输出均值0.82,标准差0.03,看起来稳如老狗 # 但一上测试集还是崩了问题出在哪里?我仍然在用全局shuffle,完全没有考虑到同一个病人的多张切片会被分散到训练和验证折里,导致验证折的指标虚高。后来在机器学习基础课程中看到关于分层采样和分组交叉验证的讲解,监督学习任务的交叉验证必须根据数据的内在结构来设计,比如GroupKFold才能保证同组的样本不出现在训练和验证两方。修正后的分折逻辑:from sklearn.model_selection import GroupKFold import numpy as np # patient_ids 是每张切片对应的病人ID gkf GroupKFold(n_splits5) for fold, (train_idx, val_idx) in enumerate(gkf.split(X, y, groupspatient_ids)): X_train, X_val X[train_idx], X[val_idx] y_train, y_val y[train_idx], y[val_idx] # 确保不同病人的数据不会互相泄漏 model.fit(X_train, y_train) val_f1 f1_score(y_val, model.predict(X_val), averagemacro) print(fFold {fold1} F1: {val_f1:.3f}) # 输出各折F1在0.71到0.75之间,标准差0.02 # 这才反映真实泛化能力分组交叉验证给出0.73的均值,比之前shuffle版低了整整0.09,但这个数字才是上线后能稳定保持的水平。这个教训让我彻底折服--机器学习管道不仅要管模型训练那一截,评估环节的任何放水都会导致决策层对性能的严重误判。学完后的改变:用数据管道重构了整个项目两周后我把整个训练管线重新写了一遍,核心是四个改动:按病人分组划分数据、针对少样本类别做有约束的数据增强、引入分组交叉验证、以及根据混淆矩阵反馈动态调整类别采样策略。最终测试集F1从0.58拉到了0.81,虽然没到训练集的0.89,但已经超过了医学影像辅助诊断系统的上线门槛。更重要的是我的工作方式变了。以前拿到一个数据集,我习惯立刻开始调SOTA模型架构,现在第一件事是去分析数据的分布、标注质量和验证策略。这完全是机器学习基础课程给我补上的思维习惯--它让我从“调参侠”变成了能系统地排查问题的工程师。后来我把重新搭建的管道部分做成了模板,团队里两个新同事接手类似项目时直接复用,开发时间从两周缩短到了五天。组长还让我在内部做了一次分享,题目叫“警惕那个训练集F1 0.95的模型”。那次分享里我反复提到监督学习中数据质量的决定性作用,以及亚马逊云科技机器学习平台上那些用于数据漂移检测和管道监控的工具,虽然没有当时就用上,但至少让团队对模型生命周期管理有了整体概念。给类似处境的人几条硬建议经历过这次从过拟合到数据层面止血的全过程,我把踩坑结晶成七条,条条带血泪:先看数据分布,再看模型架构。模型在训练集上的完美表现毫无意义,如果监督学习的评估环节有数据泄漏,那所有的调参都是在自欺欺人。别只看全局指标,把混淆矩阵铺开。F1只是一个摘要,类别级别的精确率和召回率才能暴露样本失衡和标注偏差。数据增强要按类别定制。把机器学习基础中数据预处理的原则吃透,尤其是医学影像这种对几何变换敏感的任务,否则增强就是在注入噪声。交叉验证的分组策略比n_folds更重要。如果数据有天然聚合结构(病人、设备、采集批次),用GroupKFold而不是普通的shuffle拆分,这点在机器学习管道搭建时就要定死。过拟合先别急着调正则化。把Dropout和L2当成最后的手段,优先检查数据划分、样本平衡度、增强合理性。记录每一个实验的数据前提。我后来用WB记录了每次实验的数据划分方式、采样策略和增强参数,这才让可复现性有了根基。系统补上数据层面的基础课。如果你的机器学习入门只停留在调sklearn接口的阶段,那迟早会遇到我这种翻车。很多AWS官方课程已经把数据预处理和管道搭建放在了核心位置,值得点进去核对一下自己有没有漏掉关键环节。回头看,这次事故最讽刺的地方在于:我一开始把过拟合的锅全甩给模型复杂度,却没意识到深度学习入门再熟也覆盖不了监督学习数据层面那些极其朴素但致命的陷阱。现在每次有新项目启动,我都会先把数据划分的逻辑给组里讲一遍,用组长的话说:“你这种被测试集F1 0.58毒打过的,说话比文档好使。”