
这次的项目不算复杂就是围绕YOLOv5m做一次完整的目标检测训练与验证但从环境搭建到数据集清洗再到训练日志分析整个过程走下来还是有不少值得复盘的地方。我用的是工业零件表面缺陷检测这个场景目标类别一共4类划痕、凹坑、黑点、边缘破损。如果你也准备在自有数据集上训一个YOLOv5m这篇应该能帮你绕开大部分我踩过的坑。1. 项目整体设计与模型选型思考1.1 为什么选YOLOv5m而不是s/l/xYOLOv5系列按照网络宽度和深度分为n、s、m、l、x五个版本底层结构都是一套CSPDarknet骨干加SPPF特征融合加PANet检测头区别在于重复模块的数量和通道数。我在这个项目里的硬件条件是单张RTX 3060 12G显存目标场景是工业质检既要求推理速度能跟上产线节拍又希望漏检率尽量低所以直接锁定了m版本。m版本的参数量大概在21M左右计算量约51 GFLOPs相比s版本7M参数精度上能高出三到五个百分点而推理时间只增加大约20%。相比l和x版本m的内存占用和训练耗时又友好得多我用640分辨率训练batch size开到16刚刚好如果是l版本可能就得降到8训练时间直接翻倍。在这里补充一个经验如果你的目标是很小尺寸的物体或者图片分辨率特别高可以优先考虑l或者x否则m是性价比最高的选择。1.2 训练与验证如何形成闭环训练集负责更新模型权重验证集负责评估模型在未见数据上的泛化能力这两者必须严格互斥。我在这个项目里遇到过一个很典型的反面案例有同学把同一批图片既放进了训练集又放进了验证集训练出来的指标漂亮得吓人mAP0.5达到0.98结果一上线立刻被打回原形原因就是数据泄露导致模型根本没有真正学到泛化特征。正确的流程是先把全部图片按照生产批次或者工件编号做分组再按大约8:1:1的比例分成训练集、验证集和测试集。训练过程中每个epoch结束之后模型会自动在验证集上推理一次记录当轮的Precision、Recall和mAP指标如果比历史最好成绩高就把当前权重保存为best.pt否则继续训练。这个闭环的价值在于它给训练过程装了一个仪表盘如果训练loss持续下降但验证指标不再上升那就说明模型开始过拟合了可以提前止损。2. 环境准备与工程目录搭建2.1 硬件要求与运行环境先说我这次的运行环境Ubuntu 20.04显卡RTX 3060 12GCUDA 11.3PyTorch 1.10.0Python 3.9。整套配置不算新但跑YOLOv5m完全够用。如果你的显卡只有8G显存建议把输入分辨率降到416或者batch size减半硬扛640大概率会直接OOM。训练过程中还有一个容易被忽略的瓶颈是数据读取速度。YOLOv5默认边训练边从磁盘读图如果图片放在机械硬盘上GPU会频繁处于饥饿状态利用率上不去。我这次把所有图片缓存到内存里命令加一个--cache ram训练速度至少提升了30%。内存不够的话可以用--cache disk也能有不错的效果。建议用SSD存放数据集这一步能省下大量等待时间。2.2 拉取YOLOv5源码与数据目录组织环境搭建的步骤并不复杂但版本兼容问题值得认真对待。我建议创建一个独立的conda环境Python版本选3.8或者3.9然后用官方仓库的requirements.txt安装依赖不要自己手动装一堆最新版本的库有些新版本算子跟旧版YOLOv5代码不兼容跑起来会莫名其妙报错。git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt数据目录结构一定要严格按照YOLOv5约定的方式来组织否则训练脚本会卡在路径解析上datasets/defect/ ├─ images/ │ ├─ train/ │ └─ val/ ├─ labels/ │ ├─ train/ │ └─ val/ ├─ train.txt └─ val.txtimages和labels两个目录下的文件名必须一一对应标签文件是.txt格式每行描述一个目标框。如果你的数据是从其他标注工具导入的文件格式可能会有差异一次性转换成标准YOLO格式再开始训练后面会省心很多。3. 数据准备标注检查与yaml配置3.1 YOLO标签格式与质量检查YOLO标签格式是每行五个字段类别ID、目标框中心点x坐标、中心点y坐标、框宽度、框高度。所有坐标值都是相对于图片宽高的归一化数值范围在0到1之间。一个目标占一行如果一张图里有三个目标那这个txt文件就有三行。0 0.531250 0.478906 0.085938 0.079688 1 0.289063 0.617188 0.053125 0.065625 2 0.750000 0.320312 0.067188 0.045312这里有个高频坑标注框坐标有时候会出现负数或者宽度高度为0训练时轻则报警告重则loss直接变成nan。我习惯在训练前写一个脚本把所有标签扫一遍把异常行揪出来。下面这段代码是我每次都会跑的清理检查import os for split in [train, val]: label_dir fdatasets/defect/labels/{split} for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f)) as fp: for line in fp: parts line.strip().split() if len(parts) ! 5: print(f{f} 行数异常: {line}) try: cx, cy, w, h map(float, parts[1:]) except ValueError: print(f{f} 坐标非数字: {line}) continue if cx 0 or cy 0 or w 0 or h 0 or cx 1 or cy 1: print(f{f} 坐标越界: {line})3.2 按场景划分数据集避免验证指标虚高很多人在划分训练集和验证集时习惯直接随机切分这在工业场景下是有隐患的。因为同一个工件的多张照片往往高度相似随机划分会导致训练集和验证集之间出现近亲关系验证指标虚高模型一上线就露馅。我这次的做法是按生产批次分组把A产线采集的图片全部放进训练集把B产线采集的图片全部放进验证集完全没有交叉。这样模拟的是模型上线后面对新产线数据的真实情况验证结果更有参考价值。如果你没有产线批次信息可以按拍摄时间分组或者按工件编号分组原则是保证同一实体的图片不要同时出现在两个集合里。data.yaml配置文件需要指向训练和验证的图片列表文件同时声明类别数量和类别名称train: datasets/defect/train.txt val: datasets/defect/val.txt nc: 4 names: [scratch, dent, blackspot, edge_break]train.txt和val.txt这两个文件里存的是图片的绝对路径每行一张可以直接用命令生成find datasets/defect/images/train -name *.jpg datasets/defect/train.txt find datasets/defect/images/val -name *.jpg datasets/defect/val.txt3.3 数据增强策略与超参数选择YOLOv5自带的增强策略非常丰富核心配置在data/hyps/hyp.scratch-low.yaml文件里包含Mosaic、MixUp、HSV颜色扰动、水平翻转等。其中Mosaic是把四张训练图片随机裁剪拼接成一张新图对于小目标检测效果提升非常明显因为它强迫模型在大尺度背景下学习小目标的特征。不过Mosaic也不是没有副作用。训练后期模型已经收敛得差不多时Mosaic引入的拼接伪影反而会成为干扰。YOLOv5官方版本在最后若干epoch会自动关闭Mosaic但如果你用的旧版本没有这个逻辑我建议手动改一下增强配置把最后10个epoch的Mosaic概率调成0验证指标通常会有小幅回升。另外要提醒一点不要一上来就改各种增强参数。默认参数是官方在COCO数据集上调试出来的在你自己的小数据集上如果欠拟合再逐步增强如果模型已经过拟合了反而要减弱增强。我在这个项目里就用默认参数跑的第一版效果中规中矩后来根据验证集的表现微调了HSV饱和度增益最终mAP才稳定下来。4. 训练实操命令、日志与参数调优4.1 一次完整的训练命令训练指令本身并不难难的是理解每个参数在干什么。这是我这次用的最终训练命令python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data datasets/defect/data.yaml \ --weights yolov5m.pt \ --cache ram \ --name defect_m \ --hyp data/hyps/hyp.scratch-low.yaml逐项说明一下--img 640输入分辨率。工业质检图片如果缺陷目标偏小可以考虑1280但显存和推理耗时都会成倍增长。我先用640跑通全流程后续再根据实际效果决定要不要升级分辨率。--batch 16batch size在这个显存容量下的临界值。batch太小会导致BN层统计不稳定模型训练过程震荡batch太大显存撑不住。如果你只有8G显存建议开8。--weights yolov5m.pt官方预训练权重。强烈建议从预训练权重开始训练而不是从零开始收敛速度快得多最终精度也更高。--cache ram把图片缓存进内存。这里强调一下如果你的内存小于16G建议改用--cache disk否则有可能内存溢出。--name defect_m实验名称训练结果会输出到runs/train/defect_m/目录下。--hyp超参数文件用于指定数据增强和学习率参数。4.2 训练日志怎么读核心指标拆解训练开始后终端会滚动输出这样的信息Epoch gpu_mem box obj cls labels img_size 50/99 4.32G 0.02342 0.01783 0.00321 5 640 P R mAP.5 mAP.5:.95 val_box val_obj val_cls 0.8642 0.8217 0.9023 0.6731 0.02418 0.01973 0.00122第一行是训练集lossbox是框回归损失obj是置信度损失cls是分类损失。第二行是验证集指标。我最关注的是val_box和val_obj这两个值如果它们持续下降、mAP同步上升说明训练健康如果训练loss已经很低但验证loss开始反弹就说明过拟合了。P和R是一对矛盾指标。P是查准率预测为正的样本中真正的正样本比例R是查全率真实正样本中被正确找出来的比例。mAP0.5是IoU阈值取0.5时的平均精度mAP0.5:0.95则是把IoU从0.5到0.95每间隔0.05计算一次再取平均对框定位精度更敏感。工业场景里我两个都看但更重视后者因为定位不准在实际抓取或者后续处理时同样会出问题。4.3 训练异常信号的识别与处理训练过程中最常见的异常信号有这么几种GPU显存OOM优先把--batch从16降到8如果还不行再考虑把--img从640降到512。降分辨率的代价通常比降batch更小大多数场景下512和640的精度差距在可以接受的范围内。loss直接变成nan大概率是标签文件里有异常数据比如坐标越界也可能是学习率过大。先清洗标签再把初始学习率调低一个量级比如从0.01改到0.001。mAP一直为0这种情况通常是类别编号错位或者data.yaml中names的顺序与标注文件里的class id对不上。先确认类别索引如果确认无误关掉Mosaic再试一次有时候Mosaic拼接后的图片里目标被割裂得太严重小数据集上会导致模型学不好。训练速度慢先检查数据加载是不是瓶颈加上--cache ram如果还慢再看GPU利用率利用率低于50%基本就是CPU读图拖后腿了。4.4 断点续训与早停策略训练是一个长耗时操作断电、内存溢出、手动中断都是常有的事。YOLOv5的断点续训非常简单python train.py --resume runs/train/defect_m/weights/last.pt--resume会自动读取上次训练的epoch数和优化器状态接着往下跑。注意不要手动修改--epochs否则会有逻辑冲突。早停方面YOLOv5通过patience参数控制在hyp文件中配置。它的原理是如果连续N个epoch验证指标都没有刷新最好成绩就自动终止训练并保存best.pt。我这个场景里设了20个epoch的耐心值原因是训练后期指标提升明显放缓早停能避免浪费算力还防止过拟合。5. 验证评估val.py与结果文件分析5.1 运行一次完整的验证训练结束后runs/train/defect_m/weights/目录下会生成两个权重文件best.pt是验证集表现最好的last.pt是最后一个epoch的。正式评估用best.ptpython val.py \ --weights runs/train/defect_m/weights/best.pt \ --data datasets/defect/data.yaml \ --task val \ --img 640执行完成后验证结果保存在runs/val/exp/目录下包含混淆矩阵confusion_matrix.png、PR曲线PR_curve.png、F1曲线F1_curve.png、标签分布labels.jpg以及一批带有预测框的可视化样例。5.2 混淆矩阵与PR曲线的实际使用方式混淆矩阵是我每次必看的文件。它能直观地告诉你模型在哪些类别之间容易混淆。我这次训练完发现blackspot和scratch之间有大约7%的互相误判看样本才发现这两种缺陷在灰度图上确实很相似后来通过补充两类特征的极端样本才把混淆比例压下来。PR曲线用来挑选置信度阈值。如果你打开val.py生成的PR_curve.png横轴是Recall纵轴是Precision曲线越靠近右上角说明模型越强。实际部署时如果场景对漏检零容忍就把置信度阈值调低到0.25甚至0.2接受更多误检如果误检成本高就调高到0.5以上牺牲一部分召回率换取干净的输出结果。5.3 best.pt与last.pt的选择策略绝大多数情况下直接选best.pt。我这次训练100个epochbest出现在第87个epoch最后十来个epoch验证指标一直在小幅波动没有再创新高说明模型已经进入平台期这时候继续训练纯粹是浪费算力。如果你发现best和last的mAP差距很大比如差了5个点以上那就要检查验证集是不是太小或者分布不合理导致指标震荡剧烈这种情况下的best反而没有参考价值。6. 常见问题与避坑经验6.1 高频报错速查表现象可能原因解决方法FileNotFoundError: dataset path not founddata.yaml里路径写错确认train.txt/val.txt路径绝对正确AssertionError: train: No labels in xxxlabels目录下没有对应标签检查标签文件名和图片文件名是否一致CUDA out of memorybatch或img尺寸过大降batch或分辨率或加--cachelossnan标签异常或学习率过大清洗标签降低初始学习率验证mAP一直为0类别编号不匹配检查names顺序尝试关闭mosaic增强Unknown class index 5标签类别ID超出nc范围重新生成标签或修改nc数量日志出现validation failed提示数据集文件对齐出现问题对比images和labels目录文件集合是否一一对应6.2 我踩过的三个细节坑第一坑是标注完之后直接开训。我第一次做的时候用LabelImg标了两千张图觉得差不多了就开始训练结果mAP一直在0.2附近上不去。后来把标注框可视化到原图上才发现问题有一批图片的类别ID整体错位所有划痕都被标成了凹坑。从那以后我每次都会抽查至少100张可视化结果再开训。第二坑是训练中断后手动改epoch续训。有一次训练到第60个epoch时机器重启了我自作聪明地把--epochs改成40重新跑结果优化器状态和调度器状态全乱了后面的训练质量明显下降。正确做法就是直接用--resume别的参数都不用动。第三坑是验证集划分太随意。第一次按文件名随机切分模型指标看起来不错但换了新产线的数据之后效果差很多。后来改成按生产批次划分验证集才真实反映出模型的泛化能力。这也是我前面反复强调按场景分组的原因。6.3 关于validation阶段的一些心得如果你在验证阶段碰到了val failed或者类似字样的报错信息先别慌大部分情况下不是模型本身的问题而是数据和环境层面的问题。我整理了几个排查步骤先确认数据yaml文件里的train和val路径指向正确检查images和labels目录下的文件数量是否一致用脚本比对文件名集合检查标签文件的类别ID是否都在0到nc-1之间确认val.py和train.py用的是同一个data.yaml不要出现训练用一份、验证用另一份的情况。按照这个顺序排查95%的验证阶段报错都能解决。我个人在实际操作中的体会是YOLOv5m的整个训练验证流程并不复杂真正的难点在于数据质量管理和对指标变化的敏感度。数据干净了参数合理了模型效果自然会上去。最后再分享一个小技巧训练完拿到best.pt之后不要只盯着mAP看多花点时间看预测可视化结果把预测错误的图片单独拉一个文件夹逐张过一遍你会在里面找到比任何指标都更有价值的改进线索。