Python+CNN垃圾分类:从数据到模型训练全流程
2026/9/17 8:58:11 网站建设 项目流程

简介:面向计算机专业毕业设计、课程设计及期末大作业需求,这套基于Python的CNN卷积神经网络垃圾分类项目,提供了经导师指导并获评99分的高分完整方案。资源压缩包共20个文件,大小约14.42MB,构成十分清晰:Python源码负责模型定义、训练和预测流程,Jupyter Notebook提供逐步演示,h5模型权重文件可直接加载使用,docx和Markdown文档说明设计思路与使用方式,另有xml配置与测试图片辅助环境部署和效果验证。项目重点解决垃圾图像分类中的数据处理、卷积网络搭建、训练调参与结果评估问题,既覆盖完整的项目开发脉络,也包含可直接复用的模型与脚本,适合正在做毕设、需要快速搭建可用系统的学生,也适合希望以真实项目提升深度学习实战能力的学习者。目前已有200人学习下载,是一份完成度高、可运行性强、具备参考价值的毕业设计资源。

1. 为什么垃圾分类要用 CNN

一个塑料瓶和一片菜叶在像素层面上并没有“可回收”或“厨余”的标签,人扫一眼就能把垃圾丢进正确的桶,靠的是长期积累的材质、形态和颜色经验。基于 Python 的 CNN 卷积神经网络模型实现垃圾分类,本质上是把这种视觉直觉翻译成可计算的映射:输入一张图片,输出一个类别概率分布。这套方案的核心价值不在于堆叠多少层网络,而在于你能不能把数据组织、卷积结构、训练参数和验证手段串成一条可复现的流水线。下面按数据准备、模型搭建、训练调试、验证交付四个环节展开,给出一套在一台普通电脑上就能跑通的最小实践路径,代码以 PyTorch 为载体,兼顾实验过程和答辩所需的记录素材。适合正在选毕业设计题目的学生,也适合第一次接触图像分类、想搞懂 CNN 各层到底在做什么的工程师。

2. 卷积神经网络与 Python 选型:先看卷积核在做什么

2.1 一张厨余垃圾照片如何变成特征图

垃圾分类任务的输入是 3 通道彩色图,通常缩放到 224×224。一张 224×224×3 的图像直接展开成向量交给全连接层,意味着约 15 万维输入,参数规模巨大且没有泛化能力。CNN 的做法是用“局部感受野”扫描全图:第一个卷积层用 3×3 或 5×5 的卷积核逐窗口计算点积,输出值表示该位置与卷积核模板的匹配程度,这些响应组合起来就是特征图。

在垃圾图片上,低层卷积核倾向于收敛为边缘、颜色突变和纹理基元——瓶身反光产生的高亮边缘、纸张纤维的周期性纹理、沾油菜叶的不规则色块。经过两到三次池化下采样,高层特征图把局部模板组合成“看起来像塑料”或“看起来像有机物”的语义响应。最后将特征图展平,接全连接层映射到类别数。这也解释了为什么传统颜色直方图方案不可靠:透明瓶子和不透明瓶子在色调统计上差异巨大,而 CNN 的卷积核是自动学习的,对光源和角度变化有更强的容忍度。

2.2 为什么选 PyTorch 而不是从零实现反向传播

从零写卷积、池化和反向传播不是不能做,但毕业设计的时间应该花在模型效果与工程化交付上。常见做法是使用 PyTorch:它保留 Python 语法自由度的同时,把自动微分和 GPU 调度封装在框架内部,网络前向过程就是模块拼接,反向传播由loss.backward()完成,中间可以随时打印任意一层特征图的形状,这对排错非常重要。

# 创建虚拟环境,安装 CPU 版 PyTorch python -m venv .venv source .venv/bin/activate pip install torch torchvision
# 验证安装与硬件加速是否可用 import torch print("torch version:", torch.__version__) print("cuda available:", torch.cuda.is_available()) if torch.cuda.is_available(): print("gpu name:", torch.cuda.get_device_name(0))

参数说明:venv隔离依赖,避免把系统 Python 装乱;torchvision提供 ImageFolder 数据集工具和预训练权重入口,后续迁移学习也要用到;torch.cuda.is_available()决定训练设备走向,CPU 机器训练速度会慢 5~20 倍,但代码逻辑不变。安装命令不锁版本号,pip 会拉取与当前 Python 匹配的最新稳定版;需要复现他人实验时,再把版本固定进requirements.txt

2.3 CNN 核心层的职责一览

