SSD目标检测实战:自定义数据集训练与调参全流程解析
2026/9/17 1:34:08 网站建设 项目流程

1. 此SSD非彼SSD:动手之前先搞清楚模型与场景

1.1 SSD是目标检测领域的"老牌选手"

看到SSD这个缩写,不少人的第一反应是电脑里那块固态硬盘,毕竟装系统、迁移数据、RAID配置这些话题最近在社区里确实很热闹。但做深度学习目标检测的人看到SSD,脑子里出现的应该是Single Shot MultiBox Detector——一个2016年由Wei Liu等人提出的单阶段目标检测模型。我这次要聊的,是后者,不是前者。

我自己前段时间正好接到一个需求:用SSD在完全自定义的数据集上做检测,类别总共只有4类,但样本形态跟公开数据集差距很大。整个过程从采集图片、标注、搭目录、改配置到最终调参,花了我差不多两周时间。中间踩的坑不少,但把流程理顺之后发现,SSD这套从数据到训练再到调参的链路,其实非常适合作为目标检测自定义数据集的第一条完整路线。它没有某些框架那么高的封装度,很多环节需要自己动手,但也正因为如此,你能把数据是怎么被吃进去的、损失是怎么算出来的、锚框是怎么匹配的这些东西一一搞明白。

如果你之前只用yolov5或yolov8的训练脚本跑过现成数据集,那么这次用SSD训练自定义数据,体验会很不一样。YOLO系把大部分细节都藏在了train.py和yaml文件背后,而SSD的主流实现里面,数据集解析、锚框生成、匹配策略很多都是显式写出来的,出问题时更容易从原理层面排查。这篇文章就按我这次实际操作的顺序来写:数据集怎么做、配置文件怎么改、训练时怎么看曲线、调参时优先动哪些参数,以及最后怎么验证结果。适合那种已经跑通过一两个公开检测模型,准备开始碰自己数据的读者。

1.2 它跟YOLO相比,各自的优劣势在哪

目标检测模型选型时,现在大家默认先看YOLO,这个趋势我能理解。YOLO迭代到v5、v8、v11,工程化做得确实好,社区生态也丰富。但SSD并没有被淘汰,它在某些场景下依然有自己的价值,尤其当你对模型结构、数据管线有定制需求时。

单从结构上说,YOLO把检测头挂在主干网络的几个输出层上,而SSD更激进一点,从主干网络中途的多个卷积层引出分支,分别对浅层和深层特征图做检测。浅层特征图分辨率高、感受野小,适合检测小目标;深层特征图分辨率低、感受野大,适合检测大目标。这种多尺度输出设计,让SSD在中等规模数据集上往往比当时的YOLOv1/v2表现更稳。

对比维度SSDYOLO(v5/v8系列)
多尺度策略多特征层预测,显式设计多组锚框多层输出加锚框或anchor-free两种路线
训练复杂度需要关注锚框匹配、难例挖掘工程化封装好,默认参数即用
自定义数据集友好度需要自己改的数据点多,但透明只需改data.yaml,简单直接
部署体积模型结构轻量,适合移动端/嵌入式不同型号可选,体积可控
社区生态实现版本多,PyTorch/MMDetection都有官方仓库维护完善,文档全

我选SSD并不是因为它比YOLO更准,而是因为这次的数据集类别少、背景相对固定、对实时性要求不高,且我想把检测管线整体吃透。SSD的损失函数分成confidence loss和localization loss两部分,其中confidence loss基于softmax多分类交叉熵,localization loss基于smooth L1回归,理解起来非常直观。这比直接丢给一个黑盒训练脚本要踏实得多。

1.3 本实战采用的SSD实现与运行环境

SSD的开源实现太多了,Caffe时代的原版、PyTorch复刻版、TensorFlow Object Detection API里的版本、还有MMDetection里的SSD配置,都是可选项。我这次用的是基于PyTorch的常见复刻版本,数据集读取逻辑是标准的VOC格式解析,训练代码里显式暴露了锚框生成和匹配的源码,这样后面调参时能直接改到核心逻辑。

硬件方面,我用的是一张RTX 3060 12GB显卡,老实说这个显存跑SSD300非常宽裕,batch size开到32都没问题。如果你手里显卡只有6GB甚至4GB,也不用慌,把batch size降到8或16,输入尺寸用300x300的原始设计,照样能跑。CPU训练不建议尝试,虽然SSD300计算量不大,但梯度反传和锚框匹配矩阵运算在CPU上会慢得让人崩溃。

