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

资讯详情

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

基于CNN的智能垃圾分类系统:从模型训练到Gradio界面部署的完整实战

基于CNN的智能垃圾分类系统:从模型训练到Gradio界面部署的完整实战 简介这份资源是面向计算机、通信、人工智能、自动化等专业学生与从业者的智能垃圾分类系统完整项目源码基于深度学习卷积神经网络实现可作为毕业设计、课程大作业或期末课程设计的参考方案。项目为个人毕设成果答辩评审分达98分代码经过调试测试确保可运行适合小白入门学习也便于基础较好的读者在此基础上修改调整、扩展功能。压缩包共216个文件约17.29MB包含Python脚本、Java源码、XML配置、PNG与JPG图像样本、模型检查点与权重数据文件以及Gradle、Maven构建脚本和说明文档覆盖数据处理、模型训练与界面交互等模块。目前已有183人学习关注。读者可从中获取完整的卷积神经网络垃圾分类实现思路、可复现的工程目录结构、模型权重与训练检查点以及项目说明文档便于快速理解项目全貌并开展二次开发。1. 从一张宿舍楼下的垃圾桶照片说起这套智能垃圾分类系统到底解决什么问题去年帮学弟看毕业设计他选题是「基于深度学习卷积神经网络的智能垃圾分类系统」代码跑通了答辩前一周却慌了——老师问「你这模型在真实照片上准确率多少」他答不上来因为训练集里全是白底单物体的标准图一换成宿舍楼下那种堆叠、遮挡、光线昏暗的实拍图模型直接崩。这不是个例是绝大多数 Python 毕业设计里 CNN 项目的通病实验室指标漂亮落地场景翻车。这套系统的核心其实就三件事用卷积神经网络做图像特征提取与分类、用 Python 把训练和推理串起来、最后包一个能演示的界面。它适合三类人正在做计算机/电子信息方向毕业设计的学生、想入门深度学习但缺一个完整项目练手的初学者、以及需要快速搭一个图像分类 demo 的开发者。读完你能拿到一条从数据准备、模型搭建、训练调参到界面集成的完整路径知道每一步参数为什么这么设、哪里最容易出问题。热词里「深度学习入门」「卷积神经网络原理」「python深度学习教程」这些搜索意图本质都是想找一个能跑通、能改、能讲清楚的项目这篇就按这个标准写。2. 卷积神经网络凭什么能认垃圾从一张 224×224 的图到四个类别2.1 为什么垃圾分类天然适合 CNN而不是全连接网络垃圾图像分类的本质是「从像素里找可区分的纹理和形状模式」——塑料瓶有反光的高光带和圆柱轮廓纸板有纤维纹理和折痕金属罐有镜面反射和拉环结构玻璃瓶有透明边缘和折射。这些特征有强烈的局部性和平移不变性一个瓶盖出现在图片左上角还是右下角不影响它是塑料这个判断。全连接网络把图像拉成一维向量等于把空间结构彻底打散相邻像素的关系丢失参数量还爆炸——一张 224×224×3 的图接一个 512 维隐层光这一层就是 7700 万参数。CNN 用两个机制解决这个问题局部感受野让每个神经元只看一小块区域权值共享让同一个卷积核在整张图上滑动。一个 3×3 的卷积核只有 9 个权重却能扫描全图参数量直接降几个数量级。卷积层之后接池化层做下采样把特征图尺寸逐步缩小、通道数逐步增大浅层学边缘和颜色深层学部件和整体形状。最后接全连接层或全局平均池化做分类。这套结构对垃圾这种「类内差异大、类间差异有时小」的任务比传统 HOGSVM 方案鲁棒得多尤其是光照和角度变化时。2.2 用 PyTorch 搭一个能跑通的四分类 CNN下面是一个最小可用的 CNN 定义输入 224×224 三通道输出四类可回收、厨余、有害、其他。我一般用 PyTorch因为调试直观、报错清晰对毕业设计来说改结构也方便。import torch import torch.nn as nn class GarbageCNN(nn.Module): def __init__(self, num_classes4): super().__init__() # 特征提取3 个卷积块通道 32-64-128 self.features nn.Sequential( # 块1: 224x224 - 112x112 nn.Conv2d(3, 32, kernel_size3, padding1), nn.BatchNorm2d(32), # 加速收敛缓解内部协变量偏移 nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 块2: 112x112 - 56x56 nn.Conv2d(32, 64, kernel_size3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 块3: 56x56 - 28x28 nn.Conv2d(64, 128, kernel_size3, padding1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), ) # 分类头全局平均池化替代大全连接减少过拟合 self.classifier nn.Sequential( nn.AdaptiveAvgPool2d(1), # 128x28x28 - 128x1x1 nn.Flatten(), nn.Dropout(0.5), # 训练时随机丢弃防过拟合 nn.Linear(128, num_classes) ) def forward(self, x): x self.features(x) x self.classifier(x) return x if __name__ __main__: model GarbageCNN(num_classes4) dummy torch.randn(8, 3, 224, 224) # 模拟一个 batch out model(dummy) print(out.shape) # 期望 torch.Size([8, 4])逻辑说明features负责逐层提取空间特征每个卷积块是「卷积 BN ReLU 池化」的标准组合。BN 放在卷积后、激活前是常见做法能让训练更稳、学习率可以设大一点。classifier用AdaptiveAvgPool2d(1)把每个通道压成一个数再接 Dropout 和线性层这样参数量小、对输入尺寸也不敏感。参数说明kernel_size3, padding1保证卷积后空间尺寸不变尺寸缩小全靠池化这样每层输出尺寸好推算。通道数 32→64→128 是逐层翻倍的经典节奏显存不够可以整体减半。Dropout(0.5)是分类头常用的丢弃率数据量小可以调到 0.3数据量大可以不用。num_classes4要和你数据集的实际类别数一致改类别数时只改这一个参数。2.3 数据准备从原始图片到 DataLoader 的完整链路模型定义只是骨架真正决定成败的是数据。毕业设计常见的数据组织方式是每个类别一个文件夹用ImageFolder直接读。from torchvision import datasets, transforms from torch.utils.data import DataLoader, random_split # 训练集做增强验证集只做缩放和归一化 train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), # 随机水平翻转 transforms.RandomRotation(15), # 随机旋转 ±15 度 transforms.ColorJitter(brightness0.2, contrast0.2), # 亮度对比度扰动 transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) # ImageNet 统计值 ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 假设数据目录结构: data/train/可回收/*.jpg 等 full_ds datasets.ImageFolder(data/train, transformtrain_tf) n_val int(len(full_ds) * 0.2) n_train len(full_ds) - n_val train_ds, val_ds random_split(full_ds, [n_train, n_val]) val_ds.dataset.transform val_tf # 验证集换用无增强的 transform train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4) print(f训练样本 {n_train}验证样本 {n_val}类别 {full_ds.classes})逻辑说明训练集用增强是为了让模型见到更多变化缓解过拟合验证集绝不能加随机增强否则每次评估结果都在抖指标不可信。Normalize用的均值方差是 ImageNet 的统计值如果你从零训练可以用自己数据集的统计值但用预训练权重时必须保持一致。参数说明batch_size32是显存和训练稳定性的折中显存小就降到 16 或 8同时把学习率按比例调小。num_workers4在 Windows 上如果报错就改成 0。RandomRotation(15)的 15 度对垃圾图像够用转太多会让瓶罐倒置反而不符合真实场景。3. 训练、调参与评估让模型在真实照片上也能打3.1 训练循环与学习率调度训练循环本身不复杂关键是损失函数、优化器和调度器的搭配。分类任务用交叉熵优化器用 AdamW学习率用余弦退火。import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR device torch.device(cuda if torch.cuda.is_available() else cpu) model GarbageCNN(num_classes4).to(device) criterion nn.CrossEntropyLoss() optimizer optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max30) # 30 个 epoch 内余弦下降 def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss, correct, total 0.0, 0, 0 for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * imgs.size(0) correct (outputs.argmax(1) labels).sum().item() total imgs.size(0) return total_loss / total, correct / total torch.no_grad() def evaluate(model, loader, criterion, device): model.eval() total_loss, correct, total 0.0, 0, 0 for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) outputs model(imgs) loss criterion(outputs, labels) total_loss loss.item() * imgs.size(0) correct (outputs.argmax(1) labels).sum().item() total imgs.size(0) return total_loss / total, correct / total best_acc 0.0 for epoch in range(30): tr_loss, tr_acc train_one_epoch(model, train_loader, optimizer, criterion, device) val_loss, val_acc evaluate(model, val_loader, criterion, device) scheduler.step() if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_garbage_cnn.pth) # 只存最优 print(fEpoch {epoch1:02d} | train {tr_loss:.4f}/{tr_acc:.3f} | val {val_loss:.4f}/{val_acc:.3f})逻辑说明每个 epoch 先训练后验证验证用torch.no_grad()关闭梯度节省显存。保存策略是「只存验证集最优」避免最后一个 epoch 过拟合反而变差。scheduler.step()放在 epoch 末尾让学习率按余弦曲线从 1e-3 平滑降到接近 0。参数说明lr1e-3是 AdamW 的常用起点如果 loss 震荡就降到 3e-4。weight_decay1e-4是 L2 正则数据量小可以加到 1e-3。T_max30要和总 epoch 数一致否则学习率曲线对不上。如果验证准确率卡在某个值不动先看是不是学习率太大再看数据增强是不是过猛。3.2 评估不能只看准确率混淆矩阵和每类指标准确率在类别不平衡时会骗人。如果「其他垃圾」占了 60%模型全猜这一类也有 60% 准确率。必须看混淆矩阵和每类的精确率、召回率。from sklearn.metrics import confusion_matrix, classification_report import numpy as np torch.no_grad() def get_all_preds(model, loader, device): model.eval() all_preds, all_labels [], [] for imgs, labels in loader: imgs imgs.to(device) outputs model(imgs) all_preds.extend(outputs.argmax(1).cpu().numpy()) all_labels.extend(labels.numpy()) return np.array(all_preds), np.array(all_labels) preds, labels get_all_preds(model, val_loader, device) print(confusion_matrix(labels, preds)) print(classification_report(labels, preds, target_namesfull_ds.classes, digits3))逻辑说明混淆矩阵的行是真实类别、列是预测类别对角线是分对的非对角线能看出哪两类容易混。classification_report给出每类的 precision、recall、f1-score比一个总准确率信息量大得多。参数说明target_names要按ImageFolder的类别顺序传顺序错了报告就张冠李戴。如果发现「玻璃」和「塑料」互相误判严重说明这两类的视觉特征太接近要么补数据要么在增强里加更强的颜色扰动逼模型学形状而不是颜色。3.3 迁移学习小数据集上把准确率拉起来的最快手段毕业设计的数据集通常每类只有几百张从零训练很难到 90% 以上。用 ImageNet 预训练的 ResNet18 做迁移学习通常能把验证准确率直接拉到 95% 左右。import torchvision.models as models def build_resnet18(num_classes4, freeze_backboneTrue): model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) if freeze_backbone: for p in model.parameters(): p.requires_grad False # 冻结主干 # 替换最后的全连接层 in_features model.fc.in_features model.fc nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, num_classes) ) return model model build_resnet18(num_classes4, freeze_backboneTrue).to(device) # 冻结时只优化 fc 层学习率可以大一点 optimizer optim.AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr1e-3)逻辑说明weights...IMAGENET1K_V1加载预训练权重freeze_backboneTrue时只训练新换的分类头训练快、不容易过拟合。等分类头收敛后可以解冻最后几个 block 做微调学习率降到 1e-4。参数说明filter(lambda p: p.requires_grad, ...)只把需要梯度的参数交给优化器冻结的参数不更新。如果显存够、数据量也还行可以一开始就不冻结用 1e-4 的学习率整体微调效果通常更好但更慢。4. 从模型到能演示的系统推理封装、界面与部署4.1 推理函数单张图片到类别和置信度训练完要能对任意一张图给出结果这个函数是界面调用的核心。from PIL import Image CLASS_NAMES [可回收, 厨余, 有害, 其他] def predict_image(model, image_path, device, class_namesCLASS_NAMES): model.eval() tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) img Image.open(image_path).convert(RGB) # 强制三通道防灰度图报错 tensor tf(img).unsqueeze(0).to(device) # 加 batch 维度 with torch.no_grad(): logits model(tensor) probs torch.softmax(logits, dim1)[0] conf, idx probs.max(0) return class_names[idx.item()], conf.item(), probs.cpu().numpy() label, conf, probs predict_image(model, test.jpg, device) print(f预测: {label}, 置信度: {conf:.3f})逻辑说明convert(RGB)是关键一步用户上传的图可能是灰度或带透明通道不转会在ToTensor后通道数不对。unsqueeze(0)补上 batch 维度因为模型期望输入是[N, C, H, W]。softmax把 logits 转成概率方便展示置信度。参数说明class_names的顺序必须和训练时ImageFolder的类别顺序完全一致否则标签全错。置信度低于某个阈值比如 0.6时界面上最好提示「不确定」而不是硬给一个答案。4.2 用 Gradio 快速搭一个可交互界面毕业设计答辩需要一个能现场演示的界面Gradio 几行代码就能搞定比 PyQt 省事得多。import gradio as gr def classify(img): if img is None: return 请上传图片, {} tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) tensor tf(img.convert(RGB)).unsqueeze(0).to(device) model.eval() with torch.no_grad(): probs torch.softmax(model(tensor), dim1)[0].cpu().numpy() return {CLASS_NAMES[i]: float(probs[i]) for i in range(len(CLASS_NAMES))} demo gr.Interface( fnclassify, inputsgr.Image(typepil), outputsgr.Label(num_top_classes4), title智能垃圾分类系统, description上传一张垃圾图片模型会给出四个类别的概率 ) demo.launch(server_name0.0.0.0, server_port7860)逻辑说明gr.Image(typepil)直接把上传的图转成 PIL 对象省去自己处理文件流。gr.Label输出概率条答辩时视觉效果直观。server_name0.0.0.0让局域网内其他设备也能访问方便老师用手机现场测试。参数说明num_top_classes4显示全部四类概率。如果部署在服务器上server_port要选一个没被占用的端口。模型加载要在demo.launch()之前完成避免每次请求都重新加载权重。4.3 模型导出与轻量化让系统能在没有 GPU 的机器上跑答辩教室的电脑不一定有 GPU把模型导出成 ONNX 或做动态量化CPU 上也能跑到实时。# 方式一导出 ONNX dummy torch.randn(1, 3, 224, 224).to(device) torch.onnx.export( model, dummy, garbage_cnn.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}}, opset_version12 ) # 方式二动态量化仅 CPU quantized torch.quantization.quantize_dynamic( model.cpu(), {nn.Linear}, dtypetorch.qint8 ) torch.save(quantized.state_dict(), garbage_cnn_quantized.pth)逻辑说明ONNX 导出后可以用 onnxruntime 推理跨平台、不依赖 PyTorch。动态量化把线性层权重从 float32 压到 int8模型体积约缩到四分之一CPU 推理速度提升明显精度损失通常在一个百分点以内。参数说明opset_version12兼容性较好太新的版本某些推理引擎不支持。dynamic_axes让 batch 维度可变方便一次处理多张图。量化只对nn.Linear做卷积层量化需要静态量化流程复杂一些毕业设计用动态量化足够。5. 避坑与排查那些让答辩翻车的细节5.1 训练准确率 99%验证准确率 60%现象训练集上几乎全对验证集惨不忍睹loss 曲线一个往下走一个往上翘。原因典型过拟合数据量太小或增强太弱模型把训练样本背下来了。解决先加数据增强翻转、旋转、颜色扰动再加 Dropout 和 weight_decay还不行就换迁移学习冻结主干最后才考虑减模型容量。5.2 换了台电脑就报「CUDA out of memory」现象在自己电脑上跑得好好的换到答辩教室或同学机器上直接显存溢出。原因batch_size是按自己显卡设的别人显存更小或者忘了model.to(device)导致部分张量还在 CPU。解决把batch_size做成可配置启动时根据torch.cuda.get_device_properties(0).total_memory自动降档所有输入和模型统一.to(device)推理时用torch.no_grad()。5.3 预测结果永远是同一类现象不管传什么图输出都是「其他」置信度还很高。原因最常见的是类别顺序错位——训练时ImageFolder按文件夹名字母序排推理时CLASS_NAMES手写的顺序对不上。其次是归一化参数不一致训练用了 ImageNet 均值方差推理忘了加。解决打印full_ds.classes确认顺序把它直接存成 json 供推理读取归一化 transform 抽成一个函数训练和推理共用。5.4 中文路径导致图片读不进来现象Image.open报FileNotFoundError或乱码路径里带中文。原因某些库在 Windows 下对非 ASCII 路径处理有问题。解决读文件用open(path, rb)再交给 PIL或者统一把数据集路径改成英文。这个坑血泪经验答辩前一天才发现数据集读不全后悔药都没得吃。5.5 界面卡死或每次预测都很慢现象点一次分类要等好几秒连续点几次界面无响应。原因每次请求都重新加载模型或重新构建 transform。解决模型和 transform 在全局初始化一次推理函数只做前向如果还是慢检查是不是跑在 CPU 上而模型没量化或者图片分辨率被意外放大。6. 把准确率再往上推一把几个我实际用过的技巧模型能跑通只是及格线答辩想拿高分、或者你真想把这套东西用到实际场景还得在细节上抠。下面几个技巧是我在多个图像分类项目里反复验证过的按投入产出比排序。第一用 TTA测试时增强白捡一两个点。推理时对同一张图做原图、水平翻转、轻微缩放三个版本分别预测后把概率平均。代码上就是把predict_image包一层循环成本是推理时间翻三倍但准确率通常能涨 1~2 个百分点答辩演示时很稳。def predict_with_tta(model, img, device, n3): tf_list [ 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])]), transforms.Compose([transforms.Resize((256, 256)), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])]), ] probs [] model.eval() with torch.no_grad(): for tf in tf_list[:n]: t tf(img.convert(RGB)).unsqueeze(0).to(device) probs.append(torch.softmax(model(t), dim1)[0].cpu().numpy()) return np.mean(probs, axis0)第二处理类别不平衡用加权损失或重采样。如果「有害垃圾」样本特别少模型会倾向忽略它。在CrossEntropyLoss里传weight参数权重按类别频率的倒数设或者用WeightedRandomSampler让每个 batch 里各类别比例均衡。前者改一行代码后者要改 DataLoader效果都不错。第三把验证集做成「真实场景集」。这是我最想强调的一点。训练和验证都从同一个分布里切指标再高也不代表真实可用。单独收集一批手机实拍、带背景、有遮挡的图做测试集哪怕只有几十张也能暴露真问题。我见过太多项目在标准验证集上 98%在实拍测试集上不到 70%差距全在数据分布上。第四模型集成是最后的保险。训一个 ResNet18 和一个自己搭的 CNN推理时把两者 softmax 概率平均。两个模型结构不同、犯错的地方往往不一样集成后准确率通常比单模型高 2~3 个点。代价是训练和推理成本翻倍但对毕业设计这种一次性投入的场景完全值得。最后说个习惯每次改完超参数或数据增强都把验证集准确率、混淆矩阵、几个典型错例截图存到一个experiments/目录里文件名带上日期和改动点。答辩前翻一遍你能清楚说出每一步为什么这么调而不是「试出来的」。这套系统本身不难难的是把每个环节的因果关系讲明白希望帮到你。本文还有配套的精品资源点击获取
返回列表