ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于深度学习的垃圾分类算法:从CNN训练到边缘部署实践

基于深度学习的垃圾分类算法:从CNN训练到边缘部署实践 简介一份围绕“基于深度学习网络的生活垃圾分类算法研究”的毕业论文资源包面向计算机视觉、深度学习方向的本科生和入门研究者解决垃圾图像分类模型从数据构建到训练调参的完整流程问题。文档基于128×128降维图像数据经神经网络调参与多轮实验最终模型分类准确率超过93%适合需要在低算力环境下完成图像分类课题的读者参考。包体为单个docx文件压缩包整体约631KB内容以毕业设计论文为主包含中英文摘要、目录、引言、相关工作、研究内容及附录等章节附录中附有可运行的代码下载后可直接查阅便于复现实验与理解算法细节。目前已有82人浏览学习适合正在撰写相关课题论文或希望快速搭建垃圾分类识别实验的读者作为模板与参考。1. 基于深度学习网络的生活垃圾分类算法论文到底在解决什么问题我最早接触这个课题是被一个很具体的场景逼的社区投放点的摄像头已经装好了但后端识别用的还是传统图像特征加SVM遇到矿泉水瓶压扁、外卖盒带油污就频繁误判保洁员每天要手工分拣出将近三分之一被投错的袋子。后来我把方案换成基于深度学习网络的垃圾分类算法用python和神经网络重新走了一遍流程才意识到这个课题的真正价值不在网络堆得多深而是在易混淆类上把分类边界画清楚。这篇论文面向的是三类人要做毕业设计的学生想在树莓派或Jetson上部署轻量分类器的工程师以及想优化现有巡检系统识别率的算法岗。它要解决的就是一个核心问题给定一张生活垃圾照片如何用深度卷积网络给出可解释、可部署、可迭代的类别判断。2. 从论文到可复现的算法原型网络结构、特征提取与类别映射2.1 为什么选卷积神经网络而不是手工特征生活垃圾分类在图像层面有一个特点类别内差异大类别间差异小。一个纸箱和一个文件夹从纹理上很像但一个是可回收物一个是其他垃圾一块带油渍的纸巾和一张干净纸巾颜色接近但归属完全不同。传统方法依赖颜色直方图、HOG、LBP这类手工特征在光线变化、遮挡、形变面前非常脆弱。而基于深度学习网络的方案是让卷积核自己学习油渍边界压扁轮廓材质反光这些高维特征不需要人为定义规则。用python实现时最常用的就是Pytorch或Keras我一般选Pytorch因为它的张量接口更接近论文里写的数学推导调试梯度也直观。训练阶段用GPU部署阶段可以转成ONNX或TensorRT算力要求并不离谱。2.2 网络骨干的Pytorch实现一个能跑通的最小原型我们先搭一个适合垃圾分类的轻量CNN原型结构参考VGG的思路但砍掉全连接层的冗余参数。输入统一为224×224×3经过五组卷积块每组包含两个3×3卷积、BN和ReLU最后一组后接全局平均池化输出维度是类别数。以40个类别为例代码核心如下import torch.nn as nn class GarbageNet(nn.Module): def __init__(self, num_classes40): super().__init__() self.features nn.Sequential( # Block1: 224x224 - 112x112 nn.Conv2d(3, 32, 3, padding1), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.Conv2d(32, 32, 3, padding1), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.MaxPool2d(2, 2), # Block2: 112x112 - 56x56 nn.Conv2d(32, 64, 3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.Conv2d(64, 64, 3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(2, 2), # Block3: 56x56 - 28x28 nn.Conv2d(64, 128, 3, padding1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.Conv2d(128, 128, 3, padding1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.MaxPool2d(2, 2), # Block4: 28x28 - 14x14 nn.Conv2d(128, 256, 3, padding1), nn.BatchNorm2d(256), nn.ReLU(inplaceTrue), nn.Conv2d(256, 256, 3, padding1), nn.BatchNorm2d(256), nn.ReLU(inplaceTrue), nn.MaxPool2d(2, 2), # Block5: 14x14 - 7x7 nn.Conv2d(256, 512, 3, padding1), nn.BatchNorm2d(512), nn.ReLU(inplaceTrue), nn.Conv2d(512, 512, 3, padding1), nn.BatchNorm2d(512), nn.ReLU(inplaceTrue), nn.MaxPool2d(2, 2), ) self.classifier nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Dropout(0.5), nn.Linear(512, num_classes) ) def forward(self, x): return self.classifier(self.features(x))这段结构里有几个关键选择每层卷积后都接BN防止前几层梯度消失尤其在数据集只有几万张时BN的稳定作用非常明显。全连接层只保留一层是为了减少参数量让模型在树莓派这类设备上也能跑到10帧以上。Dropout放在全连接前是因为垃圾分类的类间特征差异小过拟合往往发生在最后这个线性层上。如果你觉得这个结构太浅后续可以直接把features部分换成ResNet18的stem只保留自己的分类头。2.3 SoftMax输出的类别映射与阈值调优网络最后的线性层输出的是logits必须经过Softmax才能变成概率分布。这一步看似简单但有一个容易被忽视的坑如果直接取概率最大的类模型会对所有样本都给出一个分类结果哪怕是模糊的、不属于任何已知类的图片。训练时我们优化的是交叉熵损失它只关心正确类别的概率够不够高并不强制给错误类别一个低概率上限。所以论文里普遍会用加一个置信度阈值的做法比如Softmax最大概率低于0.75就判为待人工确认。实际代码中这只多了一行prob torch.softmax(logits, dim1) conf, pred torch.max(prob, dim1) if conf.item() 0.75: label need_manual_check阈值0.75不是拍脑袋定的我用验证集调过0.85时误检少了但召回率掉得很快0.7时人工复核量增加30%最终定在0.75是个平衡点。如果你用的是迁移学习模型这个阈值通常可以调到0.8以上因为预训练模型的特征空间更平滑。3. python实现的完整训练流程与四个必调参数3.1 数据集组织与标签编码垃圾分类公开数据集常见的有华为的垃圾分类数据集和TrashNet前者40类后者只有6类但每张图质量很高。无论用哪个都要先按标准方式组织目录每个类别一个文件夹图片全部统一尺寸。我在训练前会用下面这段代码做数据增强和归一化这一步对垃圾分类尤其重要因为实际投放点拍到的照片有大量旋转和遮挡。from torchvision import transforms train_transforms transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.6, 1.0)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(degrees20), transforms.ColorJitter(brightness0.3, contrast0.3), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])注意scale参数我设的是0.6到1.0而不是默认的0.08到1.0因为垃圾分类目标通常占据画面主体裁剪太狠会把瓶子截成两段反而学到错误特征。ColorJitter的对比度扰动是为了模拟夜晚或背光条件下的照片但亮度扰动不能太大否则油渍和污渍的区分度会被抹掉。Normalize用的是ImageNet统计量如果你不用迁移学习而是随机初始化建议重新统计自己数据集的均值和方差。3.2 训练循环、损失函数和学习率策略损失函数用交叉熵这是分类任务最稳的起点。但有经验的人会告诉你一个细节数据集类别不平衡时给每个类别加上权重会比简单交叉熵有效得多。比如玻璃瓶类样本很多而废电池类样本很少可以这样设置import torch import torch.nn as nn weights torch.tensor([1.0, 1.2, 0.8, ..., 2.0]) # 按类别样本数倒数归一化 criterion nn.CrossEntropyLoss(weightweights)训练时的优化器和学习率策略我推荐AdamW配合CosineAnnealing比固定学习率的SGD收敛快且最终精度不差。关键是warmup前5个epoch用线性升温到初始学习率后面再衰减可以避免前期因为数据扰动大而震荡。典型配置是初始学习率3e-4warmup 5 epoch总训练60 epochbatch size 64。如果GPU显存不够batch降到32时学习率也要相应降到1.5e-4否则梯度噪声变大会让loss曲线像锯齿。3.3 训练脚本的骨架一个完整的训练脚本建议包含验证集评估和模型保存不要等到全部训完再手动找最优权重。我一般每个epoch结束后在验证集上计算准确率并且保存两份权重一份是准确率最高的一份是最后一轮的这样可以随时后悔药回退。best_acc 0.0 for epoch in range(60): model.train() for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() logits model(images) loss criterion(logits, labels) loss.backward() optimizer.step() model.eval() acc evaluate(model, val_loader) if acc best_acc: best_acc acc torch.save(model.state_dict(), best_garbage.pth)我在验证时把Top-1和Top-5都打印出来因为垃圾分类40类的难度差异很大Top-5能告诉你模型是不是在正确答案附近打转。如果Top-1一直不动但Top-5很高说明类别嵌入空间基本正确是最后的线性层分类边界没学精这种情况稍微调大epoch或加数据增强往往就能改善。另外保存权重时别只存state_dict连optimizer_state和当前epoch一起存断点续训省下的时间足够再做三轮CV。3.4 四个必调参数的经验区间第一个是批大小它决定了BN的统计量稳定性我实测16、32、64差别很大64最稳128在显存允许时精度没有明显提升反而容易过拟合。第二个是Dropout率0.3到0.6之间值得扫一遍ResNet这类带BN的网络Dropout率可以稍微调低VGG类调高。第三个是权重衰减我固定用1e-4太大会欠拟合太小验证集准确率会先升后突降。第四个是显著图增强数据比例这不是超参数但值得记把训练集里加入3%的旋转90度或倒置的混淆样本能明显降低部署时误检率。4. 训练结果分析与评价准确率、混淆矩阵与特征可视化4.1 用混淆矩阵定位易混淆类别训练结束后只报一个准确率是不够的论文评审或项目汇报最常问的就是错分集中在哪。我的做法是每次跑完测试集都输出一个40×40的混淆矩阵重点看对角线之外的高亮块。以我之前的结果为例模型最容易把一次性纸杯和纸盒混在一起因为两者在224×224分辨率下折痕和印刷图案几乎一样。其次容易混的是干净塑料袋和保鲜膜材质都是半透明高光卷积核很难区分。用混淆矩阵定位之后我会针对这两对类别专门裁剪样本、加强材质边缘的对比度增强比盲目加数据量有效得多。from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt cm confusion_matrix(all_labels, all_preds) plt.figure(figsize(20, 16)) sns.heatmap(cm, cmapBlues, fmtd) plt.show()另一个角度看混淆矩阵统计每一类被分错时目标类别是什么做成条件熵。如果条件熵在某个类别上特别高说明这个类的特征本身定义就不清晰比如其他垃圾这个类本来就是垃圾桶里剩下的所有东西类别没有统一的视觉共性这时模型无法学出一个紧凑的聚类中心。解决办法是把它在算法内部拆分几类再在后端归并或者在损失函数里用class-aware的temperature来拉远边界。4.2 用Grad-CAM验证模型到底在看什么网络精度够了但你说不清它为什么对这在工程评审时就是隐患。我习惯给每一张错分图跑一个Grad-CAM热力图看看模型在做出错误判断时目光落在哪。比如前面说的纸杯和纸盒混淆热力图显示模型盯着的是印刷文字区域而不是杯口边缘的卷边结构这就说明特征注意力偏了。常见的做法是在倒数第二层卷积输出上计算梯度加权平均最后把热力图叠到原图上。Pytorch里可以用torch.autograd.grad来实现或者在forward中挂钩子不复杂但非常能说明问题。def warm_up(images): logits.retain_grad() ...这条链路做完论文里结果分析章节的图就齐了准确率曲线、混淆矩阵、Grad-CAM热力图再加几张典型正确/错误样本。比贴一张test acc的数字有说服力得多。5. 模型优化避坑指南5个常见翻车点和排查方法5.1 现象loss不降反升或一直在0.5附近抖动这是最常见的问题尤其在从头训练而不是迁移学习的时候。原因通常有三个学习率太大让梯度在最优值附近震荡数据集类别严重不平衡少数类贡献的梯度被淹没另一个是BN的batch size太小导致协变量偏移不断累积。解决顺序应该是先看训练集和验证集的loss差如果验证集loss走高而训练集没降是过拟合加大Dropout或数据增强如果训练集loss也在抖把学习率降低一个数量级再试。我遇到过最隐蔽的情况是从零训练但把图像归一化错了std用了[0.5,0.5,0.5]而不是ImageNet的前20个epoch模型完全无法收敛。5.2 现象测试集准确率在训练后期突然下降这个问题几乎都出现在用余弦退火学习率的后半段。模型在最高点附近反复摩擦而学习率已经降得过于低导致参数在局部极小值里过拟合了训练集。解决方法是保存最高验证准确率的模型这是所有训练脚本都应该有的底线我见过太多人最后一轮权重直接报废。也可以用early stopping但注意垃圾分类这类任务最好用验证集Top-1而不是loss值来触发因为Top-1更直观反映业务目标。5.3 现象摄像头实拍场景准确率比测试集低10个百分点以上这属于典型的域偏移。实验室测试集的图和实际投放点拍到的图光照、角度、背景完全不同。我踩过一次坑模型在公开数据集上做到92%上线后第一周只有81%后来发现是摄像头俯拍角度下垃圾桶内壁的黑色塑料片和黑色垃圾袋被大面积误判为其他垃圾。解决办法有两个层面训练时加入域随机化比如改装HSV范围、加随机噪声、模拟俯视透视变换另一层面是部署端用像素级预处理把高光、阴影区域局部扣掉再送入网络。注意不要在训练集里加太多背景噪声否则模型会把背景物体当成特征。5.4 现象模型对特定物品的材质反光极度敏感玻璃瓶和塑料瓶在强光下的高光区域几乎一模一样模型很容易只依据高光面积来判断。这类问题用普通数据增强很难根治我试过有效的做法是加入随机圆形高光模拟遮挡或者用mixup把两个类别的样本人为混合强制模型学习更多判别特征。Mixup的alpha建议设0.2太大会让样本语义完全混乱。这个坑没有银弹核心思路是构造特征冲突的样本让网络学会跳过亮度特征优先级。5.5 现象迁移学习时冻结层数不合适导致收敛极慢很多人直接用torchvision的ResNet18把最后的全连接层换掉然后整网一起训练结果发现前面层几乎不动后面层在疯狂变。原因是预训练权重的梯度尺度天然比随机层小你用相同学习率时分类头被完全压制。常见做法是先把冻结的特征层lr设成分类层的0.1倍或者用两段式训练前10个epoch只训练分类头再解冻最后的block微调全部层。如果你用的是EfficientNet这类结构注意解冻时BN层最好保持eval模式否则训练集和验证集的分布又会不一致。6. 让模型更实用的锦上添花类间相似样本的主动挖掘到最后当准确率卡在92%左右上不去时我一般不会再盲目调参而是去挖那些测试集里最容易被错分的样本把它们挑出来专门看。做法是保存每个测试样本的logits向量和真实标签自己写一个聚类脚本找出所有被错分样本中分布最集中的几簇。这些簇往往对应某一类特定的疑难模式比如带吸管的杯子被压扁的易拉罐针对它们单独生成一个数据子集做微调往往比多加几千张随机图片效率更高。这也正是论文里算法研究和工程落地最大的分水岭一个是用验证集找模型问题一个是用模型问题找训练集死角。最后提醒一句千万别把测试集里错分的样本直接加到训练集里那是自欺欺人重训后评估指标会虚高只有保留完全独立的测试集才能告诉别人算法真实的泛化能力。希望这些从训练到排查、从参数到上线的经验能帮到你。本文还有配套的精品资源点击获取
返回列表