☰
Python与深度学习实现垃圾分类系统:从数据集到Flask部署
2026/10/2 19:02:11 网站建设 项目流程

简介:一套基于Python与深度学习的垃圾分类系统设计与实现资料包,面向计算机专业学生、毕业设计开发者以及环保智能应用入门者。资源围绕图像识别垃圾分类的完整流程展开,从数据采集、预处理到CNN模型构建、训练优化与部署,同时融入TensorFlow、Keras、PyTorch等框架的实践应用,帮助读者建立端到端项目认知。包体共10个文件,核心以5个Python脚本为主,分别承担数据抓取、模型训练、推断检测与串口发送等功能;另含训练启动脚本、真实垃圾图片样本与说明文档,压缩包整体约5.73MB,结构紧凑。目前已有5115人学习下载。资料价值在于提供可直接参考的代码骨架与项目组织方式:既有图像爬取工具,也有模型重训练与部署逻辑,同时附带BANANA、BOTTLE等测试图片,便于快速验证识别效果。对需要完成课程设计、竞赛实践或入门深度学习图像分类的读者而言,是一份实用且易于上手的参考资料。

1. 垃圾分类系统不用从零起步:python + 深度学习先解决一个具体分类问题

基于 python 与深度学习的垃圾分类系统设计与实现,听起来像课程设计题目,但它落地起来就是一套很具体的流程:用 python 调起深度学习模型,对一张垃圾图片判断出类别,再把类别对应到投放桶和回收提示上。它解决的问题是督导员人力不够、居民对“纸盒算可回收还是其他”这类判断反复纠结;适合两类人——计算机专业做毕设或课设的学生,需要一段从数据集到部署的完整链路;社区、园区里想先验证垃圾分类识别可行性的工程师,需要先跑起来一台能识别的 demo,而不是直接上硬件。深度学习出识别能力,python 负责数据、训练、封装,系统设计决定它到底是一个会出结果的模型,还是一个能用的产品。

2. 数据与模型选型是垃圾分类系统的地基:两个数据集路线与选型对照

2.1 数据集怎么找、怎么整理:两种公开路线与目录结构约定

垃圾分类没有统一的大数据集,常见做法是用公开数据集先跑通链路,再逐步加入自己的现场照片。公开数据里两条路线最常用:一类是 TrashNet 这种按材料分的基础数据集,包含 glass、paper、cardboard、plastic、metal、trash 六类,图片大多是白底单物体,优点是干净、启动快;另一类是竞赛发布的中文标签数据集,按可回收、厨余、有害、其他四分类展开,类别能细分到几十种,更贴近国内投放点。我的第一版建议从四分类或六分类开始,先把整条链路跑通,再扩类。真要从零爬图片也不是不行,但公开数据集几十万张的收集和清洗成本,对毕设和原型来说不划算。

拿到数据后的第一步是统一目录结构。PyTorch 的 torchvision.datasets.ImageFolder 天然按“根目录/类别文件夹/图片”读取,所以目录名本身就是标签。我一般会做成 train、val、test 三级:

# 以四分类为例,建立训练/验证/测试目录 mkdir -p dataset/train/{recyclable,kitchen_waste,hazardous,other} mkdir -p dataset/val/{recyclable,kitchen_waste,hazardous,other} mkdir -p dataset/test/{recyclable,kitchen_waste,hazardous,other} # 按名单把图片复制到目标目录,每行:图片路径 空格 类别 while read -r src category; do cp "$src" "dataset/train/$category/" done < train_list.txt

这段 bash 脚本做两件事:建目录和按名单分配图片。关键是 train_list.txt 每一行的类别名要和目录名严格一致,否则 ImageFolder 会把同名目录当成同一个类。实际项目里我更推荐用符号链接而不是 cp,因为数据集可能十几 GB,复制两份磁盘就满了:

ln -s /data/raw/recyclable_001.jpg dataset/train/recyclable/001.jpg

数据整理阶段最容易翻车的不是脚本,而是混入损坏文件和通道异常图。PIL 读不了断头的 JPEG,训练到一半就会崩。我会先跑一遍过滤脚本,把打不开的文件直接挪到 discard 目录:

# verify_images.py:检查图片是否能被 PIL 正常打开 from pathlib import Path from PIL import Image for root in ['dataset/train', 'dataset/val', 'dataset/test']: for img_path in Path(root).rglob('*'): if img_path.suffix.lower() not in {'.jpg', '.jpeg', '.png', '.bmp'}: continue try: Image.open(img_path).verify() except Exception: dst = Path('dataset/discard') / img_path.name img_path.rename(dst) print('move to discard:', img_path)