软件环境方面,PyTorch 1.13或2.x均可,CUDA版本对应好即可。数据标注工具我用的是LabelImg,后面会细说。

2. 从头到尾做一套VOC格式数据集:采集、标注、目录搭建

2.1 采集数据的几项硬指标

这次项目的检测对象是四类工业零件:螺丝、垫圈、弹簧、轴承。我原以为数据集嘛,拿手机拍一拍就有,结果真正采集完发现了很多问题——大量照片背景太杂、光照不均、目标尺度变化极大。这里我总结出几个对后续训练影响最大的采集指标,供你参考:

每类样本不少于300个实例。目标检测和分类任务不一样,分类一张图只有一个标签,而检测一张图可能包含多个目标,所以有效标注框的数量才是关键。如果一个类别只出现50个框,训练时类别学不出来,loss降不下去,最后mAP铁定惨不忍睹。我这次每类都保证500个以上标注框,最终效果才比较稳。

目标的尺度分布越接近真实应用越好。如果你的检测场景是机械臂抓取工位,相机固定,目标尺寸基本在一个范围内,那就按这个范围去采集。如果目标大小浮动很大,比如近处远处都可能有零件,就必须在数据里包含这种尺度差异,否则训练完对某个距离的检测效果会很差。采集时不要只对着零件拍特写,要模拟真实部署时相机的角度、高度和背景。

尽量收集一定数量的负样本。所谓负样本,就是一张图里完全没有目标类别的图片。VOC格式允许一张图没有任何object标签,这样的图片会被模型当作全背景样本。它对抑制误检非常有帮助。我这次在纯背景工作台上拍了100多张负样本,训练后误检率明显下降。这一点很多人会忽略,但它真的很有用。

2.2 用LabelImg把图片变成XML

标注工具我选了LabelImg,没有别的原因,它是VOC格式生态里最经典、最顺手的一个。安装方式就不赘述了,pip或者release包都行,启动后打开图片文件夹,开始画框。

几个必须记住的快捷键:W是开始画框,A是上一张,D是下一张,Ctrl+S保存。很多人刚用时一张张点保存按钮,效率太低,我习惯画完一张图立刻Ctrl+S,然后D切下一张,形成肌肉记忆之后一天标几百张不成问题。

标注时有两个细节直接决定后面训练是否顺利:

第一个是类别名的一致性。LabelImg里你输入什么类别名,XML里就存什么类别名。最忌讳的是有时候输入"screw",有时候输入"螺丝",有时候手滑打成"screw "(多了一个空格)。这是VOC格式解析里最常见的翻车点,数据校验时查这类错误能让人崩溃。建议一开工就固定类别名清单,比如screw、washer、spring、bearing,中途不要改。

第二个是bounding box边界的处理。VOC格式的xmin、ymin、xmax、ymax要求是整数像素坐标,而且必须在图片尺寸范围内。如果目标被截断了一半,有两种处理方式:要么把框画到可见部分的边界,并把truncated标记为1;要么干脆不标,不要把框画出图片边界。LabelImg默认允许你画出边界,XML里就会存负数坐标,很多训练代码读进来之后对齐到[0, width]区间倒也不会崩,但坐标值会失真,对应关系不准确,所以尽量别画出边界。

每个XML文件长这样:

<annotation> <folder>VOC2007</folder> <filename>img_0001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>screw</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>452</xmin> <ymin>318</ymin> <xmax>521</xmax> <ymax>397</ymax> </bndbox> </object> </annotation>

2.3 按PASCAL VOC规范搭建目录结构

标注完成的图片和XML不能随便扔在一个文件夹里,SSD的数据加载器通常按照PASCAL VOC规范来定位文件。标准目录结构是这样的:

VOCdevkit/ └── VOC2007/ ├── Annotations/ # 所有xml文件 ├── JPEGImages/ # 所有jpg图片 └── ImageSets/ └── Main/ # 存放train.txt、val.txt等索引文件

JPEGImages里放了原始图片,Annotations里放了对应的标注XML,图片和XML的文件名要一一对应,比如img_0001.jpg对应img_0001.xml。ImageSets/Main下的文件则是纯文本索引,每行一个文件名,不带目录前缀也不带扩展名。

