简介:一份基于卷积神经网络的垃圾分类系统Python毕业设计资料,面向计算机相关专业正在准备毕业设计的学生,以及需要项目实战练习的初学者。项目经导师指导审定,评审得分98分,源码已本地编译调试通过,可稳定运行,适合作为课程设计、期末大作业或毕业设计的参考范本。资源以压缩包形式提供,整体约5.1MB,包含完整项目源码与配套论文PDF;源码模块覆盖垃圾分类模型的构建、训练与识别流程,论文则可用于理解系统设计思路与实现细节。目前已有156人学习下载。对于想要快速搭建深度学习图像识别项目、掌握卷积神经网络实际应用的同学,这份资源既能提供可运行的代码基础,也能借助论文厘清原理与排错思路,帮助节省选题与开发时间。
1. 基于卷积神经网络的垃圾分类系统,毕业设计到底要做成什么样
毕业设计题目拿到手,很多人第一反应是“分类模型网上有现成的,源码抄一抄就行”,实际答辩时却连训练曲线都解释不清楚。这个标题的核心不是“识别准确率有多高”,而是用 Python 把卷积神经网络在垃圾分类场景下完整落地:数据怎么整理、模型怎么搭、训练怎么收敛、最终怎么演示,以及论文里每一项实验数据从哪里来。我需要先说清楚,这类系统本质是图像分类任务,CNN 负责从图片中自动提取颜色、纹理、边缘到局部形状特征,比手工特征工程更省事。适合计算机、人工智能、物联网方向的学生,也适合想快速搭一套可演示系统的开发者。先想清楚交付物边界,后面才不会返工。
2. 垃圾分类系统的需求拆解与CNN选型依据
一个可答辩的垃圾分类系统,通常包含四部分:图片输入、特征提取、分类输出、结果展示和论文佐证。在写代码前,先把需求拆成硬性和软性。硬性需求是模型能在本地 GPU 或 CPU 上完成训练和推理;软性需求是界面要简单、训练过程要能复现、论文里要有数据支撑。不建议一上来就追求多类别,常见做法是先固定 4 类:其他垃圾、可回收物、厨余垃圾、有害垃圾,再往下细分塑料、玻璃、纸张等。类别数量影响数据集规模和数据均衡程度,这直接决定后边要不要做数据增强。
2.1 先定类别体系,再定数据集规模
垃圾类别没有一个全球统一标准,国内做毕业设计最常见的是按《生活垃圾分类标志》分四类,也有项目为了体现工作量拆成几十类。我的经验是:如果从零开始做,4 类或 6 类最稳妥。类别太少显得没工作量,类别太多容易出现“同一张图在不同数据源里标签不一致”的问题。
数据规模方面,每类图片最好不少于 300 张。300 张只是下限,如果能到 1000 张,训练效果会有明显改善。数据可以来自开源数据集,也可以自己拍照、爬取公开图片再人工清洗。注意爬取图片时要手动去掉截图和重复图,噪声会直接反映在验证集准确率上。开始训练前,先用一段小脚本确认数据分布:
from pathlib import Path data_root = Path("dataset/train") for cls_dir in sorted(data_root.iterdir()): img_count = sum(1 for p in cls_dir.rglob("*") if p.suffix.lower() in {".jpg", ".png", ".jpeg"}) print(f"{cls_dir.name}: {img_count} 张")这里用rglob("*")而不是glob("*"),是因为有的数据集会把同一类图片放在多级子目录里;后缀判断里加.jpeg是为了兼容常见图片格式。如果发现某个类明显比其他类少,就需要补数据或做数据增强,不要等到训练完再看分类报告。
2.2 为什么这类任务默认用卷积神经网络而不是全连接网络
垃圾分类看的是图像内容,不是像素点的绝对位置。全连接网络会把每个像素当作独立特征,两张内容相同但位置有微小偏移的图片,在全连接网络里可能被当成完全不同的东西。卷积神经网络通过卷积核在图像上滑动,每次只看一小块局部区域,再通过堆叠卷积层把局部特征组合成高阶特征。
这里的几个名词要摊开讲:卷积核(kernel)是一个小矩阵,比如 3×3;步长(stride)是卷积核每次滑动的距离;填充(padding)是在图像边缘补零,目的是控制输出尺寸;池化(pooling)做下采样,最常见的是最大池化,取窗口内的最大值,这样能让模型对微小位移鲁棒。一个典型结构是:输入图片 → 卷积 + ReLU → 池化 → 再卷几轮 → 展平 → 全连接输出。后文第四章给出的代码就是按这个顺序写的,卷积层负责提取特征,池化层负责缩小特征图尺寸,全连接层负责把特征映射到类别概率。
2.3 自己搭 CNN 和用 ResNet 迁移学习怎么选
如果论文想重点讲网络结构设计,就自己搭一个 3 到 4 层卷积的小网络,训练快,参数好解释;如果数据量不够,用 PyTorch 里现成的 ResNet18 做迁移学习,去掉最后的全连接层,换成自己的分类头,准确率会明显更高。我的建议是两手都做:先写一个自定义 CNN 作为 baseline,再迁移学习一个 ResNet18 做对比,论文实验部分就有两组数据了。下面是一份选型对比表,帮你在开题阶段就确定主线:
| 方案 | 数据集要求 | 训练速度 | 准确率起点 | 论文价值 | 适用场景 |
|---|---|---|---|---|---|
| 自建 3 层 CNN | 每类 300+ 张 | 快,CPU 可跑 | 75% 左右 | 结构可解释,参数好写 | 强调网络设计 |
| ResNet18 迁移学习 | 每类 100 张也能跑 | 需要 GPU 或较慢 CPU | 90% 左右 | 方法成熟,重点在调参 | 强调实际工程效果 |
注意:准确率起点是典型参考范围,不是固定值,最终论文里写的是你自己跑出来的结果。迁移学习也不是无脑换模型,输入图片要缩放成预训练模型要求的尺寸,数据标准化要跟着预训练统计量走,这些在下一章会体现。
3. 数据集预处理与PyTorch数据管线搭建
模型结构先放一边,数据管线是第一个坑。很多同学在一个文件夹里放所有图片,然后用一个 CSV 文件记录标签,跑起来没问题,但论文里不好描述,答辩也不好解释数据划分。我建议直接用“train / val 两个目录,每个类别一个子目录”的结构,和torchvision.datasets.ImageFolder、自定义 Dataset 都能无缝衔接。
3.1 数据目录怎么组织,训练集和验证集不能混
典型目录结构如下:
dataset/ ├── train/ │ ├── other/ │ ├── recyclable/ │ ├── kitchen_waste/ │ └── hazardous/ └── val/ ├── other/ ├── recyclable/ ├── kitchen_waste/ └── hazardous/训练集和验证集按目录分开,不要在代码里做随机切分时把同源图片同时分到两边。尤其自己拍照时,同一物体的多张相似照片不应同时出现在训练集和验证集,否则验证集准确率虚高,答辩时容易被问穿。常见做法是按 8:2 分层抽样,每个类别独立切,保证每个类别在验证集里都有样本。
3.2 用 torchvision.transforms 做预处理和数据增强
PyTorch 的数据预处理核心是torchvision.transforms。训练集用随机的数据增强来增加多样性,验证集只做缩放和标准化,不做随机翻转和旋转,否则评估结果不稳定。下面是一组常见配置:
from torchvision import transforms train_transforms = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(degrees=15), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) val_transforms = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])这里Resize((224, 224))把图片统一成 224×224,这是 ImageNet 预训练模型的常用输入尺寸;RandomHorizontalFlip和RandomRotation让模型看到更多样化的垃圾摆放角度,对瓶罐、纸盒这类方向性弱的类别比较有效;Normalize使用 ImageNet 的均值和标准差,目的是让输入分布接近预训练权重的假设。如果完全从零训练 CNN,Normalize仍然建议保留,可以加快收敛。
常用变换的参数需要记住,论文里往往要列一张表:
| 变换 | 参数 | 作用 | 建议 |
|---|---|---|---|
| Resize | 尺寸元组 | 统一图片大小 | 224×224 或 256×256 |
| RandomHorizontalFlip | p=0.5 | 水平翻转增强 | 对文字类图片要慎用 |
| RandomRotation | degrees=15 | 小角度旋转 | 旋转过大破坏物体结构 |
| ColorJitter | brightness/contrast | 亮度对比度变化 | 适合光线不均的实拍图 |
| ToTensor | 无 | 转为 Tensor 并缩放到 [0,1] | 必须 |
| Normalize | mean/std | 标准化 | 与预训练模型匹配 |
3.3 自定义 Dataset 读取图片并划分数据
ImageFolder可以直接用目录结构生成数据集,但自定义 Dataset 更方便在论文里展示你处理了哪些字段,也方便以后加标签平滑或辅助信息。下面是一个最小实现:
from torch.utils.data import Dataset from PIL import Image class GarbageDataset(Dataset): def __init__(self, root_dir, transform=None): self.samples = [] self.transform = transform for cls_id, cls_name in enumerate(sorted(root_dir.iterdir())): for img_path in cls_name.rglob("*"): if img_path.suffix.lower() in {".jpg", ".png", ".jpeg"}: self.samples.append((img_path, cls_id)) def __len__(self): return len(self.samples) def __getitem__(self, idx): img_path, label = self.samples[idx] img = Image.open(img_path).convert("RGB") if self.transform: img = self.transform(img) return img, labelenumerate(sorted(root_dir.iterdir()))保证了类别顺序固定,同一个类别文件夹在每次运行里拿到相同的索引,这对后续计算混淆矩阵很重要。Image.open(...).convert("RGB")强制转成 RGB,防止某些灰度图或带透明通道的 PNG 导致通道数不匹配。实例化后,用DataLoader分批取数据:
from torch.utils.data import DataLoader train_dataset = GarbageDataset("dataset/train", transform=train_transforms) val_dataset = GarbageDataset("dataset/val", transform=val_transforms) train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=2) val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=2)shuffle=True只对训练集开启,验证集保持固定顺序评测;num_workers根据电脑内存调整,Windows 上设 0 或 2,Linux 可以更高。数据管线到这里就能跑通了,下一步才是把网络结构写出来。
4. 构建卷积神经网络:卷积、池化、步长、核、填充的落地写法
数据准备好了,开始写模型。这里不会贴很长的成熟网络,而是从零搭一个能跑通、能解释的 CNN,把卷积、池化、步长、核、填充这些概念落进 PyTorch 代码里。论文的“网络结构设计”一章可以直接引用这段代码,配一张自己画的流程框图。
4.1 卷积层参数:in_channels、out_channels、kernel_size、stride、padding
nn.Conv2d的构造参数里,in_channels是输入特征图通道数,RGB 图第一次卷积是 3,后面每经过一层卷积,out_channels就是这一层卷积核的数量,同时也是输出特征图的通道数。kernel_size是卷积核尺寸,stride是步长,padding是补零圈数。输出尺寸计算公式是:
输出边长 = (输入边长 + 2 * padding - kernel_size) / stride + 1这个公式要刻进脑子里。例如输入 224×224,kernel_size=3, stride=1, padding=1,输出仍是 224×224;下一层MaxPool2d(2)把尺寸减半成 112×112。反复卷、池化之后,特征图尺寸会变成 28×28,最后展平成一维向量接全连接层。
各参数的常见取值和作用,可以直接放在论文“参数设置”小节里:
| 参数 | 常见值 | 作用 | 注意点 |
|---|---|---|---|
| in_channels | 3/16/32/64 | 输入特征图通道数 | 上一层输出通道必须一致 |
| out_channels | 16/32/64/128 | 卷积核数量 | 数量越多特征越丰富,计算量越大 |
| kernel_size | 3/5 | 卷积核尺寸 | 3×3 最常用,两个 3×3 等效一个 5×5 的感受野 |
| stride | 1/2 | 滑动步长 | stride=2 可以直接下采样 |
| padding | 0/1 | 边缘补零 | 保持尺寸时用 padding=1 |
4.2 一个能跑通的 CNN 网络结构示例
下面这个GarbageCNN是三段“卷积+池化”结构,输入 224×224 彩图,输出 4 类概率。代码量不大,CPU 也能在合理时间内跑完 15 轮:
import torch.nn as nn class GarbageCNN(nn.Module): def __init__(self, num_classes=4): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2), nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2), ) self.classifier = nn.Sequential( nn.Flatten(), nn.Linear(64 * 28 * 28, 128), nn.ReLU(inplace=True), nn.Dropout(0.5), nn.Linear(128, num_classes) ) def forward(self, x): return self.classifier(self.features(x))三个MaxPool2d(2)会把 224×224 逐步压到 112×112、56×56、28×28,所以全连接层输入维度是64 * 28 * 28。Dropout(0.5)放在全连接前,训练时随机丢弃一半神经元,有助于缓解过拟合。如果你改成 ResNet18,这段代码不需要直接保留,但理解这个结构能帮你解释迁移学习里“替换分类头”是什么意思。
4.3 损失函数、优化器和训练闭环
图像分类默认用交叉熵损失,优化器用 Adam 起步,学习率从 1e-3 开始。下面的训练循环是完整的,可以直接放到train.py里:
import torch.optim as optim model = GarbageCNN(num_classes=4) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=1e-3) for epoch in range(1, 16): model.train() total_loss = 0.0 for images, labels in train_loader: optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() total_loss += loss.item() * images.size(0) avg_loss = total_loss / len(train_dataset) print(f"epoch {epoch} | loss {avg_loss:.4f}")optimizer.zero_grad()在每次迭代前清零梯度,否则 PyTorch 会默认累积梯度;loss.backward()计算梯度,optimizer.step()更新参数。这段代码没有做验证和模型保存,更完整的版本会在下一章补齐,因为要配合调参和实验记录一起写,不然你无法判断第几轮最好。
5. 训练调参与实验记录,论文里的图表从哪来
训练不只是“跑起来”,毕业设计论文里需要训练曲线、准确率对比、混淆矩阵,所以每一轮实验都要留下记录。常见做法是每轮同时做验证集评估,保存验证集准确率最高的权重,并把 loss 和 acc 写进日志文件。这样论文里的图表就不是事后补画的,而是训练过程中自动生成的。
5.1 影响收敛的三个关键参数:学习率、Batch Size、Epoch
学习率太大,loss 会震荡;太小又收敛太慢。Adam一般从1e-3开始,如果验证准确率在 10 轮后还在 60% 以下,降到1e-4重试。Batch Size 决定每次更新参数看到的样本数,32 是平衡显存和稳定性的常用起点;显存不够就降到 16,但不要降到 1,否则梯度噪声很大。Epoch 不是一个需要拍脑袋定的参数,应该配合早停来设上限。
| 参数 | 推荐起点 | 范围说明 | 过小/过大的表现 |
|---|---|---|---|
| learning rate | 1e-3 (Adam) | 1e-4 到 1e-2 | 过小收敛慢,过大 loss 震荡 |
| batch size | 32 | 16 到 64 | 过小不稳定,过大显存不够 |
| epoch | 20 上限 | 配合早停 | epoch 太多会过拟合 |
| optimizer | Adam | SGD+momentum 也行 | Adam 鲁棒,SGD 调好了更稳 |
5.2 训练循环、验证逻辑和模型保存
下面是带验证和保存最优权重的完整代码。注意验证阶段必须用model.eval()和torch.no_grad():
from tqdm import tqdm best_acc = 0.0 patience = 0 max_patience = 5 for epoch in range(1, 21): model.train() train_loss = 0.0 for images, labels in tqdm(train_loader, desc=f"epoch {epoch}"): optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() train_loss += loss.item() * images.size(0) model.eval() correct = 0 total = 0 with torch.no_grad(): for images, labels in val_loader: outputs = model(images) _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() val_acc = 100.0 * correct / total print(f"epoch {epoch} | loss {train_loss / len(train_dataset):.4f} | val_acc {val_acc:.2f}%") if val_acc > best_acc: best_acc = val_acc torch.save(model.state_dict(), "best_model.pth") patience = 0 else: patience += 1 if patience >= max_patience: print("early stop") breaktorch.max(outputs, 1)返回每行最大值和对应索引,索引就是预测类别编号。model.eval()会关闭 Dropout,torch.no_grad()关闭梯度计算,这两行缺一不可。保存模型时只保存state_dict()而不是整个模型,答辩时更清晰,也方便迁移到部署脚本。
5.3 用训练日志自动生成论文图表
把每轮的train_loss、val_acc存到两个列表里,训练结束直接用 Matplotlib 画曲线,并保存成 PNG 放进论文。代码很简单:
import matplotlib.pyplot as plt plt.figure(figsize=(8, 4)) plt.plot(range(1, len(train_losses) + 1), train_losses, marker="o", label="train loss") plt.xlabel("epoch") plt.ylabel("loss") plt.title("Training loss curve") plt.grid(True) plt.legend() plt.savefig("figures/train_loss_curve.png", dpi=200)再画一张验证准确率曲线,用相同方式保存到figures/目录。论文里“实验与分析”一节放这两张图,再配一个分类报告或混淆矩阵,就足够支撑结论了。混淆矩阵可以用sklearn.metrics.confusion_matrix对验证集统一预测后生成,注意类别顺序要和训练时一致,否则横纵坐标对不上。如果你使用迁移学习,把同样的数据丢给 ResNet18 再跑一遍,得到第二条曲线,两张图对比,论文技术含量直接上来了。
6. 用 Gradio 快速搭出可演示页面,再把源码和论文PDF整理成附件
6.1 用 Gradio 把模型包装成上传图片即出分类结果
演示环节不要写复杂 Web 后端,用 Gradio 几十行就能搞定。Gradio 会自动生成上传控件和结果展示,适合开题、中期、答辩现场直接运行。在app.py里加载训练好的模型和val_transforms,定义预测函数后启动:
import gradio as gr import torch model = GarbageCNN(num_classes=4) model.load_state_dict(torch.load("best_model.pth", map_location="cpu")) model.eval() labels = ["other", "recyclable", "kitchen_waste", "hazardous"] def predict(img): tensor = val_transforms(img).unsqueeze(0) with torch.no_grad(): logits = model(tensor) pred_idx = torch.argmax(logits, dim=1).item() return {labels[i]: float(logits[0, i]) for i in range(len(labels))} gr.Interface( fn=predict, inputs=gr.Image(type="pil"), outputs=gr.Label(num_top_classes=4), title="垃圾分类识别" ).launch(share=False)val_transforms(img).unsqueeze(0)把单张图片补成 batch 维度,share=False是在本地演示,如果需要手机在同局域网调试可以加server_port=7860固定端口。输出用gr.Label显示每个类别的置信度,比只显示字符串更能体现“模型不是乱猜”。
6.2 源码结构和论文PDF整理的常见做法
源码建议按功能拆文件:data_loader.py负责 Dataset 和数据增强,model.py放GarbageCNN,train.py放训练循环,app.py放 Gradio 界面,requirements.txt写死主要依赖版本。论文 PDF 在封面和目录之外,一定要包含:数据预处理过程、网络结构图、训练超参数表、loss/acc 曲线、验证集混淆矩阵和演示截图。提交时把源码、训练好的best_model.pth、论文 PDF 放在一个文件夹里,README 里写明“先装依赖,再跑python train.py,然后python app.py”三步操作,答辩老师照着就能完整复现。
本文还有配套的精品资源,点击获取