verify() 只读文件头不动原始数据,比 open 后 load 快很多。这个过程要放在划分数据之前,否则坏图一旦混进验证集,模型指标会时高时低,看起来很像“玄学”。数据划分比例我一般用 8:1:1 或 7:2:1。验证集用来调参,测试集只做最终验收;垃圾分类数据往往类别不平衡,划分前最好按类别分层抽样,避免某个小类全被分到测试集里。

提示:数据集路径统一用 pathlib 处理,别用字符串拼接路径,Windows 下反斜杠和斜杠混用经常出问题。

2.2 模型选型:从零训练与迁移学习一张表看清

对垃圾分类这种图像分类任务,从零训练一个深度卷积网络不是不行,只是公开数据规模通常在几千到几万张,普通 CNN 参数量却有百万级,从零训练很容易过拟合。所以行业里最可靠的做法是迁移学习:用 ImageNet 预训练权重做初始参数,只把最后的全连接分类头换成任务类别数,再对整个网络微调。这样做的收益很直接——模型已经学会了纹理、边缘、物体轮廓这些通用视觉特征,垃圾分类只需要在它的基础上再学“什么特征属于塑料瓶”这种高层语义。

方案参数量CPU 单张推理耗时精度上限适合场景
从零训练 5 层小 CNN低10-30ms中等,受数据量制约教学演示、理解原理
ResNet18 迁移学习约 11M40-80ms高通用分类,部署容易
MobileNetV3-Small 迁移约 2.5M20-50ms较高摄像头实时、嵌入式
EfficientNetV2-S 迁移约 20M100ms+最高离线批量识别、精度优先

我自己的第一版会直接选 MobileNetV3-Small 或 ResNet18,理由是训练和部署都不依赖独立显卡,CPU 就能跑推理。PyTorch 里从预训练权重切到目标类别只改一行:

import torchvision.models as models model = models.mobilenet_v3_small( weights=models.MobileNet_V3_Small_Weights.IMAGENET1K_V1) num_classes = 4 model.classifier[-1] = torch.nn.Linear( model.classifier[-1].in_features, num_classes)

这里把最后一层全连接的输出改成 4,对应四分类。前面的卷积特征提取器保留 ImageNet 学到的能力,这是迁移学习的核心。注意如果把 weights 参数去掉,就是随机初始化,和从零训练没有本质区别。MobileNetV3 的 classifier 是 Sequential 结构,取 [-1] 拿最后一层比较稳,不依赖具体下标。

2.3 数据增强与类别权重:让模型不被多数类样本绑架

垃圾分类最坑的情况是:可回收类样本占 70%,其他三类共占 30%,模型全预测成可回收就有 70% 准确率,但对真实投放毫无意义。解决分两步:数据增强和损失函数加权。

数据增强用 torchvision.transforms 实现。要点是增强只在训练时做,验证和测试只做缩放与归一化:

from torchvision import transforms train_transform = transforms.Compose([ transforms.RandomResizedCrop(224, scale=(0.7, 1.0)), transforms.RandomHorizontalFlip(p=0.5), transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])

RandomResizedCrop 会随机裁一块再缩放到 224x224,模拟物体在画面里位置和大小的变化,这是效果最明显的一项;ColorJitter 专门对付不同光照下的垃圾桶照片;Normalize 的 mean 和 std 是 ImageNet 统计值,迁移学习时不要改。类别不均衡的权重可以在训练脚本里提前算好:

import torch counts = torch.bincount(label_tensor) # 每个类别的样本数 weights = counts.float().max() / counts.float() # 多数类权重小,少数类权重大 weights = weights / weights.sum() # 归一化 criterion = torch.nn.CrossEntropyLoss(weight=weights)

CrossEntropyLoss 的 weight 参数会在计算损失时放大少数类的梯度,模型不敢只学多数类。比起人工复制少数类图片,这个做法更干净,不会因为同一张图反复出现造成过拟合。

3. 用 PyTorch 把垃圾分类模型训练跑通:从图片路径到 checkpoint 的全流程

