简介:这份资源面向计算机类毕业设计与课程作业场景,聚焦深度学习中的垃圾分类实战任务,适合具备一定Python基础、正在寻找完整项目参考的本科生或自学者。包内共6个文件,以2个py源码、2个pt权重文件为主,另含1个zip压缩包与1份md说明文档,整体约4MB,体量轻便,便于快速部署与二次修改。源码部分涵盖模型定义与预测推理脚本,权重文件可直接加载用于验证CNN与ResNet两种网络结构的分类效果,md文档则提供项目说明与使用指引。已有77人学习下载,说明该方案在同类毕设选题中具备一定参考价值。读者可借此获得一套可运行的垃圾分类识别流程,理解卷积神经网络与残差网络在图像分类任务中的差异,并在此基础上调整网络结构、替换数据集或补充训练脚本,完成从模型搭建到推理验证的完整闭环,为毕设答辩或课程提交提供扎实的代码与实验支撑。
1. 垃圾分类深度学习毕设包:从 CNN 到 ResNet 的完整落地路径
很多同学做毕设时卡在同一个地方:算法原理背得滚瓜烂熟,真到要交一个能跑起来、能演示、能写进论文的系统时,却不知道从哪下手。这个压缩包解决的正是这个问题——它把垃圾分类这个经典课题的完整链路打包好了,包含model.py、model1.pt、model.pt、predict.py和README.md,覆盖了从模型定义到推理预测的全流程。垃圾分类本身是深度学习图像识别的入门级任务,但它的工程结构和大项目完全一致:数据预处理、模型搭建、训练保存、加载推理。适合正在做深度学习毕设或课程作业的本科生,也适合想找一个干净模板快速验证自己想法的开发者。包里同时提供了 CNN 和 ResNet 两条技术路线,你可以根据算力和精度要求灵活选择。
2. 拆开压缩包:文件结构与两条模型路线的选型逻辑
拿到一个源码包,第一件事不是急着跑predict.py,而是先搞清楚每个文件在系统里扮演什么角色。这个包的结构很典型,没有多余的目录嵌套,说明作者是按“最小可运行系统”的思路组织的。
2.1 文件清单与各自职责
| 文件 | 类型 | 作用 |
|---|---|---|
model.py | 源码 | 定义 CNN 和 ResNet 两种网络结构 |
model.pt | 权重 | 训练好的模型参数文件(主模型) |
model1.pt | 权重 | 另一个版本的模型参数(备用/对比) |
predict.py | 源码 | 加载权重,对单张图片做推理 |
README.md | 文档 | 环境依赖和基本使用说明 |
Graduation Design validation.zip | 压缩包 | 验证集数据或相关验证脚本 |
model.py是整个项目的核心,它决定了网络长什么样。model.pt和model1.pt是两个已经训练好的权重文件,意味着你不需要从零训练就能直接跑推理。predict.py是入口脚本,负责把图片喂给模型并输出分类结果。这种“定义-权重-推理”三件套的结构,在深度学习项目里是最常见的组织方式。
2.2 CNN 和 ResNet 该怎么选
包里的model.py同时提供了 CNN 和 ResNet 两种结构,这不是凑数,而是对应了两种不同的使用场景。
CNN 版本通常是几层卷积加池化的堆叠,参数量小,训练快,适合以下情况:你的电脑没有独立显卡,只能用 CPU 跑;或者你需要在论文里展示一个“从零搭建”的网络结构,体现你对卷积核、池化、全连接层的理解。CNN 的缺点是层数一深就容易出现梯度消失,精度上限有限。
ResNet 版本引入了残差连接,核心思想是让网络学习输入和输出之间的“残差”而不是直接学习映射。这样做的好处是梯度可以通过跳跃连接直接回传,使得训练几十层甚至上百层的网络成为可能。垃圾分类这种任务,类别之间的区分度主要在颜色、纹理和形状上,ResNet 的深层特征提取能力明显更强。如果你有 GPU 可用,或者追求更高的分类准确率,直接用 ResNet 版本。
提示:如果你不确定选哪个,先跑
predict.py看默认加载的是哪个权重文件,再决定要不要切换。
2.3 环境依赖与快速验证
在跑任何代码之前,先把环境配好。这个项目依赖 PyTorch 和 torchvision,另外需要 Pillow 做图像读取。常见做法是用 conda 建一个独立环境,避免和系统里的其他包冲突。
# 创建并激活虚拟环境 conda create -n garbage_cls python=3.9 -y conda activate garbage_cls # 安装核心依赖 pip install torch torchvision pillow numpy安装完成后,进入项目目录,直接运行推理脚本验证环境是否正常:
python predict.py --image test.jpg如果predict.py没有提供命令行参数接口,那就需要打开脚本,找到图片路径的变量,手动改成你要测试的图片路径。这一步的目的是确认模型能加载、图片能读取、输出能打印。跑通了再往下做定制化修改。
3. 模型定义与推理脚本:从 model.py 到 predict.py 的代码拆解
环境跑通只是第一步,真正要拿这个包做毕设,你得能看懂model.py里网络是怎么搭的,以及predict.py是怎么把图片变成分类结果的。这一章把这两个文件的核心逻辑拆开讲。
3.1 model.py 里的 CNN 结构
CNN 版本的典型写法是继承nn.Module,在__init__里定义层,在forward里定义数据流向。下面是一个符合这个项目场景的 CNN 结构示例:
import torch.nn as nn import torch.nn.functional as F class SimpleCNN(nn.Module): def __init__(self, num_classes=4): super(SimpleCNN, self).__init__() # 第一层卷积:输入3通道RGB,输出32通道特征图,卷积核3x3 self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1) # 第二层卷积:32通道扩展到64通道 self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1) # 第三层卷积:64通道扩展到128通道 self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1) # 最大池化:2x2窗口,步长2,特征图尺寸减半 self.pool = nn.MaxPool2d(2, 2) # 全连接层:128*28*28是经过三次池化后的特征维度 self.fc1 = nn.Linear(128 * 28 * 28, 256) self.fc2 = nn.Linear(256, num_classes) self.dropout = nn.Dropout(0.5) def forward(self, x): # 每层卷积后接ReLU激活,再池化 x = self.pool(F.relu(self.conv1(x))) # 224->112 x = self.pool(F.relu(self.conv2(x))) # 112->56 x = self.pool(F.relu(self.conv3(x))) # 56->28 x = x.view(-1, 128 * 28 * 28) # 展平 x = F.relu(self.fc1(x)) x = self.dropout(x) # 防止过拟合 x = self.fc2(x) return x这段代码的逻辑很直白:三层卷积逐级提取特征,每层后面跟池化把特征图尺寸缩小,最后展平送进全连接层做分类。num_classes=4对应垃圾分类的四个类别(通常是可回收、厨余、有害、其他)。padding=1保证卷积后特征图尺寸不变,只有池化才会降维。Dropout(0.5)是训练时的正则化手段,推理时会自动关闭。
参数调整的关键点:如果你用的图片尺寸不是 224×224,全连接层的输入维度需要重新计算。计算方法是:输入尺寸经过三次池化(每次除以2),得到最终特征图边长,再乘以通道数。比如输入 128×128,三次池化后是 16×16,全连接层就是128 * 16 * 16。
3.2 ResNet 版本的加载方式
ResNet 版本通常不从头定义网络,而是用 torchvision 提供的预训练模型做迁移学习。这样做的好处是预训练权重已经在大规模数据集上学到了通用特征,你只需要替换最后的全连接层,用垃圾分类数据微调即可。
import torchvision.models as models import torch.nn as nn def get_resnet_model(num_classes=4): # 加载ResNet18预训练权重 model = models.resnet18(pretrained=True) # 冻结前面的层,只训练最后的分类层 for param in model.parameters(): param.requires_grad = False # 替换全连接层:ResNet18的fc输入是512维 model.fc = nn.Linear(512, num_classes) return modelpretrained=True表示加载在 ImageNet 上训练好的权重。requires_grad = False冻结了卷积层的参数,训练时只更新fc层。如果你的数据集比较小(几千张以内),这种做法能有效防止过拟合。如果数据量够大,可以把requires_grad设为True做全网络微调,但学习率要调小。
3.3 predict.py 的推理流程
推理脚本的核心步骤是:加载模型结构 → 加载权重 → 图片预处理 → 前向传播 → 输出类别。下面是一个标准的推理流程:
import torch from PIL import Image from torchvision import transforms from model import SimpleCNN # 或 get_resnet_model # 定义预处理:和训练时保持一致 transform = transforms.Compose([ transforms.Resize((224, 224)), # 统一尺寸 transforms.ToTensor(), # 转成Tensor,归一化到[0,1] transforms.Normalize( # 标准化,均值方差用ImageNet的 mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] ) ]) # 加载模型 model = SimpleCNN(num_classes=4) model.load_state_dict(torch.load('model.pt', map_location='cpu')) model.eval() # 切换到推理模式,关闭Dropout和BatchNorm的更新 # 读取并预处理图片 image = Image.open('test.jpg').convert('RGB') input_tensor = transform(image).unsqueeze(0) # 增加batch维度 # 推理 with torch.no_grad(): # 关闭梯度计算,节省内存 output = model(input_tensor) _, predicted = torch.max(output, 1) classes = ['可回收', '厨余', '有害', '其他'] print(f'预测结果:{classes[predicted.item()]}')model.eval()和torch.no_grad()是推理时必加的两行。前者关闭 Dropout 和 BatchNorm 的训练行为,后者关闭梯度计算。unsqueeze(0)是因为模型期望的输入是[batch, channel, height, width],单张图片只有后三个维度,需要手动补一个 batch 维度。map_location='cpu'保证在没有 GPU 的机器上也能加载权重。
注意:预处理中的
Normalize参数必须和训练时一致。如果训练时没用标准化,推理时也不要加,否则输入分布不匹配,预测结果会完全错误。
4. 避坑与排查:权重加载、类别映射和预处理不一致的常见翻车
这个包虽然结构简单,但实际跑起来翻车的概率不低。下面是我在帮别人看毕设代码时最常遇到的几个问题,按“现象 → 原因 → 解决”整理。
4.1 权重加载报错:Missing key(s) in state_dict
现象:运行predict.py时抛出RuntimeError: Error(s) in loading state_dict for SimpleCNN: Missing key(s)...。
原因:model.py里的网络结构和model.pt保存时的结构不一致。常见情况是作者训练时用了 ResNet,但推理时实例化的是 CNN,或者改了全连接层的输出维度。
解决:先打印权重文件的键名,和当前模型对比:
state_dict = torch.load('model.pt', map_location='cpu') for k, v in state_dict.items(): print(k, v.shape)对比model.py里定义的层名,找到不匹配的地方。如果是全连接层维度不对,修改num_classes或fc层的输入维度。如果是层名对不上,说明模型结构定义有差异,需要根据权重文件的键名反推网络结构。
4.2 预测结果全是同一类
现象:不管输入什么图片,输出的类别都一样。
原因:预处理不一致是最常见的原因。训练时用了Normalize,推理时没加;或者训练时图片是 BGR 格式,推理时用了 RGB。另一个可能是权重文件本身就没训练好。
解决:先检查predict.py里的transform是否和训练脚本一致。如果找不到训练脚本,可以尝试去掉Normalize再跑一次,看结果是否变化。如果还是全同一类,用torch.load检查权重里的数值是否正常(有没有大量零值或 NaN)。
4.3 CUDA out of memory
现象:在有 GPU 的机器上跑推理时报显存不足。
原因:predict.py默认把模型和输入都放到了 GPU 上,但显存被其他进程占用了,或者图片尺寸太大。
解决:在加载权重时加map_location='cpu',强制用 CPU 推理。或者在推理前加torch.cuda.empty_cache()清理缓存。如果必须用 GPU,把输入图片的 batch size 设为 1,不要一次加载多张。
4.4 类别标签对不上
现象:模型输出0,但classes列表里0对应的类别和实际不符。
原因:训练时类别的索引顺序和推理时classes列表的顺序不一致。比如训练时0是可回收,推理时写成了0是厨余。
解决:找到训练时的类别映射关系。通常在训练脚本里会有一个class_to_idx的字典,或者在数据加载时用ImageFolder会自动按文件夹名排序生成索引。以那个顺序为准,不要凭记忆写。
4.5 model.pt 和 model1.pt 搞混
现象:加载model.pt效果正常,加载model1.pt效果很差。
原因:两个权重文件对应不同的模型结构或不同的训练轮数。model1.pt可能是早期保存的检查点,或者对应的是 CNN 而不是 ResNet。
解决:分别用两个权重跑同一张测试图,对比输出。如果model1.pt效果差,检查它对应的模型结构是否和当前实例化的模型一致。在README.md里通常会说明哪个是最终版本。
5. 从能跑到能用:替换数据集、调整类别数和验证模型效果的实操技巧
把预训练权重跑通只是验证了环境,真正要做自己的毕设,你得把模型适配到自己的数据集上。这一章讲怎么替换数据、调整类别数,以及怎么验证模型是不是真的学到了东西。
5.1 用自己的数据重新训练
假设你的数据集按文件夹组织,每个类别一个文件夹:
dataset/ ├── recyclable/ ├── kitchen_waste/ ├── hazardous/ └── other/用ImageFolder加载数据,然后微调 ResNet:
from torchvision import datasets, transforms from torch.utils.data import DataLoader # 数据增强:训练时随机裁剪、翻转,增加泛化能力 train_transform = transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) train_dataset = datasets.ImageFolder('dataset/', transform=train_transform) train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True) # 类别数自动从数据集获取 num_classes = len(train_dataset.classes) print(f'类别:{train_dataset.classes}') # 打印索引映射关系ImageFolder会按文件夹名的字母顺序生成索引,这个顺序就是推理时classes列表应该用的顺序。batch_size=32是常见起点,显存不够就降到 16 或 8。shuffle=True打乱训练顺序,防止模型学到数据排列的规律。
5.2 训练循环的关键参数
import torch.optim as optim device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = get_resnet_model(num_classes).to(device) criterion = nn.CrossEntropyLoss() # 只优化fc层的参数,学习率设大一点 optimizer = optim.Adam(model.fc.parameters(), lr=0.001) for epoch in range(10): model.train() running_loss = 0.0 for images, labels in train_loader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() print(f'Epoch {epoch+1}, Loss: {running_loss/len(train_loader):.4f}') # 保存权重 torch.save(model.state_dict(), 'my_model.pt')CrossEntropyLoss是分类任务的标准损失函数,内部包含了 softmax。Adam优化器对学习率不敏感,lr=0.001是安全起点。如果只训练fc层,10 个 epoch 通常就能收敛。如果做全网络微调,学习率要降到0.0001左右,否则预训练权重会被破坏。
5.3 验证模型效果的三个检查点
训练完不能只看 loss 下降就完事,得做实际验证。
第一,看混淆矩阵。用验证集跑一遍,统计每个类别的正确率和误判方向。如果“厨余”大量被误判为“其他”,说明这两个类别的特征区分度不够,需要增加数据或调整网络。
第二,看单张图片的置信度。torch.max返回的不仅是类别索引,还有置信度分数。如果所有图片的置信度都在 0.3 左右,说明模型没学到东西,大概率是预处理或标签映射出了问题。
第三,用训练时没见过的图片测试。从网上找几张垃圾分类的图片,手动跑predict.py,看输出是否合理。这一步能暴露过拟合问题——训练集准确率很高但新图片全错。
5.4 类别数调整的注意事项
如果你把四分类改成二分类(比如只分可回收和其他),需要改两个地方:model.py里的num_classes参数,以及推理时的classes列表。如果用的是 ResNet,model.fc = nn.Linear(512, num_classes)会自动适配。但要注意,如果加载的是原来四分类的权重,全连接层的形状不匹配,需要重新训练或者只加载卷积层的权重。
# 只加载卷积层权重,跳过fc层 pretrained_dict = torch.load('model.pt') model_dict = model.state_dict() # 过滤掉形状不匹配的键 pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict and v.shape == model_dict[k].shape} model_dict.update(pretrained_dict) model.load_state_dict(model_dict)这段代码的作用是:把预训练权重里和当前模型形状匹配的层加载进来,不匹配的层(比如fc)跳过,用随机初始化。这样可以在小数据集上快速启动训练。
从那以后我每次拿到一个深度学习源码包,都强制先跑一遍predict.py确认推理链路通畅,再动model.py改结构。顺序反了的话,出了问题你分不清是权重的问题还是代码改错了。希望帮到你。
本文还有配套的精品资源,点击获取