YOLO检测头自定义与P2小目标头添加实战
说实话YOLO的检测头是我见过最容易被低估的一块。大多数人用YOLO就是改个yaml里的nc然后训练、预测完事。但真正做项目、做改进、做落地的人都会知道检测头是决定模型最终输出质量、训练收敛速度、以及能不能适配你任务场景的关键一环。Neck再强Backbone再深最后那一截输出结构不合适前面全是白搭。这篇攻略就是把你从会用YOLO推到会改YOLO这一层。我会以目前社区里用得最多的Ultralytics实现以YOLO11为例你用的YOLOv8其实也基本一致带你走一遍完整流程读检测头代码、理解它的设计逻辑、自定义替换、加P2小目标头、跑通训练验证、最后把常见报错和坑一次性说完。内容不挑基础只要你用过哪怕一次YOLO训练就能跟上。1. 换检测头前先把这几件事想明白1.1 检测头到底管什么YOLO整个模型可以粗分为三段Backbone负责提特征Neck负责把不同尺度的特征融合Head负责出结果。检测头干的事说起来很简单——从Neck给过来的特征图上预测出目标的类别、位置、大小。但简单只是表象。检测头内部涉及三件核心决策第一要不要Anchor。旧版YOLOv5之前的检测头是Anchor-Based需要预设一堆锚框YOLOv8开始全面转向Anchor-Free直接预测目标中心点相对网格的偏移、宽高、以及分类得分。Anchor-Free省掉了聚类锚框的步骤训练和部署都更省心。第二分类和回归是否解耦。早期YOLO用同一个卷积层同时输出类别和框坐标这叫耦合头。后来大家发现分类和回归关注的特征其实不同就把它们拆成两个分支各干各的这叫解耦头。YOLOv8之后的检测头都是解耦的。第三DFLDistribution Focal Loss的引入。这一步在YOLOv8里出现把边框回归从直接回归一个值改成预测一个离散分布再对分布求期望得到坐标值。好处是对边界框不确定性建模更精细坏处是理解成本高、改头时容易踩坑。所以换检测头不是在yaml里改几行代码那么简单。你换的是Anchor锚定方式、分类回归耦合方式、损失计算方式甚至包括标签分配策略。这些决策和你选的模型版本、训练数据、任务场景强相关。改之前不把整条链路理清后面改完必炸。1.2 什么场景真的需要换头先泼一盆冷水很多情况下你不需要换检测头。我见过不少人把自己的YOLO11从Detect换成各种论文里的变形头跑了两天mAP掉了几个点然后又花三天排查最后发现是学习率没调好。换头属于手术级操作不是换皮式操作你得有明确的换头理由。真正值得换头的场景大概有这么几类小目标检测。这是最刚需的场景。默认YOLO11只有3个检测头分别对应80x80、40x40、20x20的特征图。20x20这个头负责大目标80x80负责中目标但很多应用里的目标无人机航拍、远处车辆、太空碎片在图像里只占几十个像素3个头根本不够。这时候就要加P2层——一个160x160的高分辨率检测头。轻量化部署。检测头虽然参数占比不算最大但三个独立解耦分支叠加的计算量在边缘设备上还是心疼。常见的做法是把三个检测头的卷积层权重共享Shared Head或者把分支里的卷积改成深度可分离卷积能省下一大截FLOPs。多任务扩展。目标检测和实例分割、旋转框检测、姿态估计这些任务常常共用同一个Backbone和Neck区别就在检测头的输出结构。YOLOv8-seg其实就是把Detect换成Segment多输出一个mask分支。你如果想做一个检测分类或检测计数的多任务模型换头几乎是必经之路。比赛和论文刷点。这里我不评价对错但确实大量YOLO改进工作都集中在检测头这个模块。改输出分支结构、改匹配策略、改损失权重都是换头的变体是可以重复实验拿收益的改进方向。1.3 换头的三条主流路线根据你要解决的目标检测头改造大致有三条路线路线一改输出结构。这是最纯粹的换头。比如把普通Anchor-Free解耦头换成更轻量的共享头或者换成更复杂的多头注意力头。特点是改动的代码集中在一个类里影响范围可控适合练手。路线二改输出尺度。这是解决小目标问题最直接的手段。在保留原有三个检测头的前提下向上增加一个高分辨率P2检测头对应160x160特征图让模型能看到更小的目标。这条路线要动Backbone和Neck结构改动面大但效果一般立竿见影。路线三改匹配与损失策略。严格说这不是换头而是换头上面的策略。YOLOv8之后默认用TaskAlignedAssigner做标签匹配DFL做回归分布CIoU算框损失。你把它换成OTAOptimal Transport Assignment或者EIoU效果也会变化。很多人说换了检测头mAP涨了其实只是损失函数带来的收益要分清楚。我的建议是第一次做检测头自定义替换从路线一路线二的组合练手——先读懂Detect类再尝试加一个P2头逐步动手风险最低成就感也最直接。1.4 别一上来就改先保住baseline这个坑我踩过真的踩过。有一回我想把YOLOv8的检测头换成某个论文里的结构改完之后训练Loss曲线一直在震荡我以为是我改坏了反复排查了一整天。最后发现是我删掉了一个残差连接导致梯度传播有问题跟检测头本身无关。所以在动手改头之前先做两件事第一用当前环境的代码和配置把原始模型完整训练一遍跑通到收敛记录mAP基线第二把代码提交一次Git确保后面无论改坏成什么样都能无脑回滚。改完之后先用少量数据、少量epochs跑一个冒烟测试确认Loss能正常下降、输出shape没问题再放到全量数据上认真跑。记住一个原则一次只改一个变量。改检测头结构就不要同时动数据增强、学习率、Batch Size。否则出了问题你根本没法定责。2. 读代码Ultralytics检测头究竟长什么样2.1 核心文件与注册机制用Ultralytics库的时候你可能不知道它内部的结构。大体上你改头需要关心的文件主要就这几个ultralytics/nn/modules/head.py所有检测头类都在这包括Detect、Segment、Pose、OBB等。核心工作都在这。ultralytics/nn/tasks.py负责把yaml配置解析成真正的PyTorch模型。你yaml里写的每个模块最后都是在这里被实例化并拼接。ultralytics/nn/modules/block.py、conv.py如果你要自定义一些Block、Conv、Attention结构一般加在这里。ultralytics/nn/extra_modules.py这个文件是给用户自定义模块留的集散地。你写在别的文件里的类只要在extra_modules里导入并加入__all__就能被yaml识别。这里有个非常关键的机制Ultralytics的模型构建不是像普通PyTorch那样写一个类然后一层层堆而是靠解析yaml文件来动态构建。你在yaml里写一个不认识的模块名parse_model函数会在全局作用域里查找有没有对应的类找不到就直接报错。所以想用自定义检测头只写一个类还不够必须确保这个类被导入到了tasks.py能触及的名字空间里。最简单的做法在你的自定义类文件底部把它导入到ultralytics.nn.modules包里或者在__init__.py里暴露。注意不同版本的Ultralytics在模块导入机制上有差别。旧版本把一堆自定义模块都塞在extra_modules.py里新版本结构更清晰。你先跑通官方模型再动代码可以避免很多环境问题。2.2 Detect类结构拆解打开head.py找到Detect类。不同版本里代码会有些差异但核心结构基本没变。我以常见版本为例拆几个关键部分class Detect(nn.Module): YOLO Detect head for detection models. dynamic False # force grid reconstruction export False # export mode shape None anchors torch.empty(0) # init strides torch.empty(0) # init def __init__(self, nc80, ch()): super().__init__() self.nc nc # number of classes self.nl len(ch) # number of detection layers (3 by default) self.reg_max 16 # DFL dimensions self.no nc self.reg_max * 4 # number of outputs per anchor self.stride torch.zeros(self.nl) # strides computed during build c2, c3 max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], min(self.nc, 100)) self.cv2 nn.ModuleList( nn.Sequential(Conv(x, c2, 3), Conv(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for x in ch ) self.cv3 nn.ModuleList( nn.Sequential(Conv(x, c3, 3), Conv(c3, c3, 3), nn.Conv2d(c3, self.nc, 1)) for x in ch ) self.dfl DFL(self.reg_max) if self.reg_max 1 else nn.Identity()这段代码信息量很大。nc是类别数ch是Neck送给检测头的三个特征图的通道数nl是检测头个数。reg_max16是DFL的分布bin数量no是每个位置预测的总输出维度。cv2就是回归分支输出4 * reg_maxcv3是分类分支输出nc类别的置信度。两个分支都是Conv Conv Conv2d的三层结构第一层和第二层卷积核都是3x3。注意一个细节——变量名c2和c3的计算方式。c2是回归分支的隐藏层通道数约等于输入通道数的四分之一但最小16c3是分类分支的隐藏层通道数最小是min(nc, 100)。在实际推理时forward方法会按输入特征图尺寸计算网格点然后把cv2的输出reshape成(batch, nl, 4, reg_max, h, w)再通过dfl求期望得到真实的中心点和宽高偏移。def forward(self, x): shape x[0].shape for i in range(self.nl): x[i] torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1) if self.training: return x ... return y # 或 (y, x) 取决于是否导出训练阶段直接返回list每个元素是[B, nc 4*reg_max, H, W]的原始特征图后面的损失计算在loss.py里。推理阶段会做解码、网格坐标叠加、阈值过滤、NMS等。如果你在YOLOv8和YOLO11之间切换看代码会发现这块几乎没有本质区别。这说明Ultralytics的核心架构其实相当稳定检测头的替换思路完全可以跨版本复用。2.3 yaml配置里那些数字的含义现在来看模型配置yaml。打开一个yolo11.yamlbackbone部分每一行类似- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2这里-1表示输入来自上一层1表示重复次数Conv表示模块名[64, 3, 2]是模块参数——64是输出通道3是卷积核尺寸2是步长。head部分每一行类似- [-1, 1, Conv, [512, 1, 1]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 6], 1, Concat, [1]] # cat backbone P4[-1, 6]表示把上一层的输出和第6层backbone的P4层的输出做Concat[1]表示在channel维度拼接。最后三行就是检测头的配置- [[-1, 22], 1, Detect, [nc]] # Detect(P3, P4, P5)这一行把来自不同尺度的3个特征图拼接输入给Detect模块nc是类别数会自动传入。这也解释了为什么Detect类的构造函数要接收一个tuple类型的ch参数。如果你要加P2头核心就是在head部分用一个上采样把浅层特征和深层特征融合生成一个高分辨率特征图然后把它也接进Detect。具体做法我下一章详细写。提示yaml里模块名后面跟的#注释不要删。Ultralytics构建模型时会解析注释里的层号信息比如0-P1/2用于后面计算stride。删了注释模型照样能跑但检测头的stride计算可能就乱了推理时坐标会偏。2.4 换头的基本套路读完类结构和yaml写法换头的基本套路其实就清晰了第一写一个新的检测头类继承自Detect或者完全自定义。如果只是加一个尺度直接复用Detect就行如果想换结构继承后重写__init__和forward。第二把新类注册到能被parse_model识别的名字空间里通常放到extra_modules.py或直接在head.py里加然后更新__all__。第三修改yaml在head部分调整Neck结构把新检测头要用的特征图都引过来最后一行换成你的新模块名。第四如果新结构的forward和Detect不同还需要同步修改loss.py。这里最容易出Bug。比如你把DFL去掉了或者把回归分支的输出维度从4*reg_max改成了4那loss里decode的部分必须对应调整。这也是为什么我建议新手先从只加不删开始——保留Detect原本输出结构不变只是增加尺度这样loss完全不用动。3. 实操给YOLO11加一个P2小目标检测头这一章我们直接跑一个完整实战。目标很明确在YOLO11的检测头前面插入一个P2特征层让模型多出一个160x160的小目标检测头用于提升小目标召回率。整个改造我分成环境、yaml、代码、训练与验证四步。3.1 环境准备与数据先准备环境。我这里以桌面端为例子不需要GPU也能跑通验证流程有GPU更好。建议用conda建独立环境别污染你原有的PyTorch环境conda create -n yolo_head python3.10 -y conda activate yolo_head pip install ultralytics装完后先确认版本。不同版本间head.py里类名可能不一样后面的代码要对应调整python -c import ultralytics; print(ultralytics.__version__)数据方面不建议一上来就用完整COCO跑太耗时。先弄一个几十到几百张图的小数据集类别就一两类能验证流程就行。你可以用官方示例数据集或者拿手机拍几张自己的东西标注一下。标注工具我推荐CVAT或VSCode里装YOLO标注插件导出格式选YOLO就行。标注后按下面的目录结构放datasets/mydata/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/然后写一个mydata.yamlpath: datasets/mydata train: images/train val: images/val names: 0: person 1: car先跑一个5个epochs的baseline确认环境和数据都没问题同时把原始mAP记下来作为对照基准。这一步千万别跳后面所有改动都是跟它比的。如果你用的是AMD显卡Ultralytics官方对ROCm的支持其实已经比较成熟很多情况下可以直接装对应版本的PyTorch跑。实在遇到兼容问题的建议先在CPU模式下把流程完整跑通确认模型结构和逻辑没问题后再回头处理硬件加速排查效率会高很多。3.2 修改模型yaml插入P2特征层进入正题。复制一份yolo11.yaml命名为yolo11-p2.yaml。打开它先看head部分结构。默认yolo11.yaml的head大概是这样的版本不同会有差异但思路一致head: - [-1, 1, Conv, [256, 1, 1]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 6], 1, Concat, [1]] # cat backbone P4 - [-1, 1, C3k2, [256, False, 0.25]] ... - [[-1, 22], 1, Detect, [nc]] # Detect(P3, P4, P5)注意看最后一行Detect前面有三个特征图会被concat传给Detect分别对应P3、P4、P5。backbone的输出从P3开始往下越来越抽象。默认没有P2因为P2层还在backbone很浅的位置分辨率高、计算量大直接加会让训练变慢、显存暴涨。我们的目标是在backbone的P2位置通常是第2层或第3层输出的高分辨率特征图下拉一条分支向上采样并与neck的深层特征融合生成一个160x160的高分辨率检测特征图。下面是我在YOLO11上改好的一段head配置仅供参考具体层号要根据你的yaml版本确定head: - [-1, 1, Conv, [256, 1, 1]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 6], 1, Concat, [1]] # cat backbone P4 - [-1, 1, C3k2, [256, False, 0.25]] # 新增从P3继续上采样到P2 - [-1, 1, Conv, [128, 1, 1]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 4], 1, Concat, [1]] # cat backbone P2 - [-1, 1, C3k2, [128, False, 0.25]] # 原有P3/P4/P5头保留 ... - [[-1, 27], 1, Detect, [nc]] # Detect(P2, P3, P4, P5)改完之后最后一行Detect收到的特征图列表从原来的3个变成4个通道数也变了。Detect类会按传入的ch个数自动把nl设为4cv2和cv3都会自动为每个尺度创建对应的卷积分支。这一行改动是加P2头最核心的部分。注意不同版本里backbone的层号注释如4-P2/4不同你加Concat时引用的层号一定要对照你改的那个yaml里的实际层号。出错了大概率报IndexError。别怕报错后会明确告诉你是索引问题回去检查一下数字。3.3 自定义一个轻量化检测头并在配置里生效光加P2头还不够这里的篇幅想单独写一个自定义检测头的完整流程正好可以把P2和轻量化改造结合起来。我们在P2这个新增检测头上不要一次引入太多改动这里单独演示一个完全自定义的轻量检测头怎么落地。先创建一个新文件ultralytics/nn/extra_modules.py如果存在就直接打开追加。这个文件是放自定义模块的集散地比直接改head.py稳妥——升级库时不会被覆盖。然后在其中写一个自定义检测头继承自Detectimport torch import torch.nn as nn from ultralytics.nn.modules import Conv from ultralytics.nn.modules.head import Detect class LiteDetect(Detect): 轻量化检测头使用深度可分离卷积替换普通卷积降低参数量。 def __init__(self, nc80, ch()): super().__init__(nc, ch) # 重新定义cv2/cv3分支使用深度可分离卷积 c2, c3 max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], min(self.nc, 100)) self.cv2 nn.ModuleList( nn.Sequential( Conv(x, c2, 3, 1, None, 1, 1, gx), # depthwise Conv(c2, c2, 3, 1, None, 1, 1, gc2), # depthwise nn.Conv2d(c2, 4 * self.reg_max, 1) ) for x in ch ) self.cv3 nn.ModuleList( nn.Sequential( Conv(x, c3, 3, 1, None, 1, 1, gx), # depthwise Conv(c3, c3, 3, 1, None, 1, 1, gc3), # depthwise nn.Conv2d(c3, self.nc, 1) ) for x in ch )这里用了深度可分离卷积——把普通3x3卷积替换成逐通道卷积参数量直接从3x3xk降到3x3x1每通道。代价是卷积的跨通道信息融合能力弱了所以后面通常要接一个1x1卷积补充跨通道信息。我这里为了结构简单只在原始三个卷积之上直接替换你可以根据实验效果再调整。但是仅仅写一个类还不行要让yaml能识别它需要做导入注册。编辑ultralytics/nn/modules/__init__.pyfrom .extra_modules import LiteDetect然后在你的自定义yaml文件最后一行把Detect换成LiteDetect- [[-1, 27], 1, LiteDetect, [nc]] # LiteDetect(P2, P3, P4, P5)这个时候你可能会问改了分支结构loss还能用吗好消息是LiteDetect继承自Detect我们只是改了中间的卷积实现输出维度没动——依然是nc 4*reg_max。所以loss.py完全不用改。这是继承玩法最大的优势。如果你非要完全从零写一个检测头不继承Detect那你就要处理decode、锚框、stride计算、损失计算等一系列问题复杂度会指数上升。强烈建议刚开始玩的时候只做用继承掩盖差异的改动先把机制跑通再说。3.4 训练与验证现在开始训练。用ultralytics CLI就能跑yolo detect train \ modelyolo11n-p2.yaml \ datamydata.yaml \ epochs30 \ imgsz640 \ batch16 \ device0如果是CPU或AMD环境device参数改成cpubatch调小。第一次训练建议epochs设少一点比如30先看Loss能不能稳下来。训练完成后验证一下效果yolo detect val modelruns/detect/train/weights/best.pt datamydata.yaml跑完之后看mAP50和mAP50-95。和baseline对比如果加了P2之后小目标mAP明显涨了说明改造成功如果大目标掉点了可能得调整P2分支的通道数或者微调P2特征图和原有P3/P4/P5的权重比例。这里有个小技巧训练完别急着关控制台看一眼输出里的Model Summary确认检测头的层数和特征图尺度是4个而不是默认的3个。如果想更进一步确认可以写几行代码打印模型结构并测试forwardimport torch from ultralytics import YOLO model YOLO(yolo11n-p2.yaml) # 打印模型摘要看检测头层数 print(model.model) # 模拟一个输入确认forward能跑通 x torch.randn(1, 3, 640, 640) with torch.no_grad(): out model.model(x) print(type(out), len(out) if isinstance(out, list) else tensor)在训练模式下输出是一个list每个元素对应一个检测尺度的特征图。len为4说明P2头已经生效。4. 常见问题与排查技巧实录下面这些坑基本涵盖了你做检测头自定义替换时会遇到的绝大多数问题。我自己踩过不少这里挑最有代表性的写出来。4.1 换头后shape mismatch这是最高频的报错。通常长这样RuntimeError: The size of tensor a (80) must match the size of tensor b (20) at non-singleton dimension 3出现这个首选查三处第一检查yaml最后一行Detect接收的特征图数量。如果Detect的nl3但你给他送了4个特征图基本必炸。在head.py的Detect的__init__里打一行print看self.nl。第二检查特征图通道数是否和Detect内部构造的ch匹配。Detect里的cv2和cv3是在__init__时就创建好的如果你修改yaml时改变了某个特征层的输出通道数但没同步改Detect的输入就会产生卷积输入维度不匹配。这个报错比较直观通常指向某个Conv的in_channels。第三检查特征图空间分辨率是否对得上。P2高分辨率头的尺寸应该是P3的两倍如果上采样倍数错了后面计算网格坐标时也会炸。另外一个优先级比较低的原因你改了Detect类但strides计算错误。strides是模型build阶段根据backbone的stride自动算出来的如果你改动太多导致某个特征图尺度和实际stride对不上推理时坐标会严重偏移表现为检测框在乱飘。这不会报错但效果很差。排查方法是在推理模式下手动打印每个检测头的stride看看是否和预期一致。4.2 训练时Loss瞬间变成NaNLoss变NaN是换头新手最头疼的问题。常见原因和排查顺序第一学习率太大。自定义检测头的参数初始化和原版不同直接套用默认lr可能过冲。把lr从0.01降到0.001或0.0001试试如果NaN消失说明是学习率问题。第二类别数设置错误。nc设成0或者没传对可能导致标签分配时除零。检查data.yaml里names的数量和Detect的nc是否一致。第三DFL维度错误。如果你动过reg_max但loss里decode逻辑没跟着改容易出现数值越界。最好别改reg_max保持16直到你完全读懂了DFL的源码。第四输入图像里有NaN像素。这个很少见但如果你用了特殊的数据增强方式有可能生成异常像素。把这个因素排除掉也很快——画几张增强后的图看看。我最推荐的排查方式用最小数据集、固定随机种子、关闭数据增强增强参数全关然后跑几个step逐个环节加回来。这样能把Range缩小到具体模块。4.3 加载预训练权重报错换了检测头之后用COCO预训练权重作为初始权重经常报这种错Error(s) in loading state_dict for YOLO: Missing key(s) in state_dict: model.24.cv2.0.2.weight, ...原因很直白官方权重里的检测头参数名字和你自定义模型里的不完全一致。如果你是继承Detect但改了cv2/cv3的内部结构参数名还是一致的都是cv2.0、cv2.1这种大概率能直接加载。如果你彻底重写了检测头参数名变了那旧权重自然对不上。解决办法有三个方法一忽略检测头部分只加载Backbone和Neck权重。加载时用strictFalse加载完检查一下缺失的key是否只集中在model.24.cv2*和model.24.cv3*这几个危险区域。方法二只保留Backbone权重。修剪state_dict去掉检测头相关的key再加载。方法三如果改动不兼容干脆从随机初始化开始训。对自定义检测头而言从头训并不一定效果差尤其在目标domain和COCO差异很大时有时候从头训反而更好。from ultralytics import YOLO model YOLO(yolo11n-p2.yaml).load(yolo11n.pt) # load方法内部用strictFalse加载检测头缺失的参数会用随机初始化补上你可能会看到不少warning不用太慌只要确认缺失的key集中在检测头相关区域就行。4.4 显卡与训练速度问题“AMD显卡能不能跑YOLO”这个搜索词热度一直不低。我简单说下结论能跑但看你的显卡代数体验有差别。AMD显卡上跑PyTorch有两种主流路径一种是利用ROCm构建的PyTorch版本在很多Linux发行版上可用第二种是Windows下利用DirectML等后端进行推理和训练。Ultralytics官方本身不做底层加速它依赖的是PyTorch的后端支持。所以问题核心是你能不能装上一个能识别AMD显卡的PyTorch。如果你只想验证代码逻辑、改检测头流程最快的做法是在CPU上跑。CPU训练肯定慢但我们的目的是验证结构不是刷精度。等确认代码没问题后再部署到目标环境。另一个常见问题是训练时OOMOut of Memory。加了P2头之后显存占用会显著增加因为多了一个160x160的高分辨率特征图参与检测计算。降低imgsz、下调batch、或者把P2分支的通道数砍一半都能缓解。我见过有人加的P2分支通道数比原backbone的P2还宽纯属浪费资源没必要。4.5 数据标注与格式问题很多人在数据集环节栽跟头其实和检测头没关系。比如你用CVAT导出了COCO格式但YOLO训练需要的是txt格式或者你用VSCode的标注插件标完了但label编号和yaml里的names对不上。这里给一个通用建议把数据格式转换看成流水线的一部分。标注工具导出、格式转换脚本、yaml检查三步分开每一步都写个小脚本验证——比如检查有没有负坐标、有没有超出图像边界的框、类别编号是否连续从0开始。别小看这些一旦标签里混进一个异常框检测头训练时损失函数很可能直接给你涨起来还特别难排查。如果你拿到的数据是别人给的MOT16等跟踪数据集想转成YOLO检测格式核心就是读取轨迹框的坐标通常是左上角和宽高除以图像宽高归一化然后写成cls cx cy w h的txt。转换完记得画几张图可视化验证一遍别光看数字。4.6 问题排查速查表症状可能原因优先排查顺序shape mismatch 报错检测头个数/通道数/空间尺寸不匹配1. 检查yaml最后一行Detect输入个数 2. 检查通道数 3. 检查上采样倍数Loss变NaN学习率过大 / nc设置错误 / DFL维度错误1. 降低学习率 2. 检查nc 3. 检查reg_max加载权重报错检测头参数名不匹配1. 用strictFalse加载 2. 只加载backbone 3. 随机初始化检测框乱飘strides计算错误1. 检查yaml注释层号 2. 检查stride打印 3. 检查上采样配置显存OOMP2头计算量过大1. 降batch 2. 降imgsz 3. 砍P2通道数mAP无提升甚至下降改动太大/训练不充分/学习率问题1. 检查baseline 2. 延长训练 3. 降低学习率CPU跑得巨慢正常现象1. batch调到最小 2. 只跑冒烟测试 3. 优先验证逻辑最后再分享一个我个人在实战中摸索出来的心得改检测头最怕的其实不是不会写代码而是改完以后你没法确定是哪个改动带来了收益。我强烈建议用Git记录每一次实验的yaml和代码改动并且用一个简单的实验记录表记下每次实验的mAP、训练耗时、显存占用。你以为你在改模型其实你是在做一组对比实验没有记录就没有结论。如果你按这篇攻略走通了一遍后面还可以往三个方向继续延伸一是用同样的套路替换成Segment头做实例分割任务二是研究一下如何把自定义检测头导出到ONNX或者TensorRT部署到FPGA或边缘盒子上去三是尝试把P2头和多尺度训练、数据增强配合起来进一步榨干小目标的性能。检测头这个模块一旦搞懂整个YOLO家族的模型就都看得懂了。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →