4. YOLOv5 损失函数:CIoU Loss、二元交叉熵与调优实战

损失函数,说白了就是告诉模型「你错哪儿了」。YOLOv5 的损失函数由三部分组成:边界框回归损失、置信度损失和分类损失。我刚开始接触时也觉得公式多,但拆开看其实就两套逻辑:一套管位置准不准,一套管分类对不对。

4.1 边界框回归:从 IoU 到 CIoU

早期 YOLO 用 MSE 算框的偏差,但 MSE 对尺度太敏感。大框和小框同样偏移 10 像素,损失值一样,但实际影响天差地别。后来大家改用 IoU 系列损失,YOLOv5 选的是 CIoU Loss。

4.1.1 IoU 的演进路线

损失函数 核心思想 解决的问题
IoU Loss 直接优化交并比 尺度不变性
GIoU Loss 引入最小外接矩形惩罚项 两框不重叠时梯度消失
DIoU Loss 加入中心点距离惩罚 GIoU 收敛慢、退化问题
CIoU Loss 中心点距离 + 长宽比一致性 框的形状也参与回归

YOLOv5 用的是 CIoU,公式长这样:

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

其中:
ρ²(b, b_gt) = 预测框与真实框中心点的欧氏距离平方
c = 最小外接矩形的对角线长度
v = (4/π²) * (arctan(w_gt/h_gt) - arctan(w/h))²
α = v / ((1 - IoU) + v)   # 平衡系数

嗯,这里要注意 α 的设计。当 IoU 很小时,α 也小,模型优先对齐位置;当 IoU 变大后,α 增大,开始精细调整长宽比。这个自适应机制很巧妙。

个人经验: 我在做交通标志检测时,发现 CIoU 对细长标志(比如限高牌)的召回率比 DIoU 高了 3 个点。原因就是长宽比惩罚项让模型学会了「形状感」。

4.2 分类与置信度:二元交叉熵损失

YOLOv5 的分类和置信度都用了 二元交叉熵损失(BCE Loss)。为什么不用多分类 Softmax?因为 YOLOv5 支持多标签分类——一个目标可以同时是「车」和「红色车」。Softmax 强制互斥,BCE 则允许每个类别独立判断。

BCE Loss = -[y * log(p) + (1 - y) * log(1 - p)]

其中:
y = 真实标签(0 或 1)
p = 模型预测的概率

你想想看,如果一张图里同时有猫和狗,Softmax 会把概率总和压成 1,猫 0.6 狗 0.4,这显然不合理。BCE 则各自独立,猫 0.9 狗 0.8,互不影响。

4.2.1 置信度损失的特别之处

置信度损失只对 正样本 和 负样本 计算,忽略忽略样本(ignored)。正样本是匹配到 GT 的 anchor,负样本是 IoU 低于阈值的 anchor。中间那些 IoU 在 0.2~0.5 之间的,直接跳过——它们太模糊,硬学反而有害。

避坑指南: 我曾经把忽略阈值设得太低(0.3),结果模型把大量模糊样本当正样本学,导致输出框又飘又散。后来改成 0.5 才稳定下来。这个阈值建议不要低于 0.4。

4.3 损失平衡策略:三部分怎么加权

YOLOv5 的总损失是三个损失的加权和:

Total Loss = λ_box * CIoU Loss + λ_obj * BCE(obj) + λ_cls * BCE(cls)

默认权重是:

损失项 默认权重 作用
λ_box 0.05 边界框回归
λ_obj 1.0 置信度(有无目标)
λ_cls 0.5 类别预测

为什么 λ_box 这么小?因为 CIoU 的值域在 [0, 2] 左右,而 BCE 在 [0, 1]。如果不降权,框的损失会主导梯度,分类几乎学不动。我习惯把 λ_box 调到 0.05~0.1 之间,具体看数据集的框大小分布。

4.3.1 正负样本平衡:Focal Loss 的影子

YOLOv5 没有直接用 Focal Loss,但做了类似的事:对负样本的置信度损失降权。具体来说,负样本的损失乘以一个系数 (1 - p_obj)^γ,γ 默认 0。如果你想开启难例挖掘,把 γ 设成 1.5 或 2.0 就行。

注意: 开启 Focal 风格降权后,训练初期损失会跳得很厉害。我建议先关掉(γ=0)跑 50 个 epoch,等模型稳定了再打开。否则模型一开始就「躺平」,负样本全判 0,正样本也学不动。

4.4 超参数调优:我的实战经验

调损失函数的超参数,说白了就是找平衡。我总结了一套「三步走」策略:

  1. 先固定 λ_box 和 λ_cls,只调 λ_obj。因为置信度是 YOLO 的命脉,框再准,置信度判错也没用。
  2. 观察训练曲线:如果 CIoU 损失降得慢,适当增大 λ_box;如果分类精度上不去,增大 λ_cls。
  3. 用验证集 mAP 做最终裁决:不要只看损失值,损失低不一定 mAP 高。
场景 建议调整 原因
小目标多 λ_box 增大到 0.1 小框的 CIoU 对位置更敏感
类别不平衡 λ_cls 增大到 0.7 让分类损失多贡献梯度
误检率高 λ_obj 增大到 1.5 压制负样本的置信度
漏检率高 降低负样本降权系数 让模型更「大胆」地预测
我的习惯: 每次只改一个参数,记录 mAP 变化。改完跑 20 个 epoch 就能看出趋势,不用等全收敛。我曾经一次性改了三个参数,结果 mAP 掉了 5 个点,查了半天才发现是 λ_box 和 λ_obj 互相抵消了。

4.5 损失函数整体流程

下面这张图把 YOLOv5 的损失计算流程串起来了,我建议你对照着看:

YOLOv5 损失函数计算流程 模型输出 + GT CIoU Loss(框回归) BCE(obj)(置信度) BCE(cls)(分类) λ_box = 0.05 λ_obj = 1.0 λ_cls = 0.5 正负样本筛选 + 忽略样本跳过 Total Loss = 加权求和 说明:CIoU 只对正样本计算;BCE(obj) 对正负样本都算;BCE(cls) 只对正样本算

从流程图能看出来,三个分支各自独立计算,最后加权求和。正负样本筛选在中间层做了一次「过滤」,把模糊样本剔除掉。这个设计让梯度更干净,训练更稳定。

4.6 小结

YOLOv5 的损失函数设计,核心就三句话:

  • 框回归用 CIoU,兼顾位置、距离和形状
  • 分类和置信度用 BCE,支持多标签,独立判断
  • 三部分加权求和,权重靠经验和实验调

调参没有银弹,但记住一个原则:先让模型学会「有没有目标」,再学「在哪儿」,最后学「是什么」。按这个顺序调 λ_obj → λ_box → λ_cls,基本不会跑偏。