news 2026/10/7 5:50:08

CNN+ResNet垃圾分类实战:代码包详解与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CNN+ResNet垃圾分类实战:代码包详解与避坑指南

简介:面向计算机类毕业设计或课程作业的垃圾分类实战源码包,基于CNN与ResNet两种主流网络结构,适合高校学生快速完成图像分类课题中的模型搭建、训练与预测,也适合对深度学习实战感兴趣的初学者作为参考。压缩包总计6个文件、约4MB,以Python脚本、PyTorch权重、验证用zip包和说明文档为主要构成:model.py定义网络结构,predict.py执行推理预测,两个.pt文件提供针对性训练好的权重,Graduation Design validation.zip用于效果验证,README.md则给出环境配置与运行指引。目前已有77人浏览/学习,资源较新但结构完整。读者拿到后可同时对照CNN与ResNet两套模型权重,免去从零训练的时间成本;也能参考代码组织垃圾分类演示,或进一步调整网络层、超参数,支撑毕业论文实验与答辩展示。整体包体小巧、目录清晰,适合快速部署与二次开发。

1. 垃圾分类实战:这套 CNN+ResNet 代码包能省你两周时间

拿到这份“基于CNN和ResNET的垃圾分类实战”代码包时,我第一反应是这大概率是某个毕设的完整交付物:model.py 定义了网络结构,predict.py 负责推理,model.pt 和 model1.pt 是训练好的权重,README.md 里写了环境依赖。它不是一篇教程,而是一个能直接跑的深度学习项目。对正在做毕业设计、课程作业,或者想快速搭一个图像分类系统的同学来说,这类代码包的价值在于:你不用从零搭数据加载、网络定义、权重保存这三件破事,省下的时间至少两周。下面我按“文件结构 → 推理跑通 → 训练闭环 → 踩坑记录 → 进阶验证”的顺序拆一遍,新手照着敲,熟手可以直接跳到第 5 章看坑。

2. 项目文件与模型结构:先搞清 model.py 和两个 .pt 权重

2.1 文件清单:每个文件是干嘛的

在动手跑代码之前,建议先把压缩包里的东西整理一遍。解压后你会看到这些文件:

文件作用备注
model.py定义 CNN 和 ResNet 的网络结构训练和推理共用,结构不能乱改
model.pt训练好的权重文件默认被 predict.py 加载
model1.pt另一个权重文件可能是 CNN 小模型或 ResNet 训练中间产物的权重
predict.py推理脚本输入一张图片,输出垃圾类别
README.md说明文档环境依赖、运行步骤、参数含义
Graduation Design validation.zip验证集或参考资料压缩包里面通常是测试图片或数据说明

这里要提醒一点:model.pt 和 model1.pt 是 PyTorch 的序列化权重,不是完整的模型结构。加载时必须有对应的 model.py 结构,否则会报错或出现权重张量对不上。

2.2 为什么选 CNN 和 ResNet:分类任务的选型逻辑

这个项目把 CNN 和 ResNet 这两类放一起,本身就是在向答辩老师展示比较实验。CNN 是图像分类的基础框架:卷积层提取局部特征,池化层降维,全连接层输出类别分布。它结构简单、参数少、训练快,缺点是网络加深后容易出现梯度消失和退化问题。

ResNet 解决的就是这个退化问题。它引入残差连接,让梯度能跨层传播,所以可以堆到 18 层、34 层甚至更深。在垃圾分类这个场景里,垃圾图片的类别差异往往在纹理和形状局部特征上,比如塑料瓶和玻璃瓶的形状接近,但瓶口的螺纹和反光不同。CNN 能提取这些局部特征,ResNet 则能把低层纹理和高层语义结合起来。常见做法是浅层 CNN 作为 baseline,ResNet 作为提升版,两个模型对比着做消融实验。

选择 ResNet 而不是 VGG 或 MobileNet,主要看训练资源。VGG 参数多存储开销大,MobileNet 更适合移动端部署,ResNet18 在分类精度和训练速度之间最平衡,所以多数毕设项目用 ResNet18 打底。

2.3 解读 model.py:一个能跑的垃圾分类网络长什么样

model.py 通常会把 CNN 和 ResNet 两个类都写上,用一个 get_model(name, num_classes) 函数统一创建。常见写法是这样:

import torch.nn as nn from torchvision import models class SimpleCNN(nn.Module): def __init__(self, num_classes=10): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 32, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2), nn.Conv2d(64, 128, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2), ) self.classifier = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(128, num_classes), ) def forward(self, x): x = self.features(x) x = self.classifier(x) return x def get_resnet(num_classes): model = models.resnet18(weights=None) in_features = model.fc.in_features model.fc = nn.Linear(in_features, num_classes) return model

