基于深度学习的垃圾分类系统:从数据准备到部署的完整实战指南
简介这份资源是面向高校Python课程大作业场景的完整项目包主题为基于深度学习的垃圾分类系统适合正在准备课程设计、需要可运行参考项目的本科生或自学者。压缩包共134个文件约75.59MB包含20个py源码、13个ipynb实验笔记、12个vue前端页面、14个js脚本、19张png效果图以及pptx汇报演示、docx参考报告、pdf文档、onnx模型、sqlite3数据库和Dockerfile部署配置等覆盖从模型训练、图像预处理到前端展示与部署的完整链路。资源中源码均经本地编译调试可直接运行并附有部署指南与全部文档方便对照理解数据采集、特征提取、分类输出等模块的实现思路。目前已有89人学习下载评审分达到95分以上难度适中适合作为课程实践与深度学习入门的参考方案。1. 从一份课程大作业说起垃圾分类系统到底要交付什么每年到了期末问得最多的一类问题就是「基于深度学习的垃圾分类系统」这个 Python 课程大作业到底该怎么做、做成什么样才算过关。我见过太多同学卡在同一个地方模型训练脚本跑通了准确率也刷到了 90% 以上但一到验收环节就翻车——老师问「你怎么部署的」「换个摄像头还能不能识别」「报告里的数据是怎么来的」答不上来。这个标题背后其实是一套完整的工程交付物源码、部署指南、报告 PPT、全部文档四样东西缺一不可而真正拉开差距的不是模型本身是你能不能把「训练好的权重」变成一个别人能跑起来、能看懂、能复现的系统。这篇文章面向两类人一类是正在做这个课程大作业、需要一份能照着落地的完整路径的同学另一类是想拿垃圾分类当深度学习入门实战项目、但不想只停留在跑通 demo 的从业者。我会按「数据怎么准备 → 模型怎么选和训 → 服务怎么部署 → 文档和 PPT 怎么写 → 坑在哪」的顺序讲中间给到能直接抄的命令和代码参数也会说清楚为什么这么设。垃圾分类这个场景看着简单实际上类别不均衡、背景干扰、部署环境差异这几个问题一个都不少把它做扎实比刷十个 MNIST 变体有用得多。2. 数据集准备与增强从原始图片到能喂给模型的数据2.1 垃圾分类的数据集从哪来、怎么划分常见做法有两种一是用公开数据集比如 TrashNet 这类按纸板、玻璃、金属、纸张、塑料、其他分成 6 类的数据集图片量在几千张级别二是自己用手机拍按可回收物、厨余垃圾、有害垃圾、其他垃圾这四分类去采集。课程大作业我更推荐第二种因为四分类和国内实际垃圾桶对应答辩时好讲而且自己拍的数据能体现工作量。采集时注意每类至少 300 张拍摄角度、光照、背景尽量多样否则模型学到的只是「白色桌面」这种无关特征。划分比例用 7:1.5:1.5 比较稳也就是训练集 70%、验证集 15%、测试集 15%。这里有个血泪经验不要用随机划分就完事如果同一张图的不同角度被分到了训练和测试两边测试准确率会虚高。正确做法是按「拍摄批次」或「物体实例」划分同一个物体的所有照片只能出现在一个集合里。下面这段脚本按文件名前缀分组划分能避免这个问题。import os import random import shutil from collections import defaultdict # 原始数据目录结构: raw_data/类别名/xxx.jpg RAW_DIR raw_data OUT_DIR dataset SPLIT {train: 0.7, val: 0.15, test: 0.15} random.seed(42) # 按文件名前缀(视为同一物体)分组, 保证同组不跨集合 def group_by_prefix(files): groups defaultdict(list) for f in files: prefix f.split(_)[0] # 约定命名: 物体ID_角度.jpg groups[prefix].append(f) return list(groups.values()) for cls in os.listdir(RAW_DIR): cls_dir os.path.join(RAW_DIR, cls) if not os.path.isdir(cls_dir): continue files [f for f in os.listdir(cls_dir) if f.lower().endswith((.jpg, .png))] groups group_by_prefix(files) random.shuffle(groups) n len(groups) n_train int(n * SPLIT[train]) n_val int(n * SPLIT[val]) split_map { train: groups[:n_train], val: groups[n_train:n_train n_val], test: groups[n_train n_val:], } for split, grps in split_map.items(): dst os.path.join(OUT_DIR, split, cls) os.makedirs(dst, exist_okTrue) for g in grps: for f in g: shutil.copy(os.path.join(cls_dir, f), os.path.join(dst, f)) print(f{cls}: train{n_train} val{n_val} test{n - n_train - n_val} 组)逻辑说明先按前缀把同一物体的多张照片聚成组再以「组」为单位随机打散后切分这样同一个物体的照片不会同时出现在训练集和测试集。参数上SPLIT可以按数据量调整数据少于 2000 张时验证集比例可以降到 0.1把更多数据留给训练。random.seed(42)固定随机种子保证每次划分结果一致方便复现。2.2 数据增强的度怎么把握垃圾分类的增强不能乱来。水平翻转、随机裁剪、颜色抖动这三样是安全且有效的但垂直翻转要慎用因为垃圾桶和垃圾的上下关系是有语义的倒过来的塑料瓶在现实中几乎不存在强行翻转会让模型学到错误特征。旋转角度控制在 ±15 度以内太大容易把物体转出画面。用 torchvision 的 transforms 组合起来大概是这样from torchvision import transforms train_tf transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomResizedCrop(224, scale(0.7, 1.0)), # 随机裁剪, 保留70%-100%面积 transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(15), # 小角度旋转 transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], # ImageNet 统计值 std[0.229, 0.224, 0.225]), ]) 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]), ])参数说明RandomResizedCrop的scale(0.7, 1.0)表示裁剪面积占原图的 70% 到 100%这个范围对垃圾分类够用再小会把物体裁没。Normalize用的是 ImageNet 的均值和方差因为后面要用预训练权重输入分布必须对齐这一步漏了准确率会掉好几个点。验证集只做 Resize 和归一化不能加任何随机增强否则验证指标会抖动没法判断模型好坏。提示如果某一类样本特别少比如有害垃圾只有 100 张优先用「过采样 强增强」而不是直接复制图片复制会导致过拟合。也可以在损失函数里加类别权重这个后面训练部分会讲。3. 模型选型与训练迁移学习为什么是课程大作业的最优解3.1 从零训练还是迁移学习算一笔账课程大作业的数据量通常在一两千到一万张之间这个规模从零训练一个 CNN 基本等于自杀——收敛慢、准确率低、还容易过拟合。迁移学习是标准答案拿 ImageNet 上预训练好的骨干网络换掉最后的分类头用垃圾分类数据微调。骨干网络的选择上ResNet18 和 MobileNetV3 是两个最常被推荐的。ResNet18 参数量约 1100 万准确率稳适合追求指标的场合MobileNetV3-Small 参数量只有 250 万左右推理快适合后面要部署到边缘设备或者做成 Web 服务的场景。课程大作业我一般推荐 MobileNetV3因为部署环节会轻松很多答辩时「能在普通笔记本上实时识别」是个加分项。选型对比可以看这张表骨干网络参数量输入尺寸适合场景训练显存(bs32)ResNet18~11M224追求准确率、有 GPU~4GBMobileNetV3-Small~2.5M224要部署、要实时~2GBEfficientNet-B0~5.3M224折中方案~3GB3.2 训练脚本与关键参数下面是一个完整的训练脚本骨架用 PyTorch 写包含类别权重处理不均衡、学习率调度、验证和保存最优权重。import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, models from torch.optim import Adam from torch.optim.lr_scheduler import CosineAnnealingLR DEVICE torch.device(cuda if torch.cuda.is_available() else cpu) BATCH_SIZE 32 EPOCHS 30 LR 1e-3 NUM_CLASSES 4 # 可回收、厨余、有害、其他 train_ds datasets.ImageFolder(dataset/train, transformtrain_tf) val_ds datasets.ImageFolder(dataset/val, transformval_tf) train_loader DataLoader(train_ds, batch_sizeBATCH_SIZE, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_sizeBATCH_SIZE, shuffleFalse, num_workers4) # 计算类别权重, 缓解不均衡 counts [0] * NUM_CLASSES for _, label in train_ds.samples: counts[label] 1 weights torch.tensor([sum(counts) / (NUM_CLASSES * c) for c in counts], dtypetorch.float).to(DEVICE) model models.mobilenet_v3_small(weightsmodels.MobileNet_V3_Small_Weights.IMAGENET1K_V1) model.classifier[3] nn.Linear(model.classifier[3].in_features, NUM_CLASSES) # 换分类头 model model.to(DEVICE) criterion nn.CrossEntropyLoss(weightweights) optimizer Adam(model.parameters(), lrLR) scheduler CosineAnnealingLR(optimizer, T_maxEPOCHS) best_acc 0.0 for epoch in range(EPOCHS): model.train() 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() scheduler.step() model.eval() correct total 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(DEVICE), labels.to(DEVICE) pred model(imgs).argmax(1) correct (pred labels).sum().item() total labels.size(0) acc correct / total print(fepoch {epoch1} val_acc{acc:.4f}) if acc best_acc: best_acc acc torch.save(model.state_dict(), best_model.pth)逻辑说明weights按类别样本数的倒数计算样本少的类权重高让损失函数更关注它们。CosineAnnealingLR让学习率从 1e-3 余弦下降到接近 0比固定学习率收敛更稳。每轮验证后只在准确率提升时保存权重避免保存到过拟合的模型。参数说明BATCH_SIZE32是显存和稳定性的折中显存不够就降到 16同时把学习率按比例降到 5e-4。EPOCHS30对迁移学习足够通常 15 轮左右验证准确率就趋于平稳。LR1e-3是 Adam 微调的常用起点如果发现 loss 震荡降到 3e-4。num_workers4在 Windows 上如果报错就改成 0这是 Windows 下多进程的经典坑。注意换分类头时model.classifier[3]这个索引是 MobileNetV3 的结构决定的换成 ResNet 就是model.fc换 EfficientNet 是model.classifier[1]写之前先 print 一下模型结构确认别照抄。4. 部署落地把权重变成能用的服务4.1 三种部署形态怎么选课程大作业的部署常见有三种命令行脚本、Flask Web 服务、打包成 exe。命令行脚本最简单适合演示Flask Web 服务能上传图片返回结果答辩时用浏览器演示最直观打包 exe 适合交给完全不懂技术的老师双击就能跑。我一般推荐 Flask 方案工作量适中效果好看而且部署指南写起来清晰。Flask 服务的核心代码from flask import Flask, request, jsonify from PIL import Image import torch from torchvision import transforms, models import torch.nn as nn import io app Flask(__name__) DEVICE torch.device(cpu) # 部署环境通常没 GPU CLASSES [可回收物, 厨余垃圾, 有害垃圾, 其他垃圾] model models.mobilenet_v3_small() model.classifier[3] nn.Linear(model.classifier[3].in_features, len(CLASSES)) model.load_state_dict(torch.load(best_model.pth, map_locationcpu)) model.eval().to(DEVICE) tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) app.route(/predict, methods[POST]) def predict(): file request.files[image] img Image.open(io.BytesIO(file.read())).convert(RGB) tensor tf(img).unsqueeze(0).to(DEVICE) with torch.no_grad(): prob torch.softmax(model(tensor), dim1)[0] idx prob.argmax().item() return jsonify({class: CLASSES[idx], confidence: round(prob[idx].item(), 4)}) if __name__ __main__: app.run(host0.0.0.0, port5000)逻辑说明加载权重时map_locationcpu保证在没有 GPU 的机器上也能加载。unsqueeze(0)给单张图补上 batch 维度。返回结果里带上置信度前端可以据此决定是否提示「识别不确定」。参数说明host0.0.0.0让局域网内其他设备也能访问答辩时用手机拍一张传上去演示效果很好。port5000是 Flask 默认端口被占用就换 5001。生产环境不要用app.run但课程大作业够用。4.2 部署指南文档该写哪些内容部署指南不是把命令堆上去就完事要按「一个没接触过你项目的人」的视角写。必备内容Python 版本要求建议 3.8 到 3.103.11 以上有些包会出问题、依赖安装命令、模型权重文件放哪、启动命令、访问方式、常见报错。依赖建议导出成 requirements.txtpip freeze requirements.txt # 别人拿到后 pip install -r requirements.txtrequirements.txt 里 torch 的版本要写清楚比如torch2.0.1不写版本号别人装到最新版可能 API 变了跑不起来。这是部署指南里最容易被忽略、又最容易导致「在我电脑上能跑」的一条。5. 避坑与排查那些让答辩翻车的细节5.1 准确率虚高测试集一跑就露馅现象训练时验证准确率 95%老师拿自己的照片一测错得离谱。原因通常是数据泄漏——同一物体的多角度照片被分到了训练和验证两边模型记住了物体而不是类别特征。解决按第 2 章的按组划分重做数据集并且留一批完全没参与训练的「外部测试图」做最终验证。5.2 部署时报 No module named torch现象在自己电脑上跑得好好的换台机器就报找不到 torch。原因requirements.txt 没写版本或者对方 Python 版本不兼容。解决明确写死版本号并在部署指南里注明 Python 版本范围。如果对方是 Windows 且装 torch 失败多半是没装对应 CUDA 版本CPU 版用pip install torch --index-url https://download.pytorch.org/whl/cpu更稳。5.3 上传图片后服务卡死或报错现象Flask 服务收到某些图片就 500。原因图片是 CMYK 模式或带透明通道Image.open后直接转 tensor 会出错。解决打开后统一.convert(RGB)这一步在第 4 章代码里已经加了但很多人会漏。另外限制上传大小app.config[MAX_CONTENT_LENGTH] 5 * 1024 * 1024防止超大图拖垮服务。5.4 报告 PPT 里数据对不上现象PPT 写的准确率和代码跑出来的不一致。原因PPT 用的是某次训练的中间结果后来重训了没更新。解决所有指标从最终保存的best_model.pth重新在测试集上跑一遍把混淆矩阵、每类准确率都导出来PPT 直接引用这份结果。混淆矩阵用 sklearn 的confusion_matrix几行就能出。5.5 打包 exe 后模型加载失败现象用 PyInstaller 打包后运行报找不到模型文件。原因打包后的路径和开发时不一样相对路径失效。解决用sys._MEIPASS处理资源路径或者干脆把模型文件放在 exe 同目录下用绝对路径读取。这个坑在答辩现场演示 exe 时特别致命提前测。6. 让这个项目更值钱的两个进阶技巧第一个技巧是把单张图片识别升级成视频流实时识别。思路很简单用 OpenCV 读摄像头帧每隔几帧送一次模型把结果画在画面上。这样答辩演示时对着垃圾桶一扫就出结果比上传图片震撼得多。核心代码就是在第 4 章 predict 逻辑外面套一层cv2.VideoCapture循环注意控制推理频率每 5 帧推理一次就够否则 CPU 扛不住。这里有个参数值得调置信度阈值设 0.6低于这个值显示「不确定」避免模型瞎猜导致演示尴尬。第二个技巧是给报告加一个「错误分析」章节。把测试集里预测错的图片挑出来按类别归类分析是背景干扰、光照问题还是类别本身容易混比如纸杯到底算可回收还是其他。这一章能体现你对数据的理解是拉开报告分数的关键。用几行代码就能导出错误样本import torch from torchvision import datasets from torch.utils.data import DataLoader test_ds datasets.ImageFolder(dataset/test, transformval_tf) loader DataLoader(test_ds, batch_size1, shuffleFalse) model.eval() wrong [] with torch.no_grad(): for i, (img, label) in enumerate(loader): pred model(img).argmax(1).item() if pred ! label.item(): path, _ test_ds.samples[i] wrong.append((path, test_ds.classes[label.item()], test_ds.classes[pred])) for w in wrong[:20]: print(f真实{w[1]} 预测{w[2]} 文件{w[0]})把这份错误清单整理进报告配上几张典型错图比单纯堆准确率数字有说服力得多。我自己做这类项目养成的习惯是模型跑完先不看准确率先看错误样本因为错误样本告诉你的信息永远比一个数字多。这两个技巧加上前面完整的训练部署流程一份课程大作业的源码、部署指南、报告 PPT 和文档就都有扎实的内容支撑了剩下的就是按部就班填进去。希望帮到你。本文还有配套的精品资源点击获取