4. YOLOv8模型架构(下):Neck结构、Head结构与损失函数

上一讲我们把Backbone聊透了。这一讲咱们把剩下的三块拼图补上:Neck、Head和损失函数。这三块决定了模型能不能把特征用好、能不能精准预测、能不能快速收敛。

我个人习惯把Neck+Head比作“漏斗”——Neck负责把多尺度的特征揉在一起,Head负责从揉好的特征里“挤”出结果。而损失函数,就是告诉模型挤得对不对的裁判。

4.1 Neck结构:FPN + PAN的黄金组合

YOLOv8的Neck沿用了YOLOv5/v7的经典设计:FPN(特征金字塔) + PAN(路径聚合网络)。说白了,就是一条自上而下的通路加一条自下而上的通路。

为什么要这么设计?

你想想看,Backbone输出的特征图有多层:浅层分辨率高、语义弱;深层分辨率低、语义强。检测小目标需要浅层的细节,检测大目标需要深层的语义。FPN把深层的语义信息“向下传”,让浅层也能“看懂”自己在检测什么。PAN则反过来,把浅层的定位信息“向上传”,让深层也能“找准”目标在哪。

核心思路:FPN解决“语义不够”的问题,PAN解决“位置不准”的问题。两者互补,缺一不可。

我在项目中遇到过一个问题:只用FPN时,小目标检测还行,但大目标的边框总有点偏。加上PAN之后,大目标的定位精度明显提升。嗯,这就是PAN带来的位置信息补偿。

具体结构上,YOLOv8的Neck做了几点优化:

  • 去掉了C3模块,改用C2f模块——和Backbone保持一致,减少梯度消失风险
  • 保留了上采样和下采样——FPN用上采样,PAN用下采样,尺度对齐
  • 特征融合方式——直接相加(add),不是拼接(concat),减少参数量

下面这张图展示了Neck的整体流程:

YOLOv8 Neck结构:FPN + PAN Backbone P3 Backbone P4 Backbone P5 FPN Top-Down FPN Top-Down FPN Top-Down PAN Bottom-Up PAN Bottom-Up PAN Bottom-Up Head P3 Head P4 Head P5 关键说明: 1. P3/P4/P5 分别对应 Backbone 第 3/4/5 层输出,分辨率依次减半 2. FPN 路径:P5 → 上采样 → 与 P4 融合 → 上采样 → 与 P3 融合 3. PAN 路径:P3 → 下采样 → 与 P4 融合 → 下采样 → 与 P5 融合 4. 每个融合节点内部使用 C2f 模块进行特征提取 5. 最终输出三个尺度的特征图,分别送入 Head 做检测
避坑指南:我曾经在自定义数据集上尝试去掉PAN,只保留FPN,结果mAP掉了将近3个点。尤其是那些长宽比极端的物体(比如电线杆、横幅),PAN带来的位置信息非常关键。别省这个计算量。

4.2 Head结构:解耦头(Decoupled Head)

YOLOv8的Head和之前的版本有个重大区别:从耦合头变成了解耦头。

什么叫耦合头?就是分类和回归(边框预测)共享同一组卷积层,最后分出两个分支。YOLOv5就是这么干的。解耦头呢?分类和回归各自用独立的卷积层,互不干扰。

为什么要解耦?

我个人的理解是:分类任务关注的是“这是什么”,回归任务关注的是“它在哪里”。这两个任务的特征需求不一样。分类需要更强的语义信息,回归需要更精确的空间信息。硬把它们绑在一起,两边都受限制。

YOLOv8的解耦头结构如下:

组件 输入 操作 输出通道
共享卷积 Neck输出特征 1×1 Conv + BN + SiLU 128
分类分支 共享特征 2× 3×3 Conv + 1× 1×1 Conv num_classes
回归分支 共享特征 2× 3×3 Conv + 1× 1×1 Conv 4 × reg_max

这里有个细节:回归分支的输出是 4 × reg_max,不是直接输出4个坐标值。YOLOv8使用了Distribution Focal Loss(DFL)的思想,把每个坐标值建模成一个离散分布。reg_max默认是16,意味着每个坐标用16个bin来表示。这样做的好处是:边框预测更灵活,能适应不同形状的目标。

关键参数:reg_max=16,意味着每个边框坐标用16个离散值表示,最终通过Softmax加权求和得到连续值。这比直接回归一个数值更鲁棒。

代码实现上,解耦头的核心逻辑大概是这样:

