news 2026/9/30 9:08:20

改进YOLOv8的生活垃圾分类检测:注意力机制与BiFPN实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
改进YOLOv8的生活垃圾分类检测:注意力机制与BiFPN实践

1. 为什么要折腾一个"改进版YOLOv8"去识别生活垃圾

1.1 垃圾分类图像识别到底难在哪

先聊点实际的。我今年做生活垃圾图像识别这个课题时,第一反应也是"直接拿YOLOv8官方权重跑一下不就行了"。说实话,用COCO预训练模型在公开垃圾分类数据集上直接做迁移学习,效果并不算差,mAP@0.5能到85%上下,看起来好像足够交差了。

但问题在于:**真实场景里的垃圾图片,和公开数据集里的干净样本完全是两回事。**我拿手机去小区垃圾桶旁边拍了一圈,回来用模型一测,发现漏检和误检比预想严重得多。啤酒瓶盖、烟头、纸屑这类小目标,模型经常直接无视;一个被压扁的矿泉水瓶和塑料袋缠在一起时,模型会把它们框成同一个目标;阴天或者傍晚光线不足的时候,置信度普遍掉到0.4以下,压根没法用。

翻来覆去琢磨,生活垃圾识别这个任务有四个天然难点:

  • 目标尺度跨度大:一个废弃的冰箱和一个烟头在画面里可能是同一个尺度下的两个极端。检测头对中等尺度目标敏感,对极端小目标不敏感。
  • 类间形似严重:玻璃瓶和透明塑料瓶在视觉上高度相似,纸箱和报纸容易被归为一类,金属罐表面反光后会呈现出完全不同的颜色特征。
  • 遮挡和堆叠严重:垃圾桶里的垃圾是互相缠绕、堆叠、挤压的,目标边界非常模糊,目标框之间的IoU经常超过0.5。
  • 环境干扰强:光照变化、镜面反射、雨水、尘土附着都会改变目标的表观特征,尤其是玻璃和金属这两类。

这些难点放在一起,意味着单纯调参解决不了问题。必须从数据、网络结构、损失函数三个层面同时下手,这才有了"基于YOLOv8改进算法"这个课题的立项思路。

1.2 标准YOLOv8在生活垃圾任务上有哪些明显短板

先声明,我没有任何贬低YOLOv8的意思。它是我目前用过综合性价比最高的检测框架,训练快、部署方便、社区生态好,Ultralytics团队把工程化做到了极致。但正因为它是面向通用目标检测设计的,在生活垃圾这个垂直场景下会暴露几个结构层面的问题。

第一是小目标检测能力不足。YOLOv8默认从P3层(80x80特征图)开始做检测,输入640x640时,每个网格对应原图8x8像素区域。一个只有20x20像素的瓶盖,映射到特征图上只剩下2到3个像素点的信息,分类分支基本只能靠猜。虽然YOLOv8的anchor-free设计对目标尺寸的适应能力强于YOLOv5,但缺少高分辨率特征层仍然是个硬伤。

第二是特征融合方式偏"平均主义"。PAN-FPN的结构在自顶向下的路径里做的是简单相加或者拼接,所有层次的特征被一视同仁地融合。但垃圾图片中不同尺度的目标对语义信息和高频细节的需求是完全不同的,小目标更需要底层的高分辨率细节,大目标更需要高层的语义抽象。不加区分的融合方式会稀释掉关键信息。

第三是损失函数对低质量样本不够鲁棒。训练集中必然存在大量边界框标注不够精确、目标严重遮挡的样本。YOLOv8默认的CIoU损失对这些低质量样本一视同仁地施加梯度压力,导致模型在"该关注高置信度目标"的时候,反而被大量含噪样本带偏。

这三个短板正好对应我在第4节会详细展开的改进方向:注意力机制、加权特征融合、损失函数替换。

1.3 这个项目想达成的目标和约束条件

做课题和做工程有一个重要区别:课题有明确的评价指标和约束条件。我给自己定的目标是:

  • 检测精度:在自建的6类生活垃圾测试集上,mAP@0.5从基线的85%左右提升到92%以上。
  • 小目标召回率:针对面积小于32x32像素的目标,Recall不低于70%。
  • 参数量和速度:改进后模型参数量增量控制在20%以内,推理速度不低于基线模型的90%,方便后续在RK3588这类边缘设备上部署。
  • 算法可解释性:每处改进要有明确的依据和消融实验支撑,不能是"加了一堆模块然后碰巧涨点"。

这个约束直接决定了我后续所有方案选择的走向。比如很多人会直接换backbone换成RepVit或者MobileNet,我评估后发现这样的改动虽然能提升速度,但会明显拉低精度上限,而且和"基于YOLOv8改进"的课题定位有冲突。所以在backbone阶段我只做轻量级的注意力嵌入,把更重的结构改动放在neck和损失函数上。