开始训练之前先把深度学习环境配置好。PyTorch 2.x 在纯 CPU 机器上也能训练四分类小模型,只是慢一些,不一定要一步到位上 GPU。下面这套流程就是一个典型的深度学习实战项目案例,工程链路和真实项目完全一致:自定义数据集、训练循环、保存模型,再被后端的服务调用。

3.1 自定义 Dataset:把图片目录变成训练批次

torchvision 的 ImageFolder 最省事,但要在数据路径里带额外元信息、或者想严格记录 class_to_idx 时,自己写 Dataset 反而更稳。自定义 Dataset 只需要实现len和getitem:

from pathlib import Path from PIL import Image import torch from torch.utils.data import Dataset class GarbageDataset(Dataset): def __init__(self, root, transform=None): self.transform = transform self.samples = [] # [(图片路径, 标签下标), ...] self.classes = sorted(p.name for p in Path(root).iterdir() if p.is_dir()) self.class_to_idx = {cls: i for i, cls in enumerate(self.classes)} for cls_name in self.classes: cls_dir = Path(root) / cls_name label = self.class_to_idx[cls_name] for img_path in cls_dir.glob('*'): if img_path.suffix.lower() in {'.jpg', '.jpeg', '.png', '.bmp'}: self.samples.append((str(img_path), label)) def __len__(self): return len(self.samples) def __getitem__(self, idx): img_path, label = self.samples[idx] image = Image.open(img_path).convert('RGB') # 统一三通道 if self.transform: image = self.transform(image) return image, torch.tensor(label, dtype=torch.long)

这个类在初始化时扫描 root 下的子目录,sorted 保证类别顺序稳定,class_to_idx 就是后面部署时要用的“类名到编号”映射。getitem里 convert('RGB') 是为了避免四通道 PNG 进来后通道数不统一。Dataset 本身不内置数据增强,transform 从外部注入,这样训练集和验证集可以复用同一个 Dataset,只是 transform 不同。

把 Dataset 接上 DataLoader 后,参数设置也有讲究:

from torch.utils.data import DataLoader train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=2, pin_memory=True) val_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=2, pin_memory=True)

shuffle 只用于训练集;验证集必须固定顺序,否则每次验证指标会像噪声一样跳动。num_workers 在 Windows 上设成 0 最省事,Linux 上取 CPU 核数的一半通常体验最好;pin_memory 在 GPU 训练时有收益,CPU 训练不影响。

3.2 训练主循环与超参数:让 loss 稳定下降的三个关键

训练脚本可以缩成一个主循环。下面是完整的最小实现,CPU 和 GPU 都能跑:

import torch import torch.nn as nn device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = model.to(device) optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=8, gamma=0.5) criterion = nn.CrossEntropyLoss() def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss, correct, total = 0.0, 0, 0 for images, labels in loader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() total_loss += loss.item() * images.size(0) pred = outputs.argmax(dim=1) correct += (pred == labels).sum().item() total += labels.size(0) return total_loss / total, correct / total for epoch in range(30): train_loss, train_acc = train_one_epoch( model, train_loader, optimizer, criterion, device) model.eval() val_correct, val_total = 0, 0 with torch.no_grad(): for images, labels in val_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) pred = outputs.argmax(dim=1) val_correct += (pred == labels).sum().item() val_total += labels.size(0) val_acc = val_correct / val_total print(f'epoch={epoch+1:02d} loss={train_loss:.4f} ' f'train_acc={train_acc:.4f} val_acc={val_acc:.4f}') scheduler.step()

核心思路是三步:zero_grad 清掉上一轮梯度,backward 算反向梯度,step 更新参数,顺序不能乱。Adam 的 lr 从 1e-3 起步适合大多数迁移学习任务;weight_decay 加一点做正则,防止 train_acc 一直涨、val_acc 停滞。StepLR 每 8 个 epoch 把学习率乘以 0.5,让后期收敛更稳。如果 loss 一开始很高且不退,先把 lr 降到 3e-4;如果 loss 很快到 0.1 以下但 val_acc 波动大,把 weight_decay 提到 1e-3。验证集评估必须包在 no_grad() 里,省显存也省时间。30 个 epoch 对四分类通常十几分钟到一个小时就能看出趋势,不用急着加早停,先看前 8 个 epoch 曲线方向。

提示:Windows 上训练慢时先检查杀毒软件是否在扫描数据集目录,把数据集目录加入白名单能快不少。

3.3 保存 checkpoint:别丢掉类别映射和优化器状态