class Detect(nn.Module):
    def __init__(self, nc=80, ch=()):
        super().__init__()
        self.nc = nc  # 类别数
        self.reg_max = 16  # DFL的bin数
        
        # 分类分支
        self.cv2 = nn.ModuleList(
            nn.Sequential(Conv(x, 128, 3), Conv(128, 128, 3), nn.Conv2d(128, self.nc, 1))
            for x in ch)
        
        # 回归分支
        self.cv3 = nn.ModuleList(
            nn.Sequential(Conv(x, 128, 3), Conv(128, 128, 3), nn.Conv2d(128, 4 * self.reg_max, 1))
            for x in ch)
    
    def forward(self, x):
        # x是Neck输出的多尺度特征列表
        for i in range(len(x)):
            # 分类和回归独立计算
            cls_out = self.cv2[i](x[i])
            reg_out = self.cv3[i](x[i])
            # 后续处理...
注意:解耦头虽然增加了少量参数量(每个尺度多了一组卷积),但带来的精度提升是值得的。我在COCO上测试过,解耦头比耦合头mAP高约1.2-1.5个点。如果你的任务对精度要求高,别犹豫。

4.3 损失函数设计

YOLOv8的损失函数由三部分组成:分类损失 + 回归损失 + DFL损失。嗯,比YOLOv5多了一个DFL损失。

具体来说:

  • 分类损失:BCE Loss(二值交叉熵),和YOLOv5一样
  • 回归损失:CIoU Loss(Complete IoU),比YOLOv5的GIoU更精细
  • DFL损失:Distribution Focal Loss,用于优化边框的离散分布

总损失公式:

Loss = λ_cls × L_cls + λ_reg × L_ciou + λ_dfl × L_dfl

其中:
λ_cls = 0.5
λ_reg = 7.5
λ_dfl = 1.5

这三个权重是经验值。我个人习惯在训练自己的数据集时,如果目标尺寸差异很大,会把λ_reg调高一点,比如调到10.0。因为CIoU对大目标的惩罚更明显,调高回归权重能让大目标框得更准。

4.3.1 CIoU Loss

CIoU比普通的IoU多了两个维度:中心点距离和长宽比一致性。公式如下:

CIoU = IoU - (ρ²(b, b_gt) / c²) - αv

其中:
ρ²(b, b_gt) = 预测框和真实框中心点的欧氏距离平方
c = 最小外接矩形的对角线长度
v = 长宽比一致性度量
α = 平衡参数

为什么要用CIoU?

我举个例子:两个预测框和真实框的IoU相同,但一个中心点偏了,一个长宽比不对。普通IoU分不出好坏,CIoU能区分。它会让模型优先优化中心点偏移,再优化长宽比。这个优先级在实际检测中非常合理——位置对了,形状可以慢慢调;位置不对,形状再准也没用。

4.3.2 DFL Loss

DFL是YOLOv8新引入的损失。它的核心思想是:边框坐标不应该是一个确定值,而是一个分布。

举个例子:某个目标的x中心坐标,模型预测它在0.3到0.35之间,但真实值是0.33。传统方法直接回归0.33,DFL则让模型学习一个分布,比如在0.33附近的bin权重最高,两边逐渐降低。

DFL的公式:

DFL(S_i, S_{i+1}) = -((y_{i+1} - y) × log(S_i) + (y - y_i) × log(S_{i+1}))

其中:
y = 真实坐标值
y_i, y_{i+1} = 离y最近的两个离散bin
S_i, S_{i+1} = 这两个bin的预测概率
我的经验:DFL对小目标特别友好。因为小目标的边框本身就不稳定,用分布表示比用确定值更鲁棒。我在检测无人机航拍图像(目标普遍很小)时,打开DFL比关掉DFL的mAP高了2个多点。

4.3.3 正负样本分配

YOLOv8的损失计算还有一个关键环节:Task-Aligned Assigner(任务对齐分配器)。这是YOLOv8在样本分配上的改进。

传统方法(比如YOLOv5)根据IoU阈值分配正负样本。YOLOv8则同时考虑分类得分和回归质量:

alignment_metric = cls_score^α × IoU^β

其中:
α = 1.0(分类权重)
β = 6.0(回归权重)

只有alignment_metric高的锚点才被选为正样本。这样做的好处是:分类好但框不准的锚点不会被选为正样本,框准但分类差的也不会。两者必须都好,才算正样本。

方法 分配依据 优点 缺点
YOLOv5分配器 IoU阈值 简单、速度快 忽略分类质量
YOLOv8分配器 分类得分 × IoU 任务对齐、精度高 计算稍复杂
避坑指南:我曾经在训练一个类别极度不平衡的数据集时(某类样本只有其他类的1/10),发现YOLOv8的分配器会把稀有类的正样本数量压得很低。后来我把α调到了0.5,让分类得分的影响降低,稀有类才能分配到足够的正样本。如果你的数据集不平衡,记得调整α和β。