
简介这份资源是面向高校学生与深度学习入门者的垃圾识别分类课程设计完整项目基于卷积神经网络实现图像分类可直接用于课程设计或期末大作业无需二次修改即可运行。压缩包共约2000个文件以1196张jpg与789张jpeg图像构成训练与测试数据集另含13个Python源码文件、1个json配置与1个md说明文档整体约564.69MB覆盖数据读取、模型搭建、训练与推理等环节。目前已有263人学习下载说明该方案在同类课设中具备一定参考价值。项目已通过导师指导并获得97分高分读者可据此掌握CNN图像分类的完整流程包括数据集组织方式、模型结构设计、训练参数配置与结果评估思路同时可借助现成权重与脚本快速复现实验节省从零搭建的时间适合作为课设模板或深度学习实践练手项目。1. 垃圾识别分类系统从一张照片到四个桶CNN 到底替你做了哪些判断你拍一张外卖盒的照片系统告诉你「其他垃圾」拍一节废电池它告诉你「有害垃圾」。这件事听起来像是个简单的四分类问题但真正动手做过的人都知道难点从来不在模型结构本身而在数据质量、类别边界和部署时的输入一致性。基于深度学习卷积神经网络实现垃圾识别分类系统本质上是把「图像分类」这套成熟范式套进一个类别定义模糊、样本分布极不均衡的真实场景里。Python 源码加数据集的组合意味着你拿到的不只是一份作业而是一条从数据预处理、模型搭建、训练调参到推理验证的完整链路。适合谁正在做课程设计的学生、想跑通第一个 CNN 落地项目的初学者以及需要一套可复现基线来改造成自己分类任务的工程师。接下来我会按「先立住原理、再动手复现、最后讲坑」的顺序把这条链路拆开讲清楚。2. 卷积神经网络凭什么能认出垃圾结构、选型与数据集的真实边界2.1 从像素到类别CNN 在这套系统里到底学了什么一张垃圾图片送进网络之前只是一个三维数组比如 224×224×3。卷积层做的事是用一组可学习的卷积核在图像上滑动提取边缘、纹理、局部形状这些低级特征再通过堆叠的层逐步组合成「瓶盖的圆环」「纸板的褶皱」这类高级语义。池化层负责降维把特征图缩小但保留关键响应。最后全连接层把特征映射到四个类别的概率分布上取最大值就是预测结果。这套机制之所以适合垃圾识别是因为垃圾类别的区分往往依赖局部纹理和形状而不是全局布局。一个塑料瓶和一个玻璃瓶颜色可能接近但反光纹理和边缘锐度不同卷积核恰好对这类局部差异敏感。相比之下如果把图片拉平直接送进全连接网络参数量会爆炸而且丢失空间信息效果通常差很多。需要提前说清楚一个边界CNN 不是万能的。如果两类垃圾在视觉上几乎无法区分比如「污染过的纸巾」到底算其他垃圾还是厨余垃圾模型只能学到训练集里的标注倾向换一个城市的标准就可能翻车。所以这套系统的上限很大程度上由数据集标注的一致性决定而不是由网络深度决定。2.2 选 ResNet 还是自己搭课程设计场景下的模型取舍课程设计常见的做法有两种一是自己搭一个 4 到 6 层的简单 CNN二是用预训练模型做迁移学习。两者没有绝对优劣取决于你的目标和算力。自己搭的优点是结构透明每一层为什么这么设计都能讲清楚适合答辩时展开。缺点是参数量少、特征提取能力有限如果数据集只有几千张准确率可能卡在 70% 上下。常见做法是堆三层卷积加两层全连接每层后接 ReLU 和最大池化最后用 Softmax 输出四类。迁移学习的优点是收敛快、准确率高通常拿 ResNet18 或 MobileNetV2 在 ImageNet 上的预训练权重把最后一层全连接改成四输出冻结前面的层先训几轮再解冻微调。缺点是如果答辩老师追问「这个网络每一层在干什么」你需要额外准备。我一般会建议如果时间紧、想先把系统跑通用迁移学习如果重点是展示对 CNN 原理的理解自己搭。方案参数量级训练数据需求典型准确率适用场景自建 4 层 CNN几十万300070%~85%课程设计、原理展示ResNet18 迁移千万级100085%~95%快速落地、追求精度MobileNetV2 迁移百万级100085%~93%后续要部署到边缘设备2.3 数据集长什么样四个类别、不均衡分布与清洗要点标题里提到含数据集这类课程设计数据集通常是按文件夹组织的图像分类格式每个类别一个子目录。四个类别一般对应可回收物、有害垃圾、厨余垃圾、其他垃圾。实际拿到手后第一件事不是直接训练而是统计每个类别的样本数。常见情况是「其他垃圾」样本最多「有害垃圾」样本最少比例可能达到 5:1 甚至更高。这种不均衡会让模型倾向于预测多数类导致有害垃圾的召回率很低。处理方式有三种一是对少数类做数据增强比如随机翻转、旋转、颜色抖动二是在损失函数里给少数类更高权重三是过采样少数类。我一般先用增强简单且不容易引入偏差。清洗环节要重点检查三类问题重复图片、标注错误、尺寸异常。重复图片会让训练集和验证集产生泄漏验证准确率虚高。标注错误在垃圾数据集里很常见比如把「一次性餐盒」标成可回收实际应该看材质。尺寸异常指个别图片分辨率远低于其他图片送进网络前统一 resize 到 224×224 即可但要注意保持宽高比避免拉伸变形。提示在划分训练集和验证集之前先按图片内容去重否则同一张图可能同时出现在两边验证结果没有参考价值。3. 用 Python 跑通训练全流程从目录结构到第一个可用的模型文件3.1 环境准备与目录结构把源码和数据放到正确的位置拿到压缩包后先解压观察目录结构。典型布局是源码文件夹、数据集文件夹、模型保存文件夹并列。Python 环境建议用 3.8 到 3.10太新的版本有时会和旧版 PyTorch 冲突。依赖主要是 torch、torchvision、numpy、Pillow、matplotlib。# 创建虚拟环境避免污染系统 Python python -m venv venv # 激活环境Windows 用 venv\Scripts\activate source venv/bin/activate # 安装核心依赖torch 版本按自己显卡情况调整 pip install torch torchvision numpy pillow matplotlib这段命令做三件事建独立环境、激活、装依赖。参数说明python -m venv venv里的第二个 venv 是环境目录名可以改。source在 Windows 下换成对应激活脚本。torch 如果要用 GPU需要去官网查对应 CUDA 版本的安装命令不要直接 pip install torch否则可能装到 CPU 版本。目录结构建议整理成下面这样后面代码里的路径才不用反复改project/ data/ train/ recyclable/ hazardous/ kitchen/ other/ val/ 同上四个子目录 src/ train.py predict.py dataset.py models/ best_model.pth3.2 数据加载与增强用 DataLoader 把图片变成批次张量数据加载的核心是自定义 Dataset 或直接用 ImageFolder。ImageFolder 要求每个类别一个子目录正好匹配上面的结构。训练集做增强验证集只做 resize 和归一化这一点不能搞反。import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader # 训练集增强随机裁剪、翻转、颜色抖动提升泛化 train_tf transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) # 验证集只做确定性变换保证评估可复现 val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) train_ds datasets.ImageFolder(data/train, transformtrain_tf) val_ds datasets.ImageFolder(data/val, transformval_tf) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers2) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers2) print(train_ds.classes) # 打印类别顺序后面预测要用逻辑说明训练集先 resize 到 256 再随机裁到 224是为了让每次裁到的区域略有不同相当于免费扩充数据。Normalize 里的均值方差是 ImageNet 的统计值用预训练模型时必须一致自己搭网络也建议沿用。参数说明batch_size 设 32 是显存和收敛速度的折中显存不够就降到 16 或 8。num_workers 在 Windows 下有时会报错改成 0 即可。train_ds.classes打印出来的顺序就是模型输出索引对应的类别务必记下来。3.3 搭建与训练模型损失函数、优化器和训练循环的关键参数如果自己搭网络结构可以这样写。三层卷积每层后接 ReLU 和池化最后接全连接。import torch.nn as nn class SimpleCNN(nn.Module): def __init__(self, num_classes4): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 32, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(64, 128, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 28 * 28, 256), nn.ReLU(), nn.Dropout(0.5), nn.Linear(256, num_classes) ) def forward(self, x): return self.classifier(self.features(x))逻辑说明每次池化把特征图边长减半224 经过三次池化变成 28通道数从 3 升到 128。Dropout 放在全连接前防止过拟合。参数说明卷积核统一用 3×3padding1 保证卷积后尺寸不变。如果输入尺寸改了128 * 28 * 28这个数要跟着改否则会报维度不匹配。训练循环里几个关键选择损失函数用 CrossEntropyLoss优化器用 Adam学习率初始 1e-3。如果类别不均衡给 CrossEntropyLoss 传 weight 参数。import torch.optim as optim device torch.device(cuda if torch.cuda.is_available() else cpu) model SimpleCNN(num_classes4).to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3) for epoch in range(20): model.train() for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() # 每个 epoch 后在验证集上评估 model.eval() correct, total 0, 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) preds model(imgs).argmax(dim1) correct (preds labels).sum().item() total labels.size(0) print(fepoch {epoch1}, val acc {correct/total:.4f})逻辑说明optimizer.zero_grad()清空上一轮梯度不能省。loss.backward()反向传播optimizer.step()更新参数。验证阶段用torch.no_grad()关闭梯度计算省显存。参数说明epoch 数 20 是起点如果验证准确率还在涨就继续加。学习率如果 loss 震荡明显降到 1e-4。训练完把model.state_dict()保存到 models 目录后面预测直接加载。4. 推理、评估与踩坑记录模型训完之后才是真正的问题开始4.1 单张图片预测把模型文件变成可调用的接口训练保存的模型文件只包含权重预测时需要重新构建网络结构再加载权重。这一步经常有人忘记直接 load 会报错。from PIL import Image # 重建结构并加载权重 model SimpleCNN(num_classes4).to(device) model.load_state_dict(torch.load(models/best_model.pth, map_locationdevice)) model.eval() def predict(img_path): img Image.open(img_path).convert(RGB) tensor val_tf(img).unsqueeze(0).to(device) # 增加 batch 维度 with torch.no_grad(): prob torch.softmax(model(tensor), dim1) idx prob.argmax().item() return train_ds.classes[idx], prob[0][idx].item() print(predict(test.jpg))逻辑说明unsqueeze(0)把单张图的维度从 C×H×W 变成 1×C×H×W因为网络要求有 batch 维度。softmax把输出转成概率方便看置信度。参数说明map_location在只有 CPU 的机器上加载 GPU 训练的权重时必须加。val_tf必须和验证集一致不能用训练集的增强变换否则结果不稳定。4.2 评估指标不只看准确率混淆矩阵暴露的真实问题准确率在类别不均衡时会骗人。如果其他垃圾占 60%模型全预测其他垃圾也有 60% 准确率。所以要打印混淆矩阵看每个类别的召回率。from sklearn.metrics import confusion_matrix, classification_report all_preds, all_labels [], [] model.eval() with torch.no_grad(): for imgs, labels in val_loader: preds model(imgs.to(device)).argmax(dim1).cpu() all_preds.extend(preds.tolist()) all_labels.extend(labels.tolist()) print(confusion_matrix(all_labels, all_preds)) print(classification_report(all_labels, all_preds, target_namestrain_ds.classes))逻辑说明把验证集所有预测收集起来一次性算矩阵和报告。classification_report会给出每个类别的 precision、recall、f1。重点看有害垃圾的 recall如果低于 0.6说明模型没学好这个少数类需要回头做增强或加权。参数说明target_names传入类别名报告可读性更好。4.3 避坑记录五个让课程设计翻车的典型问题现象一验证准确率 99%换一张新图就乱猜。原因通常是训练集和验证集划分时没有去重同一张图两边都有模型只是记住了。解决方式是先对全部图片做哈希去重再按 8:2 划分确保验证集图片在训练集里没出现过。现象二训练 loss 一直不降卡在 1.38 左右。这是四分类的随机猜测水平说明模型没学到东西。常见原因是学习率太大导致梯度爆炸或者数据归一化没做。检查 Normalize 是否加在 ToTensor 之后学习率降到 1e-4 再试。现象三预测时所有图片都输出同一个类别。多半是类别不均衡加上没做处理。先统计训练集各类数量如果比例超过 3:1在 CrossEntropyLoss 里加 weight或者对少数类做 oversample。另一个可能是加载权重时类别顺序和训练时不一致检查train_ds.classes是否和训练时打印的一样。现象四GPU 显存不够batch_size 降到 1 还是报错。检查是不是在验证阶段忘了加torch.no_grad()导致梯度图一直累积。另外图片 resize 尺寸如果设成 512显存占用会翻好几倍课程设计用 224 足够。现象五换一台机器跑源码报路径找不到。源码里如果写了绝对路径换机器必挂。把所有路径改成相对路径或者在代码开头用os.path.dirname(__file__)拼出项目根目录。数据集的目录名如果有中文或空格也可能导致 ImageFolder 读取异常建议改成英文。注意模型文件.pth只存权重不存网络结构。换一份源码加载时必须保证网络定义完全一致否则load_state_dict会报 key 不匹配。5. 把准确率再推一截迁移学习微调与置信度阈值的实战技巧自己搭的 CNN 跑通之后如果准确率卡在 80% 左右上不去最划算的升级路径是换成预训练模型做微调。做法不复杂把 ResNet18 的最后一层全连接换成四输出先冻结卷积层只训全连接几轮之后再解冻最后两个卷积块一起微调。学习率要调小微调阶段用 1e-4否则预训练学到的特征会被冲掉。from torchvision import models model models.resnet18(pretrainedTrue) for param in model.parameters(): param.requires_grad False # 先冻结全部 model.fc nn.Linear(model.fc.in_features, 4) # 替换分类头 # 第一阶段只训 fc学习率 1e-3 optimizer optim.Adam(model.fc.parameters(), lr1e-3) # 第二阶段解冻 layer4整体微调学习率降到 1e-4 for param in model.layer4.parameters(): param.requires_grad True optimizer optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr1e-4)逻辑说明冻结参数可以防止随机初始化的分类头在训练初期把预训练特征带偏。解冻 layer4 是因为这一层对应高级语义特征和垃圾类别最相关。参数说明pretrainedTrue会下载权重第一次运行需要联网。如果环境不能联网需要提前把权重文件放到 torchvision 的缓存目录。另一个实用技巧是设置置信度阈值。四分类问题里模型对某些模糊图片的最高概率可能只有 0.4这时候强行输出类别不如提示「无法确定」。在预测接口里加一个判断最高概率低于 0.6 就返回待人工确认。这个阈值可以根据验证集上的准确率-覆盖率曲线来定一般取 0.5 到 0.7 之间。微调策略可训练参数比例收敛轮数验证准确率参考只训分类头约 1%5~1082%~88%解冻 layer4约 30%10~2088%~94%全部解冻100%2090%~95%但易过拟合最后说一个我自己的习惯每次改完超参数或数据增强策略一定把验证集准确率、混淆矩阵和当前配置记在一个文本文件里和模型文件放在一起。课程设计答辩时被问到「你这个 92% 是怎么来的」能直接翻出记录比现场回忆靠谱得多。这套垃圾识别分类系统本身不复杂真正拉开差距的是数据清洗的细致程度和排错时的耐心。希望帮到你。本文还有配套的精品资源点击获取