ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

机器学习课程设计:Python垃圾分类系统源码解析与实战避坑指南

机器学习课程设计:Python垃圾分类系统源码解析与实战避坑指南 简介这份Python垃圾分类系统课程设计源码包面向机器学习课程设计学生及垃圾分类入门开发者提供一套从模型训练到界面演示的完整个人大作业方案。项目基于TensorFlow 2.3核心包括MobileNet模型训练脚本、窗口端垃圾分类测试程序、模型评估与混淆矩阵结果配套图像样本与XML标注数据便于快速理解图像分类落地流程。包体共32个文件以Python脚本、JPG/JPEG/PNG图像样本、XML标注文件为主另含评估用的Excel矩阵表总大小约2.27MB结构紧凑适合直接解压运行与二次修改。项目经严格调试评审分达95分以上可保障基本可用性。目前已有1884人学习下载。用户可从中获得课程设计源码、训练与测试脚本、可视化结果及数据组织方式参考其目录与代码逻辑可省去从零搭建的时间也能帮助理解数据准备、模型微调、结果导出等环节。1. 打开压缩包之前先搞清楚这套“机器学习课程设计Python垃圾分类系统源码”到底能干什么如果你是在交机器学习课程设计的前一晚拿到这个.zip你的诉求通常很具体里面有没有能直接跑起来的数据集、有没有训练好的模型、代码改哪里能换成自己的名字、交上去会不会被老师一眼看穿是拿别人的。这套“机器学习课程设计Python垃圾分类系统源码”解决的就是这件事——它把一条完整的机器学习应用链路打包给你图像数据集、模型训练脚本、预测脚本以及一个能把模型调用起来的系统界面。它的价值不在于算法有多前沿而在于它补全了课程设计里最容易被扣分的那几块数据从哪来、模型怎么训练、结果怎么展示。适合它的读者有两类一类是做课程设计或毕业设计的学生需要尽快弄懂每个文件的作用并改成自己的东西另一类是刚入门机器学习的开发者想找一个不算太大、能完整跑通的图像分类项目作为练手。后面的内容我会按最可靠的从业方案来拆目录结构怎么设计、数据怎么组织、模型怎么选、训练参数怎么调、坑在哪。你有源码包在手但更重要的是知道每一层为什么这么写。2. 垃圾分类系统的技术底座模型选型和数据集组织方式2.1 图像分类任务里为什么 ResNet 和 MobileNet 是课程设计的主流选型垃圾分类从技术上讲是一个典型的图像分类问题输入一张照片输出它属于哪一类垃圾。常见的分类体系有可回收垃圾、厨余垃圾、有害垃圾、其他垃圾四个大类细分的话有几十个小类。课程设计场景下模型不需要做到工业级精度但要在你自己拍的测试图片上表现稳定这就决定了模型架构怎么选。我在这类项目里最常见的做法是选 ResNet18 或 MobileNetV2而不是自己搭一个几十层的卷积网络。原因有三个第一ResNet18 有预训练权重PyTorch 里torchvision.models一行就能加载迁移学习可以让训练时间从几小时缩到十几分钟第二ResNet18 参数量适中CPU 也能完成预测课程设计答辩现场不一定有 GPU 机器用 CPU 能跑是硬指标第三MobileNetV2 更轻如果你打算把系统打包成 exe 或者部署到树莓派之类的小设备上它是更好的选择。如果你拿到的源码里用的是别的网络也不要急着换先看它有没有提供预训练权重的加载逻辑。课程设计评分更看重你是否理解了整个流程而不是模型名字是否高大上。2.2 数据集应该长什么样从 ImageFolder 目录结构说起不管是自己下载的数据集还是源码包里自带的图像分类任务的数据集目录组织方式基本都遵循一个约定PyTorch 的torchvision.datasets.ImageFolder可以直接读取这种结构。它的要求是根目录下按类别建子文件夹每张图片放在对应类别的文件夹里。以垃圾分类为例标准目录结构应该是dataset/ ├── train/ │ ├── recyclable/ │ │ ├── img_001.jpg │ │ └── img_002.jpg │ ├── kitchen_waste/ │ ├── hazardous/ │ └── other/ └── val/ ├── recyclable/ ├── kitchen_waste/ ├── hazardous/ └── other/如果你拿到的源码包里的数据组织方式和这个不一样比如所有图片都在一个文件夹里、类别信息写在 CSV 中那你需要写一个小脚本把它转换成上面这种结构。转换时要注意类别的命名保持一致因为训练脚本里ImageFolder会根据文件夹名字自动生成标签索引如果在train里叫recyclable而在val里叫recyclables模型验证时就会报错或者标签错位。数据量方面我建议每个类别至少准备 200 张以上图片。课程设计用的数据集常见的规模是每类 300 到 500 张四个大类总共 1200 到 2000 张。如果源码包自带的数据不足可以用数据增强来弥补后面会讲具体的增强配置。2.3 迁移学习为什么课程设计不需要从零训练从零训练一个图像分类模型需要用 ImageNet 那种千万级的数据集才能收敛到可用的精度课程设计的数据量撑不起这种训练。所以现实的方案是加载预训练权重然后在自己的垃圾分类数据上微调。这两者的区别在于预训练模型已经学会了边缘、纹理、形状这些通用特征微调时只需要让它重新学习把哪些特征组合对应到“可回收垃圾”这个类别上。在 PyTorch 中迁移学习的常见做法是冻结特征提取层、只训练分类头或者对整个网络用一个很小的学习率微调。对于课程设计的数据集规模我一般只训练最后一个全连接层这样速度快且不容易过拟合。这个思路可以直接替换源码里的训练逻辑即使你拿到的源码没有做迁移学习你也可以自己加上。import torch import torchvision.models as models # 加载预训练的 ResNet18 model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) # 替换最后一层全连接层输出类别数设为 4 num_classes 4 model.fc torch.nn.Linear(model.fc.in_features, num_classes) # 冻结所有特征层只训练全连接层 for param in model.parameters(): param.requires_grad False for param in model.fc.parameters(): param.requires_grad True这段代码的关键在最后两行先把所有参数设为不可训练再单独把fc层的参数打开。后面做反向传播时优化器只会更新model.fc里的权重特征提取部分保持预训练原样。这样做的好处是训练速度快、不容易过拟合坏处是如果数据集和你预训练任务的图像风格差异很大精度上限会低一些。垃圾分类图片和 ImageNet 里的日常物体风格接近所以这个方案可行性很高。3. 把模型训起来训练脚本、数据增强与参数设定3.1 数据加载与增强如何用小数据集训练出不翻车的模型拿到源码包后第一件事不是看模型定义而是看数据加载部分有没有做标准化和增强。如果数据加载只是简单地ImageFolder读进来就训练效果一般不会太好。图像分类任务里标准化和增强是决定精度的关键因素。标准化处理是把三通道图像数据缩放到模型期望的分布上。ResNet 系列模型在 ImageNet 上预训练时图像的 mean 和 std 分别是[0.485, 0.456, 0.406]和[0.229, 0.224, 0.225]加载预训练权重做微调时这一组值必须一致否则输入分布和预训练时不一致精度会明显下降。数据增强则是在训练时对图片做随机变换相当于变相扩充数据集。常见做法是随机旋转、随机水平翻转、随机裁剪和 ColorJitter。注意验证集不要做增强只需要标准化因为验证集要反映模型的真实表现。from torchvision import datasets, transforms from torch.utils.data import DataLoader # 训练集增强 标准化 train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.8, 1.0)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 验证集只做变换到 Tensor 和标准化不做增强 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]) ]) train_dataset datasets.ImageFolder(dataset/train, transformtrain_transform) val_dataset datasets.ImageFolder(dataset/val, transformval_transform) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_dataset, batch_size32, shuffleFalse, num_workers4)注意RandomResizedCrop(224, scale(0.8, 1.0))和CenterCrop(224)这一对组合训练时随机裁剪一块区域并缩放到 224 像素验证时先缩放到 256 再取中心 224 像素这是图像分类任务最常见的做法。num_workers4会用 4 个子进程加载数据Windows 上如果报错把它改成 0 只使用主进程。3.2 训练循环与学习率让 loss 稳定下降的三个关键细节训练循环本身不复杂但有几个参数直接决定你训练出来的模型能不能用。先说优化器和学习率。微调场景下Adam 优化器的初始学习率设1e-4到3e-4比较安全。1e-3对预训练模型来说偏大了微调时可能直接把权重冲坏。如果你用的是 SGD学习率可以适当调大配合 momentum 0.9 使用。学习率策略上课程设计场景用固定学习率即可不必上 CosineAnnealing 或 ReduceLROnPlateau。加一个可选的 StepLR 每 5 个 epoch 把学习率乘以 0.5 就够了。另一个容易忽略的细节是 loss 的计算方式。分类任务用nn.CrossEntropyLoss()它已经把 LogSoftmax 和 NLLLoss 包在一起了不要在模型的输出层再手动加 Softmax否则训练时 loss 的数值和梯度行为都会出问题。import torch.nn as nn import torch.optim as optim criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.fc.parameters(), lr1e-4) best_acc 0.0 num_epochs 15 for epoch in range(num_epochs): model.train() running_loss 0.0 for inputs, labels in train_loader: # 把输入数据转为浮点类型 inputs inputs.float() optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() * inputs.size(0) epoch_loss running_loss / len(train_dataset) print(fEpoch {epoch1}/{num_epochs}, Loss: {epoch_loss:.4f}) # 每个 epoch 结束后在验证集上评估精度 model.eval() correct 0 total 0 with torch.no_grad(): for inputs, labels in val_loader: inputs inputs.float() outputs model(inputs) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() val_acc correct / total print(fValidation Accuracy: {val_acc:.4f}) # 保存验证精度最高的模型参数 if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pth)model.eval()和torch.no_grad()这两行在验证时缺一不可。model.eval()切换 Batch Normalization 和 Dropout 的行为torch.no_grad()停止梯度计算避免显存被验证过程占满。保存模型时用model.state_dict()而不是整个 model 对象state_dict 方式更省空间且跨机器兼容性好。加载时你需要先按 2.3 节的代码构建模型结构再把 state_dict 装进去。epoch 数量设 15 是比较稳妥的起点数据量小、只训练全连接层时10 个 epoch 左右验证精度基本就收敛了设太多反而容易过拟合。3.3 模型推理脚本如何用训练好的模型对一张新图片做预测训练完模型后你需要一个独立的推理脚本来验证效果这也是课程设计报告中“系统测试”环节的内容。推理脚本做的事情是加载图片、做与验证集一致的预处理、经过模型、输出各类别概率。from PIL import Image import torchvision.transforms as transforms # 类别名称顺序必须与数据集的文件夹顺序一致 class_names [recyclable, kitchen_waste, hazardous, other] def predict(image_path, model_path, devicecpu): # 加载模型 model models.resnet18(weightsNone) model.fc torch.nn.Linear(model.fc.in_features, len(class_names)) model.load_state_dict(torch.load(model_path, map_locationdevice)) model.eval() # 与验证集相同的预处理 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]) ]) image Image.open(image_path).convert(RGB) input_tensor transform(image).unsqueeze(0) with torch.no_grad(): outputs model(input_tensor) probabilities torch.softmax(outputs, dim1) confidence, predicted_idx torch.max(probabilities, 1) return class_names[predicted_idx.item()], confidence.item() # 使用示例 category, score predict(test_photo.jpg, best_model.pth) print(f预测类别: {category}, 置信度: {score:.4f})推理脚本里最容易翻车的地方是transform和数据加载时的 transform 不一致。有人训练时用Resize(256) CenterCrop(224)推理时只做了Resize(224)尺寸差一点精度下滑厉害。最好的习惯是推理脚本里的预处理逻辑与验证集保持一致直接复用训练代码里的预处理定义。另外Image.open之后建议加.convert(RGB)这样可以避免某些 PNG 图片的 RGBA 四通道问题导致模型输入维度不匹配。4. 把它做成一个能演示的“系统”文件组织与界面接入4.1 除了训练和推理一个完整系统还需要什么文件课程设计评分时老师说“你这个项目要做成一个系统”意思是项目不能只有训练脚本和模型文件还需要有输入输出的交互载体。一个典型的 Python 垃圾分类系统源码包通常包含这些文件模型训练脚本、模型推理脚本、数据预处理工具、交互界面脚本、模型权重文件、数据集文件夹、README 说明文档和环境依赖清单。把这些文件理清后推荐的项目目录结构如下garbage_classification/ ├── dataset/ # 数据集train/val 子目录 ├── models/ # 模型定义代码 │ └── resnet.py ├── scripts/ │ ├── train.py # 训练脚本 │ └── predict.py # 单张图片预测脚本 ├── app.py # 图形界面/Web 界面入口 ├── requirements.txt # 依赖清单 ├── README.md # 项目说明 └── best_model.pth # 训练好的模型权重如果你的源码包文件命名不是这样也不影响使用重点是你能在 README 或代码注释里找到每个文件的用途。如果源码包没有 README建议自己补一份写明运行步骤、Python 版本和依赖库课程设计答辩时老师很可能会问“这个项目怎么运行”一份清晰的说明比临场找命令要好得多。4.2 快速搭一个可交互的界面从命令行到 Tkinter课程设计系统最常见的要求是有一个可操作的界面而不只是一段能输出文字的脚本。常见的方案有三种命令行交互、Tkinter 图形界面、Flask Web 页面。命令行最简单但观感弱Web 页面效果好但代码量大折中最优方案是 Tkinter——Python 自带的 GUI 库不需要额外安装写一个能选图片、显示预测结果的窗口只要几十行代码。import tkinter as tk from tkinter import filedialog, Label from PIL import Image, ImageTk import torch class GarbageClassifierApp: def __init__(self, model_path): self.model self.load_model(model_path) self.window tk.Tk() self.window.title(垃圾分类识别系统) # 图片显示区域 self.image_label Label(self.window, width300, height300, bggray) self.image_label.pack(padx10, pady10) # 预测结果标签 self.result_label Label(self.window, text未选择图片, font(SimHei, 14)) self.result_label.pack(pady5) # 按钮 select_btn tk.Button(self.window, text选择图片并识别, commandself.select_and_predict) select_btn.pack(pady10) def load_model(self, model_path): # 模型结构定义与训练时一致 model torchvision.models.resnet18(weightsNone) model.fc torch.nn.Linear(model.fc.in_features, 4) model.load_state_dict(torch.load(model_path, map_locationcpu)) model.eval() return model def select_and_predict(self): file_path filedialog.askopenfilename( filetypes[(Image files, *.jpg *.jpeg *.png)]) if not file_path: return # 显示选中的图片 img Image.open(file_path) img img.resize((300, 300)) photo ImageTk.PhotoImage(img) self.image_label.config(imagephoto) self.image_label.image photo # 调用预测并更新结果 category, confidence self.predict(file_path) self.result_label.config( textf识别结果: {category} 置信度: {confidence*100:.1f}%) def predict(self, image_path): # 复用推理脚本的预处理和预测逻辑 ... def run(self): self.window.mainloop() if __name__ __main__: app GarbageClassifierApp(best_model.pth) app.run()这段代码把训练好的模型封装成了桌面应用。注意第 10 行的模型结构定义必须和训练时完全一致如果训练时用的是 ResNet50这里写 ResNet18 就会在上述load_state_dict时报错。另外ImageTk.PhotoImage有一个容易忽视的坑它不支持直接显示 RGBA 模式的图片所以读取图片后确保转换为 RGB 或 RGBA 再显示。4.3 GUI 会在哪些环节出问题打包和路径的常见坑Tkinter 界面在开发机上跑没问题一旦要拷到别的电脑上运行或者打包成 exe问题就来了。最常见的坑是best_model.pth文件的路径写死。如果你在代码里写的是相对路径best_model.pth而用户在另一个目录下双击运行程序就找不到模型文件。稳妥做法是在代码里获取当前脚本所在目录再拼接模型路径import os base_dir os.path.dirname(os.path.abspath(__file__)) model_path os.path.join(base_dir, best_model.pth)这样不管在哪个目录下运行都能正确定位到模型文件。另外一个坑是用 PyInstaller 打包时.pth文件和数据集资源不会被自动包含在 exe 里需要额外处理。如果答辩现场不需要打包成 exe在装有 Python 环境的机器上直接运行脚本是最省事的方案。5. 从训练到演示的几处关键避坑记录5.1 训练时 loss 不降或大幅波动现象是 loss 一直在 1.3 到 1.5 之间上下跳动怎么训练都降不下去。这个 loss 数值正好对应四分类任务的随机猜测水平ln 4 ≈ 1.386。原因通常是学习率设置过大或者模型根本没有加载预训练权重从随机初始化开始训练。解决方法是先把学习率降到1e-4并确认代码中确实加载了weightsmodels.ResNet18_Weights.IMAGENET1K_V1。如果确认了这两点后 loss 仍然不降检查数据增强里是否做了标准化未标准化的输入会让模型训练极其困难。5.2 验证精度高但测试图片识别一塌糊涂这种情况我用两个字概括过拟合。模型在训练集和验证集上表现很好——比如验证集有 95% 以上的精度但你随便照一张手机相册里的照片让它识别结果完全不对。原因是你训练时见过的数据风格太单一验证集的图片和训练集来自同一数据源模型没有学会泛化。解决思路有两个方向一是数据增强加猛一点把ColorJitter的亮度、对比度范围调大加入随机旋转二是训练时把冻结的特征层解开用更低的学习率微调整个网络让模型的特征提取部分也适应真实图片风格。5.3 Windows 下 DataLoader 报 BrokenPipeError 或训练中途卡死Windows 上跑 PyTorch 的 DataLoader 经常报多进程相关的错误原因是num_workers0时 Windows 的进程管理方式和 Linux 不同。现象就是程序刚开始跑就报 BrokenPipeError或者训练中途界面卡住。最省事的解决方案是把num_workers设为 0。代码需要改两个地方训练脚本和验证脚本的DataLoader。如果你的代码里已经用了if __name__ __main__:保护了入口则可以把num_workers保持为 2 或 4但为了稳定我一般还是建议在课程设计演示时直接设置 0机器性能差异不影响训练结果。5.4 加载模型时报 size mismatch 错误现象是在推理或者加载预训练模型时torch.load之后model.load_state_dict报错提示size mismatch for fc.weight从一个形状到另一个形状。原因是训练时输出类别数和加载时定义的num_classes不一致或者训练时用的网络结构和加载时定义的不是同一个模型。解决方法是先确认数据集有几个类别再把加载模型的代码里的model.fc torch.nn.Linear(...)的输出数字改成一致。另一个容易让人忽视的地方是如果你在训练代码里把num_classes4但推理代码的模型定义写的是num_classes5也会报 mismatch。可以把num_classes提取成脚本顶部的一个常量训练和推理共同引用。5.5 预测时所有图片都输出同一个类别这个坑我遇到过好几次训练过程看起来一切正常但系统无论输入什么图片都输出“可回收垃圾”而且置信度很高。原因通常是类别不平衡。数据集里某一个类别的图片数量远多于其他类别模型学会了输出这个类就能拿到低 loss根本没有学到真正区分各类的特征。解决的第一个方法是统计数据集每个类别的图片数量四个类别的数量尽量均衡。第二个方法是调整CrossEntropyLoss的类别权重让少的类别对 loss 的贡献更大。最简单的做法是给nn.CrossEntropyLoss(weightclass_weights)传入一个长度为类别数的张量这个张量按“样本数越少权重越大”的原则生成。6. 把系统再往前推一步用混淆矩阵验证模型可信度训练完模型、系统能跑起来不代表你的项目就做完了。课程设计评审时老师最常问的一个问题是“你这个模型的准确率是怎么计算出来的每个类别的表现分别是多少”。如果只能回答“验证集准确率 95%”看起来就像只跑了脚本没有做分析。我做这类项目时有个习惯训练结束后生成一张混淆矩阵把每个类别的精确率和召回率列出来。import numpy as np from sklearn.metrics import confusion_matrix, classification_report import matplotlib.pyplot as plt def evaluate_model(model, val_loader, class_names): model.eval() all_preds [] all_labels [] with torch.no_grad(): for inputs, labels in val_loader: inputs inputs.float() outputs model(inputs) _, predicted torch.max(outputs, 1) all_preds.extend(predicted.tolist()) all_labels.extend(labels.tolist()) # 计算混淆矩阵和分类报告 cm confusion_matrix(all_labels, all_preds) report classification_report(all_labels, all_preds, target_namesclass_names) print(report) # 画混淆矩阵图并保存 fig, ax plt.subplots(figsize(8, 6)) im ax.imshow(cm, cmapBlues) ax.set_xticks(range(len(class_names))) ax.set_yticks(range(len(class_names))) ax.set_xticklabels(class_names, rotation45) ax.set_yticklabels(class_names) ax.set_xlabel(预测类别) ax.set_ylabel(真实类别) plt.colorbar(im) plt.savefig(confusion_matrix.png, dpi150, bbox_inchestight)混淆矩阵的价值在于它能告诉你模型的错误都集中在哪些类别上。比如“可回收垃圾”和“厨余垃圾”之间经常混淆说明这两类垃圾的外观形态确实接近下一步可以考虑是否需要细分处理或者为这两类单独扩充样本。这一段分析写进课程设计报告里是实打实的加分项。我的个人习惯是系统做完后再找二三十张没见过的真实垃圾照片——比如自己家里的饮料瓶、外卖盒——让系统识别把识别正确和错误的照片一起截图存档。这些截图配合混淆矩阵形成一条完整的测试链路训练阶段看 loss 曲线验证阶段看准确率测试阶段看真实场景的泛化表现。选题是“垃圾分类系统”也好换成一个花卉识别或商品分类系统也好——核心玩法是一致的。做课程设计最怕的不是模型精度不够而是你不知道它为什么不够、差在哪。希望这篇笔记能帮你把这套源码变成真正属于自己的系统。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进