1. 为什么YOLOv8需要Focal Loss在目标检测任务中我们经常会遇到类别不平衡的问题。比如在垃圾分类场景中其他垃圾这类别的样本数量可能是有害垃圾的几十倍。传统的交叉熵损失函数对所有样本一视同仁导致模型对少数类别的学习不足。我曾在某园区安防项目中发现模型对垃圾桶的检测准确率比行人低23%这就是典型的难样本问题。Focal Loss的聪明之处在于它给每个样本加了动态权重容易分类的样本预测概率高权重自动降低难分类样本预测概率低权重保持较高。这个机制通过两个关键参数实现γgamma控制难易样本权重的差异程度经验值通常设为2αalpha调节类别间权重可以手动指定每个类别的权重系数实测在COCO数据集上使用Focal Loss后模型对小目标的检测精度AP_S能提升1.5-2个百分点。特别是在垃圾检测这类场景对模糊、遮挡目标的召回率提升更为明显。2. 手把手改造YOLOv8损失函数2.1 定位关键代码文件YOLOv8的损失函数实现集中在ultralytics/yolo/utils/loss.py文件中。建议先用官方预训练模型跑通原始流程再开始修改。我遇到过新手直接改代码导致训练报错的情况建议先做好以下准备备份原始loss.py文件创建新的Git分支准备可快速验证的小规模数据集2.2 实现自定义Focal Loss类原始文章提供的代码基本可用但需要做三点改进才能稳定训练解决GPU张量类型不匹配问题优化alpha参数的设备转移逻辑添加调试信息输出开关改进后的完整实现如下class FocalLoss(nn.Module): def __init__(self, alphaNone, gamma2, num_classes80, size_averageTrue, debugFalse): super().__init__() self.size_average size_average self.debug debug if alpha is None: self.register_buffer(alpha, torch.ones(num_classes)) elif isinstance(alpha, list): assert len(alpha) num_classes self.register_buffer(alpha, torch.tensor(alpha)) else: assert alpha 1 alpha_tensor torch.zeros(num_classes) alpha_tensor[0] alpha alpha_tensor[1:] (1 - alpha) self.register_buffer(alpha, alpha_tensor) self.gamma gamma if debug: print(fFocal Loss Config: alpha{self.alpha.cpu().numpy()}, gamma{gamma}) def forward(self, preds, labels): preds preds.view(-1, preds.size(-1)) labels labels.view(-1).to(torch.long) # 确保标签为int64 preds_logsoft F.log_softmax(preds, dim1) preds_softmax torch.exp(preds_logsoft) # 获取每个样本对应类别的概率 preds_softmax preds_softmax.gather(1, labels.unsqueeze(1)) preds_logsoft preds_logsoft.gather(1, labels.unsqueeze(1)) alpha self.alpha.gather(0, labels) loss -torch.mul(torch.pow(1 - preds_softmax, self.gamma), preds_logsoft) loss torch.mul(alpha, loss.t()) return loss.mean() if self.size_average else loss.sum()关键改进点说明使用register_buffer让alpha参数自动跟随模型设备移动提前统一标签数据类型避免运行时报错添加debug模式方便调参观察2.3 集成到DetectionLoss类在YOLOv8的DetectionLoss类中分类损失默认使用BCEWithLogitsLoss。我们需要修改两处class DetectionLoss: def __init__(self, ...): # 原始代码 # self.bce nn.BCEWithLogitsLoss(reductionnone) # 修改为 self.focal FocalLoss(gamma2, alpha0.25, num_classesself.nc) def __call__(self, preds, targets): # 找到原始分类损失计算位置 # loss_cls self.bce(pred_scores, target_scores) # 替换为 loss_cls self.focal(pred_scores, target_scores.argmax(1))注意这里target_scores需要从one-hot格式转换为类别索引这是与原始BCE损失的主要区别。3. 训练调优实战技巧3.1 参数组合实验经过20次实验验证推荐以下参数组合作为起点场景类型gammaalpha学习率系数严重类别不平衡2.0[0.8,0.2,...]1.0x中度不平衡1.50.251.2x小目标检测3.0None0.8x特别提醒当gamma2时建议降低学习率否则容易出现训练震荡。我在某次实验中设置gamma3但忘记调整学习率导致前5个epoch的mAP波动幅度达到15%。3.2 报错解决方案汇总类型不匹配错误Expected object of scalar type Long but got scalar type Float解决方法确保在FocalLoss的forward中执行labels labels.to(torch.long)CUDA设备不一致Expected all tensors to be on the same device解决方法使用register_buffer注册alpha参数维度不匹配shape mismatch: value [32,1] cannot be broadcast to indexing result [32]解决方法检查gather操作的输入维度确保使用unsqueeze处理4. 性能对比与效果验证在自定义的垃圾检测数据集上含12000张图像8个类别使用相同训练配置进行对比指标原始CE LossFocal Loss提升幅度mAP0.568.273.55.3垃圾桶检测AP62.171.89.7模糊目标召回率58.367.49.1训练收敛epoch4538-7从验证集可视化结果看Focal Loss对以下场景改善明显被部分遮挡的垃圾桶反光金属表面的垃圾小尺寸垃圾堆积区域训练曲线显示Focal Loss在epoch15左右就能达到原始损失函数epoch25的水平说明其确实加速了难样本的学习。不过要注意前3个epoch的loss值会比CE Loss高这是正常现象。