训练完只是第一步,系统后面要加载这个模型做识别。如果只保存 state_dict,推理输出的是一个编号,你不知道它对应“plastic_bottle”还是“cardboard”。所以 checkpoint 里除了权重,还要存类别映射和优化器状态:

checkpoint = { 'model_state': model.state_dict(), 'optimizer_state': optimizer.state_dict(), 'class_to_idx': train_ds.class_to_idx, 'epoch': epoch + 1, 'val_acc': val_acc, } torch.save(checkpoint, 'garbage_model.pth')

加载时严格按相反顺序做:

def load_model(model, ckpt_path, device): ckpt = torch.load(ckpt_path, map_location=device) model.load_state_dict(ckpt['model_state']) model.to(device) model.eval() return model, ckpt['class_to_idx']

注意保存前把 train_ds.class_to_idx 带上,加载后用 model.eval() 切到推理模式,否则 BatchNorm 和 Dropout 的行为在推理时不等价,识别结果会不稳定。要继续训练的话,用 optimizer.load_state_dict(ckpt['optimizer_state']) 可以把学习率和动量状态一起恢复。把 val_acc 存进去也很有用,后面回溯“哪个版本效果最好”时不需要重新打一遍验证集。

4. 系统设计与实现:把训练好的模型封装成摄像头和 Web 都能调用的服务

4.1 系统架构与模块划分:识别、知识库、交互三段式

模型训练好之后,剩下的问题是“怎么让别人用”。一个可维护的垃圾分类系统,我会拆成三个模块:识别模块、知识库模块、交互模块。识别模块负责把图片变成类别和置信度,它只关心模型的输入输出;知识库模块负责把类别映射到投放桶编号、回收提示、常见误判提醒;交互模块负责摄像头抓拍、图片上传、结果展示。三个模块各自独立,模型迭代只动识别,投放规则变化只改知识库,UI 怎么换都不影响核心逻辑。

把识别逻辑写进 GUI 按钮回调里的做法是我见过最多的“坏味道”。临时用可以,但后面要加一个 Web 端或摄像头端时,就得把识别代码从按钮回调里抠出来重写一遍,浪费时间。我一般会先写一个 Predictor 类,让所有交互端共用它:

# predictor.py:统一识别入口,避免每个界面重复写预处理 import torch from PIL import Image import torchvision.transforms as T class Predictor: def __init__(self, ckpt_path): import torchvision.models as models self.model = models.mobilenet_v3_small(weights=None) self.model.classifier[-1] = torch.nn.Linear(1024, 4) ckpt = torch.load(ckpt_path, map_location='cpu') self.model.load_state_dict(ckpt['model_state']) self.model.eval() self.class_to_idx = ckpt['class_to_idx'] self.idx_to_class = {v: k for k, v in self.class_to_idx.items()} self.transform = T.Compose([ T.Resize((224, 224)), T.ToTensor(), T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) def predict(self, image): x = self.transform(image).unsqueeze(0) with torch.no_grad(): logits = self.model(x) prob = torch.softmax(logits, dim=1) p, idx = torch.max(prob, dim=1) return self.idx_to_class[int(idx)], float(p.item())

Predictor 在初始化时就加载模型和权重,识别时只走前向推理。这样桌面端、Web 端、测试脚本共用同一个类,不会出现“命令行里准、到 GUI 里就不准”的差异。一个容易踩的坑是:加载权重前要先构建出和训练时一样结构的模型,再 load_state_dict,顺序反了会报 size mismatch。ckpt 里的 idx_to_class 是训练时 class_to_idx 的反转,这里提前算好,推理代码就干净了。

4.2 用 Flask 封装识别接口:客户端只要发一张图

垃圾分类落地的常见形态有两种:居民用手机拍照上传,投放点摄像头自动抓拍。两种形态都可以统一走一个 HTTP 接口,Flask 是最省事的选择:

# app.py:Flask 识别接口 from flask import Flask, request, jsonify from predictor import Predictor from PIL import Image import io app = Flask(__name__) predictor = Predictor('garbage_model.pth') bucket_map = { 'plastic_bottle': '可回收', 'kitchen_waste': '厨余', 'hazardous_waste': '有害', 'other': '其他' } @app.route('/predict', methods=['POST']) def predict(): if 'image' not in request.files: return jsonify({'error': 'no image'}), 400 file = request.files['image'] img = Image.open(io.BytesIO(file.read())).convert('RGB') category, confidence = predictor.predict(img) if confidence < 0.6: return jsonify({'category': None, 'confidence': confidence, 'message': 'conf_low'}), 200 return jsonify({ 'category': category, 'confidence': round(confidence, 4), 'bucket': bucket_map.get(category, '其他') })