2. 数据集是地基:生活垃圾数据从哪来、怎么标、怎么扩

2.1 公开数据集的选择与取舍

深度学习项目里,数据的重要性永远高于模型结构。我见过太多人花两周调模型结构,结果因为数据标注质量差导致涨点全部失效。垃圾分类领域有几个常用的公开数据集,我逐一评估过:

TrashNet:斯坦福团队整理的经典数据集,2527张图片,6个类别(玻璃、纸张、纸板、金属、塑料、其他)。优点是小而干净,类别划分清晰;缺点是样本量太少、拍摄场景单一,基本都是纯色背景下的单目标特写,迁移到真实垃圾桶场景后泛化能力很差。

华为云垃圾分类数据集:包含约40个常见生活垃圾类别,图片数量在一万张以上,覆盖了饮料瓶、易拉罐、电池、果皮、剩饭等常见物品。类别粒度很细,但部分类别样本不均衡严重,比如"一次性餐盒"的图片数量是"指甲油瓶"的十几倍。

Kaggle上的各类生活垃圾数据集:质量参差不齐,有些是直接从搜索引擎抓的图,存在大量重复、错误标注和无关背景。

我的最终方案是混合策略:以华为云数据集中筛选出来的8个高频类别为基础,合并TrashNet的对应类别,再补充自己拍摄的2000多张实地垃圾图片,最后统一整理成6个类别。为什么是6类而不是40类?原因很简单:类别越多,类间相似度越高,检测难度越大,而实际垃圾桶清运场景需要的只是"可回收、厨余、有害、其他"四个大类下的子类别识别。我最终选择了塑料瓶、纸类、玻璃、金属罐、厨余垃圾、其他垃圾这6个类别,兼顾了区分度和实用性。

2.2 类别体系的确定:不是越细越好

这里想多说一句类别体系设计的经验。**垃圾分类识别和通用目标检测有一个很大的区别:垃圾的外观形态极度不稳定。**一个纸箱,可以是完整的方盒子,也可以是被踩扁的不规则薄片;一个塑料瓶,可以是透明的矿泉水瓶,也可以是绿色的雪碧瓶。如果类别划分太细,比如把"塑料瓶"分成"透明塑料瓶"和"绿色塑料瓶",模型学到的特征会过拟合到颜色上,遇到新的瓶子颜色就失效了。

所以我在设计类别时遵循三个原则:

  1. 按回收处理工艺分,而不是按外观分。因为下游是分类回收,不是单纯识别物体。
  2. 每个类别内部的外观方差足够大,迫使模型学习到结构特征而不是表面特征。比如"纸类"就包含报纸、纸箱、纸巾、纸杯,模型必须学会"只要是纸制品就归到这一类"。
  3. 类间重叠度尽量低。我专门统计过,透明玻璃瓶和透明塑料瓶是最容易混淆的两类,最终靠增加拍摄角度和光照变化的样本,才把这两类的区分度拉上去。

这个类别设计过程花了我将近两天时间,很多人在做课题时急于开始标注,结果类别体系下面全是灰色地带。我建议动手标注前,先把每个类别收集50张代表图片过目一遍,判断自己能不能一眼分清楚,如果自己都分不清,模型更学不会。

2.3 标注质量控制的三个血泪教训

标注这个环节,看起来是最没有技术含量的,实际上对结果的影响比改十层网络结构都大。我用的标注工具是X-AnyLabeling和LabelImg,前者支持自动标注辅助,后者是纯手标。讲讲我踩过的坑:

**第一个坑:标注框画太大。**一开始我对边界不敏感,框子总会比目标外圈大上两三个像素。别小看这几个像素,在计算IoU时,目标中心点的回归梯度会全部指向错误方向,导致最终模型框的位置偏大、置信度偏低。后来我严格要求标注框紧贴目标边缘,宁可剪掉一点边缘也不能多出背景。

**第二个坑:遮挡目标的处理不一致。**两个瓶子叠在一起时,是只标露出来的部分,还是标完整目标?我一开始是凭感觉来,后来模型在遮挡场景下反复出错,排查了很久才发现标注标准前后不一致。后来统一规则:只要目标可见面积超过30%,就标注完整的包围盒,让模型自己去学遮挡推理。

**第三个坑:类别标签的错标漏标。**5000多张图,人工标注到后面眼睛都花了,玻璃和透明塑料这种高相似类别经常标错。我的补救措施是做了两轮交叉复核:第二轮标注者只看类别不看位置,专门检查标签是否合理。虽然多花了半天时间,但训练出来的模型收敛速度和最终精度都明显改善。

2.4 数据增强策略的取舍

YOLOv8的训练管线里自带了一整套增强策略,包括Mosaic、MixUp、HSV随机扰动、随机翻转、随机缩放等。我一开始是全部默认开启,结果训练出来的模型在真实场景泛化得并不好。分析之后发现问题出在增强过度上。

