简介:这套Python垃圾识别分类系统源码,主要面向需要完成环保类课程设计或入门深度学习图像识别的开发者与学生。压缩包共28个文件,大小约1.79MB;核心为12个Python脚本,既包含基于CNN和MobileNet的训练代码,也提供测试脚本、窗口推理程序与依赖清单;其余配套文件涵盖xml配置、txt说明、docx设计与计划文档、pdf风险管理报告、md说明以及pptx项目计划。目前已有336人学习。通过学习源码,读者可以理解垃圾分类数据集的预处理思路、模型训练与验证流程、推理部署方式,以及项目风险管理等工程文档的写法;源码中附带的详细设计文档、项目计划书、风险登记册和模型链接,也为二次开发或论文撰写提供了可用素材,适合循序渐进地对照调参、训练与评估。整体上,这份资源为从零开始搭建图像识别项目提供了完整的参考路径。
1. 打开这份垃圾识别系统源码之前,先想清楚你要拿它做什么
市面上的垃圾分类需求这几年一直没凉过,但真正能跑到生产环境、或者能拿来交课程设计/毕业设计的完整代码,其实并不多。这份「一个Python写的垃圾识别分类系统源码.zip」正是冲着这个缺口来的:它用Python的深度学习图像分类流程,把「识别一张照片里的垃圾是什么类别」这件事,从数据集准备、模型训练到Web界面部署,拧成了一条可以落地的链路。你拿到手的不该是一堆只能看的.py文件,而是能跑通、能换数据、能调参的一整套骨架。
这个系统对三类人最有价值:第一类是正在做课程设计或毕业设计、需要「能演示+能讲清楚原理」的学生;第二类是被老板丢来调研垃圾分类可行性的工程师,想快速评估准确率和部署成本;第三类是刚学完Python基础、想看看一个完整项目长什么样子的自学者。系统核心其实不神秘,就是一个图像分类模型,常见做法是用ResNet或MobileNet这类预训练网络做迁移学习,再封装一个上传图片看结果的界面。
但我要先说句大实话:源码能跑通不等于你的环境能跑通。这个标题里带着zip,意味着整个项目从Python版本到pip依赖版本都被锁过一次,你在自己的机器上复现时,八成会把时间花在配环境而不是改代码上。所以这篇笔记里,我会先把一个能用的实现方案完整拆开,再把我踩过的坑——Python版本适配、模型文件缺失、中文路径乱码这类——一条条摆出来。毕竟我是拿这类项目摸爬滚打过来的,有些坑不写出来,你能折腾一晚上。这份笔记不仅是项目说明书,更是帮你做技术选型和避坑的路径图。
2. 识别系统背后的技术链路:从图像到分类结果,中间发生了什么
2.1 图像分类为什么是垃圾识别的主力方案,而不是目标检测
很多刚接触这个题目的人会问:垃圾识别不应该是把瓶子、袋子框出来吗?为什么要用图像分类?这是一个特别关键的选型问题。这套系统里最核心的深度学习任务是图像分类,而不是目标检测或实例分割。区别在于:分类回答的是「这张图里最主要的东西是什么」,检测回答的是「图里有哪些东西、分别在什么位置」。
对于一份课程设计或一个初版验证系统,用图像分类是完全正确的选择。理由有三个:第一,分类模型简单,数据标注成本极低,只需要给图片打标签;第二,训练和推理速度快,CPU也能勉强跑起来;第三,源码里一般配套的是现成的垃圾分类数据集,比如按厨余、可回收、有害、其他四类划分,或者更细的几十类划分,分类任务足够支撑演示。等你把分类链路跑通,再往检测方向扩展也有清晰的路径。
我一般会劝第一次做这个项目的人不要一上来就上YOLO这类检测模型。虽然检测能「框出」垃圾位置,看起来更炫,但它要求的数据标注成本直接翻好几倍,后台源码量也大得多。对于没接触过深度学习的人来说,把分类系统跑通、指标讲明白、界面能交互,已经是一个完整且有说服力的项目了。
2.2 迁移学习为什么是唯一现实的选择:从零训练的成本算给你看
一部分源码里其实保留了从零训练模型的入口,但现实情况是:你自己几乎不可能从零训练出一个可用的图像分类模型。这里先算一笔账。
一个像样的垃圾图像分类模型,如果从零训练ResNet50,在单张消费级显卡上跑完十几个epoch(训练轮次),动辄要十几个小时以上,这还只是训练时长;更核心的问题是数据量——你需要上万张每类的标注图片,否则模型根本学不到泛化特征,会严重过拟合。对于拿到一份zip源码、想快速看到效果的人来说,这两个条件基本都不满足。
所以这套系统的正常配方是迁移学习:加载在ImageNet上预训练好的模型权重(比如ResNet50、MobileNetV2),把最后一层全连接层替换成你自己的分类器,然后只对这个「头」做微调。这样做的好处是,你只需要几百张到几千张垃圾图片,训练时间压缩到几十分钟甚至几分钟,效果却比从零训练好得多。下面是典型的迁移学习声明代码:
import torchvision.models as models import torch.nn as nn # 加载预训练权重,保持特征提取层不变 model = models.resnet50(pretrained=True) # 获取全连接层的输入维度 num_features = model.fc.in_features # 替换最后一层:垃圾类别数 num_classes = 4 model.fc = nn.Linear(num_features, num_classes) # 冻结除最后一层之外的所有参数,加快训练 for param in model.parameters(): param.requires_grad = False for param in model.fc.parameters(): param.requires_grad = True这里第一个关键逻辑是pretrained=True,它会自动下载在ImageNet上训好的权重,这是效果下限的保障;第二个逻辑是requires_grad = False,把前面那些层锁死,只训练新的全连接层。有些人会问:为什么不全模型微调?省时间当然是一方面,但更重要的是,垃圾图片和ImageNet的自然图像在底层特征(边缘、纹理、颜色)上是通用的,锁住特征提取器不容易在少量数据上跑飞。
如果你用MobileNetV2这类轻量网络替代ResNet50,训练时间更长或更短要斟酌,轻量网络在CPU服务器上也能玩得动。源码里换模型也简单,把上面代码里的torchvision.models.resnet50换成torchvision.models.mobilenet_v2即可,代价是准确率略降,换来部署体积和速度的优势。这是我在实际项目里常做的替换,因为课程设计答辩现场不一定有GPU。
2.3 数据处理链路:目录命名、标签映射与验证集划分
垃圾识别系统源码拿到手之后,你首先打开的大概率不是一个模型文件,而是数据集的目录结构。数据集的组织方式直接决定了DataLoader(数据加载器)好不好写。最常见的做法是ImageFolder风格:根目录下每个子文件夹名对应一个类别,文件夹里的图片全部属于该类。
dataset/ ├── train/ │ ├── 厨余垃圾/ │ │ ├── 101.jpg │ │ ├── 102.jpg │ ├── 可回收垃圾/ │ ├── 有害垃圾/ │ └── 其他垃圾/ └── val/ ├── 厨余垃圾/ ├── 可回收垃圾/ ├── 有害垃圾/ └── 其他垃圾/这种结构下,PyTorch的torchvision.datasets.ImageFolder可以直接把目录名转成数字标签,省去手写标签映射表。对应的数据加载代码长这样:
from torchvision import datasets, transforms # 训练集数据增强:提高泛化能力 train_transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p=0.5), 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_transform = 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_dataset = datasets.ImageFolder(root='dataset/train', transform=train_transform) val_dataset = datasets.ImageFolder(root='dataset/val', transform=val_transform) # 分批加载,shuffle打乱训练顺序 train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=4) val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=4)这里的四个参数值得你多看一眼:Resize((224, 224))是ResNet的标配输入尺寸,如果你换了模型输入尺寸要跟着换;Normalize的三组数值是ImageNet的均值和标准差,迁移学习中别乱改;batch_size=32在8GB显存下比较稳,显存不够就先降到16;num_workers=4在Windows下要小心,多进程加载偶尔会报错,真遇到就改成0,用主进程加载,慢一点但稳定。
还有个很多新手会忽略的问题:验证集和训练集不能有交集。源码里如果从数据集里随机抽样划分还好,但如果你自己收集图片加到训练集里,一定要确保同一张图不会同时出现在训练集和验证集里,否则验证准确率是虚高的,答辩时一问就露馅。
2.4 训练循环里必须监控的指标:loss下降之外还要看什么
很多从零学深度学习的同学在训练时只盯着一个数字:精确率(Accuracy)。这个习惯在垃圾分类场景下会带来误导。原因很简单,垃圾分类数据集的类别往往不平衡——「其他垃圾」的图片数量可能是「有害垃圾」的好几倍。这时候模型只需要把所有图片都预测成「其他垃圾」,准确率也能轻松刷到70%以上,但这种模型没有任何实用价值。
我在调这类模型时,训练循环里至少同时记录三个指标:每个epoch的平均损失值、验证集准确率、每个类别的召回率。损失值告诉你模型有没有在正常收敛;准确率给你一个直观的整体水平;分召类别回的召回率能暴露「某个类别根本没学会」的问题。下面这段代码展示的是最朴素的训练循环骨架,所有源码项目基本都逃不出这个框架:
for epoch in range(epochs): model.train() running_loss = 0.0 for inputs, labels in train_loader: 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(f"Epoch {epoch+1}/{epochs}, Loss: {epoch_loss:.4f}") # 每个epoch结束后跑一次验证 model.eval() correct = 0 total = 0 with torch.no_grad(): for inputs, labels in val_loader: outputs = model(inputs) _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() val_acc = 100 * correct / total print(f"Validation Accuracy: {val_acc:.2f}%")有两个细节值得你特别留意。model.train()和model.eval()的切换不是走形式——它们控制着Dropout和BatchNorm层的行为,漏掉model.eval()的话,验证集的准确率会被严重低估。with torch.no_grad()也是必须的,推理时不计算梯度,内存占用直接降一个量级,否则验证几轮就可能内存溢出。这两个坑是我见过的最常见的「翻车」点。
2.5 推理与部署的两种形态:一行命令的CLI和可视化界面
模型训练好后,落地方式通常有两种。第一种是最简单的命令行推理:输入一张图片路径,输出预测类别和置信度。第二种是Web界面:上传图片,浏览器里显示分类结果。源码里通常两种都有,但Web界面往往是项目的门面,演示效果好不好全靠它。
命令行推理的核心代码非常短,实际上就是把训练时的预处理流程再走一遍:
from PIL import Image import torch def predict(image_path, model, class_names, device='cpu'): image = Image.open(image_path).convert('RGB') transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) input_tensor = transform(image).unsqueeze(0) # 加batch维度 with torch.no_grad(): outputs = model(input_tensor.to(device)) probabilities = torch.softmax(outputs, dim=1) # 转概率 confidence, predicted = torch.max(probabilities, 1) return class_names[predicted.item()], confidence.item() # 调用示例 class_names = ['厨余垃圾', '可回收垃圾', '有害垃圾', '其他垃圾'] model = load_your_trained_model() # 加载你训练好的权重 result, conf = predict('test.jpg', model, class_names) print(f"预测结果: {result}, 置信度: {conf:.2%}")注意这里的model.load_state_dict(torch.load('model.pth'))在加载权重时必须保证模型结构和你训练时完全一致,包括类别数、骨干网络名称,差一个字母都会报错。置信度输出最好保留在界面上,因为后端模型可能不确定,这时你需要把低置信度的结果打回给用户确认,这个交互环节是这类系统是否实用的关键。
3. 跑通这套代码的完整过程:从环境搭起
3.1 环境准备:用conda隔离环境是最不容易翻车的姿势
一份工程源码最怕的就是环境不干净。我吃过这个亏:一开始图省事直接在全局Python环境里pip install,结果某个依赖包升级后,原本运行的代码开始疯狂报错。从那以后我养成了一个习惯——每个项目一套虚拟环境,尤其是这种还涉及深度学习库的项目。conda天然适合做这件事,因为Python深度学习库对底层库如CUDA的依赖比较敏感,用conda装能省下编译的苦。
针对垃圾识别系统,为了网络下载稳定离线也能用,你通常要装unzip解压工具、Python 3.8到3.10之间的版本、pip包管理工具,以及PyTorch系列。一般源码里会附带一个requirements.txt文件,格式是库名加版本号。有条件的话,我建议你从镜像源安装PyTorch,比从国外源拉要快非常多。
# 创建并激活独立环境 conda create -n garbage_classifier python=3.9 -y conda activate garbage_classifier # 安装核心依赖(以CPU版本为例) pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install flask pillow numpy matplotlib # 安装源码里的其他依赖 pip install -r requirements.txt # 解压源码并确认目录结构 unzip garbage_classifier.zip -d garbage_project cd garbage_project && ls -la这里我用的是Python 3.9而非最新的3.12,原因是PyTorch对最新Python版本的支持往往滞后,源码里用到的某些旧版库在新Python下可能直接编译失败。选一个「生态里验证过的版本」比选「最新的版本」更重要。安装完成后,建议先跑一个python -c "import torch; print(torch.__version__)"验证安装,这一步能提前暴露80%的环境问题。
3.2 数据准备:源码自带数据集怎么用,如何替换成自己的数据
解压源码后,你大概率会看到两类东西:一类是train/和val/目录,里面直接装好了分类好的图片;另一类是data/目录下的原始压缩包或CSV标注文件,需要你自己划分数据集。前者开箱即用,后者需要你写个简单的脚本去划分。
如果你的数据集是CSV标注格式,比如文件名和标签分开两列,那就需要先写脚本生成ImageFolder需要的目录结构。这里给一个通用的转换脚本,无论你从哪个网站下载的垃圾分类数据集,基本都能用这把「万能钥匙」整理出来:
import csv import os import shutil from pathlib import Path def csv_to_imagefolder(csv_path, src_dir, dest_dir, train_ratio=0.8): # 读取CSV,假设格式: filename,label with open(csv_path, 'r', encoding='utf-8') as f: reader = csv.reader(f) next(reader) # 跳过表头 pairs = [(row[0], row[1]) for row in reader] # 创建目录结构 for split in ['train', 'val']: for _, label in set(pairs): (Path(dest_dir) / split / label).mkdir(parents=True, exist_ok=True) # 按比例划分 class_files = {} for fname, label in pairs: class_files.setdefault(label, []).append(fname) for label, files in class_files.items(): split_idx = int(len(files) * train_ratio) for i, fname in enumerate(files): src = Path(src_dir) / fname split = 'train' if i < split_idx else 'val' dst = Path(dest_dir) / split / label / fname shutil.copy(src, dst) print("数据集转换完成!") csv_to_imagefolder('garbage.csv', 'images_raw', 'dataset')写这类整理脚本最容易出问题的就是中文编码:CSV文件如果是Excel里直接导出的,编码多半是GBK而非UTF-8,读取时要把encoding='utf-8'换成encoding='gbk',否则读出来的标签全是乱码,最后模型的类别名也是乱码。另外shutil.copy是在复制文件,如果你的源数据集很大,建议改成os.replace配合两次遍历来做移动,能省一半磁盘空间。
关于划分比例,这里用的是8:2。训练集比例太低会欠拟合,太高又让验证集缺乏代表性。如果你发现某个类别的图片特别少,比如还不到50张,建议手工把它多复制几份到训练集里做数据扩充,让模型在训练时能把这个类别的样本看到足够多次。
3.3 训练自己的分类模型:epoch、学习率与设备选择
环境搭好、数据理清之后,终于可以开始训练了。为求稳妥,建议先不改任何参数,直接用源码里的默认配置跑一遍,目的是验证整条链路是通的。默认配置下,第一次训练往往只需要几分钟(在GPU上),也可能在CPU上跑,一旦能跑通,说明环境没问题,然后再开始逐步调参。
这里以一个常用的训练启动脚本为例,大家看到这类train.py时应该要能读懂里面每个参数的含义和制约关系:
import torch import torch.optim as optim # 训练参数 epochs = 30 # 训练轮次 batch_size = 32 # 批次大小 learning_rate = 0.001 # 学习率 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') print(f"Using device: {device}") model = create_model(num_classes=4) model.to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.fc.parameters(), lr=learning_rate) scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5) for epoch in range(epochs): # ... 训练循环代码 ... scheduler.step() # 每10个epoch把学习率减半关于参数,这是最核心的调参经验:learning_rate是0.001还是0.0001,直接决定你模型能不能收敛。对Adam优化器来说,0.001是常用起步值,但如果你发现loss剧烈震荡,降到0.0001就好很多;step_size=10, gamma=0.5表示每10轮学习率减半,这是一种简单的学习率衰减策略,能让模型后期稳定收敛;batch_size决定看到多少张图才更新一次权重,显存不够就调小,但别太小,16以下的batch在BatchNorm层上容易不稳定。
3.4 界面启动与本地演示:Flask应用的最小运行路径
系统里通常包含一个Web界面,常见的是用Flask写的,代码比直接用C++写界面要简单得多。启动它通常只需要一行命令,但关键在于模型路径和静态文件路径的配置。
# 启动Web应用 python app.py # 终端会显示 Running on http://127.0.0.1:5000浏览器打开http://127.0.0.1:5000后,正常的界面应该长这样:顶部标题「垃圾识别分类系统」,中间一个上传按钮,选择图片后点击识别,页面下方显示预测类别和置信度。如果打开后画面错乱或者图片传不上来,第一反应应该去看终端里有没有报错堆栈,90%的问题会直接写在报错信息里。
app.py里有个核心变量需要你确认:模型路径。有的源码默认加载model.pth,但你的模型文件名可能叫best_model_acc91.pth之类。找到Flask里加载模型的那一行,把路径改成你训练出的真实权重的路径。另外,有些源码的app.py会把类别编号写死,这里的类别编号要和你的训练文件夹里的文件名顺序完全一致,比如把「可回收垃圾」的编号写成0,结果模型把编号0当成「其他垃圾」,那识别结果就会全员错位,这种错误最难排查,因为训练和推理都显示正常,只是结果错得离谱。
4. 初版系统为什么常常「识别翻车」:置信度之外的真实边界
4.1 识别错误的第一类坑
拿到一个训练好的模型,无论是自己训的还是源码自带的,先在几类代表性图片上试跑一遍再谈效果。我见过很多同学把模型一跑,换张没见过的图就开始怀疑人生,其实模型的能力边界在训练集分布里就已经划定了。
最容易出现的问题集中在三类图片上。第一类是「背景干扰过大的图」,模型的主干网络是在ImageNet上预训练的,它对自然图像的背景纹理是敏感的,如果你的垃圾图片是带复杂背景拍的,模型很容易被背景里的桌子、草地「带偏」;第二类是「多物体混杂图」,比如一张照片里同时有一个塑料瓶和一张废纸,分类模型只能给出一个答案,它不会告诉你瓶子和纸各占多少;第三类是「光照异常图」,通过转成HSV色域再归一化可以缓解一部分,但根本解法是训练时多做颜色抖动增强。
4.2 置信度不是真理:低置信度输出怎么办
做系统的人总希望模型能崇高置信度地给出正确分类,但实际测试时你会经常看到这样的输出结果:预测结果是「可回收垃圾」,置信度55%,倒数第二的类别是「其他垃圾」,置信度44%。这时候如果系统直接把55%的结果当成最终答案,用户大概率会觉得这系统很蠢。
比较好的处理方式是在界面上设置置信度阈值。如果置信度大于80%,直接输出结果;如果低于80%但高于50%,显示两个候选类别让用户选择;如果低于50%,直接提示「无法确认,请换一张更清晰的照片」。在推理代码里加上这个判断并不复杂,却能显著提升用户对系统的信任感。坦白讲,垃圾识别这种细分类任务,类别间外观高度重叠时(比如被压扁的易拉罐和铝箔纸),置信度经常就在及格线边缘徘徊,这种设计是很有必要的。
4.3 类别不平衡导致的偏科:训练时的隐藏陷阱
回到训练环节,一类容易被忽视的问题就是类别不平衡。你的数据集中「有害垃圾」可能极少,几百张,而「其他垃圾」几千张,训练出的模型就会倾向于把不确定的图片统统归到大类里。这不是代码bug,而是损失函数的设计问题。
常见解法有两种,第一种在数据层面做重采样,对样本少的类别多复制几份,可以给相关类别的图片做更强的数据增强;第二种在损失函数层面调整权重,给样本少的类别设置更高的loss权重,让模型更在意它。下面是PyTorch里的写法:
from sklearn.utils.class_weight import compute_class_weight import numpy as np # 统计每个类别的样本数量 labels = train_dataset.targets class_weights = compute_class_weight( class_weight='balanced', classes=np.unique(labels), y=np.array(labels) ) class_weights = torch.tensor(class_weights, dtype=torch.float32).to(device) # 用加权损失函数替换普通的CrossEntropyLoss criterion = nn.CrossEntropyLoss(weight=class_weights)这里compute_class_weight会自动计算权重,样本越少的类别权重越高。用了这个加权损失后,低频类别的召回率通常会有明显上涨,这是处理真实垃圾数据时的必备手段。但也要注意,权重拉得过满,少数类别勉强能保住,但这个类别的训练拟合难度会显著增加,整体准确率小幅度下降是正常的,这不是缺陷,而是取舍。
4.4 过拟合与欠拟合:从训练曲线读出真实问题
最后再讲一个老生常谈但极其重要的问题——过拟合。垃圾识别数据集的规模通常不大,几百张到几千张,在这种规模下训练一个模型,过拟合几乎是必然趋势。具体表现是:训练集准确率一路涨到99%,验证集准确率只到85%以后就开始震荡或者下降。
「翻车」的常见画面是:你看着训练集loss降得挺漂亮,感觉模型要成功了,一测验证集准确率反而在掉。这时说明模型开始「背题」而不是在「学规律」了。对策是配套的:数据增强加狠一点(随机旋转、随机裁剪、MixUp都属于这一类)、提前终止做一份「后悔药」,或者加Dropout。换一批数据重新跑这件事,也是项目里的常态。
提前终止的实现比想象中简单,就是记录每个epoch的验证集准确率,只有当它超过历史最佳时才保存一次权重。带这个逻辑的代码通常是这样的:
best_acc = 0.0 best_model_path = 'best_model.pth' for epoch in range(epochs): train_one_epoch() acc = evaluate() if acc > best_acc: best_acc = acc torch.save(model.state_dict(), best_model_path) print(f"Epoch {epoch+1}: 保存更优模型,准确率 {acc:.2f}%")这段代码是全项目中最值得保留的设计。训练完你永远会拿到「验证集上最好的那一份」,而不是最后一个epoch可能已经过拟合的权重。我一直建议,只要你打算认真调试这个模型,这份保留最优权重的代码就要存在,它在后续效果对比里就是你的后悔药。
5. 从能跑到能用:提升识别效果的三条进阶路线
5.1 路线一:用更轻或更强的骨干网络替换
如果你对现有模型的准确率不满意,第一反应不应该是堆数据,而是换骨干网络。从ResNet50换成ResNet101,准确率通常能小幅上涨,但这意味着训练时间变长约一倍;从ResNet50换成EfficientNet-B4,通常能在同样量级下拿到更好的效果。不过要注意,这套垃圾识别系统的预处理尺寸要和网络匹配,EfficientNet需要根据输入图片尺寸相应调整。
在换网络时,源码里涉及的改动点通常有三个:模型声明那一行、输入预处理尺寸、全连接层输出的类别数跟数据集对齐。这三个改动点只要有一个遗漏,你就会撞上tensor维度不匹配的报错。第一次改动建议先备份原始代码,「后悔有药」才能放心折腾。
5.2 路线二:集成学习带来的准确率稳定提升
当单模型效果已达85%左右上不去的时候,我通常会尝试做集成学习。技术上最省事的是「权重平均法」和「预测结果投票法」。投票法实操起来很直白:训练两个或三个不同骨干的模型,比如一个ResNet50、一个MobileNetV2,推理时分别预测,取相同结果或平均值作为最终答案。实验结论通常是:三个模型投票的出结果比任何一个单模型都稳,但推理耗时也相应增长,如果你要考虑在嵌入式的设备上包应用,成本要算进去。
5.3 路线三:把分类系统升级成检测系统的最小改动路径
如果你的项目做完分类后还想要更完整的演示效果,想指出「垃圾在图里的位置」,最务实的升级路径是换用YOLO系列或Faster R-CNN,把它接到你当前的推理接口上。我把这称为「最小改动路径」:保住你已经写好的Web界面和数据管理代码,只把模型推理接口替换掉。Flask里只需要改一行调用,界面完全不用动。这显然是下一阶段的玩法,但把升级路线想清楚,你的项目从第一天起就有了想象空间。
整个系统的调优,其实是建立在多次训练和验证的循环上的。我习惯每换一个网络或调一组参数,就把这轮的验证结果记下来,包括数据增强、学习率、准确率、是否过拟合等。当你积累了四五条记录后,自然会找到你手头数据下的最优配置。这个习惯在答辩或交付时也很有用——你可以拿出实验记录来说明「为什么选这个模型、做了哪些对比」,比空口谈原理有说服力得多。
希望这份梳理,能帮你在垃圾识别分类这个方向少走点弯路。垃圾识别这个方向看似小众,但在智慧城市、绿色回收这些应用里一直有用武之地,我把自己从这套源码里学到的经验和踩过的坑写在这里,也是希望帮到你稳稳跑通第一个版本。
本文还有配套的精品资源,点击获取