1. 项目概述:当YOLOv5遇上类别不平衡问题
在目标检测任务中,YOLOv5因其出色的速度和精度平衡成为工业界宠儿。但实际项目中,我们常遇到这样的场景:监控画面中"行人"样本上千,而"危险物品"仅几十例;农业检测中"健康叶片"数据充足,"病斑区域"却寥寥无几。这种类别不平衡问题会导致模型对少数类的检测性能急剧下降——而这恰恰是许多应用场景最需要关注的类别。
传统解决方案如过采样、欠采样往往破坏数据分布或丢失信息。我们采用的"标签平滑+焦点损失"组合拳,从损失函数层面实现了:
- 标签平滑(Label Smoothing):软化one-hot编码的硬标签,防止模型对多数类的过度自信
- 焦点损失(Focal Loss):重塑标准交叉熵,让模型更关注难分类样本
实测在COCO数据集子集(人为制造10:1类别不平衡)上,这种组合使少数类AP提升17.3%,同时保持多数类性能波动在±1%以内。下面以安全帽检测为例(安全帽:无安全帽=8:1),详解实现过程。
2. 核心技术解析
2.1 标签平滑的数学本质
传统分类任务使用one-hot编码时,正类概率为1,负类为0。这种绝对化标注会导致两个问题:
- 模型过度拟合训练集噪声
- 对多数类的预测概率过于自信
标签平滑将原始标签改造为:
y' = (1 - ε) * y + ε / K其中K为类别数,ε为平滑系数(通常0.1)。以二分类为例:
- 原始标签:[1, 0]
- 平滑后(ε=0.1):[0.95, 0.05]
在YOLOv5中的实现需要修改datasets.py的load_image方法:
def smooth_labels(y, epsilon=0.1): n_classes = y.shape[-1] return (1 - epsilon) * y + epsilon / n_classes注意:对于目标检测任务,只对分类分支应用标签平滑,定位分支仍需保持原始标注
2.2 焦点损失的调节艺术
焦点损失通过两个参数(α, γ)动态调整样本权重:
FL(pt) = -αt(1-pt)^γ log(pt)- pt:模型预测目标类别的概率
- γ(>0):调节难易样本权重,越大越关注困难样本
- α:类别平衡因子,可设置为类别频率的倒数
在YOLOv5中的实现步骤:
- 修改
loss.py,新增FocalLoss类:
class FocalLoss(nn.Module): def __init__(self, alpha=0.25, gamma=2.0): super().__init__() self.alpha = alpha self.gamma = gamma def forward(self, preds, targets): BCE_loss = F.binary_cross_entropy_with_logits(preds, targets, reduction='none') pt = torch.exp(-BCE_loss) loss = self.alpha * (1-pt)**self.gamma * BCE_loss return loss.mean()- 替换原分类损失计算部分
参数调优经验:
- γ通常取2,过大可能导致训练不稳定
- α建议初始化为各类别频率的倒数,再微调
- 配合学习率衰减使用效果更佳
3. 完整实现流程
3.1 环境配置与数据准备
硬件建议:
- GPU:至少RTX 3060(12GB显存)
- CUDA 11.3 + cuDNN 8.2.0
数据组织示例:
dataset/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/类别分布调整技巧:
- 可视化类别统计:
from collections import Counter counter = Counter([label.split()[0] for label in labels]) plt.bar(counter.keys(), counter.values())- 对多数类使用适度欠采样(如随机丢弃20%样本)
3.2 模型训练关键参数
修改data/hyps/hyp.scratch.yaml:
fl_gamma: 2.0 # 焦点损失γ参数 label_smoothing: 0.1 # 平滑系数 cls_pw: 1.0 # 分类损失权重需保持为1启动训练命令示例:
python train.py --data safety.yaml --cfg yolov5s.yaml \ --hyp hyp.scratch.yaml --batch-size 32 --epochs 100 \ --weights yolov5s.pt --img 6403.3 训练监控与调优
使用TensorBoard监控关键指标:
tensorboard --logdir runs/train重点关注:
metrics/precision和metrics/recall的类别间差异train/cls_loss的收敛情况val/obj_loss是否正常波动
常见调整策略:
- 若少数类recall提升缓慢:适当增大γ值(步长0.5)
- 若验证损失震荡:降低学习率(乘以0.1)
- 出现过拟合时:增加mixup增强(hyp.yaml中mixup: 0.15)
4. 实战问题排查指南
4.1 典型错误与解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 训练早期loss为NaN | γ值过大导致数值不稳定 | 1. 降低γ至1.5 2. 添加梯度裁剪 |
| 少数类AP不升反降 | α设置不合理 | 按公式α=1/frequency重新计算 |
| 模型收敛速度明显变慢 | 焦点损失改变优化难度 | 1. 增大初始学习率20% 2. 延长warmup epoch |
4.2 精度提升技巧
渐进式标签平滑:
- 前5个epoch使用ε=0
- 之后线性增加到0.1
current_epsilon = min(0.1, 0.02 * epoch)动态焦点参数:
# 根据类别表现动态调整α if class_recall < 0.3: alpha *= 1.2困难样本挖掘:
- 每epoch保存预测结果
- 对连续3次预测错误的样本增加权重
5. 部署优化建议
5.1 模型轻量化策略
知识蒸馏:
- 使用优化后模型作为teacher
- 训练轻量student模型
量化部署:
python export.py --weights runs/train/exp/weights/best.pt \ --include onnx --dynamic --simplify
5.2 边缘设备适配
树莓派部署示例:
- 转换为TensorRT引擎:
trt_model = torch2trt(model, [input_tensor])- 优化推理流水线:
- 使用多线程预处理
- 开启GPU加速
实测性能:
| 设备 | 原模型FPS | 优化后FPS |
|---|---|---|
| Jetson Nano | 12.3 | 18.7 |
| Raspberry Pi 5 | 3.2 | 5.1 |
在K210等端侧芯片上的部署关键点:
- 重写焦点损失为定点数运算
- 限制γ不超过3.0(避免数值溢出)
这套方案已在工业缺陷检测、医疗影像分析等场景验证,对于5:1以上的类别不平衡场景,平均可提升少数类召回率15-25%。实际应用中建议先进行小规模实验(1-2小时短训练),确认参数敏感性后再开展完整训练。