尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

深度学习图像分类实战:垃圾分类识别工程从训练到部署全指南

发布时间:2026/9/28 1:16:11

资讯中心
01
ARTICLE

深度学习图像分类实战:垃圾分类识别工程从训练到部署全指南

深度学习图像分类实战:垃圾分类识别工程从训练到部署全指南
简介面向Python开发者和人工智能入门学习者的垃圾分类深度学习小工程整个项目基于ResNet50图像分类思路覆盖数据准备、模型训练到Demo演示的完整流程可帮助读者快速理解卷积神经网络在图像识别任务中的应用。资源压缩包共14个文件大小约4.22MB以6个Python脚本为核心包含数据集生成与预处理、ResNet50模型定义、训练入口、推理演示等模块另附2份Markdown说明、2份Word文档、1份JSON配置、1份许可证文件、示例图片及Git忽略配置分别用于环境说明、训练信息记录和理论讲解。已有1210人学习下载适合刚接触深度学习分类任务、希望获得可用小工程进行复现和改写的读者。通过该项目可以掌握垃圾分类场景下的数据组织方式、模型微调流程与基本调参思路。1. 这个Python基于深度学习的垃圾分类小工程解决的是“从零跑通到能交付”你拿到的这个Python基于深度学习的垃圾分类小工程本质是一个图像分类实战包输入一张垃圾照片模型输出它是纸板、塑料、玻璃、金属还是电池并附上置信度。它解决的不是算法创新问题而是把“数据整理→模型训练→单图推理”这条链路做成可以照着复现的标准答案。适合两类人一类是有Python基础、装过深度学习环境但还没完整跑通过一个项目的新手另一类是小团队想用它摸清垃圾分类的图像方案到底值不值得投入。别小看这个“小”字恰恰是它把环境、数据、训练、推理都装进了一个zip你只需要拆开、跑通、再按自己的数据改造。它最大的坑不在模型而在数据声明和预处理的一致性后面我会花大篇幅讲这块。2. 拆开zip之前读懂垃圾分类小工程的技术选型与代码骨架拿到zip的第一件事不是急着双击readme而是先把整个包的结构读明白。小工程最容易让人误判的是“东西少应该很轻松”实际上文件越精简隐含约定越多。你在动手前花半小时搞清楚它是一个分类任务还是一个检测任务、依赖什么框架、类别从哪来能省掉后面一整天的调试时间。2.1 垃圾分类本质是图像分类为什么小工程几乎都选迁移学习先明确任务边界这个工程要解决的是“这张图属于哪一类垃圾”不画框、不定位所以它属于图像分类不是目标检测。分类网络的结构很直白前面是卷积特征提取器负责把图像变成特征向量后面是一个全连接分类头把特征映射到类别概率。卷积部分学的是边缘、纹理、反光、颜色这样的局部模式全连接部分则负责做“综合判断”。为什么这类小工程几乎清一色用深度学习而不是传统图像处理因为垃圾外观差异太大纸箱有瓦楞纹理塑料瓶有高光玻璃瓶半透明金属反光强烈。这些特征用SIFT、颜色直方图这类手工特征也能建模但泛化性很差换个光照、换个背景就失效。CNN能自动从数据里学这些模式模型的内部机制对大多数人是个黑匣子但黑匣子的输入输出规则是固定的只要数据喂对行为就可控。模型选型上这类小工程最常见的是三条路ResNet18或ResNet34稳定、好调、资料多适合追求效果和复现性MobileNetV2或MobileNetV3参数少、CPU推理快适合以后要部署到树莓派或小程序端偶尔会遇到VGG16老工程里多特点是稳但笨重。我的习惯是先用ResNet18跑通因为它兼容性好torchvision直接能加载预训练权重改分类头也简单。数据集方面公开的垃圾分类影像集和Kaggle上的garbage classification这类数据都覆盖纸板、塑料、玻璃、金属、织物、电池等常见类别但真实垃圾桶边拍的照片和网上数据集差异很大训练集里看着干净的图到现场往往会翻车。2.2 解压后先看哪几个文件从readme到data_loader解压和初读我用这样一组命令unzip Python基于深度学习的垃圾分类小工程.zip -d garbage_sort cd garbage_sort tree -L 2 -d # 先看目录结构别急着跑训练 cat requirements.txt # 看依赖有什么 cat config.py # 看类别数、图像尺寸、模型名这里的参数要解释一下-d garbage_sort是指定解压到目标目录避免原地散落tree -L 2 -d只显示两层目录排除文件让你一眼看出有没有checkpoints、dataset、models这些关键目录cat直接读配置比用编辑器打开更快。多数垃圾分类小工程会这样组织train.py负责训练predict.py负责推理data_loader.py负责读图models或nets.py放网络结构config.py放超参和路径checkpoints放训练好的权重。如果没有config.py这些常量通常直接写在train.py顶部注意找。我建议的阅读顺序是readme → config.py → data_loader.py → train.py → predict.py。很多人习惯先看train.py的训练循环这是错的。对垃圾分类这种工程data_loader.py才是命根子它决定标签怎么映射、图像尺寸多大、增强怎么做。你后面所有调试都围绕它展开。看的时候重点确认三点类别列表是不是写死在文件里、图像统一缩放到多大、返回的batch是(images, labels)还是带路径的三元组。最后先跑通predict.py再碰训练因为权重能加载成功说明环境和模型定义前两步已经对了。2.3 标签、路径和编码暗坑先打个预防针这三个问题在垃圾分类小工程里几乎必现先说结论。类别索引从0开始由ImageFolder按目录名的ASCII顺序自动生成不是按你的数据里出现的先后顺序。目录名用0_cardboard、1_plastic这种带数字前缀的命名就是为了让排序结果和人类直觉一致。其次中文标签在Windows控制台打印时经常乱码因为终端默认GBK编码而PyTorch和Python 3的字符串是UTF-8这个不致命但很干扰判断所以我后面所有示例都用英文标签。最后路径别带中文包括你的用户名目录、解压目录、数据集目录能避开大量奇怪问题。提示解压路径、数据集路径全用英文。Windows下中文路径会让ImageFolder读取表现得很不稳定这类问题排查起来最浪费时间。3. 把深度学习环境配置跑通从python安装检查到第一张分类结果环境问题占了这类小工程总耗时的一半以上。常见的“代码没错但跑不起来”十有八九是Python版本和PyTorch对不上或者显卡驱动太老、装了CPU版torch还指望GPU跑。这一章我把从零到出结果的最小路径完整走一遍包含环境体检、依赖安装和单图推理三个步骤。3.1 环境体检python版本、CUDA与PyTorch的匹配关系我的惯例是新建一个独立环境而不是直接往base里装东西conda create -n garbage python3.9 -y conda activate garbage python --version nvidia-smi # 看显卡和驱动版本 nvcc -V # 看CUDA运行时版本 python -c import torch; print(torch.__version__, torch.cuda.is_available())先解释为什么选Python 3.9PyTorch 1.13到2.x这条主线的wheel都带3.9的包如果你用Python 3.12装旧版torch基本上要在编译报错上浪费一晚上。Python 3.8、3.9、3.10都是安全区3.9最省心。nvidia-smi看的是驱动支持的CUDA版本nvcc -V看的是本机安装的CUDA Toolkit版本这两个不完全是一回事。只要torch.cuda.is_available()返回TruePyTorch内部自带的CUDA就能用不要求你自己装Toolkit。如果打印出False常见原因是装了CPU版torch、驱动版本太旧、或者torch的CUDA编译版本高于驱动支持版本。没有GPU也不用灰心这个小工程用MobileNet在CPU上能跑推理训练慢一点但可行。提示在PyCharm里新建项目时解释器直接选刚才的garbage环境避免命令行能跑、IDE里报错这种奇怪的不一致。3.2 依赖安装requirements.txt不完全可信核心依赖自己核对requirements.txt是作者在他自己机器上验证过的版本快照但很多小工程的requirements.txt是网上抄来的版本可能过老或互相矛盾。我的做法是参考它但不盲从pip install -r requirements.txt -i 清华或阿里云PyPI镜像 pip install opencv-python pillow numpy tqdm scikit-learn matplotlibPyPI镜像这个参数很有用直接装能省不少时间。第一行把工程自带的依赖全装上第二行补装你可能用到但requirements里没写的工具库。这里要重点核对的是torch和torchvision的版本配对这两个库必须来自同一批Release比如torch 2.0配torchvision 0.15配错会在import时报“undefined symbol”之类的错。如果requirements里有个很旧的torch版本我建议先升级pip install -U torch torchvision再继续装其他依赖。opencv-python负责图像增强和IO的兜底Pillow负责读图scikit-learn用来算评估指标matplotlib用来画混淆矩阵这几个都不是可选依赖。3.3 最小推理一张图如何变成“类别置信度”不管zip里自带的predict.py长什么样我都建议先写一个最小推理脚本把“加载权重→预处理→前向→输出类别”这四个环节彻底搞明白。下面这个脚本可以直接保存为predict_min.pyimport torch import torchvision.models as models from torchvision import transforms from PIL import Image class_names [cardboard, plastic, glass, metal, fabric, battery] def load_model(num_classes, ckpt_path): model models.resnet18(weightsNone) model.fc torch.nn.Linear(model.fc.in_features, num_classes) state torch.load(ckpt_path, map_locationcpu) state {k.replace(module., ): v for k, v in state.items()} model.load_state_dict(state, strictFalse) model.eval() return model def predict(img_path, model, devicecpu): tf 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]), ]) img Image.open(img_path).convert(RGB) x tf(img).unsqueeze(0).to(device) with torch.no_grad(): logits model(x) probs torch.softmax(logits, dim1) top1 probs.argmax(dim1).item() top1_conf probs[0, top1].item() return class_names[top1], top1_conf if __name__ __main__: model load_model(num_classeslen(class_names), ckpt_path./checkpoints/best.pth) cls, conf predict(./test.jpg, model) print(f类别: {cls} 置信度: {conf:.4f})这段代码有几个关键点。torch.load(..., map_locationcpu)让没有GPU的机器也能加载权重不会因为设备名匹配不上报错。后面那行replace(module., )是处理DataParallel多卡训练留下的前缀小工程里经常遇到先写上能省一次报错。model.eval()必须调用它会切换BN层和Dropout的行为不调的话同一张图每次推理结果都可能不一样。预处理三件套Resize、CenterCrop、Normalize必须和训练时保持一致尤其是Normalize的均值方差这是ImageNet预训练权重的统计值填错会导致颜色分布偏掉、精度骤降。运行命令是python predict_min.py --image ./data/test/paper_01.jpg如果zip自带的predict.py是带argparse的优先直接用它。我写这个最小脚本是想让你理解流程理解之后任何变体都能看懂。4. 把数据喂成深度学习能学的样子目录结构、预处理与微调参数训练阶段最耗时间的往往不是模型而是数据准备。别一开箱就跑python train.py那只会对着默认的dataset目录一路飞奔。先把数据组织好把预处理和超参弄清楚训练结果才有参考价值。和论文复现不同这类深度学习实战项目案例更看重“改到自己的数据上还能跑”所以数据这一环不能跳过。4.1 用ImageFolder整理训练集目录名就是标签顺序就是索引PyTorch的torchvision.datasets.ImageFolder是分类小工程的事实标准它的规则很朴素一级子目录名就是类别名图片文件放在类别目录里。dataset/train/0_cardboard/xxx.jpg dataset/train/1_plastic/xxx.jpg dataset/train/2_glass/xxx.jpg dataset/train/3_metal/xxx.jpg dataset/train/4_fabric/xxx.jpg dataset/train/5_battery/xxx.jpg dataset/val/0_cardboard/xxx.jpg dataset/val/1_plastic/xxx.jpg用0_、1_这种前缀是刻意为之。ImageFolder会按目录名字母序排序生成class_to_idx如果你用cardboard、plastic这样纯字母命名虽然也能用但排序顺序容易和推理脚本里写死的类别列表对不上。带数字前缀后排序就和人类直觉一致排查起来不用猜。如果原始数据是一个大目录里所有类别混着可以写个脚本按比例切分import os, random, shutil random.seed(42) root dataset/full for cls in os.listdir(root): imgs os.listdir(os.path.join(root, cls)) random.shuffle(imgs) n_val int(len(imgs) * 0.2) for i, img in enumerate(imgs): subset val if i n_val else train out_dir fdataset/{subset}/{cls} os.makedirs(out_dir, exist_okTrue) shutil.copy(os.path.join(root, cls, img), out_dir)这段代码里random.seed(42)保证了每次划分结果一致这个参数很关键不然前后两轮训练用的验证集不同指标没法对比。n_val int(len(imgs) * 0.2)是切出20%作为验证集对小数据量是合理比例。要注意的是只做copy不移动原始数据别动这是后悔药。如果原始数据本来就有train和val目录直接跳过这个脚本但重点检查val目录里类别是否齐全、命名是否和train一致。4.2 训练与推理的预处理必须一致增强与归一化的边界数据增强是提高泛化能力最直接的手段但垃圾分类图有自己的特点垃圾桶附近光线差、背景杂乱、物体可能部分被遮挡。增强要能模拟这种变化又不能把图改得连人都认不出。我的常用transforms如下from torchvision import transforms train_tf transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.7, 1.0)), transforms.RandomHorizontalFlip(p0.5), transforms.ColorJitter(brightness0.3, contrast0.3), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) eval_tf 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]), ])RandomResizedCrop(224, scale(0.7, 1.0))表示裁剪后缩放到224裁剪面积是原图的70%到100%。这个下限别设太低垃圾物体本身就小裁太多会把关键特征裁掉。RandomHorizontalFlip概率0.5左右翻转对大多数垃圾是安全的但如果你识别的是有方向性的印刷文字这一项要关掉。ColorJitter的亮度、对比度各0.3模拟不同光照。归一化的均值方差用的是ImageNet统计值因为你的预训练权重是在ImageNet上训练的输入分布必须和训练时一致否则第一层卷积的输出就偏了。训练端和推理端的预处理必须严格分开训练端允许随机推理端必须确定。推理端用Resize(256)加CenterCrop(224)这是torchvision官方推荐的稳定组合。我的习惯是把这两个transforms定义在同一个文件里并导出两个脚本共用而不是在train.py和predict.py各写一份这样能从根本上杜绝不一致这个最常见的低级错误。4.3 微调超参数设置batch_size、lr、epochs怎么定迁移学习不是从零训练而是把预训练模型的通用特征拿过来用只调整部分参数。所以超参数设置和从零训练完全不同。下表是我在垃圾分类小工程上的起点值参数推荐值说明batch_size16或328G显存选16显存充足选32优化器Adam 或 SGDAdam收敛快SGD更稳、泛化更好学习率 lr1e-3只训分类头1e-4解冻backbone后epochs20到50小工程通常在30轮内收敛weight_decay1e-4防止全连接层过拟合schedulerCosineAnnealingLR周期由epochs决定先看模型改造和训练循环的最小骨架import torch.nn as nn from torchvision import models num_classes len(class_names) model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) for name, param in model.named_parameters(): if fc not in name: param.requires_grad False optimizer torch.optim.Adam( filter(lambda p: p.requires_grad, model.parameters()), lr1e-3) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_maxepochs) criterion nn.CrossEntropyLoss() for epoch in range(epochs): model.train() 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() scheduler.step() val_loss, val_acc evaluate(model, val_loader)第一段的作用是替换分类头model.fc.in_features是预训练分类头的输入维度ResNet18是512替换后输出从1000类变成你的类别数。然后遍历所有参数除了名字带fc的层之外全部冻结。冻结的意思是requires_gradFalseAdam根本不会给这些参数分配梯度状态显存和算力都省了。训练循环里用filter(lambda p: p.requires_grad, model.parameters())把可训练参数筛出来交给优化器这是冻结操作能生效的关键。什么时候解冻backbone我一般看验证集loss连续两三个epoch不下降时把最后的layer3、layer4解冻学习率降到1e-4再训10轮左右。这个操作能给模型“再松一档”的机会小数据上通常还能涨两三个点。4.4 类别不均衡用loss加权不要只用整体准确率垃圾影像数据集几乎都有类别不均衡问题纸箱、塑料瓶这些常见类别一两千张玻璃碎片、电池可能只有一两百张。如果不处理模型会对多数类过度自信少数类recall可能低到不可用。最简单的做法是给CrossEntropyLoss按类别频率反比加权from sklearn.utils.class_weight import compute_class_weight import numpy as np import torch.nn as nn labels [train_dataset.targets[i] for i in range(len(train_dataset))] class_weight compute_class_weight( balanced, classesnp.unique(labels), ynp.array(labels)) weights torch.tensor(class_weight, dtypetorch.float) criterion nn.CrossEntropyLoss(weightweights)compute_class_weight的balanced模式计算方式是每类权重 样本总数 / (类别数 × 该类样本数)。少数类因此拿到更大的loss权重梯度更新时会被更认真地对待。用这个之后整体accuracy可能微降但每类recall会均衡很多。训练结束后一定要打印每个类别的recall而不是只看总体acc不然你根本不知道电池这类危险品被模型漏掉了多少。5. 垃圾分类小工程避坑指南5个高频翻车点与修复姿势这一章写的都是我实际踩过、或者帮人排查过的坑。每个都按“现象→原因→解决”展开建议你在改代码前先对号入座。5.1 训练loss不降反升或者第10轮突然爆掉现象前几轮loss正常下降到第10轮突然从1.2跳到9.8之后再也回不去或者从一开始loss就不降剧烈震荡。原因最常见的三个学习率太大模型在损失曲面震荡数据增强过猛图被裁得面目全非标签失效更隐蔽的是标签顺序错位模型一直在学错误的映射。解决分两步。先用下面这段代码从dataloader里抽一批数据打印出图和标签人工确认图和类对得上data_iter iter(train_loader) images, labels next(data_iter) for i in range(min(8, len(labels))): print(i, class_names[labels[i].item()])确认标签无误后把学习率降到1e-4重启。如果loss曲线仍然抖动把ColorJitter全部关掉用最朴素的裁剪加翻转先跑一遍。这个“先极简后增强”的顺序能最快定位问题。5.2 显存溢出或CPU慢到不可用现象batch_size设32训练没两步就报CUDA out of memory或者CPU训练一个epoch要两小时。原因通常是输入分辨率太大、batch太大、验证阶段忘了关梯度。解决路径有四条按性价比排序把输入统一到224或256很多小工程默认用448或更大显存直接翻倍batch_size减半从32降到16在验证函数里加with torch.no_grad():不然验证阶段计算图不会被释放最后再考虑混合精度scaler torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): outputs model(images) loss criterion(outputs, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()混合精度对准确率影响很小但显存和速度都有体感提升。唯一注意点是老显卡不支持AMP设置环境变量CUDA_VISIBLE_DEVICES时会自动退化不用强求。5.3 推理结果永远是同一个类别现象不管输入是纸箱还是塑料瓶输出永远是同一个标签置信度还很高。原因有几种可能预处理和训练不一致比如训练用了224推理用了320特征分布对不上类别顺序错位模型输出的第3类是塑料瓶你打印的是第3类是纸板还有可能是softmax之后取argmax的维度写错。排查时不要只看top1把top5打印出来probs torch.softmax(logits, dim1) top5 torch.topk(probs, kmin(5, probs.size(1)), dim1) for rank in range(len(top5.indices[0])): idx top5.indices[0][rank].item() print(class_names[idx], round(top5.values[0][rank].item(), 4))如果top5的概率分布均匀且全是同一个类别说明模型或数据有问题如果第二名、第三名其实是对的类别只是被排到后面那就是类别列表顺序写错。我遇到后者居多因为训练脚本和推理脚本常各写一个类别数组。5.4 加载checkpoint报missing或unexpected key现象load_state_dict报错一串以module.开头的key找不到或者fc层的权重维度对不上。原因有两个训练时用了DataParallel保存的权重key会多出module.前缀或者微调时fc层输出维度不是1000和预训练权重冲突。解决方式统一处理state torch.load(best.pth, map_locationcpu) new_state {} for k, v in state.items(): new_state[k.replace(module., )] v missing, unexpected model.load_state_dict(new_state, strictFalse) print(missing, unexpected)strictFalse的意思是允许缺少部分key先加载能匹配的再打印缺失列表。如果你替换过fc层那么fc.weight和fc.bias出现在unexpected里是正常的不用管但如果backbone层的key出现在missing里说明网络结构定义和保存时不一致这是错误需要回头检查模型名。一个细节是load_state_dict返回的元组第一个元素是缺失key第二个是多余key千万别看反。5.5 Windows下中文路径与中文目录名的乱码和读取失败现象代码在Linux好好的Windows上一跑读取图片报路径找不到或者控制台打印的标签全是乱码。原因在于Windows控制台默认GBK编码而Python 3的字符串是UTF-8中文目录名经过ImageFolder后也可能因为文件系统编码差异对不上。解决方式最彻底的是全程英文从项目根目录到数据集命名都不要有中文。其次类别目录用0_cardboard而不是纸板即使你在代码里用class_names [纸板]映射中文名目录本身也别用中文。最后排查读取问题时用这个命令看目录里实际读到了什么python -c import os; print(os.listdir(dataset/train/0_cardboard)[:5])如果列表为空但资源管理器里明明有文件多半是路径拼接问题检查项目根目录是不是真的在当前位置以及有没有不小心用了绝对路径却指向旧目录。6. 把“能跑”变成“可信”用评估脚本验收你的深度学习小工程训练结束、推理能出图这时最需要一份评估脚本把所有类别的准确率、召回率、F1打印出来再画一张混淆矩阵。只报一个整体accuracy在垃圾类别不均衡的工程里没有说服力。评估脚本不长核心就是下面这段from sklearn.metrics import classification_report, confusion_matrix # y_true 是验证集真实标签列表y_pred 是模型预测标签列表 # 两者按同样的顺序一一对应 report classification_report( y_true, y_pred, target_namesclass_names, digits4) print(report) cm confusion_matrix(y_true, y_pred)参数里target_names必须和训练时的类别列表顺序严格一致不一致时整个报告都是错位的。digits4是因为少数类样本量小三位小数看不清差别。拿到结果后重点不是看总体accuracy而是按下面这张表的思路定位问题指标应该关注什么overall accuracy初步判断模型是否可用但不能只看这个per-class recall哪一类被漏最多比如电池recall只有0.4说明模型大量漏检confusion pair哪两类经常互相误判比如玻璃和塑料说明特征重叠严重如果混淆矩阵里固定有某两类互相串我的习惯是回到数据集里把这两类的图片调出来观察它们到底像在哪里然后去补充真实场景的负样本而不是立刻换更大的模型。数据层面的缺口靠堆网络参数是补不回来的。我自己每次接手这类深度学习小工程第一件事永远是先跑通一次评估脚本确认baseline可信再动训练参数否则后面怎么调都像在碰运气。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。