最好在搭建目录时就做一个完整性检查脚本,扫描出哪些图片缺少XML,哪些XML对应的图片不存在,还有哪些XML里没有任何object。这一步脚本化可以写得很简单,但对后面省下的时间是不可估量的。

2.4 划分train/val/test并生成索引文件

VOC格式的惯例是按约7:2:1或8:2的比例划分训练集和验证集。要注意的是,划分时必须保证同一目标的不同图片全部落在同一个集合里,如果同一目标在10张图中出现,其中8张在训练集、2张在验证集,那模型在验证集上的表现会被高估,因为验证样本跟训练样本过于相似。

我是按整个图片文件列表随机打乱划分的,因为项目场景里每张图都是独立拍摄的,不存在同一物体出现在多张图的问题。如果你的数据是从视频里抽帧得到的,那么连续帧之间非常相似,必须先按视频片段划分。这是个很多教程不会提、但实际特别重要的点。

生成索引文件的脚本逻辑很简单:

import os import random random.seed(42) img_dir = 'JPEGImages' train_ratio, val_ratio = 0.8, 0.2 filenames = [f.split('.')[0] for f in os.listdir(img_dir) if f.endswith('.jpg')] random.shuffle(filenames) train_n = int(len(filenames) * train_ratio) train_files = filenames[:train_n] val_files = filenames[train_n:] with open('ImageSets/Main/train.txt', 'w') as f: f.write('\n'.join(train_files)) with open('ImageSets/Main/val.txt', 'w') as f: f.write('\n'.join(val_files)) with open('ImageSets/Main/trainval.txt', 'w') as f: f.write('\n'.join(filenames))

如果后面要测试,再单独留test.txt。这次项目规模不大,用trainval训练、val验证就够了。

3. 动训练之前:数据校验与配置文件改造

3.1 坏图和标注错误,这一步能帮你省掉大半天

我吃过一次大亏:数据集做到800张图之后直接开训,跑到第43个epoch发现loss突然飙升到NaN。排查半天,最后发现数据里有一张损坏的PNG图片,解码出来是空的;还有两张图里标了一个宽度为1像素的"框",边界框退化成了线,回归分支直接炸掉。从那以后我养成了一个习惯:训练前必须做一轮完整的数据校验,花20分钟,省两三天。

校验清单大致包括这几项:

  • 用PIL或OpenCV打开每一张图片,确认可以被正常解码、尺寸不为0。
  • 用XML解析器检查每个标注文件能否正确解析,避免标签未闭合、中文未转义等低级错误。
  • 检查每个object的name是否都在预设类别集合里。
  • 检查每个bndbox是否存在,且xmin < xmax、ymin < ymax,坐标差不能过小。
  • 检查目标框面积与图片面积的比例,过滤掉异常极端的框,比如占不到图片面积0.1%的目标。

这段校验代码写起来没什么难度,但你可以顺手把所有图片的尺寸分布、标注框高宽比分布统计出来。这个统计信息后面调锚框参数时非常有用。比如你发现目标大多是又扁又宽的形状,那么SSD里默认的高宽比列表[1/2, 1, 2, 3/2, 3]可能就不合适,需要加入4:1甚至更极端的比例。

3.2 修改类别数、路径、锚框等核心配置

数据准备完毕,接下来是改配置文件。无论在哪个SSD实现里,有几处是必改的:

类别数。SSD分类分支的输出维度是类别数加背景。你数据里有4个类别,那么分类输出维度就是5。很多新手在这里只改了类别数但忘了背景类,维度对不上,训练直接报错,或者loss疯狂震荡。修改时注意区分num_classes参数和数据集解析里class_to_idx映射表,两处都要改。

文件路径。把train.txt和val.txt的路径、图像目录和XML目录的路径都改成自己的实际路径。注意有些实现默认路径里带VOC2007,如果你目录叫VOC2024,这些硬编码路径全都要改。

锚框尺寸。这是SSD最有技术含量的部分。SSD默认在6个不同尺度特征图上产生锚框,每个特征图上的锚框尺寸从0.2到0.9(相对于输入图片尺寸300的比例)。默认值在VOC和COCO上表现不错,但换到自定义数据集就不一定了。我这次检测的工业零件普遍较小,在整张图中占比约3%到8%,对应的锚框尺寸比应该在0.15到0.4之间,而默认最小值是0.2,不算离谱但不够优。后面我会具体说怎么调。

