尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于CNN的智能垃圾分类系统:从数据集到推理的完整落地路径

基于CNN的智能垃圾分类系统:从数据集到推理的完整落地路径 简介这份资源是面向计算机、通信、人工智能、自动化等专业学生与从业者的智能垃圾分类系统完整项目源码基于深度学习卷积神经网络实现可作为毕业设计、期末课程设计或课程大作业的参考方案。项目为个人毕设作品答辩评审分达98分代码经过调试测试确保可运行适合小白入门学习也便于基础较好的读者在此基础上修改调整、扩展功能。压缩包共216个文件约17.29MB包含Python脚本、Java源码、XML布局、PNG与JPG图像素材、模型检查点与权重数据文件以及Gradle、Maven构建配置和说明文档覆盖模型训练、界面实现与工程构建等环节。目前已有183人学习关注读者可从中获取完整的垃圾分类识别方案、卷积神经网络模型结构与权重文件、项目目录组织方式及调试排错思路对理解深度学习项目落地流程具有较高借鉴价值。1. 智能垃圾分类系统从数据集到推理一条能跑通的 CNN 落地路径宿舍楼下四个垃圾桶可回收、有害、厨余、其他每天有人站在前面犹豫三秒然后随便扔。这个场景催生了大量毕业设计选题也是「基于深度学习卷积神经网络的智能垃圾分类系统」最真实的出发点。它要解决的核心问题只有一个给一张垃圾照片让模型输出它属于哪一类并且准确率要能看。技术栈上Python 做训练和推理CNN 做特征提取数据集决定上限工程化决定能不能演示。适合谁做计算机、电子信息、物联网方向的本科毕业生以及想用一个完整项目把深度学习从课本拉到本地跑通的人。卷积神经网络原理不复杂难的是数据脏、类别不均衡、训练完不会部署。这篇笔记按「数据怎么整、模型怎么搭、训练怎么调、坑怎么避、推理怎么快」的顺序讲清楚每一步都能在你自己的机器上复现。2. 数据集准备与增强垃圾图片的清洗、划分和离线增广2.1 为什么垃圾分类的数据集比模型更决定成败很多人一上来就找 ResNet、EfficientNet 的预训练权重觉得模型越深越准。实际做下来垃圾分类项目的准确率瓶颈八成在数据。公开数据集常见的问题是同一张图被重复标注、背景里有人手或桌面、某一类只有几十张而另一类上千张。模型学到的往往不是垃圾本身的纹理而是拍摄环境的偏差。我一般会先做三件事去重、去背景干扰、统计类别分布。去重用感知哈希去背景干扰靠人工抽检加裁剪类别分布用柱状图看一眼就知道要不要做重采样。常见做法是先把原始图片统一到 224×224再按 8:1:1 划分训练、验证、测试。注意测试集必须和训练集来源不同否则准确率虚高。如果只有一份数据用分层抽样保证每个类别在三个集合里的比例一致。import os import hashlib from PIL import Image from sklearn.model_selection import train_test_split def file_hash(path): # 用文件内容哈希做粗去重避免完全相同的图重复进入训练 with open(path, rb) as f: return hashlib.md5(f.read()).hexdigest() def collect_images(root): paths, labels, seen [], [], set() classes sorted(os.listdir(root)) for idx, cls in enumerate(classes): cls_dir os.path.join(root, cls) for name in os.listdir(cls_dir): p os.path.join(cls_dir, name) h file_hash(p) if h in seen: continue seen.add(h) paths.append(p) labels.append(idx) return paths, labels, classes paths, labels, classes collect_images(./garbage_raw) # stratify 保证每个类别在训练/验证/测试中比例一致 X_train, X_tmp, y_train, y_tmp train_test_split( paths, labels, test_size0.2, stratifylabels, random_state42) X_val, X_test, y_val, y_test train_test_split( X_tmp, y_tmp, test_size0.5, stratifyy_tmp, random_state42) print(len(X_train), len(X_val), len(X_test), classes)这段代码里file_hash解决重复图问题stratify解决类别不均衡下的划分偏差。random_state固定后每次划分一致方便复现。如果你的数据里某一类少于 100 张建议在训练集上做离线增强而不是在线增强因为在线增强在小数据集上容易让 batch 内分布抖动。2.2 离线增强与在线增强怎么选在线增强用 torchvision 的 transforms 在训练时随机翻转、裁剪、调色优点是省磁盘缺点是每个 epoch 看到的图都不同小数据集上收敛曲线会抖。离线增强是提前把增强后的图存到磁盘训练时直接读适合数据量小于 5000 张的情况。我一般对垃圾分类这种纹理差异大的任务离线增强做 3 到 5 倍包括随机旋转 ±15 度、水平翻转、亮度对比度微调。注意不要做垂直翻转垃圾照片上下颠倒不符合真实拍摄习惯反而引入噪声。from torchvision import transforms from PIL import Image import os aug transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.2), ]) src_dir ./garbage_raw/plastic dst_dir ./garbage_aug/plastic os.makedirs(dst_dir, exist_okTrue) for i, name in enumerate(os.listdir(src_dir)): img Image.open(os.path.join(src_dir, name)).convert(RGB) for k in range(4): # 每张原图生成 4 张增强图 aug(img).save(os.path.join(dst_dir, f{i}_{k}.jpg))参数上RandomCrop(224)配合Resize(256)是经典组合先放大再裁剪保留更多局部纹理。RandomRotation(15)的角度不要超过 20 度否则垃圾形状失真。增强后的图要和原图分目录存放训练时只对训练集目录做读取验证和测试集保持原图避免评估泄漏。提示增强前先确认原图没有大量灰度图或带 alpha 通道的 PNG统一转 RGB否则训练时会出现通道数不匹配的报错。3. 卷积神经网络搭建从 LeNet 到 ResNet 的选型与最小可训练代码3.1 垃圾分类任务该选多深的网络卷积神经网络结构图看多了容易陷入「越深越好」的误区。垃圾分类的类别通常是 4 到 10 类图片内容是纹理和形状的组合不需要 ImageNet 上那种上千类的容量。我一般给本科毕业设计的建议是先用一个 4 层卷积的轻量网络跑通全流程再换 ResNet18 做对比。轻量网络参数量在 1M 以内CPU 上也能训练适合演示。ResNet18 预训练权重能明显提升小数据集的准确率但要注意替换最后的全连接层并冻结前几层。选型理由很直接轻量网络训练快、易调试、答辩时能讲清楚每一层的作用ResNet18 准确率高、有残差连接解决梯度消失适合写进论文做对比实验。两者都跑一遍论文里就有 baseline 和改进的对比。import torch import torch.nn as nn class GarbageCNN(nn.Module): def __init__(self, num_classes4): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 32, 3, padding1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2), # 224 - 112 nn.Conv2d(32, 64, 3, padding1), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2), # 112 - 56 nn.Conv2d(64, 128, 3, padding1), nn.BatchNorm2d(128), nn.ReLU(), nn.MaxPool2d(2), # 56 - 28 nn.Conv2d(128, 256, 3, padding1), nn.BatchNorm2d(256), nn.ReLU(), nn.AdaptiveAvgPool2d(1) # 任意输入尺寸都能压到 1x1 ) self.classifier nn.Sequential( nn.Dropout(0.5), nn.Linear(256, num_classes) ) def forward(self, x): x self.features(x) x x.flatten(1) return self.classifier(x) model GarbageCNN(num_classes4) print(sum(p.numel() for p in model.parameters()))BatchNorm2d放在卷积和 ReLU 之间是标准做法能加速收敛。AdaptiveAvgPool2d(1)替代固定尺寸的全连接避免输入尺寸变化时维度对不上。Dropout(0.5)在全连接前抑制过拟合。参数量打印出来大概 0.4M适合小数据集。3.2 训练循环里的三个必调参数学习率、batch size、epoch 是训练循环里最影响结果的三个参数。学习率我一般从 1e-3 开始配合余弦退火batch size 在显存允许下取 32 或 64epoch 看验证集准确率不再上升就停通常 30 到 50 轮。优化器用 AdamW 比 SGD 更容易调权重衰减设 1e-4。from torch.utils.data import DataLoader, Dataset from torch.optim import AdamW from torch.optim.lr_scheduler import CosineAnnealingLR from torchvision import transforms from PIL import Image class GarbageDataset(Dataset): def __init__(self, paths, labels, transform): self.paths, self.labels, self.transform paths, labels, transform def __len__(self): return len(self.paths) def __getitem__(self, i): img Image.open(self.paths[i]).convert(RGB) return self.transform(img), self.labels[i] train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) train_ds GarbageDataset(X_train, y_train, train_tf) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers2) device torch.device(cuda if torch.cuda.is_available() else cpu) model GarbageCNN(num_classeslen(classes)).to(device) criterion nn.CrossEntropyLoss() optimizer AdamW(model.parameters(), lr1e-3, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max30) for epoch in range(30): model.train() total_loss 0 for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() loss criterion(model(imgs), labels) loss.backward() optimizer.step() total_loss loss.item() scheduler.step() print(fepoch {epoch}, loss {total_loss/len(train_loader):.4f})Normalize用的均值方差是 ImageNet 统计值迁移学习时保持一致。num_workers在 Windows 上设 0 更稳Linux 上设 2 到 4。CosineAnnealingLR的T_max等于总 epoch 数学习率从 1e-3 平滑降到接近 0。如果 loss 在前几轮就变成 nan先检查学习率是不是太大再检查数据里有没有损坏的图片。注意训练前把验证集单独跑一遍确认模型初始输出接近均匀分布如果某一类概率一直是 1说明标签映射错了。4. 训练过程排查损失不降、过拟合、显存溢出的常见问题4.1 损失不下降的三种排查路径现象训练 10 轮后 loss 还在 2.3 附近震荡准确率等于随机猜。原因通常有三个学习率过大导致梯度爆炸、数据标签和文件夹顺序对不上、输入归一化用错。解决方法是先把学习率降到 1e-4 跑 5 轮看 loss 是否下降再打印一个 batch 的图片和标签肉眼确认图和类对应最后检查 Normalize 的均值和方差是否和预训练权重匹配。我踩过一次坑文件夹按字母排序是 glass、metal、paper、plastic但标签映射时用了另一套顺序结果模型学的是错位标签loss 一直不降。后来加了一行打印classes和label的对应关系才定位到。4.2 过拟合与显存溢出的处理现象训练准确率 99%验证准确率 60%。原因模型容量相对数据量太大或者增强不够。解决加 Dropout、加权重衰减、增加离线增强倍数、早停。显存溢出则通常是 batch size 太大或图片分辨率太高先把 batch size 减半再把输入从 448 降到 224基本能解决。# 早停示例验证集 loss 连续 5 轮不降就停 best_val, patience, counter float(inf), 5, 0 for epoch in range(50): # ... 训练代码 ... model.eval() val_loss 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) val_loss criterion(model(imgs), labels).item() val_loss / len(val_loader) if val_loss best_val: best_val, counter val_loss, 0 torch.save(model.state_dict(), best.pth) else: counter 1 if counter patience: print(early stop) breaktorch.save只存state_dict不存整个模型对象加载时先实例化结构再load_state_dict这样代码重构后权重还能用。早停的patience设 5 到 8太小会错过后期下降太大浪费训练时间。4.3 类别不均衡导致的偏科现象模型把大部分图都预测成样本最多的那一类。原因交叉熵损失对多数类倾斜。解决用加权交叉熵权重取类别频率的倒数或者对少数类做重采样。加权交叉熵一行代码就能改。import numpy as np counts np.bincount(y_train) weights 1.0 / counts weights weights / weights.sum() * len(counts) class_weights torch.tensor(weights, dtypetorch.float).to(device) criterion nn.CrossEntropyLoss(weightclass_weights)weights归一化后均值约为 1避免整体 loss 尺度变化太大。如果某一类样本极少加权后仍不理想就对该类做离线增强到和其他类同一量级。5. 推理部署与界面演示把模型变成能点的垃圾分类工具5.1 从 checkpoint 到单张图片推理训练完的模型要能对外服务最少要有一个推理脚本。加载权重、预处理、前向、取 argmax四步。注意推理时用torch.no_grad()关闭梯度用model.eval()固定 BN 和 Dropout。import torch from PIL import Image from torchvision import transforms def load_model(ckptbest.pth, num_classes4): model GarbageCNN(num_classesnum_classes) model.load_state_dict(torch.load(ckpt, map_locationcpu)) model.eval() return model infer_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) def predict(img_path, model, classes): img Image.open(img_path).convert(RGB) x infer_tf(img).unsqueeze(0) # 增加 batch 维度 with torch.no_grad(): logits model(x) prob torch.softmax(logits, dim1) idx prob.argmax(dim1).item() return classes[idx], prob[0][idx].item() model load_model() print(predict(./test.jpg, model, classes))unsqueeze(0)把单张图变成[1, 3, 224, 224]这是模型要求的输入形状。softmax把 logits 转成概率方便界面显示置信度。如果置信度低于 0.6界面上最好提示「不确定」而不是硬给一个类别。5.2 用 Gradio 或 Streamlit 做演示界面毕业设计答辩需要一个能点的界面。Gradio 三行代码就能起一个网页上传图片返回类别和置信度。Streamlit 适合做多页面。两者都不需要前端知识。import gradio as gr def classify(img): img img.convert(RGB) x infer_tf(img).unsqueeze(0) with torch.no_grad(): prob torch.softmax(model(x), dim1)[0] return {classes[i]: float(prob[i]) for i in range(len(classes))} gr.Interface(fnclassify, inputsgr.Image(typepil), outputsgr.Label(num_top_classes4), title智能垃圾分类).launch()gr.Image(typepil)直接接收 PIL 图片省去文件读写。gr.Label输出各类概率答辩时直观。部署到本地 7860 端口局域网内手机也能访问演示效果好。提示推理脚本和训练脚本的预处理必须完全一致Resize、Normalize 参数差一个都会让准确率掉十几个点。6. 把准确率再抬一截迁移学习微调与 TTA 的两个实用技巧如果 baseline 准确率卡在 85% 上不去最划算的两个技巧是迁移学习微调和测试时增强。迁移学习用 ResNet18 预训练权重替换最后一层先冻结特征层训练分类头 5 轮再解冻全部微调 10 轮学习率分别设 1e-3 和 1e-4。这样在小数据集上通常能到 92% 以上。TTA 是在推理时对同一张图做多次增强把预测概率平均能再涨 1 到 2 个点代价是推理时间翻几倍。from torchvision.models import resnet18, ResNet18_Weights def build_resnet(num_classes4): model resnet18(weightsResNet18_Weights.DEFAULT) for p in model.parameters(): p.requires_grad False # 先冻结 model.fc nn.Linear(model.fc.in_features, num_classes) return model # 第一阶段只训练 fc model build_resnet(len(classes)).to(device) optimizer AdamW(model.fc.parameters(), lr1e-3) # ... 训练 5 轮后解冻 ... for p in model.parameters(): p.requires_grad True optimizer AdamW(model.parameters(), lr1e-4)TTA 的实现是对验证图做原图、水平翻转、中心裁剪三种变换分别推理后取平均。def tta_predict(img, model, classes): tfs [ transforms.Compose([transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])]), transforms.Compose([transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p1.0), transforms.ToTensor(), transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])]), ] probs [] for tf in tfs: x tf(img).unsqueeze(0) with torch.no_grad(): probs.append(torch.softmax(model(x), dim1)) avg torch.stack(probs).mean(0)[0] return classes[avg.argmax().item()], avg.max().item()RandomHorizontalFlip(p1.0)在 TTA 里表示必定翻转和训练时的随机翻转不同。TTA 的变换要和训练增强同分布否则平均反而掉点。我一般只在最终评估和演示时开 TTA训练过程中不用。最后说一个血泪经验别在答辩前一天换模型结构。我见过太多人 baseline 跑通了非要换成 ViT结果训练时间翻倍、显存不够、准确率还没调好。把轻量 CNN 加迁移学习加 TTA 这套组合吃透垃圾分类这个题足够拿到好结果。希望帮到你。本文还有配套的精品资源点击获取
返回列表