接口返回 category、confidence、bucket 三个字段,调用方不需要自己维护“类别-桶”映射。置信度低于 0.6 时返回 category=None,让前端提示“请重新拍一张”,比硬给一个答案更能体现系统设计价值。这个阈值不是固定死的,要根据真实场景调:如果误判频繁,抬高到 0.7;如果感觉很多照片被拒识,再降一点。客户端上传时用 multipart/form-data,字段名必须是 image:

curl -X POST http://127.0.0.1:5000/predict \ -F "image=@test.jpg"

返回示例:

{"bucket": "可回收", "category": "plastic_bottle", "confidence": 0.91}

4.3 摄像头实时识别与桌面端:让识别框跟着画面走

实时识别的做法是 OpenCV 抓帧,每隔几帧送一次预测,再把结果显示在画面上。帧帧都跑深度学习推理在 CPU 上会很卡,间隔处理是性能与体验的平衡点:

import cv2 from PIL import Image cap = cv2.VideoCapture(0) frame_no = 0 result_text = '' while True: ret, frame = cap.read() if not ret: break if frame_no % 6 == 0: # 每 6 帧识别一次 rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) pil_img = Image.fromarray(rgb) category, conf = predictor.predict(pil_img) result_text = f'{category} {conf:.2f}' cv2.putText(frame, result_text, (30, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 0), 2) cv2.imshow('garbage-classifier', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break frame_no += 1 cap.release() cv2.destroyAllWindows()

这里把 BGR 转 RGB 是必须的,OpenCV 读进来的帧是 BGR 顺序,直接送 PIL 会导致颜色通道反掉,识别效果莫名变差。间隔帧数由推理耗时决定:一次推理 0.2 秒时,隔 6 帧大约就是 5 FPS,画面基本可看。想进一步提高实时性,优先把输入尺寸压到 192x192,而不是换更重的模型。在 PyCharm 或 VS Code 里配置 python 环境时,建议用虚拟环境跑这套代码,避免系统路径里的其他包把 torch 或 opencv 覆盖掉。摄像头编号先从 0 试起,黑屏就换 1。

5. 垃圾分类系统避坑:五条实战中反复出现的精度与部署问题

5.1 训练集准确率 95%,真实照片识别却一路翻车

现象:用公开数据集训练的模型在测试集上表现不错,可一放到小区实拍照片上,准确率暴跌,尤其是有遮挡、光线暗的图。

原因:公开数据大多是干净背景加单一物体,而真实拍摄图里有桌面纹理、手指、塑料袋反光等干扰。模型学到的特征和真实场景不匹配,准确率被“画像相似”骗了。

解决:把数据增强里的 ColorJitter 强度加大,再叠加 RandomErasing 模拟遮挡;更重要的是从真实场景里补几十张到几百张现场照片加入训练集。这一步对精度的提升幅度远大于调模型结构。加入现场照片后要重新划分验证集,验证集至少包含 20% 真实场景图,否则指标依然失真。同时把置信度阈值机制用好,低置信度图返回“无法识别”,宁缺毋滥。

5.2 塑料瓶和玻璃瓶、脏纸巾和纸张永远分不清

现象:训练 loss 已经很低,整体准确率 90%,一查混淆矩阵,错误集中在某两个特定类别之间。

原因:这两类垃圾在颜色、形状、纹理上高度相似,加上样本数不均,模型干脆押多数类。

解决:先做类别数评估。塑料瓶和玻璃瓶在二维图片上本来就很接近,如果投放场景允许,把这两类合并成“瓶罐类”,或者先做四分类、再做材质细分类。若不能合并,就对混淆对做 hard negative 挖掘:把误判样本单独挑出来,再从网上找相似图回填训练集。不要盲目加通用数据,对误判对定向补数据效率高得多。

5.3 摄像头实时识别卡顿,画面像幻灯片

现象:OpenCV 预览流畅,但识别后画面一顿一顿,操作延迟严重。

原因:每一帧都进模型推理,而 CPU 单张推理要几十到几百毫秒;或者用原图 1920x1080 直接推理,预处理和模型耗时都被放大。

