ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python垃圾分类系统课程设计:源码解析、模型训练与答辩避坑指南

Python垃圾分类系统课程设计:源码解析、模型训练与答辩避坑指南 简介面向机器学习课程设计场景的Python垃圾分类系统源码包完整覆盖数据预处理、特征工程、模型训练、评估与部署等关键环节适合高校学生、机器学习初学者用于课程作业、实训项目或毕业设计参考。压缩包共32个文件以4个Python脚本为核心分别承担MobileNet模型训练、垃圾分类窗口程序、模型测试与结果统计配套多张JPG/JPEG/PNG图片样本、XML标注文件及xls评估结果整体仅2.26MB目录结构清晰便于直接运行和二次修改。项目涉及OpenCV图像处理、sklearn、numpy、pandas等常用库的典型用法包含监督学习建模思路、交叉验证、准确率与混淆矩阵等评估方法可帮助读者快速搭建从图像识别到分类结果展示的完整实践路线。已有772人学习下载对希望快速搭建垃圾分类识别原型、理解机器学习项目组织方式或完善课程设计的读者有较高参考价值。1. 拿到“机器学习课程设计Python 垃圾分类系统源码.zip”先想清楚这到底是个课程设计不是一个成品 App第一次打开这种压缩包很多人直奔“解压后能不能跑”我的习惯是先问三个问题数据在哪、模型是哪一种、入口脚本叫什么。Python 垃圾分类系统是机器学习课程设计里出现频率极高的选题但源码包质量参差不齐有的里面是完整 PyTorch 工程有的只有一个训练脚本加半份数据甚至有人把别的项目的文件混了进来。你需要的不是“能运行”而是能讲清楚原理、能改参数、能答出老师追问的“为什么用这个模型”。这篇不是替你把源码抄一遍而是告诉你拿到 zip 之后该怎么拆、怎么补、怎么训练、怎么避坑最后把演示做得能应付现场。2. 解压后先做结构体检目录、入口脚本和数据集哪个能跑哪个是坏的2.1 先处理掉 zip 编码和嵌套目录再谈别的课程设计源码包经常是在 Windows 上压缩的中文目录名、中文脚本名一大堆到了 macOS 或 Linux 下一解压全是乱码。这不是 zip 损坏而是编码问题。Windows 老压缩工具默认用 GBK 存文件名Python 的 zipfile 模块按 cp437 解码所以中文文件名变成了一堆拉丁字母。常见做法是用一个脚本来安全解压import zipfile import os def safe_extract(zip_path, out_dirsrc): os.makedirs(out_dir, exist_okTrue) with zipfile.ZipFile(zip_path) as zf: for info in zf.infolist(): raw info.filename raw_bytes raw.encode(cp437, errorsreplace) name raw for enc in (utf-8, gbk): try: name raw_bytes.decode(enc) break except UnicodeDecodeError: continue target os.path.join(out_dir, name) os.makedirs(os.path.dirname(target), exist_okTrue) with zf.open(info) as src, open(target, wb) as dst: dst.write(src.read()) print(解压完成输出目录:, out_dir) if __name__ __main__: safe_extract(机器学习课程设计Python垃圾分类系统源码.zip)逻辑说明先把info.filename重新编码回原始字节再尝试用 UTF-8 和 GBK 解码。现代源码包优先是 UTF-8老课程设计包常见 GBK。两种都解不出来的保留原样至少不中断整个解压过程。参数上不用指定文件数zipfile 会自己读完唯一要注意的是外层嵌套目录很多包会把全部文件放进一个garbage_classify/子目录里解压后入口脚本的路径就变成src/garbage_classify/train.py。2.2 定位推理入口与模型加载找到“最小可运行路径”解压完成后不要急着训练先找有没有现成权重文件和推理脚本。很多课程设计包里train.py写得很烂但predict.py反而是能跑的。先用命令把脚本和数据目录列出来find src -maxdepth 3 -type f \( -name *.py -o -name *.pth -o -name *.pt \) | sort看到.pth或.pt文件意味着包里有别人训练好的模型可以先从推理跑通闭环。常见的推理脚本长这样import torch from torchvision import models # 先确认模型结构再加载权重 model models.resnet18(weightsNone) model.fc torch.nn.Linear(model.fc.in_features, 4) state torch.load(best.pth, map_locationcpu) # 有的包里直接存 state_dict有的存整模型 checkpoint if model_state_dict in state: model.load_state_dict(state[model_state_dict]) else: model.load_state_dict(state) model.eval() print(模型加载成功类别数4)这里有一个关键区别torch.load(best.pth)加载来的东西可能是三种形态完整的torch.nn.Module对象、只有state_dict的字典、带model_state_dict和optimizer_state_dict的 checkpoint。没有源码说明的情况下先用isinstance或keys()看一眼再决定怎么加载。另外模型结构必须和训练时一致比如原项目用mobilenet_v2你在推理脚本里换成resnet18加载一定会报size mismatch。遇到这种报错不要急着改权重文件改回对应的 backbone 才是正路。2.3 看清数据组织方式label 是文件夹名还是 CSV课程设计的数据集组织无非两种按类别分文件夹或者给一个 CSV 映射表。前者最省事也是我现在写任何训练脚本都优先采用的方式因为不容易把 label 和文件名搞错。用一段快速脚本统计出各类别数量from pathlib import Path from collections import Counter data_dir Path(src/data/train) counter Counter() for p in data_dir.rglob(*.*): counter[p.parent.name] 1 print(counter) for cls_name, count in counter.items(): print(f{cls_name}: {count})这段代码的价值在于确认num_classes。垃圾分类课程设计常见四分类厨余、可回收、有害、其他但也有的项目是六分类、八分类甚至几十分类。模型最后一层全连接层的out_features必须和这个数字对得上。如果统计出来某个文件夹里只有两张图那大概率是数据集被误删除或者压缩时丢了文件后面训练该类的 F1 会很难看。3. 自己训练一遍最小模型DataLoader、优化器与三类必调参数3.1 重写一个不依赖源码包的 Dataset 加载器源码包里的训练脚本经常会带着一堆没用的耦合代码比如从固定路径读配置、依赖某个自定义模块。我拿到包后会重写一个最简 Dataset只做一件事把图片路径和 label 拉平成列表。import torch from torch.utils.data import Dataset from PIL import Image from pathlib import Path class GarbageDataset(Dataset): def __init__(self, root, transformNone): self.transform transform self.paths [] self.labels [] # 类名排序后固定编号避免在不同机器上得到不同顺序 self.classes sorted([p.name for p in Path(root).iterdir() if p.is_dir()]) for label, cls_name in enumerate(self.classes): for p in (Path(root) / cls_name).glob(*.*): self.paths.append(str(p)) self.labels.append(label) def __len__(self): return len(self.paths) def __getitem__(self, idx): img Image.open(self.paths[idx]).convert(RGB) if self.transform: img self.transform(img) return img, self.labels[idx] # 使用示例 transform torchvision.transforms.Compose([ torchvision.transforms.Resize((224, 224)), torchvision.transforms.ToTensor(), torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) train_ds GarbageDataset(src/data/train, transformtransform) print(类别映射:, train_ds.classes)逻辑说明self.classes必须做一次排序并固定下来否则训练脚本和推理脚本各自os.listdir()得到的顺序可能不同训练出来模型类别编号全是乱的。convert(RGB)也很重要数据里若有 RGBA 的 PNG 或灰度图不统一通道数会导致ToTensor()之后的张量维度不一致训练到一半直接崩。3.2 训练主循环和参数设置epoch、lr、batch_size 怎么调课程设计里最常见的错误是不管数据量直接套 ImageNet 的训练参数。垃圾分类数据集通常很小几千张到几万张不等我的建议是预训练 backbone 小学习率 冻结前几层。下面是可复用的训练片段import argparse import torch from torchvision import models parser argparse.ArgumentParser() parser.add_argument(--epochs, typeint, default20) parser.add_argument(--lr, typefloat, default1e-3) parser.add_argument(--batch-size, typeint, default32) parser.add_argument(--num-classes, typeint, default4) parser.add_argument(--backbone, defaultresnet18) args parser.parse_args() if args.backbone resnet18: model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) model.fc torch.nn.Linear(model.fc.in_features, args.num_classes) elif args.backbone mobilenet_v2: model models.mobilenet_v2(weightsmodels.MobileNet_V2_Weights.IMAGENET1K_V1) model.classifier[1] torch.nn.Linear(model.classifier[1].in_features, args.num_classes) # 冻结 backbone只训练分类头适合千张级别的小数据集 for param in model.parameters(): param.requires_grad False for param in model.fc.parameters(): param.requires_grad True device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) opt torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lrargs.lr) criterion torch.nn.CrossEntropyLoss() for epoch in range(args.epochs): model.train() total_loss, correct, total 0, 0, 0 for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) opt.zero_grad() out model(imgs) loss criterion(out, labels) loss.backward() opt.step() total_loss loss.item() * imgs.size(0) correct (out.argmax(1) labels).sum().item() total labels.size(0) print(fepoch {epoch1}/{args.epochs}, loss {total_loss/total:.4f}, acc {correct/total:.4f})参数说明lr1e-3配上 Adam在只训练分类头时是安全的起点如果解冻 backbone 做全参数微调lr 要降到1e-4甚至1e-5否则预训练权重会被破坏。epochs20对垃圾分类这种数据量足够看到收敛趋势没必要一上来写 100。batch_size32是显存和速度的中间值用 ResNet18 输入 224×224在 8G 显存的卡上没问题显卡不够就把输入 resize 到 160×160同时调小 batch。3.3 checkpoint 与推理脚本用保存文件打通闭环训练过程中别只保存模型参数把类别列表、epoch 和 best acc 一起存进字典这样推理端能直接读出类别名不用靠猜best_state { epoch: epoch, model_state_dict: model.state_dict(), optimizer_state_dict: opt.state_dict(), classes: train_ds.classes, val_acc: val_acc, } torch.save(best_state, best.pth)推理时对应的加载逻辑ckpt torch.load(best.pth, map_locationcpu) model.load_state_dict(ckpt[model_state_dict]) classes ckpt[classes]这里有个容易翻车的细节如果 checkpoint 是在 GPU 上保存的加载到 CPU 机器必须显式写map_locationcpu否则报RuntimeError: Attempting to deserialize object on a CUDA device。另外optimizer_state_dict里存的是训练时的参数状态换了设备直接load_state_dict偶尔会报错最稳妥的做法是只有继续训练时才加载它纯推理只加载model_state_dict。4. 让“机器学习”课设名副其实设计三组对比实验而不是交作业4.1 用传统特征 SVM 作为基线拉开深度学习与经典机器学习的差距很多课程设计叫“机器学习课程设计”交上来却清一色是深度学习。老师不会觉得错但如果只跑一个 ResNet答辩时很难展示“你理解机器学习方法”。我一般会补一个经典基线HOG 颜色直方图 SVM几十行代码就能跑。import cv2 import numpy as np from skimage.feature import hog from sklearn.svm import SVC def featurize(img): img cv2.resize(img, (128, 128)) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) h hog(gray, orientations9, pixels_per_cell(16, 16), cells_per_block(2, 2)) hist cv2.calcHist([img], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256]) hist cv2.normalize(hist, hist).flatten() return np.concatenate([h, hist]) X_train np.array([featurize(cv2.imread(str(p))) for p in train_paths]) y_train np.array(train_labels) svm SVC(kernelrbf, C10, class_weightbalanced) svm.fit(X_train, y_train) print(SVM train acc:, svm.score(X_train, y_train))逻辑说明HOG 描述的是边缘纹理结构颜色直方图抓的是垃圾袋、易拉罐这类物体的色彩分布两者拼起来就是一个 4 类垃圾图片还能用的手工特征。C10是 RBF SVM 的常见初始值交叉验证可以再搜一遍但课程设计阶段这个值足够。class_weightbalanced必须有有害垃圾的样本量通常远小于可回收不去平衡的话 SVM 会直接把少数类全部判错。4.2 不同 backbone、是否预训练、输入尺寸的实验矩阵课程设计的加分项不是模型越深越好而是你能说清楚不同配置对结果的影响。我会跑一个三行五列的表格记录 val_acc、训练时长和显存占用backbone是否预训练输入尺寸val_acc示例数据单张 CPU 推理耗时ResNet18是224×2240.91约 120msResNet18否224×2240.78约 120msMobileNetV2是224×2240.90约 60msMobileNetV2是160×1600.87约 45msHOG SVM-128×1280.72约 3ms表格里的数字不一定是你的结果我写的是常见量级你拿到自己的数据集后跑一遍把真实数字填进去。表里最值得在答辩时说的一条规律输入尺寸从 224 降到 160准确率通常只掉 3 到 5 个点推理耗时却可能降一半。遇到演示机器性能差的场景这个表格就是你换模型的依据。跑实验时用time python train.py --backbone mobilenet_v2 --epochs 20记录时长显存用nvidia-smi --query-gpumemory.used看别靠感觉。4.3 Per-class 指标与混淆矩阵别只看 val_acc垃圾分类的类别天然不均衡可回收垃圾图片可能占一半有害垃圾只占百分之几。这时候 val_acc 到 0.95 也不代表模型能用因为全预测成可回收也能蒙对一半以上。所以要单独看每个类别的精确率和召回率from sklearn.metrics import classification_report, confusion_matrix y_true, y_pred [], [] model.eval() with torch.no_grad(): for imgs, labels in val_loader: imgs imgs.to(device) out model(imgs) y_true.extend(labels.numpy()) y_pred.extend(out.argmax(1).cpu().numpy()) print(classification_report(y_true, y_pred, target_namesval_ds.classes)) print(confusion_matrix(y_true, y_pred))classification_report会输出每一类的 precision、recall、f1-score 和 support。需要重点盯住 support 最小的那个类比如“有害垃圾”recall 低于 0.5 说明模型经常把有害垃圾漏掉。这时候再回去看是不是该类样本太少如果是就要做数据增强或者过采样而不是盲目加大模型。5. 运行与答辩避坑清单从解压到演示现场的常见问题排查5.1 zip 解压乱码和缺文件现象解压后脚本文件名是train.py但所有中文目录全变成乱码或者data/train里少了一个类别的文件夹。 原因Windows 下用老压缩工具打包的 zip 用的是 GBK 文件名编码Python 和 macOS 的默认解压工具按 UTF-8 解乱码是必然。缺文件通常不是丢了而是被嵌套层包住根目录下只有一个看起来像名字的文件夹。 解决用 2.1 节的安全解压脚本处理乱码处理完再find看一次目录层级。如果入口脚本用相对路径./data但它所在的目录并不和 data 同级会报找不到数据集。修法是在脚本开头用Path(__file__).resolve().parent拼出绝对路径不要让程序依赖“当前工作目录”。5.2 标签编号错位、loss 不动、全部分到同一类现象训练 loss 一开始就在 2 点几徘徊怎么调学习率都不降或者 val_acc 稳定在 0.5 附近模型把所有图都预测成“可回收”。 原因最常见是 classes 列表排序不固定训练时os.listdir和推理时os.listdir顺序不一致label 整体偏移一位。另一种可能是CrossEntropyLoss配合了错误的标签编码有的源码从 1 开始编号最后全连接层输出 4 类但标签却是 1 到 4没有 0 类模型学到的是一个坏映射。 解决先停训练打印一个 batch 的真实标签分布imgs, labels next(iter(train_loader)) print(labels.unique(), labels[:16]) print(train_ds.classes)确认标签取值范围是0..num_classes-1并且和classes顺序一致。然后在训练循环里加一个assert labels.max() model.fc.out_features有问题第一时间炸出来比训练五小时后再去查快得多。5.3 val_acc 虚高到不真实数据泄漏的三种来源现象训练集准确率正常验证集准确率却高达 0.98 以上明显不符合真实场景或者验证集曲线和训练集曲线完全同步一点 gap 都没有。 原因一是同一张图片同时出现在 train 和 val这种最蠢但最常见多半是复制粘贴数据集时没清干净二是同一个物体拍的多张连续照片被硬拆到了两个集合里模型其实记住了背景而不是垃圾本身三是验证集做了和训练集一样的数据增强或者 transform 里混入了标准化以外的扰动。 解决用 MD5 全量去重一段小脚本就能查import hashlib from pathlib import Path def md5(path): return hashlib.md5(Path(path).read_bytes()).hexdigest() train_md5 {md5(p) for p in Path(data/train).rglob(*.*)} val_md5 {md5(p) for p in Path(data/val).rglob(*.*)} print(重复文件数:, len(train_md5 val_md5))输出为非零说明泄漏存在直接删掉 val 中的重复图片。如果去重后 val_acc 还是虚高就去查同源图片很多数据集文件名是001_1.jpg、001_2.jpg这种连续帧按前缀分组再切分不要按文件名字典序硬切。5.4 显存不足和训练时间失控现象CUDA out of memory. Tried to allocate 256.00 MiB或者训练十几个小时还没跑完 20 个 epoch。 原因batch_size设太大Resize((224, 224))导致张量占用大DataLoader 的num_workers开太多把内存吃满还可能是没有冻结 backbonePyTorch 为全模型保存了梯度显存直接翻倍。 解决先降 batch_size 到 16输入尺寸改 160冻结 backbone 只训练分类头能显著减少显存。如果还想硬上大 batch用 PyTorch 自带的自动混合精度scaler torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): loss criterion(model(imgs), labels) scaler.scale(loss).backward() scaler.step(opt) scaler.update()注意torch.cuda.amp在 PyTorch 1.10 之后才稳定老版本直接用会报模块不存在。显存紧张时优先裁剪输入尺寸而不是硬堆硬件。5.5 答辩演示现场跑不动CPU 只有核没有卡现象演示电脑没有 NVIDIA GPU模型的torch.load卡住或直接报 CUDA 错误或者现场断网torchvision初次加载预训练权重时卡死在下载界面。 原因源码包里训练脚本写死了.cuda()权重是在 GPU 上保存的没有做设备自适应预训练权重没随源码包一起给代码自动从网上下载。 解决推理脚本统一用device torch.device(cuda if torch.cuda.is_available() else cpu)torch.load 必带map_locationdevice。预训练权重提前下载到本地缓存目录把整个缓存目录拷给演示电脑并在推理脚本前段设置torch.hub.set_dir(./hub_cache)这样torchvision会优先读本地缓存不再联网。现场如果还是卡就切 MobileNetV2 加 160×160 输入的 checkpoint这也是 4.2 节实验矩阵在演示环节的实际价值。6. 把推理脚本变成一个能演示的 Web 服务Flask 封装与验收口径答辩时让评委盯着黑底终端看图片路径不是个好体验。我一般会把训练好的模型封成一个 Flask 接口浏览器、手机、curl 都能访问演示效果好很多。from flask import Flask, request, jsonify from PIL import Image import torch from torchvision import transforms import model_builder app Flask(__name__) ckpt torch.load(best.pth, map_locationcpu) model model_builder.build_model(resnet18, len(ckpt[classes])) model.load_state_dict(ckpt[model_state_dict]) model.eval() transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) app.post(/predict) def predict(): file request.files.get(image) if file is None: return jsonify({error: image field required}), 400 img Image.open(file.stream).convert(RGB) x transform(img).unsqueeze(0) with torch.no_grad(): prob torch.softmax(model(x), dim1) idx int(prob.argmax(1)) return jsonify({label: ckpt[classes][idx], prob: float(prob[0, idx])}) if __name__ __main__: app.run(host0.0.0.0, port5000)这段代码里有几个细节是踩过坑才加上的file.stream避免整个文件读进内存convert(RGB)把 PNG 的 RGBA 四通道抹掉否则ToTensor出来是 4 通道模型输入维度直接报错模型在服务启动时加载一次绝不在每个请求里重新 load。验证命令也很简单curl -F imagetest_bottle.jpg http://127.0.0.1:5000/predict返回里能看到类别名和置信度说明整条链路是通的。如果想要更稳一点把app.run换成 gunicorn 起两个 worker能抗住现场多人同时访问的压测需求。我吃过一次亏答辩前十分钟发现上传的 PNG 图片崩溃原因是没做convert(RGB)。从那以后我接手任何一个课程设计源码包都会先写一个 5 分钟的 curl 压测并准备一张特殊格式的图片专门测试容错。这个习惯帮我在不少现场演示里避免了“模型翻车”的尴尬。希望帮到你。本文还有配套的精品资源点击获取
返回列表