Mosaic增强是把4张图拼成1张,这个策略对小目标训练非常有效,但在垃圾场景里它带来了一个副作用:拼接线两边的垃圾类别完全不同,模型被迫学会"同一张图里可以出现任意类别的组合",反而弱化了类别上下文信息的利用。另外我用的训练数据本身就有大量密集堆叠场景,Mosaic进一步压缩了目标尺寸,P3层只有80x80,大部分目标在Mosaic后的图片里不到16x16像素,模型根本学不到有效特征。

我的调整方案是:

  • 关闭MixUp,它对遮挡场景的负面影响大于正面收益。
  • Mosaic概率从默认的1.0降到0.5,并且在后50个epoch关闭。
  • HSV扰动强度调高,因为用户上传的垃圾图片和垃圾桶里的垃圾,色温差异巨大,模型必须对颜色变化不敏感。
  • 增加随机90度旋转,因为垃圾在搬运过程中朝向完全随机。

这些调整做完后,训练集和验证集的loss分布变得健康了,验证集精度提升了2到3个百分点。

3. 改进前的必修课:YOLOv8网络结构逐层拆解

3.1 Backbone里的C2f和SPPF到底在干什么

动手改模型之前,必须先把YOLOv8的网络结构啃明白。很多人直接去GitHub上复制别人的改进代码,模块加上去以后只看到mAP涨了,但说不清楚为什么涨,这是做课题的大忌。评审或者答辩时,一句"这里为什么用Coordinate Attention而不是SE"都能把人问住。

YOLOv8的backbone是基于CSPDarknet结构演进来的。核心模块是C2f,它解决的是梯度流和信息冗余的问题。C2f结构里,输入会先经过一个1x1卷积分成两个分支,其中一个分支进入N个Bottleneck串联,另一个分支直接连到末端,最后把多个层级的输出在通道维度上拼接起来。这种跨阶段连接的好处是:深层网络在传递梯度时不会出现信息消失,同时通过split操作控制参数量。对比之前的C3模块,C2f把每一层Bottleneck的输出都保留下来做concat,相当于给网络增加了更丰富的梯度回传路径。

SPPF(Spatial Pyramid Pooling - Fast)负责扩大感受野。它用三个串联的5x5最大池化来模拟不同尺度的金字塔,把13x13、9x9、5x5级别的上下文信息都聚合到一起。在垃圾检测里,SPPF的主要作用是让模型感知到目标的上下文环境。比如一个塑料瓶单独出现在水泥地上和出现在一堆杂物中间,需要的感受野是不一样的。

3.2 Neck的PAN-FPN多尺度融合逻辑

Neck部分是目标检测中承上启下的关键结构。YOLOv8使用的是PAN-FPN结构,在原始FPN自顶向下的路径上,增加了一条自底向上的路径增强通道。

FPN做的事情是:高层特征图分辨率低、语义信息强;低层特征图分辨率高、位置信息细。自顶向下路径把高层语义传递到低层,提升低层特征的分类能力。PAN加上的自底向上路径,则是把低层的位置细节再传回高层,增强高层特征的定位能力。两条路径通过横向连接反复融合,让每个尺度的特征图都同时具备强语义和细定位能力。

这个设计本身没什么问题,但它对所有层级的特征图一视同仁。在叠加的时候,P3、P4、P5的贡献权重是固定的,不区分这个尺度对当前任务的重要性。对于垃圾识别来说,P3层对瓶盖、烟头这类小目标至关重要,但P5层的全局语义同样重要,因为遮挡严重的场景需要依赖周围物体来判断当前目标是什么。如果简单地按固定比例融合,信息就会被稀释。这也是我在第4节做BiFPN改造的直接原因。

3.3 Anchor-Free的Decoupled Head为什么更适合垃圾识别

YOLOv8把检测头换成了anchor-free + decoupled head的设计。anchor-free的意思是,不再需要预先定义一组anchor框,而是直接预测每个位置到目标四条边的距离。这个改动对垃圾识别的意义很大:垃圾形状极不规则,一个被揉成团的塑料袋,用固定宽高比的anchor框根本没法拟合,而anchor-free的回归方式天然支持任意形状。

Decoupled head(解耦头)则是把分类和回归分成两个分支,每个分支各自使用独立的卷积层,不再共享特征。在YOLOv5时代,分类和回归共用一个检测头,两个任务的梯度在反向传播时会互相干扰。解耦之后,分类分支可以专注于"这是什么",回归分支专注于"框在哪",各自特征空间更纯粹。

解耦头的代价是参数量和计算量上升,但它带来的精度收益在垃圾识别场景非常明显。特别是纸类和厨余垃圾这类类别内部形态差异极大的目标,分类分支需要很强的分辨能力,和回归分支解耦之后,训练收敛更稳定。

4. 我的三处改进:注意力机制、特征融合、损失函数