batch size和learning rate。12GB显存跑SSD300可以直接开batch size 32。如果显存不够,优先减batch size而不是减输入尺寸,因为SSD300本身设计就是300x300,强行缩小输入会导致锚框相对尺寸关系错乱。

3.3 预训练权重的作用与下载选择

自定义数据集的样本量永远不够,这是目标检测的常态。直接用随机初始化权重从零训练,模型收敛极慢,而且容易陷入局部最优。正确做法是加载在VOC或COCO上预训练好的权重,然后迁移学习到自己的数据上。

要注意的是,预训练权重的分类层维度跟你的数据集不一致,所以加载权重时通常会跳过最后的分类层。大部分SSD复刻实现里都写了load_state_dict时如何处理num_classes不匹配的逻辑,如果没有,你需要手动过滤掉权重字典中最后分类层的键。本地化回归层的参数一般可以直接迁移,因为边界框的4个坐标回归参数与类别数无关。

选择预训练权重时,优先选跟你的实现版本完全匹配的权重文件。如果网络结构稍有差异,加载时会报unexpected key,这种权重用起来隐患很多,不如不加载。我在实践中发现,从VOC预训练权重迁移到自己的小数据集,比从COCO预训练权重迁移效果更好,主要原因是VOC类别数量级和样本形态接近工业场景,特征更通用,而COCO包含大量自然图像,特征分布差异反而大。

4. 训练开始后的第一课:看懂loss曲线和收敛状态

4.1 训练启动命令与日志记录习惯

如果你的实现基于PyTorch,训练启动通常就一行命令,比如python train.py --config config.yaml。但启动前把输出日志重定向到文件里,是个值得养成的好习惯:

nohup python train.py --config config.yaml > train_ssd.log 2>&1 &

为什么强调这个?因为训练一旦跑起来,很多时候你要回到几天前去看某个epoch的loss输出,又或者在服务器上用tail -f train.log实时观察。日志记录到位之后,分析loss趋势、对比不同实验组配置,都方便得多。

顺手做一张训练进度表也很有用。每次实验至少记录这几列:epoch、train_loss、val_loss、学习率、当前使用的锚框配置、数据增强策略。我自己习惯用csv文件记录,每个实验组一个文件,方便最后对比。

4.2 一张正常的loss曲线长什么样

SSD的loss曲线跟分类模型的loss曲线不太一样,它有两个损失,一个分类损失一个回归损失,最终训练代码里通常是两个损失相加。但日志里建议把两个loss分开打印,因为它们的含义各自不同:

  • 分类损失(confidence loss):衡量每个先验框预测的类别概率与真实类别之间的差异。初始阶段很高,因为模型还没学会区分背景和目标。
  • 回归损失(localization loss):衡量预测框坐标与真实框坐标的偏差。初始阶段相对较低,因为所有预测框都趋向于锚框默认位置。

正常收敛的情况下,训练loss在前20到30个epoch会快速下降,之后逐步变缓,到60到80个epoch基本进入平台期。验证集上的loss和mAP同步趋于稳定,说明模型已经收敛。

我在训练时习惯同时关注训练loss和验证集mAP,如果训练loss一直在降但验证集mAP不上涨甚至下降,那多半是过拟合信号,这时候该考虑加正则、减数据增强或提前停止。

4.3 识别过拟合与欠拟合的信号

自定义数据集样本少时,过拟合是头号大敌。出现以下信号时,你就要警惕了:

  • 训练loss持续降低,逼近0.1甚至0.05,但验证loss在某个点之后开始反弹。
  • 验证集上模型对小目标、遮挡目标的预测框抖动严重,同一张图两次推理结果相差明显。
  • mAP曲线在训练后期波动加剧,不再平滑上升。

对应地,欠拟合的信号也有迹可循:训练loss和验证loss都下不去,卡在某个高位;mAP一直在低位徘徊;预测框大量漏检,甚至完全不检。出现这种情况时最可能的原因是锚框匹配失败,即你的目标大小跟预设锚框完全不搭,导致正样本数量太少,损失信号微弱,模型学到的东西不足。

我在SSD实战中发现一个规律:如果前50个epoch训练loss下降趋势不明显,问题大概率不在学习率,而在锚框配置。你可以把每个batch产生多少正样本anchor数量打印出来——如果这个数字过小,比如不到100,那就是锚框严重不匹配数据。

5. 最花时间的调参环节:几个绕不开的细节