SimpleCNN 里每次卷积后接 ReLU 和 MaxPool2d,逐步把特征图从 224×224 压到 28×28,通道从 3 升到 128,最后用 AdaptiveAvgPool2d 把特征压缩成 1×1,避免全连接层输入尺寸被图片大小绑死。get_resnet 则直接复用 torchvision 的 ResNet18,只换掉最后一层全连接,输出维度等于类别数。

如果训练时 num_classes 和你预测时的 num_classes 不一致,加载权重会直接报错,这个坑在第 5 章单独讲。

2.4 model.pt 与 model1.pt:两个权重文件的来源与用途

一套代码里出现两个权重文件,常见三种可能:

  1. model.pt 是 ResNet 训练后的结果,model1.pt 是 SimpleCNN 训练后的结果。
  2. model.pt 是最后一个 epoch 的权重,model1.pt 是验证集准确率最高的权重。
  3. model.pt 是完整模型权重,model1.pt 是中间 checkpoint。

我一般会先看 README,如果 README 没写,就把两个权重各加载一次,跑同一张图对比输出。用 predict.py 的模型结构分别加载,哪个输出更符合预期就用哪个。注意模型文件名不代表结构名,两个文件也可能是同一个 ResNet 结构在两次训练后产生的不同结果。

另外,PyTorch 权重文件里保存的是 state_dict,也就是所有层的权重张量和偏置。如果你打开 .pt 文件只看到一堆张量,没有网络结构,不要慌,这正好说明它不是 torchscript 导出格式。要用它,就必须先实例化相同结构的模型,再调用 load_state_dict。

3. 把 predict.py 跑通:环境配置与单张图片推理实战

3.1 环境依赖:torch 版本、Python 版本、opencv 组装

这类毕设代码包的 README 通常写得比较省,只给到“Python 3.8 + PyTorch 1.10 + torchvision 0.11”这个量级。实际操作时我建议直接装当前稳定的 PyTorch 2.x,大部分情况下可以向下兼容读取旧权重,但有一个例外要注意:如果权重文件是用 PyTorch 1.x 保存的,直接 load 一般没问题;如果是用 PyTorch 1.x 的训练脚本保存时带了 autograd 图信息,就必须用 torch.load(..., weights_only=True) 之外的方式处理。

环境搭建顺序我习惯这样走:

conda create -n garbage python=3.9 conda activate garbage pip install torch torchvision opencv-python pillow numpy scikit-learn

torch 和 torchvision 的版本要对应,否则导入 torchvision.models.resnet18 时会报找不到函数定义。opencv-python 主要是用来做图像缩放和颜色通道转换,也可以只用 PIL,但 predict.py 里如果写了 cv2.resize,就绕不开 opencv。scikit-learn 不一定在推理阶段需要,但如果你后面要画混淆矩阵评估效果,它会非常方便。

3.2 运行 predict.py:命令与参数

predict.py 的标准跑法通常是命令行传图片路径和权重路径:

python predict.py --image test.jpg --weights model.pt --model resnet

如果 README 没写完整参数,可以先用python predict.py --help探底。常见的参数设计就那么几个:--image、--weights、--model、--num_classes、--device。

