深度学习笔记写到第三篇我越来越觉得真正卡住人的往往不是某个高深的理论推导而是训练过程中那些看起来不起眼的琐碎细节。比如学习率调小之后为什么反而一点不收敛验证集准确率突然掉到谷底或者loss已经降得很低但实际推理效果就是不行。这些场景我全都撞过而且每次回过头去查原因都不复杂但如果不记录下一次大概率还要再踩一遍。这篇笔记就把我近期完整跑通一个图像分类项目的实战过程做一个系统回顾重点放在数据组织、模型设计、训练细节和问题排查这几块希望能帮你在自己的实验里少走几段弯路。1. 动手训练之前先把任务本身想清楚很多人拿到一个深度学习项目的习惯是立刻翻模型结构、找公开代码然后直接开始训练。但我的经验是这一步越急后面返工的概率越大。任务类型、数据划分、评价指标这三件事没有定清楚后续所有的调参和优化都像在流沙上盖楼。1.1 任务定义决定模型结构先问自己一个问题这个项目本质上是分类、回归、检测还是更复杂的生成任务同样是卷积网络做图像分类时最后一层通常用Softmax输出概率分布做回归任务时则需要改成线性输出。这个选择不只是“最后换个损失函数”这么简单它会影响中间层的特征表达方式、输出的激活函数选择甚至影响数据标注方案。我近期做的项目是工业场景下的产品表面缺陷分类目标是判断一张图像属于正常、划伤、污渍、变形四类中的哪一类。这个任务定义看起来很直接但其实里面有个陷阱缺陷类别之间存在重叠比如“划伤”和“污渍”在低分辨率图像里非常像。如果一开始不把这个边界捋清训练出来的模型就会在交界区域反复出错。所以我建议动手前先做一张类别的定义表写清每一类的典型特征、容易混淆的情况、标注的优先级规则。这张表不需要很复杂但一定要让标注的人和训练模型的人看同一份说明。深度学习模型的性能上限很大程度由数据质量决定而数据质量的第一步就是任务边界的清晰度。1.2 数据划分训练、验证、测试不是随便切数据划分看起来太基础了以至于很多人直接按比例随机切分就完事但这一步处理不当会让后续所有实验结果失真。我见过好几次有人在同一份测试集上反复调参选模型最终测试准确率虚高得一塌糊涂换到新数据上直接崩盘。正确的做法是严格隔离验证集和测试集。训练集用来更新参数验证集用来做模型选择和超参数调整测试集只能作为最终结果的一次性检验。按照这个原则我的划分方式是训练集70%、验证集15%、测试集15%并且测试集单独放在另一个目录里训练过程中完全不去碰它。在划分前还要注意数据的分布问题。比如我这个项目的图像是从不同产线收集来的如果随机切分很可能某条产线的图像全部进了测试集训练集里基本没见过这种风格最后评估结果就带了很大的随机噪声。更稳妥的做法是按来源分组切分保证同一产线的图像只出现在一个集合里。类似的做法在真实场景里很关键因为在部署时你面对的往往是全新来源的数据。1.3 评价指标要匹配业务目标分类任务里准确率是最常用也最容易误导人的指标。当四类样本的占比大约是正常70%、划伤10%、污渍10%、变形10%时模型就算把所有图像都判断为正常准确率也有70%看起来好像还行实际上完全是个废物模型。所以项目中我同时关注三个指标准确率、宏平均F1和混淆矩阵。宏平均F1会把少数类的影响放大能更真实地反映模型对缺陷类别的判别能力。如果业务方更在意某种错误是不能接受的比如把缺陷品当成正常品流出那还要针对这个特定错误类型设计额外指标。评价指标最好一开始就想清楚并写进项目文档因为指标定义会影响损失函数的选择、阈值调整方式以及最终的模型筛选标准。中途想到什么换什么是最浪费时间的操作。2. 模型结构与数据预处理先求跑通再求冠军基线模型的追求不是刷榜而是让整个训练流程先跑通。第一次训练时输出NaN、形状对不上、显存溢出这一堆问题可能会让新手焦头烂额先用一个简单稳定的结构把流程理顺后面所有优化才有支撑。2.1 先从一个经典卷积网络开始接到缺陷分类任务后我的第一个模型并没有直接上很深的网络而是用一个经典的基础卷积网络跑通流程两个卷积块接最大池化然后展平接全连接层。这个网络结构大约只有几十万参数优点是训练快、显存占用小、问题容易定位不会一上来就陷入复杂的调参泥潭。为什么强调“先跑通”因为训练流程里变量太多了数据加载方式对不对、图像尺寸是否匹配网络输入、损失函数输出的维度是否正确、优化器更新是否正常……任何一个环节报错都可能让人误以为是网络结构不够强。如果基线网络能顺利训练到收敛说明整个链路没有问题之后再替换成更复杂的结构目标就很明确纯粹的精度提升。小模型的另一个好处是迭代速度极快。一个epoch只要几十秒几分钟就能看出一组超参数方案是否靠谱。到了深层次模型阶段一个epoch动辄几分钟甚至十几分钟试错成本完全不同。对于个人学习和中小项目高效迭代比“起步即巅峰”重要得多。2.2 数据预处理与数据增强的关键操作很多初学者拿到图像就直接输入网络结果训练时损失曲线抖得跟心电图一样。问题往往不是网络不行而是数据输入的数值范围太随意了。我先做的是把所有图像统一缩放到固定尺寸比如224×224然后转换成张量再按均值0.5、方差0.5做标准化。标准化的本质是把像素值从0到255的分布拉到接近均值为0、方差为1的标准正态分布这样可以避免某些特征数值过大导致梯度更新不稳。更讲究一点的做法是用ImageNet数据集的统计均值做标准化因为预训练模型是在这个分布下收敛的迁移时保留同样的标准化方式会让特征分布更匹配。数据增强是提升模型泛化能力性价比最高的手段之一。我的训练集只有不到一万张图像很容易过拟合我用的增强策略包括随机水平翻转、随机旋转±15度、随机裁剪后缩放填充以及轻微的颜色抖动。这些操作等于在有限样本上构造了更多的训练变体让网络不至于死记硬背个别样本的外观。值得提醒的是增强策略不是越狠越好。我在测试时发现颜色抖动幅度过大的话模型会学到一些与缺陷无关的颜色变换导致在原始测试图像上表现下降。所以增强强度也要通过验证集来调不能想当然地认为多加一定好。2.3 batch size与显存之间的取舍batch size是一个容易被低估的参数。理论上更大的batch size能让梯度估计更稳定但也会带来两个问题显存不够以及泛化能力可能变差。大量实验表明过大的batch size会让模型收敛到泛化较差的最小值区域有时需要同步调大学习率来补偿。我做这个项目时先用batch size为32跑通然后根据显存余量逐渐试着加大到64、128。比较下来64在这个数据集上表现最稳既不会在训练初期震荡太厉害也不会因为batch过大而明显牺牲泛化能力。如果你使用的是预训练模型微调显存不够时可以考虑用梯度累积技术它可以在不改变batch大小的情况下等效增加batch数量。一个小技巧是选择batch size时尽量让样本总数能被整除最后的不足一批的数据要么丢弃要么单独处理避免某些框架在动态shape上出现潜在问题。3. 训练环节的参数细节学习率、损失函数与正则化训练过程是整个深度学习项目中最像“炼丹”的部分核心变量无外乎学习率、损失函数、正则化手段和优化器。把这几个旋钮理解到位实验的成功率会明显上升。3.1 学习率最敏感的一个旋钮学习率决定了参数每次更新的步长。步长太大损失函数容易在最优解附近来回震荡甚至发散步长太小训练时间成倍拉长还可能陷入局部极小值。我的调试顺序一般是先把学习率设为0.001跑几十个step看损失变化如果损失在快速下降则保持如果剧烈震荡就调小到0.0003如果几乎不下降则考虑调大。实际训练中我偏好分段调整学习率的策略。前几个epoch用一个稍高的学习率快速靠近最优区域后面把学习率逐步降下来精细调节比如到第30个epoch降为原来的十分之一。这种做法很像先大步找方向、再小步微调收敛速度和最终精度通常都好于全程固定学习率。如果想让这个过程更自动化可以用学习率预热加余弦退火的调度方案。预热阶段从很小的学习率线性涨到目标值避免训练一开始就对着错误的梯度方向猛冲后续余弦退火又能平滑降低学习率让 loss 在后期尽量逼近最低点。实际跑下来的结果收敛稳定性确实有明显改善。3.2 损失函数需要匹配项目目标图像分类里最常见的组合是Softmax加交叉熵损失。交叉熵逐类别比较预测概率与真实标签的差异对分类边界附近的错误有很强的惩罚信号梯度回传也更平稳。相比之下如果错误地把任务当成回归问题用均方误差训练时梯度会变小收敛速度和最终精度通常都会打折扣。在我这个类别不平衡的项目里交叉熵的默认形式会让模型偏向样本量大的类别。解决办法是给损失函数加上类别权重让少数类的损失贡献变大。比如缺陷样本只有几百张它们却在总损失里占了更大的比例模型才会真正重视这部分的学习。我试过Focal Loss它可以在样本类别不平衡的同时压低易分样本的损失、放大难分样本的影响。实验结果显示在很少量数据下Focal Loss比加权交叉熵更容易导致过拟合因为难分样本里也包含噪声标签过分强调反而会让模型去学噪声。所以最后我采用的是加权交叉熵效果更稳定。3.3 正则化与归一化过拟合的防火墙过拟合的表现训练集指标很高、验证集指标上不去这是中小数据集最常遇到的问题。我用三件套搭配处理早停、权重衰减和数据增强。早停的机制很简单每隔若干epoch在验证集上算一次损失如果连续多个epoch没有改善就把最优权重保存下来避免后期模型在训练集上过度拟合。权重衰减是在损失函数中加一个对参数大小的惩罚项相当于让模型的复杂度别太高。这里需要注意权重衰减的系数不能设太大否则模型会偏向欠拟合。我常见的一个尝试范围是1e-4到1e-2具体值还是要靠验证集验证。还有归一化层现在几乎所有标准网络里都带批量归一化它的作用是把每层输入的数据分布拉回均值为0方差为1训练会稳定得多也允许使用更大的学习率。在微调预训练模型时我会先冻结前面的骨干网络只训练最后几层等新数据上的指标稳定后再解冻全部层做整体微调这个顺序对防过拟合很有帮助。3.4 训练过程中应该记录哪些指标训练日志如果不完整出了问题就很难定位是数据、模型还是优化的问题。我至少会记录每个epoch的以下内容训练损失、训练准确率、验证损失、验证准确率、学习率、当前epoch耗时。这些信息都汇总到一个训练日志文件里便于训练结束后画曲线对比。验证损失比验证准确率更敏感。如果你发现训练损失持续下降验证损失却开始上升这就是过拟合开始的最早信号。如果训练损失和验证损失都降不下去则要考虑模型的表达力是否不足、学习率是否过小或数据是否存在标注问题。4. 常见训练问题的排查思路与速查表训练中遇到的坑数不胜数但大部分问题都有比较固定的表现形式和排查路径。我把实际踩过的几个典型问题整理一下方便按图索骥。4.1 损失不下降先分清是“不降”还是“降得慢”遇到损失不下降的情况我的第一步是看训练集上的损失变化而不同时看验证集和测试集。如果训练损失也不降就说明模型没有在拟合数据问题可能在数据预处理、学习率或模型结构。先用小样本过拟合实验排除模型本身的问题。我会挑32张图像强制模型在这些样本上反复训练如果损失能降到接近0说明模型结构没问题、反向传播也没断问题大概率出在整体训练设置上。如果连小样本都拟合不了那大概率是数据标签错了、特征标准化出问题或者网络结构某个地方的计算姿势不对。学习率过大或过小是另一个常见原因。学习率过大时损失通常会在初始值附近震荡甚至越涨越高学习率过小时损失下降得极其缓慢看起来像没在学。验证方式比较简单把学习率调成0.1和1e-5各跑一小段时间看损失的变化形态就能大致判断问题方向。4.2 训练集指标好、验证集指标差过拟合的确认与缓解这是最典型的中小数据集问题。我做这个项目时第一次模型训练到第40个epoch训练准确率已经98.5%但验证准确率只有82%左右。这个差距是典型的过拟合警示。第一步先看验证曲线和训练曲线的分裂点通常在某个epoch开始后验证损失不再下降、训练损失继续下降。我用早停机制自动把模型保存点选在最优点上避免反复手动猜测。第二步是加正则化强度。我调高了权重衰减系数同时增加数据增强的强度给训练数据加入了随机擦除操作。这里也有个取舍增强过强会让训练损失降不到原来的水平但验证指标反而可能更好关键是看总体的泛化差距。如果条件允许增加训练数据量更有效。我用简单粗糙的方式对缺陷图像做了扩充收集了更多真实样本效果远好于纯靠增强硬撑。4.3 训练指标剧烈波动多因素排查训练准确率在80%和95%之间剧烈跳变时第一反应通常是学习率太大但还有一种可能是batch内数据分布差异过大。当batch size太小、样本比较杂时每一批的梯度方向差异会很大训练曲线自然抖动。我的处理办法是先减小学习率看看是否有改善再把batch size增大一些。如果抖动仍然存在就要检查数据加载时是否做了正确的shuffle。我遇到过一个问题数据加载顺序完全没有打乱导致每个batch都是同一类样本训练曲线呈现周期性波动虽然最后能收敛但速度明显更慢泛化也受影响。验证指标的波动也要留意。如果你的验证集太小比如只有几百张图像验证准确率本身的随机性就很大这种情况不是训练的锅而是验证集代表性的问题。适当增大验证集或者采用多次采样再平均的评估方法都可以缓解。4.4 常见问题速查表表现优先排查方向建议手段训练损失不下降数据标准化、标签正确性、学习率过小小样本过拟合实验、调整学习率至0.001左右损失震荡发散学习率过大降低学习率至1e-4级别或使用学习率预热训练好验证差过拟合增加数据增强、权重衰减、早停训练验证都差模型表达力不足、数据噪声大换更强模型、检查标注、增加有效数据验证曲线乱跳验证集太小、batch太小增大验证集和batch size按要求shuffle输出NaN学习率过大、数据存在极端值降低学习率、检查标准化、缩小数值范围5. 训练完成之后从指标到落地还要做什么模型在验证集上表现不错并不等于可以直接交付。从训练到真正可用中间还隔着评估深化、性能优化和可复现性整理这几步。年轻人最容易在这一步放飞自我结果实训时漏洞百出。5.1 多看看混淆矩阵和错误样本准确率只给一个抽象数字真正能指导优化的是混淆矩阵。我拿到训练好的模型后会把测试集全部跑一遍输出混淆矩阵。结果发现模型把7%的“污渍”样本错分成了“划伤”这个错误在实际业务场景里不太能接受因为这两类缺陷的后续处理方式完全不同。顺着错误样本再往下看发现这些被错分的图像都有一个共同点光照偏暗、纹理不明显。于是我有意识地在数据增强里加入了光照扰动同时补充了一批暗光条件下的真实样本。第二版模型的混淆矩阵改善非常明显多数情况已经能正确识别。如果没有这一步只看准确率根本不知道模型在哪些地方会犯系统性错误。5.2 推理速度与模型大小的优化训练是在GPU上进行的落地推理很多时候却要跑在CPU或者边缘设备上。这时候模型的推理耗时和参数量变得非常重要。我做了两件优化第一是把训练好的模型导出为轻量格式第二是开启低精度推理。开启低精度推理后推理速度提升显著精度受影响很小。这里要注意的是低精度推理对部分网络结构会产生数值溢出风险所以要实际跑一遍评测集对比量化前后的准确率不能只看速度。如果部署需求特别严苛还可以考虑剪枝和知识蒸馏。这类操作会明显压缩模型体积但操作不当会掉精度需要把蒸馏训练当成新的小项目来做。5.3 训练结果的可复现性整理我见过太多项目模型明明当时很好过两周却怎么也复现不出来。原因就是没记清楚随机种子、数据增强参数、学习率调度和所有超参数。现在我在每次实验开始前都会写好一份实验配置包括随机种子、数据集路径、预处理细节、模型结构、优化器参数、学习率调度、batch size、训练轮数等。训练结束后把最优权重、训练曲线和配置一起存档。这样后面无论是重新跑实验做对比还是换个人接管项目都有据可查。随机种子的固定也很重要。某些框架里数据加载和Dropout等操作本身带随机性如果不固定种子同一个配置每次训练结果都可能差异很大。固定种子后再配合随机数生成器的控制同一配置基本能复现出相近的结果。另外一个容易被忽略的点是环境版本。框架小版本升级之后同样代码可能跑出完全不一样的结果。最好在项目文档里记录框架、驱动和依赖库的版本号条件允许的话用容器镜像锁定环境。我自己在这个项目中的收获靠的是一轮轮“出现错误、记录原因、修正参数”的循环。如果你的项目也正卡在某个诡异的现象上先别急着怀疑自己的模型结构不够先进回到数据、学习率、正则化这些最基础的层面做排查往往能更快找到出路。最后再提一个小技巧把每次实验的日志文件保留好哪怕是一次失败实验后来也会成为排查问题的重要线索。
阅读完成 · 觉得有帮助?