主要作用常见参数
Conv2d提取局部特征,卷积核权重可学习in_channels、out_channels、kernel_size、padding
BatchNorm2d规范化通道分布,加速收敛无关键调参项
ReLU引入非线性,置零负响应-
MaxPool2d下采样降低分辨率,增强平移不变性kernel_size=2, stride=2
Flatten把特征图展平为向量-
Linear全连接,组合特征并映射到类别in_features、out_features

卷积核通道数通常遵循“浅层少、深层多”的经验:第一层 32~64,第二层 128,第三层 256 到 512。每翻一倍参数数量也成倍增长,毕业设计数据集只有几千到几万张时,不建议盲目把层数堆到十个以上。先用三层卷积加两层全连接跑通基线,验证集达到 80% 以上后再考虑更大的网络。

GarbageCNN 的评价要参考 4.1 节。实际调试中,BatchNorm 放在 Conv 与 ReLU 之间会使早期 loss 下降更平滑;MaxPool 固定为 2×2 步长 2,特征图宽高各减半。这些细节在后续排查过拟合时会反复用到。

3. 把垃圾分类数据装进 DataLoader:预处理与增强的 5 个关键点

3.1 目录组织与训练/验证划分

常见做法是采用公开的垃圾分类图像数据集,按 train 和 val 两个目录组织图片,每个类别一个子目录。我第一次整理数据时把所有图片塞进一个文件夹,靠文件名列表手工切分,结果换个环境就崩。后来我一般这样组织目录:

dataset/ train/ kitchen_waste/ *.jpg recyclable/ *.jpg hazardous/ *.jpg other/ *.jpg val/ kitchen_waste/ *.jpg recyclable/ *.jpg hazardous/ *.jpg other/ *.jpg
from torchvision import transforms, datasets from torch.utils.data import DataLoader transform_train = transforms.Compose([ transforms.Resize((256, 256)), # 先放大一点,给随机裁剪留空间 transforms.RandomResizedCrop(224), # 随机裁剪到 224x224,增强尺度变化 transforms.RandomHorizontalFlip(p=0.5), # 水平翻转,数据增强 transforms.ToTensor(), # HWC -> CHW,像素值归一化到 0~1 transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) transform_val = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) dataset_train = datasets.ImageFolder("dataset/train", transform=transform_train) dataset_val = datasets.ImageFolder("dataset/val", transform=transform_val) print("classes:", dataset_train.classes) print("train samples:", len(dataset_train), "val samples:", len(dataset_val))

逻辑说明:ImageFolder自动扫描每个子目录并生成整数标签,classes是排序后的类别名列表。训练集与验证集应该来自不同时段或不同拍摄条件的图片,不能简单把同一批图片随机拆开,否则验证精度虚高,答辩时容易暴露。transform_val不加入随机翻转和随机裁剪,理由在 3.2 节展开。

参数说明:Resize(256)后接RandomResizedCrop(224)是经典组合,让同一张原图每次喂进网络时采样区域不同,相当于扩增了样本局部多样性;Normalize沿用 ImageNet 的均值方差,为后续加载预训练权重做准备,从零训练时沿用同一套参数也能加速收敛。

3.2 5 个必调参数表

参数建议值调参思路
Resize训练 256,验证 224裁剪窗口必须有冗余,否则增强退化为裁剪
RandomHorizontalFlipp=0.5瓶子、纸箱不分左右,翻转是安全增强
RandomResizedCropscale=(0.5, 1.0)模拟物品远近变化
ColorJitterbrightness=0.2, contrast=0.2模拟光照变化,幅度过大会改变材质颜色
BatchSize32(CPU 可降到 8)显存不足先减 batch,而不是降低 Resize 尺寸

这组参数对应图像增强中“增强 CNN 算法”的常规配置。增强不是越狠越好:透明塑料瓶和浅色桌面在颜色扰动后可能变得无法区分,如果验证 loss 不降,优先关闭 ColorJitter 对比效果。

3.3 类别不均衡怎么调权重

垃圾数据集经常严重倾斜:塑料瓶样本几千张,灯泡可能只有几十张。若不做处理,模型几乎不会为少数类输出高概率。给损失函数传入类别权重是常见做法:

import os from torch.utils.data import WeightedRandomSampler class_names = dataset_train.classes counts = [] for name in class_names: folder = os.path.join("dataset/train", name) counts.append(len(os.listdir(folder))) weights = [1.0 / c for c in counts] samples_weight = [weights[label] for _, label in dataset_train.samples] sampler = WeightedRandomSampler(samples_weight, num_samples=len(samples_weight), replacement=True) train_loader = DataLoader(dataset_train, batch_size=32, sampler=sampler)

逻辑说明:先按类别统计样本数,再把每类权重设为样本数的倒数,每个样本的权重由其标签决定。WeightedRandomSampler在每次迭代时按权重抽样,少数类被重复采到的概率更高,实际上等价于对少数类过采样。

参数说明:replacement=True表示允许同一张图在同一个 epoch 中被重复抽取,这正是过采样机制的来源;num_samples保持和训练集数量一致,保证每个 epoch 的迭代步数大致不变。另一种做法是在CrossEntropyLoss(weight=weight_tensor)中直接传权重,两种机制不要同时使用,否则少数类过拟合风险明显上升。

3.4 增强不是万能药

数据增强的目的是提升泛化能力,而不是把每张图变成抽象画。增强越激进,训练集 loss 下降越慢,这是正常现象。我一般用两轮策略:第一轮关掉增强项跑 5 个 epoch 确认网络能拟合数据;第二轮打开全部增强跑完整训练,对比两次验证精度的差距。若验证精度反而下降,说明增强幅度过大,去掉 ColorJitter 或降低 scale 下限即可。

4. 用 Python 手写并训练一个垃圾分类 CNN:最小可跑通版本

4.1 网络结构:三层卷积 + 两层全连接

调参和排错阶段,我一般不用现成的 ResNet 作为第一版实验对象:参数多、训练慢、报错信息长。从小网络开始可以快速建立“某一改动如何影响特征图”的直觉。这里给出一个直接可跑的 GarbageCNN:

import torch.nn as nn class GarbageCNN(nn.Module): def __init__(self, num_classes=4): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 32, kernel_size=3, padding=1), # 224x224x32 nn.BatchNorm2d(32), nn.ReLU(inplace=True), nn.MaxPool2d(2), # 112x112 nn.Conv2d(32, 64, kernel_size=3, padding=1),# 112x112x64 nn.BatchNorm2d(64), nn.ReLU(inplace=True), nn.MaxPool2d(2), # 56x56 nn.Conv2d(64, 128, kernel_size=3, padding=1), # 56x56x128 nn.BatchNorm2d(128), nn.ReLU(inplace=True), nn.MaxPool2d(2), # 28x28 ) self.classifier = nn.Sequential( nn.Flatten(), # 28*28*128 = 100352 nn.Linear(128 * 28 * 28, 512), nn.ReLU(inplace=True), nn.Dropout(0.5), nn.Linear(512, num_classes), ) def forward(self, x): return self.classifier(self.features(x))

逻辑说明:输入按 224×224 计算,每次MaxPool2d(2)让宽高减半,第三次池化后是 28×28,通道数 128,展平后是 100352 维。第一个 Linear 的in_features必须和展平后的维度一致,这是 CNN 新手最容易报错的地方。代码里逐层标注尺寸,就是为了在调试时能用一行 print 核对计算是否出错。如果输入图改为 256×256,第三次池化后是 32×32,线性层入口应改为128 * 32 * 32

参数说明:kernel_size=3配合padding=1保持特征图分辨率不变;Dropout(0.5)放在最后一层全连接前,训练时随机丢弃一半神经元,抑制全连接层的过拟合;num_classes由构造参数传入,四分类和细分类复用同一结构。

4.2 训练主循环:loss、优化器与调度器

import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss, correct, total = 0.0, 0, 0 for images, labels in loader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() total_loss += loss.item() * images.size(0) preds = outputs.argmax(dim=1) correct += (preds == labels).sum().item() total += labels.size(0) return total_loss / total, correct / total def evaluate(model, loader, criterion, device): model.eval() total_loss, correct, total = 0.0, 0, 0 with torch.no_grad(): for images, labels in loader: images, labels = images.to(device), labels.to(device) outputs = model(images) loss = criterion(outputs, labels) total_loss += loss.item() * images.size(0) preds = outputs.argmax(dim=1) correct += (preds == labels).sum().item() total += labels.size(0) return total_loss / total, correct / total

逻辑说明:model.train()model.eval()切换的是 Dropout 与 BatchNorm 的行为模式。忘记model.eval()会导致验证时保留训练随机性,表现为验证精度忽高忽低。每个 batch 后的optimizer.zero_grad()必须存在,否则梯度会跨 batch 累加,loss 曲线明显抖动。argmax(dim=1)取出每个样本概率最大的类别下标,再与标签比较并累计正确数。

提示:CPU 机器训练时,DataLoader 的num_workers设为 0 或 1 更稳妥,多进程数据加载在 Windows 下容易触发重复内存拷贝;GPU 机器再考虑开到 4 以上。

4.3 训练参数设置的参考表

参数建议值备注
batch_size32(CPU 用 8~16)越小梯度噪声越大,验证曲线抖动明显
epochs20~30小网络不必训练 100 轮,关注验证精度停滞点
learning_rate1e-3Adam 默认偏大时降为 3e-4
optimizerAdam默认 β 参数即可,无需改动
schedulerCosineAnnealingLR(T_max=epochs)后 1/3 轮自动降 lr,减少后期震荡
weight_decay1e-4对权重做 L2 正则,配合 Dropout 抗过拟合

参数说明:Adam 的1e-3对三层卷积小网络偏大,训练初期若 loss 不降,优先尝试3e-4weight_decay超过1e-3会把权重显著拉向零,训练 loss 反而降不下去。CosineAnnealingLRT_max设为epochs,让它在一个完整训练周期内完成余弦退火。

4.4 loss 不降与过拟合的三类问题定位

第一类:loss 卡在 log4 附近,也就是四分类的随机猜测水平。多数原因是类别不均衡且未加权,回到 3.3 节补权重;或者学习率过高导致优化震荡,把 lr 降到 3e-4 重训。

第二类:训练精度 98%,验证精度 72%,典型过拟合。先检查数据划分是否正确,确认验证集里没有与训练集完全同源的图片。数据没问题就把 Dropout 从 0.3 提高到 0.5,weight_decay提到1e-3,必要时把全连接层的 ReLU 换成 LeakyReLU,降低神经元死亡概率。

第三类:loss 忽高忽低但整体缓慢下降,通常是 batch_size 太小或增强幅度过猛。先翻倍 batch_size;显存不够就把 Resize 从 256 降到 224,同时减小 ColorJitter 的扰动幅度,不要把验证集预处理一起改掉。

这三类问题在毕业设计答辩时也常被追问,能说清楚自己的排查路径比单纯贴一个高精度指标更有说服力。

5. 训练后的验证与文档交付:混淆矩阵与源码整理技巧

5.1 混淆矩阵与每类召回

训练收敛后不要只看总精度。垃圾细分类差异很大,总精度 85% 可能三类 94%、另一类只有 50%。在验证集上输出分类报告:

from sklearn.metrics import classification_report y_true, y_pred = [], [] model.eval() with torch.no_grad(): for images, labels in val_loader: outputs = model(images.to(device)) y_pred += outputs.argmax(dim=1).cpu().tolist() y_true += labels.tolist() print(classification_report(y_true, y_pred, target_names=dataset_val.classes))

target_names的顺序与dataset_val.classes一致。如果某一类 recall 明显偏低,说明该类样本数量不足,或与另一类视觉上高度相似,比如“其他类”中的脏纸巾和“厨余类”中的湿垃圾。处理方式是回到 3.3 节加权,或合并那些人工都难以区分的细类,换粗粒度标签重新训练一版。

5.2 迁移学习补强:加载 ResNet18 微调

import torchvision.models as models backbone = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) backbone.fc = nn.Linear(backbone.fc.in_features, num_classes) for name, param in backbone.named_parameters(): if name.startswith("fc"): param.requires_grad = True else: param.requires_grad = False optimizer = optim.Adam(filter(lambda p: p.requires_grad, backbone.parameters()), lr=1e-4)