4.1 在Backbone的C3阶段嵌入Coordinate Attention

注意力机制是改进YOLO系列的常规操作,SE模块、CBAM、ECA、CA四选一,我是直接选了Coordinate Attention(CA)。选择依据是生活垃圾任务的特点:目标经常被遮挡,模型需要感知"被遮挡部分在哪、周边是什么"。

CA的核心思想很直观:传统SE注意力只做通道维度的全局池化,把空间信息压缩成一维向量,位置关系全部丢失。CBAM虽然补充了空间注意力,但它是通过卷积生成空间权重,对长距离依赖的建模能力有限。CA模块则是把空间注意力分解成两个方向:对特征图分别做水平方向和垂直方向的全局平均池化,得到两个一维特征向量,再经过卷积和激活函数后,作为注意力权重在宽度和高度方向上分别作用。

这样做的好处是,模型既能感知通道的重要程度,又能感知某个位置在水平和垂直方向上的空间关系。我实测下来,CA对"一个瓶子被另一个瓶子挡住一半"这类场景的检测效果提升最明显,因为模型可以结合被遮挡目标的上下文位置信息来推断完整目标区域。

嵌入位置我选择了backbone的C3阶段(对应yaml中的第4、6层)。为什么不是每一层都加?我做过对比实验,全部嵌入CA比只嵌入C3层,mAP只涨了0.3个点,但参数量增加了15%,推理速度慢了12%。在C3层嵌入CA,既能影响到后续包含丰富语义信息的特征图,又不会拖慢P3/P4/P5分支的底层特征提取速度,是性价比最高的选择。

4.2 把PAN-FPN升级为带加权融合的BiFPN结构

第二处改动是把Neck的PAN-FPN换成BiFPN(Bidirectional Feature Pyramid Network)思路。BiFPN的核心理念是:不同输入特征图的贡献不同,不应该简单相加,而是通过学习得到的权重进行加权融合。

具体做法是给每个输入特征图分配一个可学习的权重,然后在融合时做加权平均。BiFPN的加权公式是:

O = sum(w_i * I_i) / (sum(w_j) + epsilon)

其中w_i = exp(alpha_i),alpha_i就是可学习的标量参数,epsilon是防止除零的小常数。这种softmax式的加权方式保证了所有权重非负且和为1,数值稳定。同时BiFPN还删除了那些只有一个输入边的节点,让特征融合图更加精简高效。

在YOLOv8里做BiFPN改造,我采用的方式是:在原有的PAN路径上,保留top-down和bottom-up两条主路径,但每条路径的节点都增加一个加权融合操作,并添加残差连接。修改后的neck结构对P3、P4、P5三个尺度特征图的融合权重由模型自己学出来。

这个改动带来的直接收益是P3小目标特征得到更强的保留。我观察到训练过程中学到的权重很接近P3 > P4 > P5,说明模型发现小目标检测比大目标检测更难,所以把更多权重倾斜到高分辨率特征层。这比人工固定权重科学得多。

需要注意的是,BiFPN改造不要照搬EfficientDet的完整结构。EfficientDet的BiFPN是一个重复多次的循环特征金字塔,计算量很大,我在YOLOv8s规模上只使用了一轮双向融合,参数量增量控制在8%左右,效果已经足够。

4.3 损失函数换成Wise-IoU,处理低质量标注样本

第三处改进是损失函数。YOLOv8默认使用CIoU作为边界框回归损失,它包含重叠面积、中心点距离和宽高比三个维度的惩罚项。CIoU在通用目标检测上表现不错,但对低质量样本不够友好。

什么叫低质量样本?就是那些标注框本身就不准、目标严重遮挡、边界模糊的样本。这些样本计算出的IoU值普遍偏低,梯度变化剧烈。CIoU对所有的样本一视同仁地计算损失,导致模型被大量低质量样本的梯度主导,反而忽略了高质量样本的学习。

我替换成了Wise-IoU(WIoU v3)。WIoU的核心创新是引入了"离群度"的概念:根据当前样本IoU值与所有样本平均IoU值的偏离程度,动态调整该样本在损失函数中的权重。

  • 对离群度高的低质量样本,降低其梯度贡献,避免误导训练。
  • 对离群度低的常规样本,维持正常梯度。
  • 对高质量样本,动态提升其权重,让模型更多地学习这些"清晰样本"。

WIoU v3还用了动态非单调聚焦机制,比v1版本的梯度分配更科学。我在替换后做了对比实验,训练过程收敛曲线更平滑,最终mAP@0.5涨了1.8个百分点。这个涨点幅度在消融实验里排第二,最关键的收益是训练过程的稳定性:WIoU在训练后期几乎没有出现验证集loss反复震荡的情况。

4.4 三处改进的消融实验与参数变化

做课题和做工程项目的一大区别是,课题必须说明清楚"哪个改进带来了多少收益"。所以我做了严格的消融实验,每一种改进单独开启、逐一叠加,结果如下表:

