简介:这是一套面向高校学生与初学者的垃圾分类深度学习实战项目源码,基于Python与主流深度学习框架实现,可直接用于毕业设计、期末大作业或课程设计场景。项目已通过教师指导与验收,属于高分完整方案,对零基础读者也较为友好,能帮助快速理解图像分类任务的完整落地流程。压缩包共收录6319个文件,约18.98MB,其中以py源码与pyc编译文件为主体,辅以mo、po多语言资源、html与js前端页面、png与svg图像素材,以及txt说明、css样式和少量exe工具,整体结构接近可直接运行的工程形态。目前已有338人学习下载,说明该方案在同类选题中具备一定参考热度。读者可从中获取完整的垃圾分类识别代码、模型训练与推理脚本、界面展示页面及配套资源文件,便于对照复现实验、梳理项目目录组织方式,并在此基础上进行功能扩展或二次开发。
1. 从一份能跑通的垃圾分类源码说起:它到底解决了什么
如果你正在为毕业设计发愁,或者想找一个能真正跑起来、有完整训练流程的深度学习项目练手,这份基于 Python 与深度学习的垃圾分类系统源码值得先看一眼。它不是那种只丢几个.py文件、连数据集路径都写死的半成品,而是一套从数据预处理、模型训练、评估到推理预测都能串起来的完整工程。垃圾分类本身是个典型的图像分类任务,类别通常包括可回收物、厨余垃圾、有害垃圾和其他垃圾四类,正好覆盖了 CNN 从入门到落地的全部关键环节。你拿到手之后,最直接的用途就是改一改数据集路径、调一调超参数,就能跑出自己的分类模型;往深了说,这套代码的结构足够清晰,方便你在上面加注意力机制、换 backbone、做消融实验,支撑起一篇像样的毕业论文。适合谁?适合已经装好 Python 环境、知道pip install怎么用、但还没完整走过一遍深度学习项目流程的人。下面我就按实际拆包复现的顺序,把这份资源从里到外讲一遍。
2. 拆开压缩包先看什么:目录结构与技术栈选型
2.1 拿到源码后的第一轮文件排查
解压之后别急着python train.py,先花五分钟把目录结构过一遍。这类毕业设计项目通常包含以下几个核心部分:data/或dataset/放原始图片,按类别分文件夹;models/存网络定义;utils/放数据加载和预处理脚本;根目录下有train.py、predict.py、evaluate.py这几个入口文件;可能还有一个config.py或args.py统一管理超参数。我一般会先执行下面这段命令,把文件树和文件大小摸清楚:
# 查看目录结构,排除缓存文件 find . -type f -not -path "./.git/*" -not -name "*.pyc" | head -50 # 统计各类图片数量,确认数据集是否完整 for dir in data/*/; do echo -n "$dir: " find "$dir" -type f \( -name "*.jpg" -o -name "*.png" -o -name "*.jpeg" \) | wc -l done第一段命令帮你快速定位入口脚本和配置文件的位置,第二段命令按类别统计图片数量。如果某个类别只有几十张图,那训练时大概率会严重过拟合,需要先做数据增强或者补充样本。这一步看着简单,但我见过太多人直接开训,跑了半天才发现某个类别文件夹是空的,血泪经验。
2.2 技术栈选型:为什么是 PyTorch + CNN
这份源码大概率用的是 PyTorch 而不是 TensorFlow,原因很实际:PyTorch 的动态图机制对调试更友好,print一下 tensor 的 shape 就能定位问题,而静态图框架报错信息往往让人摸不着头脑。网络结构方面,垃圾分类这种四分类任务,用 ResNet18 或 MobileNetV2 做迁移学习是最稳妥的选择。ResNet18 参数量约 1100 万,在 ImageNet 上预训练过的权重拿来微调,通常 20 到 30 个 epoch 就能收敛到一个不错的精度。MobileNetV2 更轻量,适合你想部署到边缘设备或者对推理速度有要求的场景。
选型理由可以归结为三点:第一,数据量通常不大,从零训练一个深层网络必然过拟合,迁移学习是正解;第二,垃圾分类的类间差异比较明显,不需要特别复杂的网络结构;第三,ResNet 的残差连接能有效缓解梯度消失,训练过程更稳定。如果你在论文里需要对比实验,可以再加一个 VGG16 或者自己搭一个简单的四层 CNN 作为 baseline,这样论文的实验部分会更充实。
2.3 环境配置与依赖安装的实操步骤
环境配置是新手翻车最多的地方。我建议用 conda 建一个独立环境,避免和系统 Python 打架:
# 创建虚拟环境,指定 Python 3.8 或 3.9 conda create -n garbage_cls python=3.9 -y conda activate garbage_cls # 安装 PyTorch,根据你的 CUDA 版本选择对应命令 # 如果没有 GPU,用 CPU 版本即可 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装其他依赖 pip install numpy pandas matplotlib opencv-python pillow tqdm scikit-learn这里有几个参数需要留意:python=3.9是我实测兼容性最好的版本,3.10 以上有时会遇到某些库还没适配的问题;PyTorch 的安装命令要根据你的显卡驱动来选,cu118对应 CUDA 11.8,如果你不确定,直接去 PyTorch 官网用它的选择器生成命令最保险。安装完之后跑一句python -c "import torch; print(torch.cuda.is_available())",返回True说明 GPU 可用,返回False就是 CPU 模式,训练速度会慢很多,但代码本身不用改。
提示:如果你用的是 VSCode,记得在右下角切换 Python 解释器到刚创建的 conda 环境,否则终端里装好了、编辑器里还是找不到包。
3. 数据管道与模型训练:从图片到可用的分类器
3.1 数据预处理与增强策略
垃圾分类数据集通常存在两个问题:类别不平衡和样本量不足。源码里一般会用torchvision.transforms做在线增强,常见组合是随机裁剪、水平翻转、颜色抖动。我一般会在这个基础上再加一个随机旋转,因为垃圾图片的拍摄角度往往很随意。下面是一段典型的数据加载代码:
import torch from torchvision import transforms, datasets from torch.utils.data import DataLoader, WeightedRandomSampler # 训练集增强:随机裁剪 + 翻转 + 旋转 + 颜色抖动 train_transform = transforms.Compose([ transforms.RandomResizedCrop(224, scale=(0.7, 1.0)), # 随机裁剪并缩放到224 transforms.RandomHorizontalFlip(p=0.5), # 50%概率水平翻转 transforms.RandomRotation(15), # 随机旋转±15度 transforms.ColorJitter(brightness=0.2, contrast=0.2), # 亮度对比度扰动 transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], # ImageNet标准均值 std=[0.229, 0.224, 0.225]) # ImageNet标准方差 ]) # 验证集只做缩放和归一化,不做增强 val_transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) # 加载数据集,ImageFolder要求按类别分文件夹 train_dataset = datasets.ImageFolder('data/train', transform=train_transform) val_dataset = datasets.ImageFolder('data/val', transform=val_transform) # 处理类别不平衡:给少数类更高采样权重 targets = [s[1] for s in train_dataset.samples] class_counts = torch.bincount(torch.tensor(targets)) weights = 1.0 / class_counts.float() sample_weights = weights[targets] sampler = WeightedRandomSampler(sample_weights, num_samples=len(sample_weights), replacement=True) train_loader = DataLoader(train_dataset, batch_size=32, sampler=sampler, num_workers=4) val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=4)这段代码的关键点有三个:RandomResizedCrop的scale参数控制裁剪区域占原图的比例,设太小会把垃圾物体裁掉,设太大增强效果不明显,0.7 到 1.0 是比较安全的范围;Normalize用的均值和方差必须和预训练模型一致,否则迁移学习的效果会打折扣;WeightedRandomSampler是解决类别不平衡的利器,它让少数类样本在每批中被抽到的概率更高,比简单的过采样更不容易过拟合。num_workers设成 4 还是 8 取决于你机器的 CPU 核心数,设太大反而会因为进程切换拖慢速度。
3.2 模型构建与迁移学习微调
模型部分的核心思路是加载预训练权重,替换最后的全连接层,然后分阶段微调。源码里可能直接写死了 ResNet18,但你可以根据自己需求换成 ResNet50 或 EfficientNet。下面是我常用的写法:
import torch.nn as nn from torchvision import models def build_model(num_classes=4, backbone='resnet18', pretrained=True): if backbone == 'resnet18': model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1 if pretrained else None) in_features = model.fc.in_features # 替换最后的全连接层,输出类别数改为4 model.fc = nn.Sequential( nn.Dropout(0.3), # 防止过拟合 nn.Linear(in_features, num_classes) ) elif backbone == 'mobilenet_v2': model = models.mobilenet_v2(weights=models.MobileNet_V2_Weights.IMAGENET1K_V1 if pretrained else None) in_features = model.classifier[1].in_features model.classifier = nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, num_classes) ) return model # 分阶段微调:先冻结骨干网络,只训练分类头 model = build_model(num_classes=4, backbone='resnet18') for param in model.parameters(): param.requires_grad = False for param in model.fc.parameters(): param.requires_grad = True # 第一阶段优化器只更新分类头参数 optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-3)这里的分阶段策略值得展开说:第一阶段冻结骨干网络,只训练新加的全连接层,学习率可以设大一点(1e-3),因为随机初始化的分类头需要快速收敛;训练 5 个 epoch 之后,解冻骨干网络的后几层,用更小的学习率(1e-4 或 1e-5)做微调,避免把预训练学到的通用特征破坏掉。Dropout(0.3)是防止过拟合的常规操作,如果你的数据集特别小(每类不到 200 张),可以加到 0.5。损失函数用CrossEntropyLoss就够了,如果类别不平衡特别严重,可以给它传weight参数,和上面的采样器二选一即可,不用同时上。
3.3 训练循环与关键参数设置
训练循环的代码看起来都差不多,但魔鬼在细节里。下面这段是我会实际用的版本:
import torch from tqdm import tqdm device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = model.to(device) criterion = nn.CrossEntropyLoss() scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30) best_acc = 0.0 for epoch in range(30): model.train() running_loss = 0.0 for images, labels in tqdm(train_loader, desc=f'Epoch {epoch+1}'): 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() # 验证阶段 model.eval() correct, total = 0, 0 with torch.no_grad(): for images, labels in val_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() val_acc = correct / total scheduler.step() # 保存最佳模型 if val_acc > best_acc: best_acc = val_acc torch.save(model.state_dict(), 'best_model.pth') print(f'Best model saved with acc: {best_acc:.4f}')几个参数需要根据实际情况调整:batch_size设 32 是 8GB 显存下的安全值,显存够大可以上 64;学习率调度器用余弦退火比固定学习率效果好,T_max设成总 epoch 数;保存模型时只存state_dict而不是整个模型,这样加载时更灵活。如果你发现训练 loss 一直在降但验证 acc 不涨,大概率是过拟合了,可以加数据增强、加 Dropout、或者提前停止。反过来,如果训练 loss 都不降,先检查学习率是不是太大,或者数据标签有没有搞错。
4. 推理部署与效果验证:模型训完之后怎么用
4.1 单张图片推理与批量预测
训练完拿到best_model.pth之后,下一步就是写推理脚本。很多人训完模型就不知道怎么办了,其实推理代码比训练简单得多:
from PIL import Image import torch from torchvision import transforms # 加载模型结构,再加载权重 model = build_model(num_classes=4, backbone='resnet18', pretrained=False) model.load_state_dict(torch.load('best_model.pth', map_location='cpu')) model.eval() # 推理预处理,和验证集保持一致 infer_transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) class_names = ['可回收物', '厨余垃圾', '有害垃圾', '其他垃圾'] def predict(image_path): image = Image.open(image_path).convert('RGB') tensor = infer_transform(image).unsqueeze(0) # 增加batch维度 with torch.no_grad(): outputs = model(tensor) probabilities = torch.softmax(outputs, dim=1) confidence, predicted = torch.max(probabilities, 1) return class_names[predicted.item()], confidence.item() # 测试 label, conf = predict('test.jpg') print(f'预测类别: {label}, 置信度: {conf:.4f}')这里有个容易忽略的点:load_state_dict之前必须先实例化模型结构,而且pretrained要设成False,否则会去下载预训练权重,浪费时间。map_location='cpu'是防止在没有 GPU 的机器上加载时报错。推理时的预处理必须和验证集完全一致,包括Resize的大小和Normalize的参数,差一点都会导致精度下降。torch.softmax把输出转成概率分布,置信度低于 0.6 的时候我一般会提示“结果不确定”,这在演示系统里很实用。
4.2 评估指标与混淆矩阵
只看准确率是不够的,尤其是类别不平衡的时候。我习惯在验证集上跑一遍完整的评估,输出分类报告和混淆矩阵:
from sklearn.metrics import classification_report, confusion_matrix import seaborn as sns import matplotlib.pyplot as plt model.eval() all_preds, all_labels = [], [] with torch.no_grad(): for images, labels in val_loader: images = images.to(device) outputs = model(images) _, predicted = torch.max(outputs, 1) all_preds.extend(predicted.cpu().numpy()) all_labels.extend(labels.numpy()) # 打印每个类别的精确率、召回率、F1 print(classification_report(all_labels, all_preds, target_names=class_names)) # 画混淆矩阵 cm = confusion_matrix(all_labels, all_preds) sns.heatmap(cm, annot=True, fmt='d', xticklabels=class_names, yticklabels=class_names) plt.xlabel('预测') plt.ylabel('真实') plt.savefig('confusion_matrix.png', dpi=150, bbox_inches='tight')classification_report会给出每个类别的 precision、recall 和 f1-score,如果某个类别的 recall 特别低,说明模型把很多该类样本漏判了,可能是样本太少或者特征不明显。混淆矩阵能直观看出模型把哪两类搞混了,比如“厨余垃圾”和“其他垃圾”经常互相误判,这时候可以考虑针对这两类补充训练数据,或者加一个二分类器做后处理。这些分析写进论文里,比单纯报一个准确率有说服力得多。
4.3 用 Grad-CAM 做可视化解释
答辩的时候老师经常会问“模型到底学到了什么”,这时候 Grad-CAM 热力图就是你的后悔药。它能把模型关注区域高亮出来,让你看到模型是根据哪些像素做判断的:
from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image import numpy as np import cv2 # 选择目标层,ResNet18的最后一个卷积层 target_layers = [model.layer4[-1]] cam = GradCAM(model=model, target_layers=target_layers) # 准备输入 rgb_img = cv2.imread('test.jpg') rgb_img = cv2.resize(rgb_img, (224, 224)) input_tensor = infer_transform(Image.fromarray(cv2.cvtColor(rgb_img, cv2.COLOR_BGR2RGB))).unsqueeze(0) # 生成热力图 grayscale_cam = cam(input_tensor=input_tensor)[0, :] visualization = show_cam_on_image(rgb_img / 255.0, grayscale_cam, use_rgb=True) cv2.imwrite('gradcam_result.jpg', cv2.cvtColor(visualization, cv2.COLOR_RGB2BGR))target_layers选最后一层卷积是因为它保留了最多的空间信息,选太浅的层热力图会很散。生成的热力图叠加在原图上,红色区域就是模型最关注的地方。如果模型关注的是垃圾物体本身,说明学对了;如果关注的是背景或者水印,那就要检查数据集里有没有引入偏差。这个工具在论文里放一张图,比写一堆文字解释都管用。
5. 避坑与常见问题排查
5.1 训练 loss 不下降或震荡严重
现象:训练几个 epoch 后 loss 一直在 2.0 附近徘徊,或者上下剧烈跳动。原因通常是学习率设太大了,或者数据标签有问题。先检查学习率,迁移学习第一阶段用 1e-3 是上限,如果 loss 炸了降到 1e-4 试试。然后抽查一批数据,把图片和标签打印出来看看是否对应,我遇到过有人把类别文件夹名字写错导致标签全乱的。解决:用torch.optim.lr_scheduler.ReduceLROnPlateau做自适应降学习率,同时写一个check_dataset.py脚本随机可视化 16 张图确认标签。
5.2 验证集准确率远低于训练集
现象:训练集准确率 95%,验证集只有 60%。这是典型的过拟合。原因可能是数据增强不够、模型参数量太大、或者训练集和验证集分布不一致。解决:先加数据增强,RandomResizedCrop、ColorJitter、RandomRotation都加上;然后加 Dropout 和权重衰减(weight_decay=1e-4);如果还不行,换更小的模型,比如从 ResNet50 换回 ResNet18。另外检查一下验证集的图片是不是从训练集里漏过来的,虽然听起来离谱,但我确实见过。
5.3 GPU 显存不足报 CUDA out of memory
现象:训练到一半突然报RuntimeError: CUDA out of memory。原因可能是 batch_size 太大,或者没有释放中间变量。解决:先把batch_size减半,从 32 降到 16;然后在训练循环里加torch.cuda.empty_cache(),虽然它不能完全解决问题,但能缓解碎片化;如果还不行,用torch.cuda.amp做混合精度训练,显存占用能降差不多一半。代码改动很小,在loss.backward()前加一个scaler.scale(loss).backward()就行。
5.4 推理时预测结果全是同一类
现象:不管输入什么图片,模型都输出“其他垃圾”。原因通常是加载权重时没有正确匹配,或者推理预处理和训练时不一致。解决:先确认load_state_dict没有报错,如果有unexpected keys说明模型结构对不上;然后检查推理的Normalize参数是不是和训练时一样;最后用训练集里的一张图做推理,如果训练集图片都预测错,那基本是权重加载的问题。我一般会在推理脚本开头加一句print(model.fc)确认分类头结构。
5.5 数据加载速度慢导致 GPU 利用率低
现象:nvidia-smi显示 GPU 利用率只有 20% 到 30%,训练一个 epoch 要很久。原因通常是num_workers设太小,或者图片尺寸太大。解决:把num_workers设成 CPU 核心数的一半左右,比如 8 核 CPU 设 4;如果图片原始分辨率是 4000x3000,先在预处理阶段统一缩放到 256x256 再存一份,能大幅减少 IO 时间。另外把数据放在 SSD 上而不是机械硬盘,这个提升立竿见影。
6. 把项目改出花:从及格到优秀的三个进阶技巧
第一个技巧是换 backbone 做对比实验。毕业设计如果只用一个 ResNet18,实验部分会显得单薄。你可以把build_model函数扩展一下,支持efficientnet_b0、resnet50、mobilenet_v2三种,然后在同一份数据上跑对比,用表格呈现准确率、参数量、推理时间三个指标。这样论文的第四章就有了扎实的内容。具体做法是在torchvision.models里找对应的类,替换分类头的代码逻辑是一样的,注意efficientnet的分类头叫classifier,resnet叫fc,改的时候别搞混。
第二个技巧是加一个简单的 Web 演示界面。用 Gradio 或者 Streamlit 十几行代码就能搭起来,答辩的时候现场上传图片、实时显示分类结果和置信度,比放 PPT 截图直观得多。Gradio 的代码大概长这样:
import gradio as gr def classify_image(image): label, conf = predict(image) return f'{label} (置信度: {conf:.2%})' interface = gr.Interface( fn=classify_image, inputs=gr.Image(type='filepath'), outputs='text', title='垃圾分类识别系统' ) interface.launch(share=False)share=False表示只在本地局域网访问,答辩时用自己电脑跑就行。这个界面不需要任何前端知识,gr.Image自动处理上传和格式转换,fn接收文件路径返回字符串。
第三个技巧是导出 ONNX 模型做推理加速。PyTorch 模型在 CPU 上推理有时候比较慢,转成 ONNX 之后可以用 ONNX Runtime 加速,在同样的硬件上通常能快 1.5 到 2 倍。导出代码:
import torch.onnx dummy_input = torch.randn(1, 3, 224, 224).to(device) torch.onnx.export( model, dummy_input, 'garbage_cls.onnx', input_names=['input'], output_names=['output'], dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}}, opset_version=11 )dynamic_axes让导出的模型支持变长 batch,opset_version=11兼容性最好。导出之后用onnxruntime加载推理,速度提升明显,而且部署到没有 PyTorch 环境的机器上也能跑。
这三个技巧我每次做图像分类项目都会走一遍,尤其是对比实验和可视化,属于投入产出比最高的部分。从那以后我每次拿到一个新的分类项目,都强制自己先把 baseline 跑通、再逐步加改进,绝不一上来就堆模块。希望帮到你。
本文还有配套的精品资源,点击获取