逻辑说明:所谓微调,是加载在 ImageNet 上预训练过的权重,冻结前面主干层,只更新最后一层全连接。验证精度稳定后再解冻最后两个残差块,用更小学习率全程微调。一次冻结训练 10 epoch、解冻再训练 10 epoch,通常比从零训练的小网络高 3~8 个百分点。

5.3 源码与文档交付三件事

交付物必写内容常见失分点
README环境版本、数据目录要求、3 条运行命令不给数据集放置说明,导致评审跑不起来
训练脚本固定随机种子、保存 best model、打印 loss/acc每次运行结果不同,无法复现
实验报告混淆矩阵、loss 曲线、三类失败案例只贴最终精度,不写过程

固定随机种子的代码放在所有导入之后,训练开始前调用:

import torch, random, numpy as np def set_seed(seed=42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True

参数说明:cudnn.deterministic=True让卷积选择确定性算法,代价是略微降低速度,但能保证相同数据和相同代码下两次训练结果一致。权重保存推荐torch.save(model.state_dict(), "best.pth"),加载时先创建模型再load_state_dict,不要直接 pickle 整个 model 对象,否则换机器或结构微调后容易出现反序列化异常。最后在本地写一个单图预测脚本,从任意路径读图,走一遍 resize 加 normalize,把模型输出分数映射回类别名,与人工判断比较,跑通这条链路后,源码、文档和演示才形成闭环。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询