5.1 锚框尺寸与目标尺度不匹配怎么办

这是我在SSD调参中花时间最多的地方,也是最有价值的部分。

SSD中的锚框参数分散在多个层上。以SSD300为例,它在6个特征图上各自配置了一组锚框,例如从conv4_3开始,特征图大小38x38,锚框尺寸最小;越往后特征图越小,锚框越大。每层的锚框尺寸由min_sizemax_size决定,另外还有aspect_ratios高宽比决定每种尺寸下不同长宽比的锚框。

默认配置如下:

特征图层特征图尺寸anchor_size(相对输入比例)
conv4_338x380.1
conv719x190.2
conv8_210x100.375
conv9_25x50.55
conv10_23x30.725
conv11_21x10.9

如果你要检测的目标普遍很小,第一种思路是整体缩放锚框尺寸,让最小的锚框从0.1降为0.05或0.03。第二种思路是保持大层锚框不变,专门增强小尺度特征图层的锚框密度,比如在conv4_3上增加更多高宽比。第三种思路是使用数据增强来变换目标大小,让单一锚框配置能够覆盖更广范围,但这样牺牲的是真实分布。

我这次选择了整体降低锚框尺寸范围,因为从统计数据看目标占图片面积的比例集中在3%~8%,原配置的最小锚框0.1基本上产生不了足够多的正样本。把最小锚框缩到0.04之后,正样本数量从每batch几十个提升到几百个,训练效果立竿见影。验证时mAP从0.53涨到0.74,提升非常显著。

5.2 正负样本极度不均衡的缓解手段

SSD天然面临正负样本极度不均衡的问题——一张图上锚框总数成千上万,但真正匹配到真实目标的锚框可能只有几十个。如果不做任何处理,模型会倾向于把所有锚框都预测为背景,因为绝大多数样本确实是背景,这个方向梯度最容易把loss压下去。

SSD有两种经典手段来压制这种不均衡:

第一是hard negative mining(困难负样本挖掘)。把所有预测为背景的锚框按分类置信度损失排序,取损失最大的那部分作为负样本,并控制正负样本比例不超过1:3。这个策略保证模型在每轮训练中看到的负样本都是它最容易搞错的那一批,而不是大量容易的负样本,学习效率高得多。

第二是背景类的权重调整。如果你不修改挖掘逻辑,可以在分类损失函数里对背景类赋予更低的权重,降低海量背景样本对梯度的主导作用。这个方案实现起来更简单,但对效果的控制精确度不如挖掘机制。

如果你用的是已有SSD实现,它大概率已经实现了第一条路线,不用额外操心。但如果训练曲线出现训练loss迟迟不降、且沉底时,优先检查hard negative mining里的正负样本比例是否异常,比如某轮的正样本只有几个,此时所有负样本都会被挑选,模型输出倾向于背景。

5.3 学习率、batch size与warmup的配合

自定义数据集上训练SSD,学习率的选择直接关系到模型能否收敛。主流开源实现里,初始学习率常设为0.001或0.01,配合multi-step衰减策略。但这里有个前提:你用了预训练权重且数据量不大。如果从零训练,0.01很可能会在训练初期就产生NaN loss,或者收敛极慢。

我的建议是初始学习率先用0.001,训练到20个epoch观察loss下降速度,如果每轮下降非常缓慢,可以把学习率调到0.005再跑一个短实验看几轮对比。但更推荐的做法是启用warmup策略,即前500步学习率从0线性增到目标值,避免刚开始时因为权重还没适配数据而产生过大梯度。

batch size与学习率之间的关系也需要配套调整。你如果从32降到8,学习率最好也相应地降低,比如0.001降到0.0005。我在迁移到12GB卡、batch size开到32时,发现学习率0.001反而过于保守,调到0.002之后收敛明显加快,loss下降过程也更平滑。这里的核心思路不是寻找一个万能参数,而是保证总体的梯度噪声与更新步长匹配得当。

5.4 数据增强的力度控制

SSD训练里,随机裁剪、水平翻转、颜色抖动是最常用的增强手段。数据增强能有效提升模型泛化能力,但用力过猛会导致数据分布严重偏离真实场景,模型在验证集上表现反而不如不增强。