实验配置mAP@0.5mAP@0.5:0.95参数量推理速度(ms)
基线YOLOv8s85.3%58.7%11.13M5.8
+CA注意力(C3层)87.6%61.2%11.52M6.1
+BiFPN加权融合88.9%63.4%12.04M6.5
+WIoU v3损失90.1%65.1%12.04M6.5
全部叠加92.4%68.3%12.04M6.6

完整的改进模型相比基线,mAP@0.5提升了7.1个百分点,参数量增加不到1M,推理速度慢0.8ms。这个结果满足了我开头设定的目标。值得注意的是,三个改进项不是简单的线性叠加,CA和BiFPN之间有协同效应,因为注意力强化后的特征在加权融合时能提取出更有区分度的信息。

5. 训练全流程实操:参数含义、Loss曲线解读和踩坑记录

5.1 环境配置与显存选择建议

我训练用的机器是RTX 3090 24GB显存,但实际训练YOLOv8s模型,用RTX 3060 12GB也完全够。环境配置很简单:

Python 3.9+ PyTorch 2.0+ ultralytics >= 8.0.0 CUDA 11.8

安装命令就一行:pip install ultralytics。这里要提醒一下,Ultralytics版本更新很快,不同版本的yaml配置格式略有差异,如果是从我的代码库复制配置文件,注意锁住版本:pip install ultralytics==8.0.230,否则一些新版本的API变动可能导致训练报错。

如果显存不够,有几个实用策略:

  • batch size减半,显存占用直接减半,代价是训练时间变长、BN层统计量的稳定性略降。
  • 开启AMP混合精度,显存占用可以减少约30%,3090上实测无精度损失。YOLOv8默认在设备支持时会自动开启amp。
  • 降低输入尺寸,从640降到512,显存占用大幅下降,但小目标检测能力会受影响,不推荐垃圾识别场景这么做。
  • 用梯度累积,ultralytics里没有直接暴露这个参数,但可以通过在自定义训练脚本里手动累积梯度实现。

5.2 训练参数逐个说清楚

很多初学者拿到训练命令就直接抄,epochs=100, batch-size=16跑起来,完全不知道每个参数在做什么。我在训练生活垃圾模型时用的命令是:

yolo train data=waste.yaml model=yolov8s.yaml pretrained=yolov8s.pt epochs=200 imgsz=640 batch=16 optimizer=SGD lr0=0.01 lrf=0.01 warmup_epochs=3.0 mosaic=0.5 close_mosaic=10 amp=True

逐个说下关键参数的考虑逻辑:

  • model=yolov8s.yaml:s版本是精度和速度的平衡点。n版本太轻,m版本在3090上训练太慢。实际测试下来,s版本对垃圾这类中等复杂度目标是性价比最高的。
  • pretrained=yolov8s.pt:用COCO预训练权重做初始化。虽然COCO里没有"厨余垃圾"这个类别,但底层特征提取器学到的是通用的边缘、纹理、形状特征,迁移到垃圾分类上收敛速度明显更快,最终精度也更高。
  • epochs=200:太少的epochs模型欠拟合,太多会过拟合。我在第150个epoch之后观察验证集loss已经平缓,最终200个epoch是合适的。如果训练集比较小(少于3000张),建议降到120到150。
  • imgsz=640:这是训练分辨率。垃圾识别里有大量小目标,我把imgsz提高到960试过,mAP@0.5涨了1.2%,但训练时间拉长了2.5倍,推理速度也下降明显。考虑到后续要部署到边缘设备,640是更务实的选择。
  • optimizer=SGD:Ultralytics默认是AdamW,很多人会忽略这个默认值。AdamW在训练前期收敛快,但后期精度往往不如SGD。我做过对比,SGD配合余弦退火在200个epoch的设定下最终mAP比AdamW高了0.6%。SGD最大的缺点是对学习率敏感,冷启动阶段要把warmup开足。
  • lr0=0.01, lrf=0.01:初始学习率0.01,结束时降到初始值的1%。这是YOLOv5/v8从COCO训练中总结出的经验值。如果你用的是AdamW,初始学习率建议降到0.001。
  • mosaic=0.5, close_mosaic=10:Mosaic概率设为0.5,最后10个epoch关闭Mosaic。这个设计很关键:Mosaic产生的拼接图会让BN层的统计量不稳定,如果训练最后阶段还开着,验证集loss会出现周期性尖峰。关闭之后让模型在"正常分布"的数据上微调,能稳定提升最终mAP。

5.3 Loss曲线的正确打开方式

训练完成后,runs/detect/train目录下会自动生成results.png,里面画了train/val两套loss曲线。我见过很多人只看"loss下降"就觉得训练没问题,实际上这里信息量很大,需要分阶段解读。

