简介:这份资源是面向人工智能初学者与高校学生的智能垃圾分类系统完整源码包,适用于毕业设计、课程作业及AI实验场景,帮助读者理解图像识别与深度学习在环保分类中的落地方式。包内共52个文件,以28个Python脚本为核心,涵盖数据预处理、模型构建、训练与预测等模块,另有13个pyc编译文件、6张运行截图及gif演示、rst与md说明文档、json配置等,压缩包约1.15MB,结构清晰便于按模块查阅。项目围绕卷积神经网络展开,涉及ResNet、CBAM等模型实现,并包含训练、评估、可视化与预测脚本,可帮助读者掌握数据增强、模型调优与结果分析的完整流程。目前已有141人学习,适合希望积累AI项目实战经验、提升编程与算法调优能力的学习者参考借鉴。
1. 从一份「智能垃圾分类系统」压缩包说起:它到底能帮你交掉什么
如果你正在翻毕设选题,或者刚被课程作业的 deadline 追着跑,看到「用于人工智能学习所实验的智能垃圾分类系统.zip」这种命名,第一反应大概是:这玩意儿能不能直接跑起来、能不能改、能不能撑起一篇论文的工作量。我先把结论摆前面——这类项目本质是一个「图像分类 + Web 演示」的完整闭环,核心是拿一个预训练卷积网络在垃圾图像数据集上做迁移学习,再套一层前后端把识别结果可视化出来。它解决的不是算法创新问题,而是让你在有限时间里把「数据—训练—推理—展示」这条链路走通,并且每一步都有东西可写、可截图、可答辩。
适合谁?三类人:一是计算机、物联网、人工智能相关专业的本科毕设选手,需要一个能落地、有界面、有指标的题目;二是选了「人工智能导论」「深度学习」这类课、期末要交大作业的同学;三是想入门计算机视觉但不知道从哪下手的新手,拿它当第一个端到端项目练手。不适合谁?想发论文、想做算法对比实验的人——这类项目的模型结构基本是现成的,创新点得你自己往上加。下面我按「先跑通、再拆解、再改造」的顺序讲,中间会给出可直接抄的命令和参数,也会说清楚哪些地方最容易翻车。
2. 智能垃圾分类系统的技术骨架:从一张图片到分类结果中间发生了什么
2.1 为什么是「分类」而不是「检测」,选型理由先讲清
很多人一上来就想做目标检测,觉得能框出垃圾位置更高级。但你要先想清楚场景:用户拍一张照片,里面通常只有一件垃圾,或者主体非常明确。这种情况下分类网络(ResNet、MobileNet、EfficientNet 这类)就够了,标注成本低、训练快、部署轻。检测网络(YOLO 系列)需要边界框标注,标注工作量是分类的 5 到 10 倍,而且毕设周期内你未必能标完一个像样的数据集。
我一般会这样定:如果数据集是「一图一物」的公开垃圾数据集(比如 TrashNet 那种 6 分类、每类几百张的规模),直接上分类;如果非要检测,就用现成 YOLO 权重做微调,别从零训。分类方案的另一个好处是,后端接口极其简单——输入一张图,输出一个类别和置信度,前端拿去做动画、做音效、做积分,都好接。
选主干网络时看两个指标:参数量和 Top-1 准确率。毕设场景我推荐 MobileNetV3 或 ResNet18,前者适合要部署到边缘设备(比如树莓派、Jetson Nano)的选题,后者适合纯服务器推理、追求稳定收敛的选题。EfficientNet-B0 是折中选项,但它的预处理归一化参数和普通 ImageNet 不一样,容易踩坑,后面避坑章节会讲。
2.2 数据集的获取、清洗与划分:别让脏数据毁掉整个训练
公开垃圾数据集常见的有 TrashNet(6 类:纸板、玻璃、金属、纸、塑料、其他垃圾)、Kaggle 上的垃圾分类数据集(通常 12 类或 30 类)。下载下来第一件事不是直接训练,而是做三件事:去重、去模糊、检查类别平衡。
去重用感知哈希(pHash),几行代码就能筛掉大量重复图。去模糊用拉普拉斯方差,阈值一般设在 100 左右,低于这个值的图大概率是糊的。类别平衡看直方图,如果某一类只有几十张而其他类有几百张,要么做数据增强补,要么在损失函数里加类别权重。
import cv2 import numpy as np from PIL import Image import imagehash def is_blurry(image_path, threshold=100): img = cv2.imread(image_path) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) variance = cv2.Laplacian(gray, cv2.CV_64F).var() return variance < threshold def get_phash(image_path): return str(imagehash.phash(Image.open(image_path))) # 遍历数据集目录,筛掉模糊图和重复图 import os seen_hashes = set() clean_list = [] for root, _, files in os.walk("dataset_raw"): for f in files: if not f.lower().endswith((".jpg", ".png", ".jpeg")): continue path = os.path.join(root, f) if is_blurry(path): continue h = get_phash(path) if h in seen_hashes: continue seen_hashes.add(h) clean_list.append(path) print(f"清洗后剩余 {len(clean_list)} 张")这段代码的逻辑是:先算拉普拉斯方差判断模糊度,再用感知哈希做去重。threshold=100是经验值,你可以根据自己数据集的实际清晰度调整,调太低会漏掉模糊图,调太高会误删正常图。phash对轻微缩放和亮度变化不敏感,适合做近似去重。
划分数据集时,训练集、验证集、测试集按 7:1.5:1.5 或 8:1:1 分。注意要按类别分层抽样,别随机分完发现某一类全进了训练集。用sklearn.model_selection.train_test_split的stratify参数就能做到。
2.3 迁移学习训练脚本:冻结哪些层、学习率怎么设
拿到清洗后的数据,下一步是训练。毕设场景不建议从零训,直接用 ImageNet 预训练权重做迁移学习。具体策略:先冻结主干网络的所有层,只训练最后的全连接分类头,跑 5 到 10 个 epoch;然后解冻最后两个 stage(对于 ResNet 就是 layer3 和 layer4),用更小的学习率做微调。
import torch import torch.nn as nn from torchvision import models, transforms, datasets from torch.utils.data import DataLoader # 数据增强与归一化 train_tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), 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]) ]) val_tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) train_ds = datasets.ImageFolder("dataset/train", transform=train_tf) val_ds = datasets.ImageFolder("dataset/val", transform=val_tf) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4) val_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=4) # 加载预训练 ResNet18 model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) num_classes = len(train_ds.classes) model.fc = nn.Linear(model.fc.in_features, num_classes) # 第一阶段:冻结主干,只训分类头 for name, param in model.named_parameters(): if "fc" not in name: param.requires_grad = False criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-3) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = model.to(device) for epoch in range(10): model.train() running_loss = 0.0 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() running_loss += loss.item() scheduler.step() print(f"Epoch {epoch+1}, Loss: {running_loss/len(train_loader):.4f}")关键参数说明:batch_size=32是 8GB 显存下的安全值,显存小就降到 16 或 8;lr=1e-3是只训分类头时的常用学习率,解冻主干后要降到1e-4甚至1e-5;StepLR每 5 个 epoch 把学习率乘 0.5,防止后期震荡。num_workers=4在 Windows 上有时会报错,改成 0 即可。
第二阶段微调的代码结构一样,只是把requires_grad全部设为True,然后换一个更小的学习率重新建优化器。注意:解冻后不要用同一个优化器直接接着训,因为 Adam 里存了之前的状态,学习率突变会导致 loss 飞掉。新建一个 Adam,学习率设1e-4,再跑 10 到 15 个 epoch。
2.4 推理接口与前后端对接:从模型文件到页面上的一个按钮
训练完保存模型权重,后端用 Flask 或 FastAPI 包一个推理接口。前端用 Vue 或原生 HTML 都行,核心就是一个上传图片的按钮加一个结果展示区域。
from flask import Flask, request, jsonify from PIL import Image import torch from torchvision import transforms app = Flask(__name__) model = torch.load("garbage_model.pth", map_location="cpu") model.eval() class_names = ["cardboard", "glass", "metal", "paper", "plastic", "trash"] tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) @app.route("/predict", methods=["POST"]) def predict(): file = request.files["image"] img = Image.open(file.stream).convert("RGB") tensor = tf(img).unsqueeze(0) with torch.no_grad(): output = model(tensor) prob = torch.softmax(output, dim=1) conf, pred = torch.max(prob, dim=1) return jsonify({ "class": class_names[pred.item()], "confidence": round(conf.item(), 4) }) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)这段代码里map_location="cpu"是为了在没有 GPU 的机器上也能加载;unsqueeze(0)是把单张图变成 batch 维度为 1 的张量;torch.no_grad()关闭梯度计算,推理时省显存。前端拿到 JSON 后直接渲染即可,置信度低于 0.6 时可以提示「请重新拍摄」,这个阈值根据你的验证集表现来定。
3. 把系统跑起来的完整路径:环境、命令与第一次推理验证
3.1 环境搭建:Python 版本、CUDA 与依赖锁定
这类项目最常见的翻车点不是模型本身,而是环境。Python 版本建议 3.8 到 3.10,太新了有些包还没适配。PyTorch 版本要和 CUDA 驱动匹配,如果你机器上没有 NVIDIA 显卡,直接装 CPU 版就行,训练慢但能跑通。
# 创建虚拟环境 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装 PyTorch(CPU 版示例) pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 安装其他依赖 pip install flask pillow numpy opencv-python scikit-learn imagehash装完后跑一句python -c "import torch; print(torch.__version__)"确认没报错。如果报DLL load failed,大概率是 Visual C++ Redistributable 没装,去微软官网下最新的装上。
3.2 训练与验证:看 loss 曲线判断有没有训崩
训练时重点盯两个信号:训练 loss 是否稳定下降、验证准确率是否跟着涨。如果训练 loss 降但验证准确率不动,说明过拟合了,加数据增强或加 Dropout。如果训练 loss 震荡剧烈,把学习率降一半。如果 loss 变成 NaN,检查输入归一化是不是漏了,或者学习率太大。
验证脚本单独写一个,加载保存的权重,在测试集上跑一遍,输出混淆矩阵和每类准确率。混淆矩阵能告诉你哪些类容易混——比如「塑料」和「其他垃圾」经常分不清,这时候要么补数据,要么在业务逻辑里做后处理。
from sklearn.metrics import classification_report, confusion_matrix import seaborn as sns import matplotlib.pyplot as plt model.eval() all_preds, all_labels = [], [] with torch.no_grad(): for imgs, labels in test_loader: outputs = model(imgs.to(device)) _, preds = torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) print(classification_report(all_labels, all_preds, target_names=class_names)) cm = confusion_matrix(all_labels, all_preds) sns.heatmap(cm, annot=True, fmt="d", xticklabels=class_names, yticklabels=class_names) plt.savefig("confusion_matrix.png")classification_report会输出每类的 precision、recall、f1-score,答辩时直接截图放论文里。混淆矩阵热力图也是加分项,能体现你对结果的分析深度。
3.3 前端联调:上传、预览、结果展示的最小闭环
前端不需要多复杂,一个<input type="file">加一个<img>预览,再用fetch把图片 POST 到后端接口。注意跨域问题,开发阶段在后端加 CORS 头,或者用 Nginx 做反向代理。
async function predict() { const fileInput = document.getElementById("fileInput"); const file = fileInput.files[0]; if (!file) return alert("请先选择图片"); const formData = new FormData(); formData.append("image", file); const res = await fetch("http://localhost:5000/predict", { method: "POST", body: formData }); const data = await res.json(); document.getElementById("result").innerText = `识别结果:${data.class},置信度:${data.confidence}`; }这段代码的核心是FormData构造和fetch调用。data.class和data.confidence对应后端返回的 JSON 字段。如果前端显示undefined,打开浏览器开发者工具的 Network 面板看响应体,大概率是后端字段名写错了。
4. 避坑与排查:这类项目最容易翻车的 5 个地方
4.1 现象:训练准确率 99%,测试准确率 40%
原因:数据集划分时没有按类别分层,或者训练集和测试集有重复图片。另一个常见原因是数据增强只加在了训练集,但验证集的归一化参数和训练集不一致。
解决:用stratify参数重新划分,跑一遍去重脚本确认没有跨集重复。检查验证集的transforms是否和训练集用了同一套Normalize参数。
4.2 现象:推理时所有图片都预测成同一类
原因:模型加载时没有调用model.eval(),Dropout 和 BatchNorm 还在训练模式。或者保存模型时保存的是整个模型对象而不是state_dict,加载时结构对不上。
解决:推理前必须model.eval(),并用torch.no_grad()包住。保存时用torch.save(model.state_dict(), path),加载时先实例化模型再load_state_dict。
4.3 现象:Flask 接口在本地能跑,部署到服务器后 502
原因:Flask 默认单线程,并发一高就卡死。或者服务器上的 Python 环境没装flask-cors,跨域请求被拦。
解决:生产环境用gunicorn或uwsgi起多进程,命令是gunicorn -w 4 -b 0.0.0.0:5000 app:app。跨域在 Flask 里加CORS(app)一行搞定。
4.4 现象:EfficientNet 训练 loss 一直不降
原因:EfficientNet 的预处理归一化参数和 ResNet 不一样,如果你直接套用了 ImageNet 的mean=[0.485, 0.456, 0.406],输入分布对不上。
解决:查你用的那版 EfficientNet 的官方预处理参数,通常mean=[0.485, 0.456, 0.406]和std=[0.229, 0.224, 0.225]也能用,但有些实现要求mean=[0.5, 0.5, 0.5]。最稳妥的办法是看torchvision里对应模型的weights.transforms()输出。
4.5 现象:GPU 显存够但训练速度极慢
原因:num_workers设成了 0,数据加载在主进程里串行执行。或者没有开pin_memory,CPU 到 GPU 的传输成了瓶颈。
解决:DataLoader里设num_workers=4(Linux)或num_workers=0(Windows 兼容),加pin_memory=True。如果还是慢,检查是不是在 CPU 上训练——torch.cuda.is_available()返回False的话,装 CUDA 版 PyTorch。
5. 从「能跑」到「能写进论文」:三个让答辩加分的小改造
5.1 加一个置信度阈值与拒识逻辑
分类模型有个天然缺陷:你给它一张完全不属于任何类别的图,它也会强行输出一个类别和置信度。在垃圾分类场景里,用户可能拍了一只手、一个键盘、甚至一片空白。加一个拒识逻辑:当最大置信度低于阈值(比如 0.55)时,返回「无法识别,请重新拍摄」。
conf, pred = torch.max(prob, dim=1) if conf.item() < 0.55: return jsonify({"class": "unknown", "confidence": round(conf.item(), 4)})这个改动很小,但在论文里可以写一节「基于置信度的拒识机制」,答辩时老师问「如果拍的不是垃圾怎么办」,你就有东西答。
5.2 用 Grad-CAM 做可视化解释
Grad-CAM 能生成一张热力图,标出模型在图片上看哪些区域做出的判断。对于垃圾分类这种场景,热力图能直观展示模型是不是真的在看垃圾主体,而不是在看背景。实现上就是用pytorch-grad-cam库,几行代码就能出图。
from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image target_layers = [model.layer4[-1]] cam = GradCAM(model=model, target_layers=target_layers) grayscale_cam = cam(input_tensor=tensor) visualization = show_cam_on_image(img_array, grayscale_cam[0], use_rgb=True)把原图和热力图并排放在论文里,比单纯放准确率数字有说服力得多。老师看到这个,基本不会再质疑「你是不是只是调了个包」。
5.3 做一个简单的模型对比实验
毕设如果只跑一个模型,工作量显得单薄。用同一份数据、同一套训练策略,分别跑 ResNet18、MobileNetV3、EfficientNet-B0,记录准确率、参数量、单张推理耗时。表格一列,结论自然就有了:MobileNetV3 参数量最小但准确率略低,ResNet18 准确率最高但模型最大,EfficientNet-B0 居中。
| 模型 | 参数量(M) | 测试准确率 | 单张推理耗时(ms) |
|---|---|---|---|
| ResNet18 | 11.2 | 92.3% | 18 |
| MobileNetV3-Small | 2.5 | 89.7% | 9 |
| EfficientNet-B0 | 5.3 | 91.1% | 14 |
这张表的数据你自己跑出来填,别抄。跑三组实验大概多花两三个小时,但论文里多了一整节对比分析,性价比很高。
最后说个我自己的习惯:每次改完代码,先在一个小批量(比如 20 张图)上跑一遍,确认输入输出形状对得上,再上全量。这个习惯帮我省了无数次「训了半天发现标签映射错了」的后悔药。希望帮到你。
本文还有配套的精品资源,点击获取