我使用的增强策略是:默认的随机裁剪(保持目标至少露出一部分)+ 水平翻转(概率0.5)+ 轻微的颜色抖动。没有加mosaic、mixup这类的重增强。原因很简单,我的目标检测场景是稳定的工位摄像头,目标和背景在真实部署时就是固定视角、固定光线的。如果我在训练时用mosaic把一堆零件P图成四宫格,模型学到的数据分布和真实场景相差过大,部署后检测效果反而不可靠。

数据增强的力度应该在实验中有意识地对比。你可以做两个实验组,一组不开增强,一组开默认增强,其他条件完全一致。跑完对比mAP后通常会发现:样本量小(每类500框左右)时,增强能带来明显收益;样本充足(每类2000框以上)时,增强的收益会递减,甚至因为破坏了某些目标的相对尺寸分布对检测精度有副作用。

5.5 显存不足时的应对思路

训练时显存不足是新手最常遇到的环境问题。显存溢出时的反应一般是报CUDA out of memory,解决手段从便宜到贵排序是这样:

  • 优先减小batch size,比如从32降为16或8,同时按比例降低学习率。
  • 开启gradient accumulation(梯度累积),把一个大batch的计算拆成几个小batch,累加梯度后再更新一次权重,用时间换显存。
  • 检查数据加载器是否一次性把全量数据加载进显存,某些实现里Prefetching会把loader读进来的图片直接搬到GPU上。可以在配置里把num_workers调低一点。
  • 如果是固定显存,实在跑不动时可以用混合精度训练,PyTorch的AMP能显著降低显存占用,同时对SSD这种以定位和分类为主的模型来说精度损失一般不大。

我自己在6GB显卡上就靠batch size=8加梯度累积跑通过一次SSD300训练,效果跟batch size 32直接训练差距很小,因为SSD本身锚框匹配就有一定的隐式正则作用。

6. 验证不止于一张效果图:mAP与坏例分析

6.1 用VOC标准计算mAP

训练结束后,你要回答的不只是"模型能不能检测出目标",而是"模型到底多准"。单张效果图带不来令人信服的结论,拿一张图里检测得很准的漂亮效果来当成果展示,是新手最容易犯的错。

PASCAL VOC的标准评测方法是mAP,计算步骤大致如下:

  • 对每个类别,以IOU>0.5为阈值判断检测框是否命中真实目标。
  • 把检测框按置信度降序排列,逐个计算precision和recall。
  • 绘制P-R曲线,计算曲线下面积作为该类的AP。
  • 所有类别AP取平均,得到mAP。

我这次四类的AP最终分别落在0.79、0.82、0.68、0.71,mAP约0.75。虽然不算顶尖,但对于少量样本的自定义数据集,这个指标是可靠的:模型不会在正常场景下犯低级错误了。相比只用效果图糊弄自己,手算一遍mAP能帮你暴露很多问题——比如某个类别AP只有0.4,说明这个类别学得很差,你必须去分析它的bad case。

6.2 从bad case反推调参方向

跑完mAP后,把AP最低的几个类别里的失败样本捞出来,逐张看它们漏检和误检的原因,这一步能告诉你的信息量是最多的。

我这次spring类别AP最低,排除了标注错误和数据量不足的可能性之后,发现漏检的原因集中在两个方面:一是spring的形状是螺旋状,边缘纹理复杂,模型经常把它跟背景混淆;二是部分sample里spring被其他零件挡住了相当大一部分面积,导致锚框匹配难度大。这说明仅仅调锚框尺寸解决不了这类问题,得从数据层面补充更多遮挡样本、增加负样本中相似纹理的图片,或者适当降低分类置信度阈值来减少漏检。

误检分析里最常见的情况是:模型把背景纹理预测成了目标。这时候优先考虑增加这类背景纹理的负样本图片,其次考虑增强negative mining力度,把负样本挖掘比例从1:3降到1:1,让模型在每步优化中对容易误判的负样本更敏感。

最后分享一个我的个人习惯:验证时除了看mAP,一定会在每轮训练结束后随机抽几十张验证图,把预测结果和真实标注拼在一起保存成文件快速浏览一遍。mAP是统计层面的结果,但真正能让你对模型建立起信心或者产生警觉的,永远是亲自看一眼前后对比的图片。这个习惯来自我踩过的多次坑——有些问题统计指标上看不出来,比如某个类别小目标明明AP还凑合,但它检测的框始终偏大,这类系统性偏差只有在视觉检查里才会显形。结合mAP与人工抽检两边验证,这个模型才算真正达到了可以交出去的状态。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询