在目标检测模型的训练过程中,分类损失函数的选择直接影响着模型对目标类别的识别能力。YOLOv8默认使用二元交叉熵损失(BCE Loss),但近年来Varifocal Loss因其在密集目标场景中的优异表现而备受关注。本文将带您深入理解两种损失函数的差异,并逐步完成YOLOv8中分类损失函数的替换改造。
Varifocal Loss(VFL)由Zhang等人在2020年提出,专门针对目标检测中正负样本不平衡和难易样本不平衡问题设计。与传统的Focal Loss相比,它具有三个显著特点:
数学表达对比:
| 损失函数 | 正样本项 | 负样本项 |
|---|---|---|
| BCE Loss | -log(p) | -log(1-p) |
| Focal Loss | -(1-p)^γ log(p) | -p^γ log(1-p) |
| Varifocal Loss | -q(q log(p)+(1-q)log(1-p)) | -αp^γ log(1-p) |
其中:
YOLOv8的损失计算主要在ultralytics/yolo/utils/loss.py文件中实现。我们需要重点关注两个类:
python复制class VarifocalLoss(nn.Module):
"""原始VarifocalLoss实现,但未被使用"""
class v8DetectionLoss:
"""主损失计算类,包含bbox和cls损失"""
默认的分类损失实现如下:
python复制# 原始BCE实现
loss[1] = self.bce(pred_scores, target_scores.to(dtype)).sum() / target_scores_sum
而Varifocal Loss的实现虽然存在,但被注释掉了:
python复制# 被注释的VFL实现
# loss[1] = self.varifocal_loss(pred_scores, target_scores, target_labels) / target_scores_sum
注意:直接取消注释会导致维度不匹配错误,因为输入数据的格式要求不同。
YOLOv8默认使用的target_labels是类别索引格式,而Varifocal Loss需要one-hot编码:
python复制# 原始target_labels形状:[batch_size, num_anchors]
# 例如:tensor([[11, 11, ...], [0, 0, ...]])
# 转换为one-hot编码
target_labels = target_labels.unsqueeze(-1).expand(-1, -1, self.nc)
one_hot = torch.zeros(target_labels.size(), device=self.device)
one_hot.scatter_(-1, target_labels, 1)
target_labels = one_hot # 形状变为[batch_size, num_anchors, num_classes]
VarifocalLoss要求pred_scores和target_scores的维度匹配:
python复制# 原始pred_scores形状:[batch_size, num_anchors, num_classes]
# target_scores需要相应调整
if target_scores.dim() == 2:
target_scores = target_scores.unsqueeze(-1).expand_as(pred_scores)
将上述修改整合到v8DetectionLoss中:
python复制class v8DetectionLoss:
def __init__(self, model):
self.vfl = VarifocalLoss()
# ... 其他初始化代码
def __call__(self, preds, batch):
# ... 前向计算代码
# 转换标签格式
target_labels = target_labels.unsqueeze(-1).expand(-1, -1, self.nc)
one_hot = torch.zeros(target_labels.size(), device=self.device)
one_hot.scatter_(-1, target_labels, 1)
target_labels = one_hot
# 调整target_scores维度
target_scores = target_scores.unsqueeze(-1) if target_scores.dim() == 2 else target_scores
# 使用Varifocal Loss
loss[1] = self.vfl(pred_scores, target_scores, target_labels) / target_scores_sum
Varifocal Loss有两个关键参数需要调整:
推荐调参策略:
对比BCE Loss和Varifocal Loss的训练动态:
| 指标 | BCE Loss | Varifocal Loss |
|---|---|---|
| 初始损失值 | 较高 | 更高 |
| 收敛速度 | 较快 | 稍慢但更稳定 |
| 最终mAP | 取决于数据集 | 密集场景提升明显 |
改造完成后,建议在验证集上对比两种损失函数的表现。在某些场景下,可能需要调整正负样本比例或数据增强策略以获得最佳效果。