import argparse import torch from PIL import Image from torchvision import transforms def preprocess(image_path, size=224): transform = transforms.Compose([ transforms.Resize((size, size)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) image = Image.open(image_path).convert('RGB') return transform(image).unsqueeze(0) def predict(model, tensor, device): model.eval() with torch.no_grad(): output = model(tensor.to(device)) prob = torch.softmax(output, dim=1) top1 = torch.argmax(prob, dim=1).item() score = prob[0, top1].item() return top1, score

这里的 preprocess 做了三件事:统一图片尺寸到 224×224、把像素值从 0-255 转为 0-1 的 Tensor、用 ImageNet 的均值和标准差做归一化。如果你训练时不是用 ImageNet 统计量,predict 阶段的 Normalize 参数也要同步改,否则模型在推理时的输入分布和训练时不一致,准确率会掉得莫名其妙。predict 函数里显式调用了 model.eval() 和 torch.no_grad(),前者关闭 dropout 和 batch norm 的动态更新,后者不建计算图,避免显存暴涨。

3.3 输出解析:从分类索引到垃圾类别名

模型输出的只是一个整数索引,比如 0、1、2,真正有意义的是这个索引对应什么垃圾。这部分信息通常藏在训练脚本的类别列表里,或者在 README 中。常见做法是在 predict.py 里维护一个类别名列表:

classes = ['cardboard', 'glass', 'metal', 'paper', 'plastic', 'trash'] top1_name = classes[top1]

如果你的数据集是按文件夹组织的,文件夹名就是类别名。跑通一次后,建议顺手把 classes 列表打印出来,和输入图片对照看看是否合理。很多同学第一次跑通后看到准确率 90%+,其实是类别索引和名字错位了,比如“塑料瓶”被显示成“纸板”,这种错位非常隐蔽。

3.4 CPU 与 GPU 切换:代码层面要改的东西

predict.py 如果默认跑 CPU,加载权重时就要加 map_location:

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') checkpoint = torch.load('model.pt', map_location=device) model.load_state_dict(checkpoint['state_dict'] if 'state_dict' in checkpoint else checkpoint)

用 cuda 时注意权重文件的保存方式。如果训练时权重被 DataParallel 包了一层,键名会带module.前缀,加载到单卡模型时要么把键名去掉,要么也包一层 DataParallel。常见做法是 load 后遍历一下 state_dict,把module.前缀删掉。CPU 和 GPU 切换这件事,代码本身改动很小,主要卡在权重键名匹配上。

4. 训练自己的分类模型:数据组织、训练脚本与调参参考

4.1 数据目录:ImageFolder 需要的结构

这份代码包里的权重能直接预测,但如果你要训练属于自己的垃圾分类模型,数据必须先整理成 torchvision 认识的格式。torchvision.datasets.ImageFolder 要求每个类一个子文件夹,结构长这样:

data/ train/ cardboard/ 001.jpg glass/ 002.jpg metal/ 003.jpg paper/ 004.jpg plastic/ 005.jpg trash/ 006.jpg val/ cardboard/ 007.jpg ...

文件夹名就是类别名,ImageFolder 会自动按字母序映射成索引。这个结构的坑在于中文字段名:如果文件夹名是“纸板”“玻璃”,ImageFolder 能正常映射,但训练脚本里的 classes 列表是拼音还是中文要看代码实现,混在一起容易出现预测脚本里类别名和训练标签对不上的情况。建议直接统一用英文文件夹名,省去一半麻烦。

4.2 训练脚本:从数据加载到权重保存

训练部分一般会单独写 train.py,核心流程如下:

import torch from torch.utils.data import DataLoader from torchvision import datasets, transforms train_transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness=0.2, contrast=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) train_data = datasets.ImageFolder('data/train', transform=train_transform) train_loader = DataLoader(train_data, batch_size=32, shuffle=True, num_workers=4) model = get_resnet(num_classes=len(train_data.classes)) criterion = torch.nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-4) for epoch in range(20): 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() * images.size(0) epoch_loss = running_loss / len(train_data) print(f'Epoch {epoch+1}/{20}, Loss: {epoch_loss:.4f}')

训练脚本里我把图像增强加在了 Resize 之后:RandomHorizontalFlip 做翻转,ColorJitter 调亮度对比度。垃圾分类场景中垃圾袋颜色变化大,亮度扰动模拟不同环境光源的效果明显。batch_size=32 是 ResNet18 在普通 8GB 显存下的安全值,显存小时降到 16,但低于 16 时 batch norm 会不稳定,分类准确率容易抖动。学习率用 Adam 默认的 1e-3 其实也可以,但 ResNet 类模型我习惯先试 1e-4,收敛更稳,后期也少出现 loss 震荡。

4.3 训练参数参考:batch size、学习率、epoch

不同的模型和数据集规模,参数设置差别很大。下面这个表是我在类似垃圾分类数据集上调过的参考值:

参数推荐范围说明
输入尺寸224×224ResNet 原生输入,调大可能提高精度但显存翻倍
batch size16 ~ 648GB 显存用 32,4GB 用 16
学习率1e-4 ~ 1e-3SGD 用 1e-3,Adam 用 1e-4 起步
epoch20 ~ 50先跑到 30,看 loss 是否还在降
优化器Adam / SGD 动量 0.9小数据集上 Adam 更省心
类别数与文件夹数一致改 model.py 中的 num_classes

先跑 30 个 epoch,观察验证集准确率。如果 30 个 epoch 结束时准确率还在缓涨,就加 epoch 到 50 或调低学习率继续训。如果 10 个 epoch 内训练集 loss 都不降,问题大概率不在参数,而在数据或代码,别急着调参。

4.4 训练完怎么导出和替换 model.pt

训练结束后的标准保存方式有两种。一种只存 state_dict,文件小,加载时必须先建模型结构;另一种把整个模型 torch.save(model) 存下来,文件大但加载省事。predict.py 一般默认前者,所以训练脚本里保存时要注意格式:

torch.save({ 'model_state_dict': model.state_dict(), 'classes': train_data.classes, }, 'model.pt')

保存 state_dict 加类别列表,是一个很实用的习惯。这样权重文件和类别映射绑定在一起,predict 阶段即使 classes 列表写错了,也能从权重文件里读出来。替换模型时,直接用训练好的 model.pt 覆盖原来的 model.pt,再跑 predict.py --weights model.pt 验证效果。注意覆盖前先备份原文件,否则实验对比就没了后悔药。

5. 避坑 / 常见问题与排查:四个最容易翻车的地方

5.1 现象:加载 model.pt 报 KeyError 或 missing keys

加载保存的 state_dict 时,经常报“Missing key(s) in state_dict”或者“Unexpected key(s)”。

原因:模型结构定义和权重文件里的层名对不上,常见原因是训练时用了 DataParallel 导致键名带module.前缀,或者训练代码里改过fc层而 predict.py 里的 model.py 没同步改。

解决:先打印权重文件的键名,确认前缀:

python -c "import torch; d=torch.load('model.pt', map_location='cpu'); print(d.keys());"

如果键名有module.,加载时过滤掉前缀。我在项目里一般会写一个这样的兼容函数,直接处理两种情况。

5.2 现象:predict.py 输入尺寸报错

报错信息类似“Expected input channels 3, got 1”或“size mismatch for fc”。

原因:测试图片是灰度图,只有单通道,或者图片尺寸不是模型输入尺寸。predict.py 里的 ColorJitter 和 Normalize 都要求 RGB 三通道,灰度图直接读入会把通道数搞成 1。

解决:在预处理里强制转成 RGB,代码里用Image.open(...).convert('RGB')。尺寸问题直接用 Resize 到 224×224,不要偷懒省这一步。正常预测前最好打印一下 tensor 的 shape,一眼就能看出问题在哪。

5.3 现象:CPU 推理慢到怀疑人生

单张图片在 ResNet18 上 CPU 推理要 1-2 秒是正常的,但如果 5 秒以上,多半是线程少或没开 no_grad。

原因:resnet18 本身就有一千多万参数,纯串行推理本来就慢。没有用 torch.no_grad() 会导致每个中间变量都建计算图,额外开销可能翻倍。

解决:推理脚本里务必加with torch.no_grad():。如果还要提速,可以设置 torch.set_num_threads(4) 开多线程,或者换更小的模型结构。如果上面的方法都试过了还慢,那就是机器太弱或者图片分辨率太大,先把 Resize 做了再进网络,不要在 ByteTensor 上跑。

5.4 现象:训练 loss 不降或准确率卡住

loss 在 0.7 附近就不动了,准确率一直在 60%-70% 之间横跳。

原因:最常见的是学习率太大,loss 在局部最优附近震荡。其次就是数据量太少,垃圾图片本身类别不均衡,模型容易把所有样本归到多数类。

解决:把学习率降到 1e-5 再训 10 个 epoch,看 loss 有没有往下走的趋势。类别不均衡就加WeightedRandomSampler,给样本少的类别更高权重。还有一个容易被忽略的原因:标签和数据不对应,比如文件夹名是塑料瓶但图片里是易拉罐,这种脏数据会直接把训练带偏。先检查训练集里的随机样本标签,别浪费算力。

5.5 现象:类别名和实际垃圾对不上

模型预测给出的类别名是“cardboard”,但图片上明显是塑料瓶。

原因:predict.py 里的classes列表顺序和训练时的ImageFolder.classes顺序不一致。ImageFolder 按字母序排序,所以你的classes = ['glass', 'metal', ...]顺序必须和它完全一致。

解决:最快的办法是从权重文件里读出训练时的类别列表。训练保存时把train_data.classes一并写进模型文件,predict.py 加载权重时也把这个列表读出来,用列表去映射索引,而不是手写死。如果你现在用的是现成的 model.pt,README 里又没写类别顺序,那就只能用几张已知类别的图片做试验,通过输出索引反推顺序,这个过程虽然有点玄学,但效果直接。

6. 进阶:用混淆矩阵和批量验证看清模型的真实水平

6.1 写一个批量验证脚本

单张图片跑通只能说明代码能跑,不代表模型真的靠谱。我会把整个验证集全部跑一遍,统计每个类别的准确率:

import os import torch from PIL import Image from torchvision import transforms def predict_image(model, image_path, transform, device): image = Image.open(image_path).convert('RGB') tensor = transform(image).unsqueeze(0).to(device) with torch.no_grad(): output = model(tensor) return torch.argmax(output, dim=1).item() correct = 0 total = 0 for cls_name in os.listdir('data/val'): cls_dir = os.path.join('data/val', cls_name) for img_file in os.listdir(cls_dir): pred = predict_image(model, os.path.join(cls_dir, img_file), transform, device) total += 1 if pred == class_to_idx[cls_name]: correct += 1 print(f'Accuracy: {correct / total:.4f}')

这个脚本只统计总体准确率,但垃圾分类数据集常见问题是卡纸和纸类容易混、玻璃和陶瓷容易混。总体准确率会掩盖这些局部问题,这时候要往下看混淆矩阵。

6.2 用 sklearn 画混淆矩阵

把所有验证集的预测结果和真实标签收集起来,用 sklearn 直接出矩阵:

import numpy as np from sklearn.metrics import confusion_matrix, classification_report y_true = [] y_pred = [] for i, (inputs, labels) in enumerate(val_loader): outputs = model(inputs.to(device)) _, preds = torch.max(outputs, 1) y_true.extend(labels.cpu().numpy()) y_pred.extend(preds.cpu().numpy()) cm = confusion_matrix(y_true, y_pred) print(cm) print(classification_report(y_true, y_pred, target_names=classes))

6.3 从混淆矩阵看模型该往哪个方向调

混淆矩阵的对角线越高,说明类别分得越清。如果发现“glass”和“metal”互相误判特别多,说明这两个类在纹理和反光特征上本身接近,单纯加深网络不一定有效,考虑两个方向:

  1. 增加这两个类的训练样本,让网络见过更丰富的玻璃瓶反光和金属材质变化。
  2. 在预测时提高置信度阈值,对低置信度预测直接输出“未知”,而不是强行分到某个类。

从那以后,我每次拿到一份新的分类模型,都会先跑一遍批量验证和混淆矩阵,把每个类别的单类准确率打出来再谈调参。以前我只看整体准确率,结果所有类平均下来 90%,实际“金属”这一类只有 60%,部署上线才翻车。现在这套流程成了习惯,模型好不好、该往哪调,一张矩阵全说清了。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 5:49:49

DeepSeek Harness全插件化与会话回放:Agent框架工程化突围

在 Agent 类项目的工程化实践里,我一直觉得有件事被很多人低估了:框架本身的扩展能力和可观测性,往往比那层“智能”更容易决定项目能不能在真实环境里活下来。DeepSeek Harness 这套东西,社区里不少朋友把它当成一个本地运行的 A…

作者头像 李华
网站建设 2026/10/7 5:49:32

基于Next.js与LangGraph.js的简历AI Agent实战:从状态设计到性能优化

做简历工具AI Agent这个项目,前前后后折腾了接近一个月。起因很简单,身边不少朋友找工作,改简历改到崩溃,就想着能不能做一个能聊天、能分析职位描述、能直接改简历内容的智能体。技术栈选了Next.js和LangGraph.js,原因…

作者头像 李华
网站建设 2026/10/7 5:48:27

斑马线目标检测数据集:真实场景标注与YOLO训练全流程

简介:YOLO斑马线目标检测数据集包含1000张真实场景的斑马线图片,图片均经labelimg工具仔细标注,标注框质量较高。数据集已整理为voc(xml)、coco(json)和yolo(txt)三种标准…

作者头像 李华
网站建设 2026/10/7 5:47:57

BqLog实时压缩日志:原理、算法与工程落地

日志不能拖慢游戏这件事,做客户端的人多少都有点体感。线上用户那里一崩,第一件事就是捞日志,结果日志被压缩阻塞卡了主线程,玩家先卡死,你再多的日志都成了案发现场的摆设。王者荣耀里那套BqLog日志组件,最…

作者头像 李华
网站建设 2026/10/7 5:47:27

RAG实战:从切块到重排序,彻底解决知识库问答“答非所问”

我们先把话说在前面:一个看起来能用的 RAG 知识库问答系统,真正放进业务里跑,十有八九会在第一周就被用户吐槽“答非所问”。更扎心的是,当你把日志翻出来检查时,经常发现模型本身没问题,Prompt 写得也还行…

作者头像 李华
网站建设 2026/10/7 5:47:10

AI代理自动生成可交互架构图:archify原理与实操指南

1. 项目核心拆解:archify 到底是什么第一次在 GitHub 上刷到 archify 这个项目时,我其实有点怀疑——又是“AI 自动生成”系列?这类宣称能让 AI 替你干活的工具,十有八九是套壳,生成的图也就唬唬外行。但点进去看了 RE…

作者头像 李华