简介:面向计算机视觉与环保信息化方向的学习者,这份资源完整呈现基于Python和深度学习的垃圾分类系统设计思路,可服务于课程设计、毕业设计或小型落地项目。压缩包共10个文件,大小约5.73MB,其中包含5个Python脚本,覆盖图像爬取与预处理、模型重训练、分类推理、串口通信输出等环节,另有Shell训练脚本、readme说明文档及多张示例分类图片,便于对照理解与运行调试。目前已有5115人学习下载。资料以CNN图像识别为主线,从数据收集、预处理、模型构建、训练调优到评估与部署均有实现脉络,并延伸到MobileNet等轻量化模型与防过拟合处理;既给出可直接复用的工程脚本,也通过说明文档交代目录组织与使用步骤。对于希望快速复现垃圾分类识别流程、完成课程设计或在此基础上做二次开发的读者,是一份结构紧凑、可操作性强的参考。
1. 垃圾分类系统:用 Python 和深度学习把四类垃圾分清楚
做过课程设计或者毕设的人应该都遇到过这种尴尬:拿到“垃圾分类系统”这个题目,第一反应是 OpenCV 阈值分割、颜色识别那一套,结果一到真实照片就翻车——塑料瓶和玻璃瓶颜色差不多,纸箱和废纸反光严重,识别率惨不忍睹。这套基于 Python 与深度学习的垃圾分类系统,走的是图像分类的正路:用 CNN 模型对垃圾图片做端到端分类,把“可回收、有害、厨余、其他”四类(也可以扩展到 40 类细分类)按深度学习标准流程跑通。它适合三类人:做课设/毕设需要完整技术栈的学生,想快速落地一个图像分类项目的开发者,以及想弄懂 PyTorch 训练到部署全流程的入门者。这套资源自带源码、课程设计文档和数据集,照着跑能出结果,改改能写进论文。
2. 系统架构与数据集:图像分类项目怎么从零搭起来
2.1 整体流程:从图片到分类结果的四条链路
垃圾分类系统本质上是一个图像分类器,它的完整链路是:图片读取 → 数据预处理 → 模型前向传播 → 输出分类置信度。底层的技术选型其实非常明确,PyTorch 负责模型构建与训练,Torchvision 提供预训练模型权重,OpenCV 负责摄像头场景的图像采集,Flask 负责把模型包装成 HTTP 接口。这套架构最核心的设计决策在于:分类任务用 CNN 而不是目标检测。垃圾图片通常是单物体为主,不需要定位多个物体,用图像分类就能解决 90% 的问题;如果硬上 YOLO 或 Faster R-CNN,训练时间和标注成本都会翻几倍,课设周期扛不住。
整个系统分为训练端和推理端。训练端做的事情是:读入数据集 → 数据增强 → 送入 CNN 网络 → 计算交叉熵损失 → 反向传播更新权重 → 保存最佳模型。推理端做的事情是:加载训练好的权重 → 对输入图片做与训练时相同的预处理 → 前向传播 → 取 softmax 概率最高的类别作为预测结果。这两个端共用同一个预处理配置,这是最容易忽略的一点——训练时用了随机裁剪和色彩抖动,推理时如果忘了关掉,预测结果就会不稳定。
常见的做法是先把数据流图画出来,定义一个Config类把所有超参数集中管理,这样后面改 batch size、学习率、图片尺寸都只改一个文件,不用到处翻代码。我一般会先把目录结构定成这样:
garbage_classification/ ├── config.py # 超参数配置 ├── dataset.py # 数据集读取与增强 ├── train.py # 训练脚本 ├── predict.py # 单张图片推理 ├── app.py # Flask 部署 ├── models/ # 保存训练好的权重 └── data/ # 数据集目录config.py里集中定义的参数包括图片尺寸(一般取 224×224,这是 ImageNet 预训练模型的默认输入)、batch size、学习率、训练轮数和类别数量。图片尺寸这个参数尤其关键,ResNet 和 MobileNet 开箱即用的输入尺寸都是 224,如果你自己改到 128 或 96,从 Torchvision 加载的预训练权重虽然也能用,但模型的全局平均池化层会改变特征图的尺寸适配逻辑,准确率通常会掉 3 到 5 个百分点,得不偿失。
2.2 数据集准备:目录结构和标签编码
垃圾分类的数据集组织方式一般有两种:一种是按类别分文件夹,每个文件夹里放该类别的图片;另一种是 CSV 标注文件记录图片路径和类别。这套系统用的是第一种,因为 PyTorch 的ImageFolder接口可以直接读取按类别分文件夹的数据集,省去自己写数据加载器的麻烦。数据集的目录结构是这样的:
data/ ├── train/ │ ├── recyclable/ # 可回收垃圾 │ ├── hazardous/ # 有害垃圾 │ ├── kitchen_waste/ # 厨余垃圾 │ └── other_waste/ # 其他垃圾 └── val/ ├── recyclable/ ├── hazardous/ ├── kitchen_waste/ └── other_waste/如果用四分类,每类训练图片建议不少于 500 张,验证集每类不少于 100 张。如果数据集图片数量不够,准确率会非常不稳定,同一张图片换一次随机裁剪结果都可能不同。数据集划分这个环节有一个常见做法:先把全部数据按 8:2 划分训练集和验证集,再用torch.utils.data.random_split从训练集里再切一部分出来做测试集,这样能保证类别分布一致。数据加载代码可以直接用ImageFolder:
from torchvision import datasets, transforms train_transform = transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), 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(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('data/train', transform=train_transform) val_dataset = datasets.ImageFolder('data/val', transform=val_transform)为什么要用 224×224?因为 ImageNet 预训练模型都是在 224×224 的输入上训练出来的,用这个尺寸可以直接加载预训练权重并且让模型正常工作。这里的Normalize参数mean和std是 ImageNet 数据集的统计值,0.485、0.456、0.406 是 RGB 三个通道的均值,0.229、0.224、0.225 是标准差,这个固定值不能自己随便改,改了之后预训练权重就不匹配了。训练集和验证集的预处理不一样,训练集需要随机裁剪和翻转来增加数据多样性,验证集只需要缩放后中心裁剪,不做随机增强。
2.3 数据增强:为什么不能只靠原始图片
垃圾分类图片的拍摄条件差异很大——光照、拍摄角度、垃圾袋的遮挡程度都会影响识别效果。如果不做数据增强,模型很容易过拟合,训练集准确率到了 99%,验证集却只有 85%,说明模型把背景特征也学进去了,而不是真正学会了辨别垃圾本身的纹理和形状。数据增强相当于用低成本把数据集扩大了若干倍,让模型见过更多形态的样本,泛化能力更强。
上面代码里的ColorJitter(brightness=0.2, contrast=0.2)专门处理光照差异问题,RandomHorizontalFlip处理拍摄角度问题,RandomResizedCrop处理物体尺寸和位置变化问题。这三项组合是垃圾图片场景下最实用的增强组合,不需要上 MixUp、CutMix 那些花哨手段,课设场景下够了。有一点要注意:增强力度不能太大,亮度偏移超过 0.3 之后图片会失真,模型反而学不到有效特征,训练曲线会变得很波动。
3. 模型训练与调参:ResNet50 和 MobileNetV2 怎么选、怎么训
3.1 预训练模型的加载方式
垃圾分类的图片特征和 ImageNet 的图片特征有重叠——都是自然图像,所以直接加载 ImageNet 预训练权重做迁移学习,效果远好于从零开始训练。从零训练一个 ResNet50 在 ImageNet 上要跑几周,但在垃圾分类这个数据集上迁移学习只需要几个小时的 CPU 训练,这就是预训练权重的价值。选模型时要看硬件条件:有 NVIDIA GPU 就上 ResNet50,准确率更高;只有 CPU 就选 MobileNetV2,推理速度快一个量级。
用 Torchvision 加载预训练模型并修改最后的全连接层,代码很简单:
import torchvision.models as models import torch.nn as nn # 方案一:ResNet50,准确率优先 model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1) num_ftrs = model.fc.in_features model.fc = nn.Linear(num_ftrs, num_classes) # 方案二:MobileNetV2,速度优先 model = models.mobilenet_v2(weights=models.MobileNet_V2_Weights.IMAGENET1K_V1) num_ftrs = model.classifier[1].in_features model.classifier[1] = nn.Linear(num_ftrs, num_classes)这里model.fc和model.classifier[1]分别是两个模型的最后分类层,num_ftrs是分类层输入特征的维度,ResNet50 是 2048,MobileNetV2 是 1280。这两个数字是网络结构决定的,不需要手动计算,直接用in_features取出来即可。如果你对weights参数不熟悉,注意新版本 Torchvision 里pretrained=True已经被弃用,需要用weights=...这种写法,老代码直接跑会报 FutureWarning,虽然不影响功能,但最好按新写法来,避免后续 Torchvision 升级后代码直接跑不了。
3.2 训练脚本结构与关键超参数
训练脚本的核心逻辑分四步:定义数据加载器、定义模型、定义损失函数和优化器、循环训练并保存最优模型。损失函数用CrossEntropyLoss就行,因为这是标准的多分类任务;优化器用 Adam,初始学习率设 0.001,比 SGD 更容易收敛,对新手更友好。训练脚本写出来大概是这样的:
import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader def train_model(model, train_dataset, val_dataset, epochs=30): train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=2) val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=2) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1) device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = model.to(device) best_acc = 0.0 for epoch in range(epochs): model.train() running_loss = 0.0 for inputs, labels in train_loader: inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() * inputs.size(0) scheduler.step() # 每个 epoch 结束后在验证集上评估 val_acc = evaluate(model, val_loader, device) print(f'Epoch {epoch+1}/{epochs}, Loss: {running_loss/len(train_dataset):.4f}, ' f'Val Acc: {val_acc:.4f}') if val_acc > best_acc: best_acc = val_acc torch.save(model.state_dict(), 'models/best_model.pth') print(f' -> 保存最佳模型, Val Acc: {val_acc:.4f}')这几个超参数是项目里最值得调的部分。batch_size选 32 是平衡显存和收敛速度的结果,GPU 显存不够就降到 16,CPU 训练可以保持 32 但速度会慢一些。lr=0.001是 Adam 的默认学习率,适合大多数迁移学习场景;如果你发现 loss 一直降不下去,可以把学习率降到 0.0003 再试,但不要轻易升到 0.01 以上,容易震荡甚至发散。StepLR每 10 轮把学习率乘以 0.1,这个设置的目的是在训练后期用小学习率精细调整权重,很多新手忽略学习率衰减,结果训练到 20 轮以后验证准确率就停滞不动了。
训练过程中要同时观察训练集 loss 和验证集准确率。正常情况下训练 loss 应该稳步下降,验证准确率同步上升;如果训练 loss 下降而验证准确率不动,说明过拟合,需要增加数据增强强度或提前停止训练;如果训练 loss 和验证准确率都不动,说明学习率太低或模型结构有问题。evaluate函数是一个独立的验证函数,计算验证集准确率时记得加torch.no_grad()并设置model.eval(),否则 dropout 和 batch normalization 的行为会不一致,验证结果失真。
3.3 训练时间与硬件的关系
用 CPU 训练 ResNet50,四分类数据集每类 500 张图,30 轮大概需要 6 到 8 小时;用 GPU 训练(GTX 1660 以上级别),同样配置只需要 20 到 30 分钟。如果只有 CPU 且时间紧张,建议直接换 MobileNetV2,参数量只有 ResNet50 的十分之一,训练时间能压缩到两三个小时,准确率只差 2 到 3 个百分点。这个取舍在课设答辩的时候说清楚,反而是加分项——比硬上 ResNet50 跑不完更有说服力。
4. 部署与推理:Flask 接口和摄像头实时识别怎么接
4.1 保存与加载模型的最佳实践
训练结束后,模型的保存方式直接决定后面部署的顺利程度。torch.save(model.state_dict(), 'best_model.pth')只保存权重,加载时需要重新构建模型结构再 load,好处是文件小、兼容性好。如果想连模型结构一起保存,可以用torch.save(model, 'best_model_full.pth'),但这种方式在跨 Python 版本或跨 Torchvision 版本时容易出兼容问题,我一般不用。加载模型的关键在于:必须把新的nn.Linear层设置成和训练时相同的输出类别数,否则 load 的时候报形状不匹配错误。
import torch import torchvision.models as models import torch.nn as nn num_classes = 4 model = models.resnet50(weights=None) model.fc = nn.Linear(model.fc.in_features, num_classes) checkpoint = torch.load('models/best_model.pth', map_location='cpu') model.load_state_dict(checkpoint) model.eval()加载权重后一定要调model.eval(),这是新手最容易漏的一步。不调eval()的话模型默认在训练模式,batch normalization 会用当前 batch 的统计量而不是训练时保存的全局统计量,单张图片推理时统计量完全失真,输出的分类概率会莫名其妙地偏向某一类。验证模式和训练模式的切换是这个项目里最隐蔽的坑之一,很多人的模型部署后准确率暴跌,就是这个原因。
4.2 单张图片的推理函数
推理时要做和训练验证集完全一致的预处理:Resize(256)→CenterCrop(224)→ToTensor()→Normalize。这里不能直接用训练集的预处理流程,因为训练集有随机增强,每次跑结果都不一样。推理函数是部署的公共模块,Flask 接口和摄像头识别都要复用这个函数,所以要写成独立函数:
import torch from torchvision import transforms from PIL import Image def predict_image(image_path, model, device): 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') tensor = transform(image).unsqueeze(0).to(device) with torch.no_grad(): outputs = model(tensor) probabilities = torch.softmax(outputs, dim=1) confidence, predicted = torch.max(probabilities, 1) class_names = ['可回收垃圾', '有害垃圾', '厨余垃圾', '其他垃圾'] return class_names[predicted.item()], confidence.item()unsqueeze(0)的作用是把单张图片从形状[3, 224, 224]变成[1, 3, 224, 224],因为 PyTorch 的模型要求输入带 batch 维度。torch.softmax把模型的 logits 输出转换成 0 到 1 之间的概率值,torch.max取概率最大的类别和对应的置信度。返回置信度这个设计对实际使用很重要——置信度低于 0.6 时系统应该提示用户“请重新拍摄”,而不是硬给一个分类结果,这样能避免模型在模糊图片上强行输出错误答案。
4.3 Flask 接口:把模型包成 HTTP 服务
垃圾识别功能如果只在训练脚本里能用,那就只是个半成品。用 Flask 把模型包成一个 HTTP 接口,前端或小程序就能通过 POST 请求上传图片并拿到识别结果。Flask 部署的核心注意点是模型只需要加载一次,不要每次请求都重新 load,否则并发稍高就超时。所以要把模型加载放在模块级别,而不是放在请求处理函数里:
from flask import Flask, request, jsonify import torch app = Flask(__name__) # 模型只在启动时加载一次 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = build_model() model.load_state_dict(torch.load('models/best_model.pth', map_location=device)) model = model.to(device) model.eval() @app.route('/predict', methods=['POST']) def predict_endpoint(): if 'image' not in request.files: return jsonify({'error': '未找到图片文件'}), 400 image_file = request.files['image'] image_path = 'temp_upload.jpg' image_file.save(image_path) try: class_name, confidence = predict_image(image_path, model, device) return jsonify({ 'class': class_name, 'confidence': round(confidence, 4) }) except Exception as e: return jsonify({'error': str(e)}), 500后端拿到上传图片后先保存为临时文件,再调用predict_image做推理。返回的 JSON 里除了类别名还要带置信度,这是给前端判断用的——如果置信度低,前端可以提示用户重新拍摄,而不是展示一个可能错误的分类结果。Flask 默认的 dev server 会打印每个请求的日志,调试接口时很方便;部署到公网时要用 gunicorn,否则性能跟不上,这点课设答辩时老师可能会问。
4.4 摄像头实时识别:OpenCV 加逐帧推理
摄像头实时分类的原理很简单:OpenCV 逐帧读取画面,把每一帧传给predict_image处理,再把结果画在视频画面上。这里有一个性能瓶颈:单张图片经过 ResNet50 推理大约需要 30 毫秒(GPU)到 300 毫秒(CPU),如果每帧都推理,画面会非常卡。处理办法通常是跳帧——每 5 帧做一次推理,中间 4 帧直接显示上次的结果:
import cv2 cap = cv2.VideoCapture(0) frame_count = 0 current_label = "等待识别..." while True: ret, frame = cap.read() if not ret: break frame_count += 1 if frame_count % 5 == 0: cv2.imwrite('temp_frame.jpg', frame) current_label, conf = predict_image('temp_frame.jpg', model, device) cv2.putText(frame, current_label, (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow('Garbage Classification', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()跳帧策略让 CPU 推理也能跑出接近实时的效果。frame_count % 5表示每 5 帧做一次推理,如果换用 MobileNetV2,可以改成每 3 帧甚至每帧都推理。cv2.imwrite把当前帧写到临时文件再读取,这样是为了复用predict_image的 PIL 加载逻辑——如果你直接用cv2.cvtColor转成 RGB 再转 PIL 也行,但多一步操作,代码没那么简洁。摄像头识别这个功能在课设演示时效果很直观,比单张图片识别更有冲击力,答辩老师一般都会给加分。
5. 避坑与排查:五个高频问题及处理方案
5.1 类别不平衡导致准确率虚高
现象:训练出来的模型整体准确率 90%,但仔细看每一类的准确率,发现“其他垃圾”只有 50%,而“厨余垃圾”到了 98%——模型只是在投机取巧,把所有不确定的图片都分到了样本量大的类别。
原因:数据集里四类图片数量差距过大,有些类别有 1000 张,有些只有 200 张。模型学到的决策边界偏向样本量大的类别,这是分类任务里最常见的问题。
解决:先统计每个类别的图片数,如果差距超过 2 倍,就需要做数据增强补齐,或者用WeightedRandomSampler给样本量小的类别分配更高的采样权重:
from torch.utils.data import WeightedRandomSampler # 统计每个类别的样本数,计算权重 labels = [label for _, label in train_dataset] class_counts = torch.bincount(torch.tensor(labels)) weights = 1.0 / class_counts.float() sample_weights = weights[labels] sampler = WeightedRandomSampler(sample_weights, num_samples=len(sample_weights), replacement=True)5.2 Windows 下数据集路径分隔符不一致
现象:在 Windows 上训练时,ImageFolder读取数据集报错,提示找不到路径或者类别文件夹为空;但代码在 Linux 上一切正常。
原因:Windows 路径分隔符是反斜杠\,Linux 是正斜杠/。如果数据集是从网上下载的压缩包,在 Windows 上用解压工具解压后路径可能变成data\train\recyclable,而代码里写的是data/train。另外,某些压缩包的路径中包含特殊字符,解压时会被截断。
解决:统一用os.path.join拼接路径,不要手写斜杠;数据集目录放到项目根目录下,避免盘符路径导致的硬编码问题。训练前先打印以下内容确认路径正确:
import os train_path = os.path.join('data', 'train') print(f'训练集路径: {os.path.abspath(train_path)}') print(f'存在: {os.path.exists(train_path)}')5.3 GPU 可用但代码跑在 CPU 上
现象:训练速度非常慢,一看任务管理器 GPU 占用率只有 5%,代码一直在用 CPU 跑。
原因:代码里用了torch.device('cuda'),但模型和数据的.to(device)调用有遗漏——模型移到了 GPU,数据还在 CPU 上,训练时 PyTorch 会报错或者干脆全程在 CPU 上计算。还有一种情况是安装的 PyTorch 是 CPU 版本,torch.cuda.is_available()返回 False。
解决:训练脚本开头加一个设备检测逻辑,把一切都放到device上。如果确认有 GPU 但is_available()返回 False,需要重装 GPU 版 PyTorch:
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118装完之后跑一段代码验证,torch.cuda.is_available()输出 True 才算真正用上了 GPU。
5.4 部署到 Flask 后并发请求卡死
现象:前端同时上传多张图片,Flask 服务就卡住不动了,单张测试时一切正常。
原因:Flask 的 dev server 默认是同步单线程处理请求,一个请求正在做模型推理时,其他请求全部排队等待。ResNet50 推理一张图需要几百毫秒,多张图片并发时队列积压,看起来就是卡死。
解决:开发环境下用app.run(threaded=True)跑多线程模式;正式部署用 gunicorn 多 worker:
gunicorn -w 4 -b 0.0.0.0:5000 app:app-w 4表示起四个 worker 进程,每个 worker 独立加载一份模型,并发能力提升 4 倍。注意每个 worker 都会占一份显存,显存不够就减少 worker 数量。
5.5 训练时 loss 出现 NaN
现象:训练跑到一半,loss 突然变成 NaN,之后所有参数都变成了 NaN,训练彻底崩溃。
原因:最常见的原因是学习率太大,Adam 虽然自适应,但遇到异常大的梯度仍然会发散;另一种可能是数据预处理出错,比如图片里有纯黑或纯白区域导致归一化后数值异常,或者 normalize 的 std 设成了 0。
解决:先把学习率从 0.001 降到 0.0003 重试。如果还出现 NaN,检查数据加载是否正常,先跑一个 epoch 用torch.isnan(outputs).any()检查模型输出;再排查有没有损坏的图片文件,可以让代码在读取图片时捕获异常:
from PIL import Image try: img = Image.open(path).convert('RGB') except Exception as e: print(f'无法读取图片: {path},错误: {e}') continue另外,ReduceLROnPlateau比StepLR更能及时响应 loss 波动——发现 loss 不下降就自动降低学习率,能减少发散的概率。
6. 进阶验证与优化:混淆矩阵和模型量化
模型训练完不等于项目做完。验证集准确率只是一个总体指标,它掩盖了类别之间的差异,所以每次训练完我都会先画一个混淆矩阵,看看模型具体在哪些类别上容易混淆。用 scikit-learn 几行代码就能生成:
import numpy as np from sklearn.metrics import confusion_matrix, classification_report import matplotlib.pyplot as plt import seaborn as sns # 假设 val_loader 是验证集,model 是训练好的模型 all_preds, all_labels = [], [] with torch.no_grad(): for inputs, labels in val_loader: inputs = inputs.to(device) outputs = model(inputs) _, preds = torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) cm = confusion_matrix(all_labels, all_preds) class_names = ['可回收', '有害', '厨余', '其他'] plt.figure(figsize=(8, 6)) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=class_names, yticklabels=class_names) plt.xlabel('预测类别') plt.ylabel('真实类别') plt.savefig('confusion_matrix.png', dpi=150)混淆矩阵能直接告诉你模型把“有害垃圾”误判成了“可回收垃圾”还是“厨余垃圾”。如果某个类别之间持续混淆,最有效的办法是补充该类别的训练图片,而不是盲目增加训练轮数。classification_report输出的精确率和召回率更能定位问题——如果某类精确率低但召回率高,说明模型倾向于把其他类的图片分到这个类;反过来就是这类图片大量被分到别的类里了。
训练完成后可以做模型量化来提升推理速度。PyTorch 官方的量化方案是把模型从 FP32 精度压缩到 INT8 精度,模型体积缩小到原来的四分之一,推理速度提升 2 到 4 倍,准确率只损失 1 到 2 个百分点。对于 CPU 部署场景来说,量化是一个性价比极高的优化手段:
model.eval() model_quantized = torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, dtype=torch.qint8 ) torch.save(model_quantized.state_dict(), 'models/best_model_quantized.pth')quantize_dynamic是 PyTorch 里最容易上手的量化方式,它只量化nn.Linear层,不需要额外的校准数据集,一行代码就能完成。课设答辩展示性能对比时,把 CPU 上 FP32 和 INT8 的推理延迟数据列出来,比空口讲理论有说服力得多。从那以后我每次做完图像分类项目都强制走一遍这个流程:训练 → 混淆矩阵评估 → 权重保存 → 部署测试 → 量化加速,缺一个环节都觉得不完整。希望这篇拆解能帮你在垃圾分类系统上少走弯路,把每一步都落到实处。
本文还有配套的精品资源,点击获取