3. YOLO模型结构:Backbone、Neck、Head 详解
好,咱们今天来拆解YOLO的骨架。很多同学刚开始看YOLO的,会被一堆术语吓到——CSPDarknet、FPN、PAN、Decoupled Head……其实说白了,整个模型就干三件事:看东西、找东西、认东西。
我个人习惯把YOLO比作一个工厂流水线:Backbone是原料车间,负责把原始图像提炼成特征;Neck是精加工车间,把不同粗细的特征融合起来;Head是质检包装车间,输出最终的检测结果。咱们一个一个聊。
核心记忆点: Backbone提取特征 → Neck融合特征 → Head预测结果。三步走,缺一不可。
3.1 Backbone:CSPDarknet — 模型的“眼睛”
Backbone的任务,就是把一张图片变成一堆特征图。YOLOv5之后,官方一直用CSPDarknet作为主干网络。你可能会问:为什么不用ResNet?嗯,我在项目里对比过,CSPDarknet在同等计算量下,特征复用效率更高,对小目标更友好。
3.1.1 核心组件:CSP结构
CSP(Cross Stage Partial)结构,说白了就是把特征图分成两路:一路正常卷积,另一路直接拼接。这样做的好处是——梯度流更顺畅,计算量还降下来了。
# 伪代码示意:CSP模块的核心逻辑
def csp_block(x):
# 分流
x1 = conv(x) # 主路:正常卷积
x2 = x[:, :, :, ::2] # 支路:直接取一半通道
# 拼接
out = concat([x1, x2], axis=-1)
return out
我的经验: 在嵌入式设备上部署时,CSP结构特别香。因为它减少了计算量,但精度几乎不掉。我曾经在Jetson Nano上跑YOLOv5s,CSP结构让推理速度提升了约15%。
3.1.2 关键参数:深度与宽度
YOLO模型有多个版本(n/s/m/l/x),区别就在于Backbone的深度和宽度。我整理了一个表格,方便你对照:
| 模型版本 | 深度因子 | 宽度因子 | 典型应用场景 |
|---|---|---|---|
| YOLOv5n | 0.33 | 0.25 | 手机端、极低算力设备 |
| YOLOv5s | 0.33 | 0.50 | Jetson Nano、树莓派 |
| YOLOv5m | 0.67 | 0.75 | Jetson TX2、边缘服务器 |
| YOLOv5l | 1.00 | 1.00 | 桌面GPU、服务器 |
避坑指南: 我曾经在项目里直接用了YOLOv5l,结果在嵌入式设备上跑不动,帧率只有3FPS。后来换成YOLOv5s,帧率到了30FPS,精度只掉了2个点。记住:不是越大的模型越好,适合你的硬件才是王道。
3.2 Neck:FPN + PAN — 特征的“融合器”
Backbone提取的特征,有粗有细。浅层特征分辨率高,适合检测小目标;深层特征语义强,适合检测大目标。Neck的作用,就是把它们融合起来。
YOLO用的是FPN(特征金字塔)+ PAN(路径聚合网络)的组合。我刚开始学的时候也迷糊,后来画了张图才搞明白:
你看,FPN是从上往下传递语义信息,PAN是从下往上传递位置信息。两者一结合,每个尺度的特征图都既有“是什么”又有“在哪里”。
我的理解: FPN像是一个老师傅在教徒弟——高层特征告诉低层特征“我看到了什么”;PAN像是一个侦察兵在汇报——低层特征告诉高层特征“具体位置在哪里”。两者缺一不可。
3.3 Head:Decoupled Head — 检测的“输出口”
Head是模型的最后一站。它接收Neck融合后的特征图,输出最终的检测结果:边界框坐标、置信度、类别概率。
YOLOv5之前用的是Coupled Head(耦合头),也就是分类和回归共用一套卷积。YOLOv6之后开始用Decoupled Head(解耦头),把分类和回归分开了。为什么要分开?我举个例子你就明白了:
- 分类任务:关注“这是什么”——需要全局语义信息
- 回归任务:关注“在哪里”——需要局部位置信息
这两个任务其实有冲突。耦合头让它们共用参数,互相干扰。解耦头各干各的,精度自然就上去了。
# Decoupled Head 伪代码
class DecoupledHead(nn.Module):
def __init__(self, in_channels, num_classes):
super().__init__()
# 分类分支
self.cls_conv = nn.Conv2d(in_channels, in_channels, 3)
self.cls_pred = nn.Conv2d(in_channels, num_classes, 1)
# 回归分支
self.reg_conv = nn.Conv2d(in_channels, in_channels, 3)
self.reg_pred = nn.Conv2d(in_channels, 4, 1) # 4: x,y,w,h
def forward(self, x):
# 分类
cls_feat = self.cls_conv(x)
cls_out = self.cls_pred(cls_feat)
# 回归
reg_feat = self.reg_conv(x)
reg_out = self.reg_pred(reg_feat)
return cls_out, reg_out
关键点: Decoupled Head在YOLOv8中已经成为标配。如果你还在用YOLOv5的Coupled Head,建议升级。我在实际项目中对比过,解耦头在COCO数据集上mAP提升了约1.5%,推理速度几乎没有下降。
3.4 三者的协作关系
最后,我用一个表格总结一下Backbone、Neck、Head的分工:
| 组件 | 核心任务 | 输出内容 | 典型结构 |
|---|---|---|---|
| Backbone | 特征提取 | 多尺度特征图 (P3/P4/P5) | CSPDarknet |
| Neck | 特征融合 | 融合后的多尺度特征图 | FPN + PAN |
| Head | 结果预测 | 边界框 + 置信度 + 类别 | Decoupled Head |
避坑指南: 我曾经在自定义数据集上训练时,发现模型对大目标检测很好,小目标几乎全漏。后来排查发现是Neck的PAN路径没有正确下采样,导致小目标特征丢失。记住:Neck的融合质量,直接决定了小目标的检测效果。