解决:动三个参数——间隔帧数调到 5-10 帧,输入 Resize 到 192 或 224,模型换成 MobileNetV3-Small。还有一个容易被忽略的点:摄像头画面里垃圾区域往往只占画面一小块,全图分类会引入大量背景噪声。更合理的架构是先用目标检测裁出垃圾区域,再送分类模型;如果只想用单模型,至少让体验保持在“隔一秒左右出一次结果”的可用状态,这是基线,不是优化。

5.4 PyInstaller 打包后双击闪退或提示模型文件不存在

现象:在 python 环境里运行正常,打包成 exe 后双击直接退出,控制台报出找不到 garbage_model.pth。

原因:PyInstaller 会把代码和资源文件分开处理,程序运行时的当前目录是临时解包目录 sys._MEIPASS,用相对路径找不到模型文件。

解决:获取资源路径用下面这段:

import sys from pathlib import Path def resource_path(relative_path): base = getattr(sys, '_MEIPASS', Path(__file__).parent) return str(Path(base) / relative_path) # 用 resource_path 而不是直接写文件名 predictor = Predictor(resource_path('garbage_model.pth'))

打包时还要把 pth 文件显式加进 datas:

pyinstaller --onefile --add-data "garbage_model.pth;." app.py

这里的“;”是 Windows 分隔符,Linux 和 macOS 是“:”。如果打包后还报 numpy 或 torch 相关错误,多半是 Python 版本不一致,尽量在干净的虚拟环境里打包。这个环节建议先用目录模式 PyInstaller 跑通,再转 --onefile,不然每次排错都要重新打包很慢。

5.5 训练 loss 一直不降或验证精度震荡

现象:loss 卡在某个值附近不下降,或者每个 epoch 的 val_acc 在 70%-85% 之间来回跳,看不出收敛趋势。

原因:常见三种——学习率太大导致梯度震荡;训练集和验证集数据有重复(公开数据下载时可能夹带重复图);class_to_idx 的排序方式在保存和加载时不一致导致标签错位。

解决:先把学习率降到 3e-4,weight_decay 设到 1e-3,看 loss 是否变平滑;再对全量图片用 SHA-1 去重,把重复文件从验证集剔除;最后检查每个类别的样本数量,最少类别连几十张都没有的话,先补数据再谈精度。数据泄露是这类问题的隐藏元凶,尤其是爬虫采集的素材,很多图在不同目录下重复出现。

6. 上线前用混淆矩阵验证并轻量化:从能跑到能用的最后一步

6.1 用混淆矩阵看透模型的短板

垃圾分类模型能跑通,不代表能上线。我最后一定会做一遍混淆矩阵验收,确认每个类别的表现:

from sklearn.metrics import confusion_matrix y_true, y_pred = [], [] model.eval() with torch.no_grad(): for images, labels in test_loader: pred = model(images).argmax(dim=1) y_true.extend(labels.tolist()) y_pred.extend(pred.tolist()) cm = confusion_matrix(y_true, y_pred) print(cm)

混淆矩阵按行看“某类图片被认成了哪些类”。如果两个互不相干但长相接近的类别错误率高,就按 5.2 的方法补数据;如果某个长尾类别完全没人认得,先合并再谈扩展。行归一化后每个类别的召回率是比整体准确率更诚实的指标——整体 95% 的系统,某类召回率只有 30% 的情况很常见。

6.2 轻量化:量化与输入尺寸的最后取舍

如果要部署到没有 GPU 的机器上,PyTorch 的动态量化可以作为最后一步:

model.eval() quantized = torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, dtype=torch.qint8) torch.save(quantized.state_dict(), 'garbage_model_int8.pth')

动态量化主要对 Linear 层生效,常见效果是模型体积减半、CPU 推理稍快,但不要期待类似 GPU 的几倍提速。更实用的轻量化是降低输入尺寸:从 224 降到 192,精度损失往往在 1-2 个百分点以内,速度却提升 30%。很多项目为了那 0.5% 的准确率死守 224,反而让摄像头方案失去可用性。

我现在的习惯是接到新任务先跑通一条最小链路:数据目录建好、模型能训练、能出一个 Flask 接口,再回头谈优化。垃圾分类系统最大的门槛从来不是深度学习模型的推理能力,而是数据与真实场景的差距。模型选型和训练细节再讲究,都不如尽早拿真实投放点的照片跑一遍。这套路径对别的图像分类任务同样适用,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询