YOLOv8的loss包含三个分量:box_loss(边界框回归损失)、cls_loss(分类损失)、dfl_loss(分布焦点损失)。三条曲线的理想形态是:训练初期快速下降,中期缓慢下降,后期进入平台期,验证集曲线和训练集曲线保持接近、没有明显上翘。

我看loss曲线时重点关注的信号有四个:

  1. 验证集loss在后期反弹:基本可以断定过拟合。解决办法是提前结束训练、增加数据增强强度或者降低模型复杂度。
  2. box_loss降了,cls_loss不动:说明模型框的位置越来越准,但类别学不会。此时要检查标注类别是否混乱,或者类别样本是否严重不平衡。
  3. dfl_loss震荡:DFL损失负责目标框分布建模,震荡通常是学习率过大或者batch size太小导致BN统计不稳定。
  4. train和val的loss差距过大:除了过拟合,还有一种可能是数据增强产生的分布偏移——训练集做了大量HSV扰动,但验证集是原始图,模型在增强分布上表现优异,在真实分布上表现一般。

如果不想用Ultralytics自带的绘图,也可以自己把日志里的loss数据抽出来画,一个简单的脚本:

import pandas as pd import matplotlib.pyplot as plt results = pd.read_csv("runs/detect/train/results.csv") # 去掉列名里的空格 results.columns = results.columns.str.strip() plt.figure(figsize=(10, 6)) plt.plot(results["epoch"], results["train/box_loss"], label="train box_loss") plt.plot(results["epoch"], results["val/box_loss"], label="val box_loss") plt.xlabel("epoch") plt.ylabel("loss") plt.legend() plt.grid(True) plt.savefig("loss_curve.png", dpi=150)

5.4 我踩过的三个训练坑

列一下训练过程中真实遇到的三个问题,都是我排查了半天才找到原因的,希望能帮后来人省点时间。

**第一个坑:AMP混合精度下出现NaN loss。**训练到第67个epoch时,loss突然变成NaN,然后一直卡在那里。排查了一圈,发现是某个batch里的数据包含了损坏的图片(下载来的公开数据集里混了几张截断的jpg)。PyTorch的AMP在遇到这种数据时会输出NaN梯度。解决方案是训练前用脚本遍历训练集图片,用PIL验证是否能正常打开,把损坏的图片全部删掉。另外Ultralytics提供了cache=True参数,训练前缓存图片并校验,可以自动跳过坏图。

**第二个坑:验证集mAP在某个epoch突然跳变。**第120个epoch之前mAP稳步上升,但第121个epoch直接掉了3个点,后面又慢慢涨回来。后来发现是Mosaic关闭导致的正常现象——close_mosaic=10会在最后10个epoch关闭Mosaic,数据分布发生突变,验证集指标自然会波动。这个波动通常会持续几个epoch,不要因为看到mAP下降就停止训练,等Mosaic关闭后的几个epoch跑完再看结果。

第三个坑:类别不平衡导致"多数类吞噬一切"。我的"其他垃圾"类别占了40%的样本,"玻璃"只占8%。训练出来的模型对"其他垃圾"的召回很高,但所有类别都被倾向性地预测成"其他垃圾"。解决办法不是简单地做欠采样,而是给每个类别的loss加权。在Ultralytics的yaml里可以给每个类别设置独立的权重,我把玻璃的权重从1调到2.2,金属罐调到1.8,训练后各类别的平衡性明显改善,整体mAP反而涨了1个点。

6. 实验对比:改进效果到底怎么样

6.1 评价指标怎么读,以及垃圾场景该重点关注哪个

目标检测的评价指标很多,precision、recall、mAP@0.5、mAP@0.5:0.95、F1-score,还有推理延迟。我最看重的是mAP@0.5:0.95,因为它把0.5到0.95区间内的所有IoU阈值都算了一遍,对框位置的精度非常敏感。如果mAP@0.5很高但mAP@0.5:0.95很低,说明模型虽然能框住目标,但框的位置总是偏大或偏小。

对于垃圾识别,还有一个容易被忽略的指标:类别平均召回率(mean recall per class)。因为垃圾场景中目标数量多且密集,漏检一个烟头带来的环境危害比误检一个塑料袋更严重。我在实验报告里单独统计了每个类别的recall,发现玻璃类最容易漏检,因为透明物体在照片中对比度低,特征本来就弱。

6.2 基线模型和改进模型的实测对比

除了消融实验的表格,我还整理了一份更接近实际场景的对比数据,用的是我自己拍摄的200张实地测试图片,覆盖了白天、傍晚、阴天三种光照条件和单目标、多目标堆叠两种场景。

场景类型基线YOLOv8s R@0.5改进模型 R@0.5提升幅度
白天单目标91.5%94.8%+3.3%
白天多目标堆叠76.2%84.5%+8.3%
傍晚弱光72.1%81.3%+9.2%
阴天散射光79.8%86.9%+7.1%

