从‘华为云杯’赛题实战到模型调优:YOLOv3在生活垃圾检测中的过拟合挑战与应对
1. 赛题背景与挑战解析
去年参加"华为云杯"生活垃圾检测比赛时,我遇到了所有目标检测选手都会头疼的经典问题:模型在训练集上表现完美,测试集却惨不忍睹。这个过拟合问题在生活垃圾这类特殊场景尤为明显——想象一下,你要教AI识别沾着酱油的外卖盒、压扁的易拉罐、缠绕着头发丝的化妆品瓶,这些物体在真实场景中的形态变化比ImageNet里的标准物体复杂得多。
比赛用的数据集包含32类生活垃圾,从可回收物到有害垃圾,每类物体的尺度差异极大。比如废旧电池可能只占图片的5%,而一张废纸板可能占据半个画面。更麻烦的是,实际拍摄的垃圾图片往往存在严重遮挡(比如垃圾桶里层层叠叠的垃圾袋),这对YOLOv3这类单阶段检测器提出了严峻挑战。
初版baseline我直接套用了经典YOLOv3配置,训练集mAP轻松突破0.85,但测试集始终卡在0.6左右。通过分析混淆矩阵发现两个明显问题:一是小物体(如瓶盖、电池)的召回率不足30%;二是特定类别(尤其是透明塑料袋、碎玻璃)的误检率极高。这引出了我们后续要解决的两个核心问题:尺度敏感性和类别不平衡。
2. 有效调优策略实战
2.1 空间金字塔池化(SPP)的妙用
原版YOLOv3只在三个尺度做检测,这对尺寸多变的生活垃圾远远不够。我们在backbone末端加入了SPP模块,就像给网络装了个"多焦距镜头"。具体实现时,采用1×1、5×5、9×9、13×13四种最大池化并行操作,将不同感受野的特征图拼接起来。这个改动让mAP提升了4.2%,尤其是对中大型物体的检测改善明显。
代码实现关键点:
class SPP(nn.Module): def __init__(self): super().__init__() self.maxpool1 = nn.MaxPool2d(5, stride=1, padding=5//2) self.maxpool2 = nn.MaxPool2d(9, stride=1, padding=9//2) self.maxpool3 = nn.MaxPool2d(13, stride=1, padding=13//2) def forward(self, x): o1 = self.maxpool1(x) o2 = self.maxpool2(x) o3 = self.maxpool3(x) return torch.cat([x, o1, o2, o3], dim=1)2.2 Stitcher数据增广的黑科技
传统mosaic增广对生活垃圾效果有限,我们借鉴了商汤提出的Stitcher方法:将4张小图拼合成大图时,刻意保留部分小物体不缩放。比如保持瓶盖、打火机等小物体以原始尺寸粘贴,同时缩小冰箱、家具等背景物体。这种"反常识"的操作让小物体mAP提升了7.3%。
实际操作中有几个细节:
- 设置尺寸阈值(如32×32像素),小于该阈值的物体保持原样
- 对背景物体随机缩放0.3-1.2倍
- 拼接时采用泊松融合避免生硬边缘
2.3 自适应锚框聚类
用常规k-means聚类锚框时,发现对小物体匹配度很差。我们改进了聚类策略:
- 对训练集所有标注框按面积分三组(小、中、大)
- 每组单独做k-means聚类(我们设为3:6:3比例)
- 计算聚类时使用1-IoU作为距离度量
这样得到的锚框分布更符合实际数据特性,特别是改善了小物体的召回率。下表对比了不同聚类策略的效果:
| 聚类方法 | 小物体AP | 中物体AP | 大物体AP |
|---|---|---|---|
| 传统k-means | 0.412 | 0.653 | 0.721 |
| 分组k-means | 0.503 | 0.667 | 0.735 |
| 改进距离度量 | 0.527 | 0.681 | 0.742 |
3. 那些看似有效却踩坑的技巧
3.1 为什么DropBlock不Work?
面对严重过拟合,我首先尝试了DropBlock这种高级正则化方法。理论上它能比普通Dropout更好地保持空间信息,但实际使用时发现两个问题:
- 在Darknet53的深层卷积层应用时,导致梯度爆炸
- block_size参数难以调优:设小了没效果,设大了会破坏关键特征
后来通过特征可视化发现,生活垃圾的判别性特征往往集中在局部区域(如瓶子的螺纹口、电池的正负极),随机丢弃特征块反而破坏了这些关键信息。
3.2 Focal Loss的陷阱
为应对类别不平衡,尝试用Focal Loss替换BCE Loss。但出现三个意外现象:
- γ=2时模型完全无法收敛
- γ=0.5时效果不如普通BCE
- 改变α参数对透明塑料类无效
分析发现主要原因在于:
- 正负样本极端不平衡(约1:1000)
- 困难样本(如透明物体)本身特征模糊
- 与GIoU Loss存在优化方向冲突
3.3 数据增强的边际效应
尝试过以下增强方法但收效甚微:
- CutMix:垃圾图片背景简单,混合后反而引入噪声
- GridMask:规则遮挡破坏了垃圾的连续性特征
- 色彩抖动:生活垃圾本身的颜色就变化无常
最终保留的有效增强只有:
- HSV空间随机调整(±30%)
- 随机旋转(±15度)
- 轻度运动模糊(模拟手机拍摄)
4. 工程实践中的关键细节
4.1 训练策略优化
经过多次实验,最优超参组合如下:
- 优化器:SGD(momentum=0.937)
- 学习率:余弦退火(base_lr=0.01,final_lr=0.0005)
- batch_size:16(显存受限时的最佳平衡点)
- 热身训练:前3个epoch逐步提升学习率
关键发现:过大batch_size会加剧过拟合。当batch从16增加到32时,测试集mAP下降2.1%,因为参数更新变得太"激进"。
4.2 测试阶段的技巧
多尺度测试时要注意:
- 尺度范围:0.8-1.2倍(超出范围会大幅降低小物体检测)
- 翻转增强:只做水平翻转(垂直翻转不符合实际场景)
- NMS阈值:设为0.5(高于常规值,因垃圾密集且重叠多)
4.3 模型部署陷阱
将PyTorch模型转到华为云ModelArts时遇到两个坑:
- ONNX导出时要指定dynamic_axes
torch.onnx.export( model, dummy_input, "model.onnx", input_names=["images"], output_names=["output"], dynamic_axes={ "images": {0: "batch"}, "output": {0: "batch"} } )- 华为云OBS存储的路径区分大小写,提交时要注意目录结构完全匹配
5. 未竟之路与进阶思考
虽然最终成绩止步0.74mAP,但积累了一些有价值的发现:
- 过拟合的本质:可视化显示网络记住了垃圾的纹理而非形状特征
- 小物体检测瓶颈:FPN顶层特征对小物体已经严重失真
- 类别不平衡的深层影响:少数类别的梯度被淹没在反向传播中
后续值得尝试的方向:
- 特征金字塔改进:借鉴BiFPN的跨尺度连接
- 解耦头设计:将分类和回归任务分离
- 动态标签分配:根据预测结果动态调整正负样本
这次比赛让我深刻体会到:垃圾检测可能是比COCO更难的目标检测任务。它不仅考验模型能力,更考验工程师对数据特性的理解。有时候,看似高级的技巧反而不如针对性的小改动有效。
