
深度学习里面模型选型、过拟合、欠拟合这三个词基本就是你调参路上的三座大山。很多人跑通一个基线模型之后就卡在“训练损失降不下去”或者“验证集损失越跑越高”这种问题上翻来覆去不知道动哪里。这篇东西我不打算给你念教科书就按我实际调模型的经验把模型选择、过拟合、欠拟合这套东西掰开揉碎讲清楚从诊断到治疗再到避开那些坑争取让你看完之后能直接拿去用。先划个适用范围这篇文章适合已经能独立跑通一个基础训练脚本、但面对“模型效果不好”时不知道从何下手的人。如果你还在纠结某个开源框架的API怎么调建议先去补基础但只要你开始面对验证集精度上不去、训练曲线乱飞这类问题这篇文章就是冲着你写的。1. 先焊死基本功欠拟合与过拟合到底是怎么回事1.1 “拟合”的直觉模型是在“画线”不是在“背书”我用一个特别俗的例子开场。假设你手里有一堆散点大概呈一条弧线分布现在让你画一条线穿过这些点。你拿一根直尺画条直线很多点都差得远这就像欠拟合——模型太简陋连数据的基本形状都表达不出来。你换成一根很软的曲线弯来弯去结果每个点都精准穿过但这根曲线弯得毫无规律新来一个点它反而猜不准这就是过拟合。深度学习里的“拟合”本质上就是模型在训练数据上逼近目标函数的能力。但区别在于模型不是真的“画线”而是通过一堆参数把输入映射到输出。参数越多、网络越深这条“线”能弯的程度就越大也就是容量越大。问题就出在这容量不够学不到规律容量过头把噪声也当成规律背下来了。1.2 欠拟合模型太笨真没学会欠拟合的状态很好认训练集上的损失就很高验证集上当然也高。我见过不少新手慌慌张张问“为什么我的准确率只有60%”一看训练集准确率也只有65%。这就不是调参的问题是模型根本还没学进去。欠拟合的本质是模型容量不足或者训练过程没走完或者优化出了问题。注意优化问题不算严格意义的欠拟合但外在表现很像。比如学习率设得太小训练几百轮loss还是稳如老狗这其实是没学好不是学不会。欠拟合更偏向结构性的表达力不足而优化不充分更像是“没找到好答案”。1.3 过拟合模型太聪明把答案背下来了过拟合的表现则是另一个极端训练损失一路猛降甚至降到接近零但验证集损失降到某个点之后就开始反弹训练和验证之间的差距越拉越大。这个现象在学术上叫泛化 gap说白了就是模型把训练集里的个性当成了共性。有个特别容易迷惑的地方很多人看到训练精度99%就以为模型很牛其实这往往是灾难的开始。真正要盯的是验证集验证集才是模拟“考试”的地方。模型在训练集上表现好有可能只是它把训练集的答案背下来了换个问法就不会了。相当于一个学生把课本例题的答案背得滚瓜烂熟考试稍微变个数字就傻眼。1.4 偏差-方差权衡一个绕不开的跷跷板欠拟合和过拟合背后其实是统计学里经典的偏差-方差权衡。偏差高意味着模型的预测结果系统性偏离真实值通常是因为模型太简单对复杂关系建模不足方差高意味着模型对训练数据的具体构成过于敏感换一批训练数据结果就剧烈抖动通常是因为模型太复杂过于贴近训练数据中的随机噪声。深度学习中我们常常同时面对这两个问题。网络容量做大一点偏差会下降但方差会上升网络容量小一点方差低但偏差高。所谓“最优容量”就是在偏差和方差之间找到平衡点。这个平衡点没有固定公式取决于你的数据量、任务难度、网络结构甚至训练策略。理解了这一点你就能明白为什么有时候强行加层数反而把模型搞坏了。2. 先确诊再开药模型状态诊断的实操方法2.1 训练损失与验证损失的“四象限”读法我调模型的第一件事永远是同时盯着训练损失和验证损失两条曲线。不要只看数值要看它们之间的关系和走势。我把常见情况整理成四种组合你们对照着看。训练损失验证损失诊断结果高高两者接近欠拟合容量或优化不足低高差距大过拟合泛化能力差低低差距小正常状态说明模型在泛化高更低或震荡异常先检查数据泄漏或评估逻辑第4种情况很多人没见过我专门说一下。如果训练损失一直很高验证损失反而很低先别高兴这是典型的“数据泄漏”信号。比如你把验证集的信息通过某种预处理方式标准化时用了全数据的均值和方差透给了训练过程。遇到这种情况先查数据管道别急着调模型。2.2 学习曲线的正确画法和用法训练过程一定要画曲线不要只盯终端打印的最后一行数字。学习曲线应该横轴是epoch纵轴是损失和准确率训练和验证各画一条四条线放在一张图里。我习惯再叠加一个 learning rate 曲线用来排查优化问题。读曲线有一个技巧看趋势别看单点。训练损失下降但验证损失平稳说明还在正常学习训练损失快到底了验证损失还在缓慢下降说明可以多训一会儿验证损失先降后升这就是过拟合的拐点早停法和模型保存就要在这里动手脚。我踩过最大的坑是训练到一半loss突然爆掉后来发现只是学习率太大导致震荡不是模型结构问题。2.3 验证集的划分原则别让“考试”泄题诊断模型状态前提是验证集必须干净。我见过不少项目验证集是从训练集中随机抽出20%完事但没做分层采样。类别不平衡的任务里随机抽样很容易让某个小类在验证集里只有两三个样本评估结果方差极大根本没法可靠判断模型是好是坏。正确做法是分层采样保证验证集和训练集中每个类别的比例大致一致。如果数据量很少K折交叉验证是更稳的选择虽然训练代价高一点但评估结果的置信度完全不同。对深度学习这种动不动训几个小时的场景5折交叉验证常常太奢侈我一般就做单次留出验证但划分时务必分层且固定随机种子。种子不固定你连模型好坏都判断不了因为每次验证集都不一样。2.4 冒烟测试先小后大的诊断顺序还有一个被严重低估的实操技巧先用极小的数据量比如几十上百张样本去跑训练目标是让训练损失降到零附近。如果连小数据都拟合不了那不用看验证集了问题出在模型结构或优化配置上网络根本学不进东西。这个测试业内叫冒烟测试花费时间少定位问题准。我有个习惯动作新建一个项目先拿64张图跑20个epoch如果loss还是纹丝不动就先查输入管道是不是喂错数据、标签对不对齐、模型输出的维度对不对。这些问题不排掉后面在大数据集上跑纯属浪费电。冒烟测试过了再把数据量放大这时候才开始谈过拟合欠拟合的事儿。3. 模型选择的决策框架容量、数据与结构怎么权衡3.1 模型容量是第一变量深度学习里“模型选择”四个字最核心的变量就是容量。容量大致可以理解为网络能表达函数的复杂程度参数量越大、层数越深、每层通道越多容量就越大。一个只有两层的卷积网络和一个五十层的残差网络在同一个任务上表现差距巨大这不是谁“更努力”而是表达空间根本不在一个量级。但容量不是越大越好。容量是双刃剑大容量在数据充足时能学到更精细的模式在数据不足时则更容易把噪声背下来造成过拟合。所以做模型选择首先要想清楚你手里有多少有效数据再反推需要多大容量。3.2 从基线模型开始为什么我坚持先小后大见过很多团队一上来就搬一个几千万参数的大网络跑小数据集训了半天验证集不升反降还找不到原因。我的经验始终是从一个小而稳的基线开始先保证它能过拟合一小批数据再逐步放大容量。这个做法有几个好处。第一小模型训练快迭代成本低你可以在几小时内验证数据管道、loss设计、评估逻辑是不是正确。第二小模型提供了一个“地板”如果小模型在验证集上能达到60%那大模型再怎么折腾目标至少是超过60%。第三从小模型到大模型的过程中你能清晰看到容量增加带来的收益曲线什么时候收益饱和了就是该收手的时候。3.3 数据量与模型复杂度的匹配关系模型容量和数据集规模要匹配有一条很粗的经验法则每增加一个数量级的有效数据你大概可以放心地把模型容量提升一个档次。有效数据指的是经过清洗、增强、去重之后的真实信息量不是文件数量。我做过一个视觉分类实验1万张图用一个小型卷积网络就能练到不错的水平但把数据扩到100万张那个小网络反而打不过一个更深的网络。原因很简单数据多了细节模式也多了小模型的表达空间不够用。反过来如果手头只有2000张图硬上一个深度残差网络大概率验证集精度远低于训练集因为模型有足够参数去记住每张图。3.4 结构选型不只是“越深越好”说到模型结构很多人第一反应是“层数加多、参数量加大”。但结构不等于深度。对图像任务来说卷积的局部归纳偏置本身就比全连接层更适合提取空间特征所以哪怕全连接层参数量大几倍效果也不一定比一个设计良好的卷积网络更好。对序列任务来说注意力机制在处理长距离依赖上又天然优于局部窗口的结构。我的建议是不要盲目追新结构先把一类任务公认好用的基础结构跑通在这个基础上做容量上的加减法。比如图像分类先跑一个经典的残差网络文本分类先跑一个预训练模型微调。基础结构是行业验证过“有效”的起点结构突变的风险远比容量增减大得多。3.5 三种状态的速查对照表把常见的模型状态和应对策略放在一起方便快速对照。状态训练/验证差距表现应对方向欠拟合训练损失高验证也很高准确率上不去loss下不来增大容量、加深网络、调整优化配置过拟合训练很低验证高且差距大训练精度高验证波动明显正则化、数据增强、早停、减少容量正常训练与验证差距小都在下降两者同步改善不要乱动继续训练或收敛后收工这张表我建议你贴在手边。每次模型出问题先判断它在哪个象限再决定往哪个方向动。方向错了后面全是白忙。4. 过拟合正面硬刚正则化三板斧加一个王炸4.1 权重衰减L2正则化让参数别那么“嚣张”过拟合时模型参数往往非常大因为只有参数值足够大才能精确穿过每个训练样本的尖锐区域。权重衰减的做法很直白在损失函数里加上对所有参数平方和的惩罚项逼着模型在“拟合数据”和“保持参数小”之间做权衡。实操上大部分框架里直接设置 weight_decay 参数就行。常见初始值在 1e-4 到 1e-2 之间具体要看任务。图像分类任务我常从 1e-4 起步如果过拟合还明显就加大到 1e-3 甚至 5e-3。注意一点权重衰减应该作用在卷积层和全连接层的权重上而不是 bias 上也不该作用在 BN 层的缩放因子上原因不细讲了反正规律就是这样。4.2 Dropout训练时“随机旷工”推理时全员到齐Dropout 的原理听上去甚至有点不靠谱训练时随机让一部分神经元输出置零等于每轮都在训练一个不同的“子网络”最后预测时把所有子网络集成起来。它本质上是让模型不要过度依赖某一个神经元逼着信息分散存储。我用 Dropout 有几个经验。第一一般放在全连接层前面卷基层后面如果要用比例要很小比如 0.1 或 0.2太大容易让训练变慢。第二全连接层的 Dropout 比例从 0.3 到 0.5 之间调我的建议初始 0.3。第三一定要搞清楚你用的框架在推理时是不是自动关闭 Dropout——大部分高级框架的 model.eval() 会自动处理但如果你手写推理逻辑忘了关闭输出结果就会不稳定。4.3 早停法在过拟合拐点及时收手早停是我最喜欢的正则化手段因为它不需要改模型结构不需要加额外参数只是改变训练流程。原理特别简单训练过程中持续监控验证损失一旦发现验证损失连续若干个 epoch 不再下降甚至开始上升就停止训练并回滚到验证损失最低的那个 checkpoint。实现上有几个细节。第一要保存验证损失最低时的模型参数不是保存最后一轮的参数这个错误很常见但代价很大。第二 patience 参数不要设得太小否则训练还没充分就被打断了。我一般设 5 到 10大模型或大规模数据可能设 15 甚至 20因为训练过程中验证损失本身有波动太敏感会误判。第三早停的判定用验证损失而不是验证准确率损失能更平滑地表达模型对数据的拟合程度。4.4 数据增强最“便宜”也最有效的正则化说它便宜是因为它不需要动模型只需要在数据加载时做变换。说它最有效是因为它可以直接扩大训练样本的有效分布范围从根源上缓解“数据不够导致背答案”的问题。图像任务里随机裁剪、水平翻转、色彩抖动、旋转、缩放是常见组合文本任务里同义词替换、回译、随机遮盖也很常用。数据增强有一个容易被忽略的原则增强的方式必须符合任务本身的语义不变性。图像分类可以把猫的水平翻转还是猫但如果你做的是数字识别镜像翻转就把“6”变成“9”了等于在制造错误标签。我做增强时一定会先问自己这个变换会不会改变正确的答案4.5 超参数搜索不是只有网格搜索一条路过拟合的调节并不只是“打开某个正则化开关”还涉及一批超参数的配合学习率、batch size、权重衰减系数、Dropout比例、网络宽度。盲目一个个试很浪费时间更靠谱的方式是有一个搜索策略。网格搜索适合超参数少、范围小的情况。随机搜索其实是比网格更好的基线因为超参数重要性往往不均匀随机采样在高维空间里覆盖更全面。如果资源和时间允许贝叶斯优化这类自适应搜索方式更适合深度学习它会在效果较好的区域加密采样。我自己常用的流程是先用随机搜索跑20组锁定较好的区域再在这个小区域里做细粒度的网格微调。5. 实战演练一次图像分类任务的完整调参日志5.1 任务背景与初始配置说这么多理论不如直接看一个真实案例。我之前处理过一个图像分类的模拟项目20个类别总共3000张图类别基本均衡。任务是做一个可用的分类器验证集要求准确率至少85%。最初配置是经典残差网络18层版本输入尺寸224batch size 32初始学习率 0.01SGD优化器学习率每30个epoch衰减为原来的1/10训练100个epoch。没有任何正则化手段。听起来配置很常规对吧这种配置在小数据集上大概率出事。5.2 第一版模型欠拟合的症状与处理第一版跑完训练集准确率大约68%验证集准确率约66%。训练损失和验证损失都偏高而且两者很接近。这典型的欠拟合模型容量不够学到足够特征。我一开始没急着换大网络先检查了学习率0.01在SGD上不算低训练曲线显示loss在前10个epoch下降挺快后面变得很平缓这更像是容量不够而不是优化受阻。于是我做了一个决定把网络从18层换成34层版本同时把初始学习率降到 0.005。换完再跑训练集准确率到了78%验证集到了75%明显提升但离85%还有距离。这说明容量增益在起作用但还不够。这一步也验证了我的判断问题确实在模型容量上。5.3 第二版模型过拟合的出现与应对眼看着加大容量有效我又试了50层版本果不其然训练集准确率冲到94%验证集却只有77%而且从第25个epoch开始验证损失一路反弹。这个信号很明确过拟合了。面对这种状态很多人会做的一件事是往回缩容量但我没有急着回到34层。我先在34层版本上加了三样东西数据增强随机裁剪水平翻转色彩抖动、权重衰减 5e-4、Dropout在全连接层前加比例0.3。同时把训练策略改成早停patience 设10。跑出来的结果是训练准确率降到82%验证准确率却上升到81%。这个“训练降、验证升”的现象就是正则化在起作用的典型信号。5.4 第三版模型收敛到合理区间验证集81%还是差一点。我在这个基础上继续微调重点换了两个东西一是把初始学习率降到 0.002并换成余弦退火调度二是把batch size从32提到64顺带把权重衰减调到 1e-3。这两个改动看似都和过拟合无关其实影响很大。更小的学习率配合余弦退火让模型在最优点附近更精细地收敛更大的batch size本身带有轻微的正则化效果让梯度估计更平稳。最终结果训练集准确率大约86%验证集准确率约87%。两者差距只有1个百分点这是非常健康的泛化表现。三个版本的对比很清楚第一版是训练验证都低第二版是训练高验证低第三版是两者平衡且都达到目标。整个过程我刻意遵循一个原则每次只动一个变量并且记录每一个版本的结果。5.5 调参心法一次只改一个变量从上面的日志你应该能看出来我每次实验都只改一个东西然后观察训练和验证曲线的变化。这是调参的铁律。如果你同时改了网络结构、学习率、batch size、数据增强结果变好了你根本说不清是哪个改动起了作用结果变坏了你也不知道该回退哪一步。我的工作流按顺序固定是先确认数据管道没问题冒烟测试再调容量从过拟合小数据开始然后调优化学习率、调度器最后处理泛化正则化、数据增强。每一步都观察一组曲线记录一版结果。后面你回看整个调参过程会发现这些记录比任何高级工具都管用。6. 常见问题排查与避坑速查6.1 症状-原因-方案速查表把我在不同项目里遇到的典型问题整理成一张速查表建议收藏出问题先对着查。症状最可能的原因检查方式解决方向训练和验证损失都高且训练不下降容量不足或学习率过小冒烟测试小数据增大容量确认优化配置训练很低验证高且抖动过拟合看训练/验证差距曲线加重正则化、增强数据、早停验证损失先降后升过拟合拐点看损失曲线拐点早停并保存最优checkpoint训练损失爆炸或震荡学习率过大或梯度不稳定看loss单步变化降低学习率添加梯度裁剪验证集上完美测试集上崩盘验证集过拟合只用一次测试集增加验证集数据或做交叉验证loss正常但准确率不变类别不平衡或评估指标错误打印混淆矩阵调整采样策略或换评估指标6.2 五个我踩过的大坑希望你们别踩第一个坑在测试集上调参。很多人为了“看效果”频繁拿测试集验证中间结果最后测试集变成了第二个验证集模型在测试集上也过拟合了。我现在的做法是测试集只在最终评估时碰一次其他时候用验证集做决策。第二个坑不设固定随机种子。网络初始化、数据打乱、数据增强都带随机性不固定种子同样的代码跑两次结果都不一样。你调参时看到的效果差异可能是随机噪声而不是真实改进。我每个实验固定三处种子全局随机种子、numpy随机种子、框架随机种子。第三个坑只看准确率不看损失。准确率是离散的整数指标变化不敏感尤其是测试阶段损失是连续的能反映模型更细微的变化。我做模型早停和模型选择都看验证损失最后报告指标时才看准确率。第四个坑类别不平衡却用准确率当主要指标。3000张图20个类别就算均衡还好但真实任务里极有可能类别极不均衡。这种情况下准确率会虚高真正有效的是查准率、查全率、F1或者AUC。我每次建模前先把类别分布打印出来看一遍。第五个坑忽略batch size与学习率的联动关系。很多人换batch size后不调整学习率结果效果下降。线性缩放法则说我实践下来大字谨慎batch size翻倍学习率可以适当提高但要配合 warmup否则容易震荡。6.3 几条杂七杂八但很关键的经验关于学习率我最常用的初始值区间是 0.001 到 0.01配合余弦退火或者阶梯衰减。如果发现损失下降得过于缓慢先确认不是学习率太小再考虑容量问题。关于batch size小batch size16-32往往泛化性更好因为它引入的噪声相当于一种隐式正则化大batch size训练快但容易收敛到尖锐极小值泛化性略差。所以别一味把batch size往大了调。关于可视化强烈建议养成每个epoch都记录训练损失、验证损失、学习率然后统一画图。我自己的习惯是用TensorBoard一行代码就能记录关键信息。曲线图带来的信息量远超控制台输出的几百行数字。关于迁移学习如果你的任务数据量不大千万不要从头训练一个大网络。用一个在大规模数据集上预训练好的模型做初始化再微调通常能大幅提升小数据任务的效果。这个操作基本等于“开挂”几乎是中小规模视觉任务的最佳起手式。关于集成如果你追求极致精度可以把多个验证集效果接近的模型做权重平均或预测平均。这个手段效果显著代价是多训练几个模型。但注意集成是精度提升的“最后手段”不要一开始就靠它掩盖单个模型的问题。最后再分享一个我比较个人的体会调模型这事最怕的不是过拟合也不是欠拟合而是凭感觉乱改。不管遇到什么问题先画曲线定位状态再做一次只改一个变量的实验。你认真记录几轮之后就会发现那些看似玄学的问题本质上都是容量、数据、优化、正则化这四个维度的权衡。模型选择没有免费的午餐但只要你摸清了这三板斧的脾气绝大部分场景你是能站稳脚跟的。