可以看到,改进模型在简单场景下的提升有限,但在多目标堆叠和弱光环境下的提升非常明显。这正好验证了注意力机制和BiFPN加权融合对复杂场景的贡献:模型学会了在杂乱背景中聚焦关键目标,并且能更有效地融合小目标特征。

6.3 混淆矩阵里暴露的分类难点

训练完成后看混淆矩阵,能直观发现类间混淆的痛点。我的模型主要存在三类混淆:

第一类是玻璃瓶和透明塑料瓶的混淆。两者都是透明或半透明材质,形状相似,常同时出现在同一个画面里。解决的思路是在数据里特意增加"两者同框"的样本,让模型学习到它们之间的细微差异——玻璃瓶的瓶口螺纹更细、瓶身通常更重,底部有接缝线,这些细节特征在注意力机制增强后才更容易被捕捉到。

第二类是纸类中的纸箱和报纸。纸箱本身是瓦楞纸材质,表面有棱纹;报纸是光滑软纸,容易被揉皱。但当两者都被压扁堆叠时,纹理特征几乎丢失。这类混淆目前只能靠增加更多"压扁、揉皱"状态的训练数据来缓解。

第三类是厨余垃圾和其他垃圾。这是最头疼的跨类混淆,因为厨余垃圾形态极度随机,果皮和剩菜的外观差异巨大,而"其他垃圾"本身就是一个兜底类别,内部包含了所有不属于前五类的东西。类内距离大于类间距离,这是多分类问题里最经典的难点。

混淆矩阵分析的结论是:类别体系的合理性决定模型的精度上限,如果"其他垃圾"里混入太多外观差异巨大的子类,任何一个检测模型都很难处理。后续优化方向是尝试用"奥卡姆剃刀"原则拆分这个兜底类别。

6.4 可视化分析:热力图和检测效果的定性观察

指标只能说明"好",不能说明"为什么好"。我还用了Grad-CAM做了特征热力图可视化,对比基线和改进模型在相同图片上的注意力分布。

基线模型在堆叠垃圾场景的热力图呈现出明显的"分散注意力"现象:高亮区域分布在图片的多个位置,包括背景地面、垃圾桶边缘等无关区域。改进模型的热力图则明显聚焦到目标主体上,且对玻璃瓶这类小目标也能产生独立的高亮区域。这说明CA注意力确实教会了模型"该看哪里"。

另外我还用TensorBoard的Image功能随机采样了一批推理结果,专门观察那些被NMS抑制掉的预测框。发现改进模型的冗余框明显减少——基线模型经常对同一个目标输出三四个重叠框,改进模型基本只有一个高置信度的框。这说明BiFPN的加权融合让特征图的目标中心区域响应更加集中,NMS更容易筛选出唯一结果。

7. 部署落地:从研究原型到真实场景

7.1 模型导出与格式转换

训练完的模型最终要落地到实际应用里。Ultralytics框架内置了多格式导出功能,一行命令搞定:

yolo export model=best.pt format=onnx opset=12 simplify=True yolo export model=best.pt format=engine device=0 # TensorRT

导出ONNX时需要注意三个细节:一是opset版本,老旧的部署端(比如某些RK3588的RKNN工具链)只支持opset 11或12,导出的opset太高会导致转换失败;二是simplify=True,用onnx-simplifier移除不必要的节点,ONNX文件大小能减少20%以上;三是动态batch,如果部署端固定单张推理,建议导出时固定batch=1,减少不必要的维度计算。

导出TensorRT引擎时,engine格式的导出在实机上直接执行,它会根据当前GPU的特有架构生成优化后的推理引擎。在3090上生成的engine文件不能直接挪到3060上跑,需要在目标机器上重新生成,这是个容易踩的坑。

7.2 在RK3588这类边缘设备上的部署经验

我手里的边缘设备是RK3588,用的是瑞芯微的NPU,算力6 TOPS。这个平台不支持直接跑PyTorch模型,流程是:PyTorch权重转ONNX,再用RKNN-Toolkit2把ONNX转成RKNN格式。

整个转换流程里我遇到两个主要难点:

第一个是算子兼容性。RKNN-Toolkit2对ONNX算子的支持还没有完全覆盖,一些新版本PyTorch产生的算子(比如aten::scaled_dot_product_attention这种)会出现不兼容。解决思路是把模型升级或降级到合理的PyTorch版本,并尽量把模型结构保持为标准卷积、池化、拼接操作。我在网络结构改进时特意避免了过于花哨的自定义算子,目的就是不让改进模块变成部署的障碍。

第二个是INT8量化精度损失。RK3588上为了达到实时推理,必须做INT8量化。600张测试图的量化校准后,mAP@0.5从92.4%降到88.1%,损失了4.3个百分点,这个损失是可以接受的。但要注意校准集的选择:校准图片必须覆盖所有类别和常见背景,如果只用干净样本做校准,量化后的模型在真实场景的掉点会严重得多。

量化部署后,模型在RK3588上的推理速度大约是35ms一帧(640x640输入),加上前后处理总共40ms左右,完全满足实时应用需求。

7.3 实际场景中检测效果下降的案例

部署到实际场景后,我做了为期两周的现场测试,发现模型在几个场景下会稳定失效,这个认知对"研究型课题落地"很关键。

雾天场景是最大的杀手。雾会让图像整体对比度下降,目标边缘模糊,模型普遍无法给出高于0.5的置信度输出。我的缓解方案是在推理前加了一个简单的去雾预处理:基于暗通道先验的算法做对比度增强,能在不增加太多耗时的情况下把雾天场景的mAP提升6到8个点。这个方法很"土",但效果实在。

夜间场景同样棘手,但和雾天原因不同。夜间照片的动态范围小,暗部细节丢失严重。解决思路是调整图像的gamma值,强制拉升暗部亮度。实测gamma=1.8时夜间检测效果最好。当然这只是应急方案,真正物理级的解决手段是增加补光设备。

极端堆叠场景,比如垃圾桶已经满了,垃圾在桶口堆成小山,目标之间相互遮挡超过70%。这种场景下即使人眼也很难区分单个物体,模型能做到的只能是对最外层的目标给出预测。我把这类场景单独标注出来,作为后续算法的优化方向。

这三个失效案例给我最大的启发是:"模型精度"和"系统可用性"是两码事。研究阶段92%的mAP看起来光鲜,但到了真实环境,需要配合预处理、后处理、传感器选择等一系列工程手段才能让这个92%真正产生价值。这也是为什么我在课题报告里专门加了一节"工程化部署与场景适配",而不仅仅是展示训练指标。

对于想复现这个项目的人,我给的建议是:不要急于往网络结构里堆模块,先把数据质量搞上去,把损失函数调对,把训练参数理解透,最后再考虑网络结构的改造。我做消融实验的体会是,数据层面的改进收益往往大于结构层面的改进,而结构层面的改进只有在数据基础扎实的前提下才能充分发挥作用。这套思路对任何垂直场景的目标检测任务都适用——不管你是做垃圾分类、工业缺陷检测还是农作物识别,先修地基,再谈上层建筑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 9:08:03

从.o文件到可执行程序,搞懂ELF和静态链接

前面已经能够自己制作 .a 和 .so 了。 但是还有一个问题一直比较绕: hello.c code.c分别编译以后得到: hello.o code.o这两个 .o 文件到底是怎么变成最后那个可以直接执行的程序的? 这部分其实就是编译和链接。 再往下研究,还会碰…

作者头像 李华
网站建设 2026/9/30 9:07:00

SpringBoot+Vue医院后台管理系统设计与全栈实现

前阵子帮人从头搭了一版医院后台管理系统,从数据库建模、后端接口、前端页面到最终部署,全程走了一遍。做这类系统的最大感受是:它看起来就是个“信息管理系统”,但真把挂号、门诊、收费、药房、床位这些环节串起来之后&#xff0…

作者头像 李华
网站建设 2026/9/30 9:05:55

钙钛矿硅叠层 34.0%,MPPT 2000h保持84%:氧化锆颗粒改造埋底界面

钙钛矿/硅叠层太阳电池把宽带隙钙钛矿顶电池与硅底电池叠在一起,大幅压制热化损失,效率越过单结Shockley–Queisser极限,认证值已达35.2%。理想结构受两点制约:制绒硅表面起伏剧烈,钙钛矿沉积不均匀;空穴传…

作者头像 李华
网站建设 2026/9/30 9:05:10

AI工程从零开始:搭建最小闭环的实战路线图

如果你是在逛技术社区的时候刷到“ai-engineering-from-scratch”这种仓库名,大概率第一反应和我一样:这年头还有人从零开始学AI工程?我真正把“从零开始”这四个字当回事,是因为一次内部评审会——一个面试者谈起接口调用、模型微…

作者头像 李华
网站建设 2026/9/30 9:04:17

医院设备报修管理系统实战:微信小程序+Flask全流程开发

上个月去一家二甲医院办事,碰巧看到设备科老师还在用手工台账登记设备维修:一台心电监护仪报修,电话打到设备科,值班员先在纸上记一笔,再翻通讯录找负责的维修工程师,修完之后补一张三联单。整个过程全靠人…

作者头像 李华
网站建设 2026/9/30 9:03:48

破解save的三重身份:从按钮文案到文件解析的实战指南

前几天朋友塞给我一个本地化的活:一套中文界面的小工具要回填英文文案,交付前再整体校对一遍。做到一个按钮时我停住了,按钮上写着“确定”,同事扫了一眼说这不就是OK吗,直接填Ok就行。我没急着动手,翻了一…

作者头像 李华