基于PyTorch与迁移学习的垃圾图像分类系统:从数据到API部署全流程实践

📅 2026/7/27 6:34:21 👁️ 阅读次数 📝 编程学习
基于PyTorch与迁移学习的垃圾图像分类系统:从数据到API部署全流程实践

1. 这篇文章真正要解决的问题

你还在为垃圾分类头疼吗?无论是小区里复杂的四色垃圾桶,还是办公室里“干垃圾”“湿垃圾”的争论,手动分类不仅耗时耗力,还容易出错。对于开发者而言,这个问题更具体:如何利用技术,让一个摄像头或传感器,像人一样识别出眼前的垃圾是“可回收物”、“厨余垃圾”、“有害垃圾”还是“其他垃圾”?这不仅仅是环保议题,更是一个典型的计算机视觉与人工智能落地场景。

本文要解决的,正是这个从想法到产品的核心路径。我们将构建一个完整的“垃圾自动分类”系统。这不仅仅是调用一个现成的API,而是从零开始,带你理解图像分类项目的全流程:数据从哪来、模型怎么选、如何训练、怎样部署成一个可用的服务。你会发现,真正决定项目成败的,往往不是最复杂的算法,而是数据质量、工程化流程和那些容易被忽略的细节。

读完本文,你将能:

  1. 掌握一个图像分类项目的标准开发流程。
  2. 获得一份可运行、可修改的完整代码,用于训练自己的垃圾分类模型。
  3. 了解如何将训练好的模型封装成REST API,供前端或移动端调用。
  4. 避开数据标注、模型选择、部署上线中的常见大坑。

无论你是想完成课程设计、参与创新竞赛,还是为社区或公司开发一个智能垃圾桶原型,这篇文章都将提供一条清晰的实践路线。

2. 基础概念与核心原理

在动手之前,我们需要统一几个关键概念,这能帮助你在后续步骤中做出正确的技术决策。

图像分类(Image Classification):计算机视觉的基础任务之一,目标是让模型识别一张图片中的主要物体属于哪个预定义的类别。在我们的场景中,输入是一张垃圾的图片,输出是“塑料瓶”、“电池”、“果皮”等具体标签,或者直接映射到“可回收”、“有害”、“厨余”、“其他”四大类。

卷积神经网络(CNN):当前图像分类任务的主流模型架构。你可以把它想象成一个具有多层“过滤器”的智能系统。第一层过滤器可能只识别简单的边缘和颜色块;随着网络加深,后面的过滤器能组合出更复杂的图案,比如纹理、形状,最终识别出整个物体。ResNet、MobileNet、EfficientNet等都是基于CNN的著名模型家族。

迁移学习(Transfer Learning):这是本文项目的关键加速器。我们不必从零开始训练一个庞大的CNN(那需要海量数据和数天甚至数周的GPU时间)。相反,我们使用一个在ImageNet(包含1000类物体,如猫、狗、汽车等)上预训练好的模型。这个模型已经学会了提取通用图像特征的强大能力。我们只需要保留它的特征提取部分,替换并重新训练最后的分类层,让它适应我们特定的“垃圾”分类任务。这就像一位已经掌握了绘画基本功(素描、色彩)的画家,再去专攻“垃圾写生”题材,效率会高得多。

数据增强(Data Augmentation):为了让我们有限的数据集发挥更大作用,防止模型过拟合(只在训练集上表现好),我们会在训练前对图片进行随机变换,如旋转、翻转、裁剪、调整亮度等。这样,模型看到的“塑料瓶”就有各种角度、光照和背景,从而学到更鲁棒的特征,而不是死记硬背某几张特定图片。

整个系统的核心流程可以概括为以下几步:

  1. 数据收集与标注:获取垃圾图片,并为每张图片打上正确标签。
  2. 模型选择与搭建:选择一个预训练模型作为基础,修改其输出层以适应我们的分类数量。
  3. 模型训练与评估:用我们的数据训练模型,并在独立的验证集上评估其准确率。
  4. 模型部署与服务化:将训练好的模型保存,并封装成一个Web服务(API),接收图片输入,返回分类结果。

3. 环境准备与前置条件

工欲善其事,必先利其器。以下是完成本项目所需的环境和工具。建议使用Python 3.8及以上版本。

操作系统:Windows 10/11, macOS 或 Linux (如Ubuntu 20.04) 均可。本文示例命令以Linux/macOS的bash为主,Windows用户可在PowerShell或WSL中执行类似操作。

核心Python库

  • 深度学习框架:PyTorch 或 TensorFlow/Keras。两者都是优秀的选择,本文将以PyTorch为例进行演示,因其动态图特性对研究和实验非常友好。
  • 图像处理:PIL (Pillow) 或 OpenCV。
  • 科学计算与数据操作:NumPy, Pandas。
  • Web框架(用于部署):FastAPI(轻量、高性能,推荐)或 Flask。
  • 其他工具:Jupyter Notebook(用于实验和可视化),Matplotlib(用于绘图)。

硬件建议

  • 强烈推荐使用GPU进行训练:即使是一块消费级的NVIDIA GPU(如GTX 1660, RTX 3060等),也能将训练时间从数小时缩短到数十分钟。确保已安装对应版本的CUDA和cuDNN。
  • CPU也可运行:对于小型数据集或仅进行推理(预测),CPU可以胜任,但训练会非常缓慢。

安装步骤: 首先,创建一个干净的Python虚拟环境是个好习惯。

# 创建虚拟环境(以conda为例,也可使用venv) conda create -n trash-classification python=3.8 conda activate trash-classification # 安装PyTorch(请根据你的CUDA版本前往PyTorch官网获取最新安装命令) # 例如,对于CUDA 11.3: pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu113 # 安装其他依赖库 pip install pillow pandas matplotlib jupyter opencv-python pip install fastapi uvicorn python-multipart

验证安装是否成功:

# 文件:check_env.py import torch import torchvision print(f"PyTorch版本: {torch.__version__}") print(f"CUDA是否可用: {torch.cuda.is_available()}") if torch.cuda.is_available(): print(f"GPU设备: {torch.cuda.get_device_name(0)}")

运行python check_env.py,如果看到CUDA可用和你的GPU型号,说明环境配置正确。

4. 核心流程拆解:从数据到模型

4.1 数据收集与预处理

这是项目中最耗时但也最重要的一环。垃圾图片数据来源可以是:

  • 公开数据集:如“华为云垃圾数据集”、“TACO”、“TrashNet”等。这是最快捷的方式。
  • 自行拍摄与网络爬取:注意版权和隐私问题。
  • 数据生成:在确保合理性的前提下,可以使用3D渲染或GAN生成一些难以获取的垃圾图片(如特定类型的有害垃圾)。

假设我们找到了一个包含四类垃圾(cardboard,glass,metal,plastic)的数据集,目录结构如下:

dataset/ ├── train/ │ ├── cardboard/ │ │ ├── img001.jpg │ │ └── ... │ ├── glass/ │ ├── metal/ │ └── plastic/ └── val/ ├── cardboard/ ├── glass/ ├── metal/ └── plastic/

train/用于训练,val/用于验证。

我们需要用torchvision.datasets.ImageFolder来加载这种结构的数据。它会自动根据子文件夹名分配标签。

4.2 模型选择与修改

在PyTorch的torchvision.models中提供了许多预训练模型。对于移动端或资源受限场景,MobileNetV3EfficientNet-B0是轻量高效的选择。对于追求更高准确率的服务器端,ResNet50EfficientNet-B4是经典选择。

我们以ResNet18为例,它在准确率和速度之间取得了很好的平衡。

# 文件:model_setup.py import torch import torch.nn as nn from torchvision import models def get_model(num_classes=4, pretrained=True): """ 加载预训练的ResNet18,并修改最后的全连接层以适应我们的分类数。 Args: num_classes: 我们的垃圾类别数量,例如4。 pretrained: 是否加载在ImageNet上预训练的权重。 Returns: 修改后的模型。 """ # 加载预训练模型 model = models.resnet18(pretrained=pretrained) # 冻结所有卷积层的参数(可选,在数据量很少时建议先冻结训练几轮) # for param in model.parameters(): # param.requires_grad = False # 获取原始全连接层的输入特征数 num_ftrs = model.fc.in_features # 替换全连接层,输出维度为我们的类别数 model.fc = nn.Linear(num_ftrs, num_classes) return model if __name__ == '__main__': model = get_model(num_classes=4) print(model) # 打印模型参数量 total_params = sum(p.numel() for p in model.parameters()) print(f"模型总参数量: {total_params:,}") trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad) print(f"可训练参数量: {trainable_params:,}")

关键点model.fc = nn.Linear(num_ftrs, num_classes)这一行是迁移学习的精髓。我们只重新训练这一个新添加的层(以及之前被解冻的层),大大减少了训练时间和所需数据量。

4.3 训练流程构建

训练一个深度学习模型包含几个核心循环:数据加载、前向传播、计算损失、反向传播、更新参数。同时,我们需要在验证集上监控模型表现,防止过拟合。

# 文件:train.py import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms, models from torch.utils.data import DataLoader import os import time # 1. 定义数据变换(数据增强) data_transforms = { 'train': transforms.Compose([ transforms.RandomResizedCrop(224), # 随机裁剪并缩放 transforms.RandomHorizontalFlip(), # 随机水平翻转 transforms.ToTensor(), # 转为Tensor,并归一化到[0,1] transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # ImageNet均值标准差 ]), 'val': transforms.Compose([ transforms.Resize(256), # 验证集不增强,只做缩放和中心裁剪 transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), } # 2. 加载数据集 data_dir = './dataset' image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ['train', 'val']} dataloaders = {x: DataLoader(image_datasets[x], batch_size=32, shuffle=True if x == 'train' else False, num_workers=4) for x in ['train', 'val']} dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']} class_names = image_datasets['train'].classes print(f"类别: {class_names}") print(f"训练集大小: {dataset_sizes['train']}, 验证集大小: {dataset_sizes['val']}") # 3. 初始化模型、损失函数和优化器 device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") model = models.resnet18(pretrained=True) num_ftrs = model.fc.in_features model.fc = nn.Linear(num_ftrs, len(class_names)) model = model.to(device) criterion = nn.CrossEntropyLoss() # 交叉熵损失,适用于多分类 # 只训练最后一层参数,学习率可以设大一点 optimizer = optim.SGD(model.fc.parameters(), lr=0.001, momentum=0.9) # 如果解冻了所有层,可以优化所有参数:optim.SGD(model.parameters(), lr=0.001, momentum=0.9) # 学习率调度器:每7个epoch将学习率乘以0.1 scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1) # 4. 训练与验证循环 num_epochs = 25 best_acc = 0.0 for epoch in range(num_epochs): print(f'Epoch {epoch}/{num_epochs - 1}') print('-' * 10) # 每个epoch都有训练和验证阶段 for phase in ['train', 'val']: if phase == 'train': model.train() # 设置模型为训练模式(启用Dropout, BatchNorm更新) else: model.eval() # 设置模型为评估模式(禁用Dropout, BatchNorm使用运行统计量) running_loss = 0.0 running_corrects = 0 # 遍历数据 for inputs, labels in dataloaders[phase]: inputs = inputs.to(device) labels = labels.to(device) # 梯度清零 optimizer.zero_grad() # 前向传播 # 只在训练阶段追踪历史计算图 with torch.set_grad_enabled(phase == 'train'): outputs = model(inputs) _, preds = torch.max(outputs, 1) loss = criterion(outputs, labels) # 反向传播 + 优化(仅在训练阶段) if phase == 'train': loss.backward() optimizer.step() # 统计 running_loss += loss.item() * inputs.size(0) running_corrects += torch.sum(preds == labels.data) if phase == 'train': scheduler.step() # 更新学习率 epoch_loss = running_loss / dataset_sizes[phase] epoch_acc = running_corrects.double() / dataset_sizes[phase] print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}') # 深度复制模型(保存最佳模型) if phase == 'val' and epoch_acc > best_acc: best_acc = epoch_acc torch.save(model.state_dict(), 'best_model.pth') print() print(f'训练完成,最佳验证准确率: {best_acc:.4f}')

这段代码是训练的核心。它包含了标准的数据加载、模型训练、验证和模型保存流程。注意其中的model.train()model.eval()的切换,这对DropoutBatchNorm层的行为至关重要。

5. 完整示例:从训练到推理API

我们将把上面的代码模块化,并增加一个使用训练好的模型进行单张图片预测的函数,最后用FastAPI将其包装成Web服务。

5.1 项目结构

建议按如下方式组织代码,使其更清晰、易维护:

trash_classification/ ├── dataset/ # 数据集目录(按前述结构存放) ├── src/ │ ├── data_loader.py # 数据加载与预处理 │ ├── model.py # 模型定义 │ ├── train.py # 训练脚本 │ └── predict.py # 单图预测函数 ├── train.py # 主训练脚本(调用src中的模块) ├── api.py # FastAPI服务入口 ├── requirements.txt # 项目依赖 └── best_model.pth # 训练好的模型权重(训练后生成)

5.2 推理脚本

训练完成后,我们需要一个脚本来使用模型。

# 文件:src/predict.py import torch from torchvision import transforms from PIL import Image from .model import get_model # 假设model.py中定义了get_model函数 import json class TrashClassifier: def __init__(self, model_path='../best_model.pth', class_names=None): self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 类别名称,需要与训练时一致 self.class_names = class_names or ['cardboard', 'glass', 'metal', 'plastic'] self.num_classes = len(self.class_names) # 加载模型结构 self.model = get_model(num_classes=self.num_classes, pretrained=False) # 加载训练好的权重 self.model.load_state_dict(torch.load(model_path, map_location=self.device)) self.model = self.model.to(self.device) self.model.eval() # 设置为评估模式 # 定义与验证集相同的数据变换 self.transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) def predict_image(self, image_path): """预测单张图片""" # 加载图片 img = Image.open(image_path).convert('RGB') # 预处理 img_tensor = self.transform(img).unsqueeze(0) # 增加batch维度 img_tensor = img_tensor.to(self.device) # 预测 with torch.no_grad(): # 不计算梯度,节省内存和计算 outputs = self.model(img_tensor) _, predicted = torch.max(outputs, 1) # 获取概率(可选) probabilities = torch.nn.functional.softmax(outputs, dim=1) class_idx = predicted.item() class_name = self.class_names[class_idx] confidence = probabilities[0][class_idx].item() return { 'class_index': class_idx, 'class_name': class_name, 'confidence': round(confidence, 4) } def predict_image_from_bytes(self, image_bytes): """从字节流预测图片,适用于API""" img = Image.open(io.BytesIO(image_bytes)).convert('RGB') img_tensor = self.transform(img).unsqueeze(0) img_tensor = img_tensor.to(self.device) with torch.no_grad(): outputs = self.model(img_tensor) probabilities = torch.nn.functional.softmax(outputs, dim=1) probs_list = probabilities.cpu().numpy()[0].tolist() result = { 'predictions': [ {'class_name': self.class_names[i], 'confidence': round(probs_list[i], 4)} for i in range(self.num_classes) ], 'top_prediction': { 'class_name': self.class_names[probs_list.index(max(probs_list))], 'confidence': round(max(probs_list), 4) } } return result if __name__ == '__main__': # 本地测试 classifier = TrashClassifier(model_path='best_model.pth') result = classifier.predict_image('./test_image.jpg') print(json.dumps(result, indent=2))

5.3 封装为REST API

现在,我们将这个分类器变成一个Web服务。

# 文件:api.py from fastapi import FastAPI, File, UploadFile from fastapi.responses import JSONResponse import uvicorn import io from src.predict import TrashClassifier # 导入我们写的分类器 import logging # 配置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = FastAPI(title="垃圾自动分类API", description="基于ResNet18的垃圾图像分类服务") # 在应用启动时加载模型,避免每次请求都加载 classifier = None @app.on_event("startup") async def startup_event(): global classifier logger.info("正在加载垃圾分类模型...") # 请确保模型路径正确 classifier = TrashClassifier(model_path='./best_model.pth') logger.info("模型加载完成!") @app.get("/") async def root(): return {"message": "欢迎使用垃圾自动分类API", "usage": "请使用POST方法访问 /predict/ 并上传图片文件"} @app.post("/predict/") async def predict(file: UploadFile = File(...)): """ 接收一张图片文件,返回分类结果。 """ if classifier is None: return JSONResponse(status_code=503, content={"error": "模型未就绪"}) if not file.content_type.startswith('image/'): return JSONResponse(status_code=400, content={"error": "请上传图片文件"}) try: # 读取上传的文件内容 contents = await file.read() # 使用分类器进行预测 result = classifier.predict_image_from_bytes(contents) logger.info(f"预测结果: {result['top_prediction']}") return result except Exception as e: logger.error(f"预测出错: {e}") return JSONResponse(status_code=500, content={"error": "内部服务器错误", "detail": str(e)}) if __name__ == "__main__": # 运行服务:uvicorn api:app --host 0.0.0.0 --port 8000 --reload uvicorn.run(app, host="0.0.0.0", port=8000)

6. 运行结果与效果验证

6.1 训练过程验证

运行python train.py后,你将在控制台看到类似以下的输出,这表明训练正在正常进行:

类别: ['cardboard', 'glass', 'metal', 'plastic'] 训练集大小: 1600, 验证集大小: 400 Epoch 0/24 ---------- train Loss: 1.0123 Acc: 0.6212 val Loss: 0.6541 Acc: 0.7750 Epoch 1/24 ---------- train Loss: 0.7234 Acc: 0.7431 val Loss: 0.5123 Acc: 0.8325 ... Epoch 24/24 ---------- train Loss: 0.1012 Acc: 0.9688 val Loss: 0.2101 Acc: 0.9275 训练完成,最佳验证准确率: 0.9300

关键指标解读

  • Loss(损失):衡量模型预测与真实标签的差距,训练过程中应总体呈下降趋势。
  • Acc(准确率):预测正确的样本比例。验证集准确率(val Acc)是衡量模型泛化能力的核心指标。最终达到0.93(93%)是一个不错的结果。
  • 过拟合观察:如果train Acc持续远高于val Acc(例如训练集99%,验证集70%),说明模型过拟合了,需要加强数据增强、使用Dropout或收集更多数据。

6.2 单图预测验证

训练完成后,运行src/predict.py的测试部分,或直接调用TrashClassifier

python -c " from src.predict import TrashClassifier clf = TrashClassifier('best_model.pth') print(clf.predict_image('你的测试图片.jpg')) "

预期输出是一个包含类别、置信度的字典,例如:

{ "class_index": 2, "class_name": "metal", "confidence": 0.9876 }

高置信度(如 >0.9)表明模型对该预测很有把握。

6.3 API服务验证

  1. 启动API服务:在项目根目录下执行uvicorn api:app --host 0.0.0.0 --port 8000 --reload
  2. 使用工具测试API:
    • 命令行(curl):
      curl -X POST "http://127.0.0.1:8000/predict/" -F "file=@test_image.jpg"
    • Python requests:
      import requests resp = requests.post("http://127.0.0.1:8000/predict/", files={"file": open("test_image.jpg", "rb")}) print(resp.json())
    • 浏览器访问Swagger UI:打开http://127.0.0.1:8000/docs,这是一个自动生成的交互式API文档,你可以直接在那里上传图片进行测试。

成功的响应将返回一个JSON对象,包含所有类别的置信度以及最可能的预测结果。

7. 常见问题与排查思路

在实践过程中,你几乎一定会遇到下面这些问题。这里提供系统的排查思路。

问题现象可能原因排查方式解决方案
训练Loss为NaN或变得巨大1. 学习率(lr)设置过高。
2. 数据未归一化或归一化参数错误。
3. 数据中存在损坏的图片文件。
1. 检查优化器的学习率参数。
2. 检查transforms.Normalize使用的均值和标准差是否与预训练模型匹配(通常用ImageNet的)。
3. 在数据加载循环中加入异常捕获,打印出问题的文件路径。
1. 将学习率调低(如从0.01调到0.001)。
2. 确保使用正确的归一化参数。
3. 清理或修复损坏的图片。
验证准确率始终很低(如50%),且不提升1. 数据标签错误或混乱。
2. 模型最后一层(fc)的输出维度num_classes设置错误。
3. 训练集和验证集数据分布差异极大。
4. 优化器在优化错误的参数(如冻结了所有层但只训练了fc层,但fc层定义有误)。
1. 随机抽样一些训练图片,可视化并检查其标签。
2. 打印model.fc确认输出维度。
3. 分别统计训练集和验证集的类别分布。
4. 打印model.parameters()requires_grad为True的参数,确认它们在训练。
1. 重新检查并修正数据标注。
2. 将num_classes设置为实际类别数。
3. 确保数据划分是随机、均匀的。
4. 检查模型修改代码,确保可训练层连接正确。
GPU内存溢出(CUDA out of memory)1. 批次大小(batch_size)设置过大。
2. 模型过大。
3. 图片分辨率过高。
1. 尝试减小batch_size(如从32减到16)。
2. 使用torch.cuda.empty_cache()清理缓存。
3. 使用nvidia-smi命令监控GPU内存使用。
1. 减小batch_size是最直接有效的方法。
2. 换用更轻量的模型(如MobileNetV3)。
3. 降低输入图片的尺寸(如从224x224降到128x128)。
API服务预测速度慢1. 模型在CPU上运行。
2. 每次预测都重新加载模型。
3. 图片预处理耗时。
1. 检查API启动日志,确认模型加载到了GPU。
2. 确保模型在服务启动时只加载一次(如示例中的startup_event)。
3. 对预测函数进行性能分析。
1. 确保服务器有GPU且PyTorch能识别到。
2. 采用单例模式或应用生命周期管理来加载模型。
3. 考虑使用更快的图片解码库(如turbojpeg),或对预处理进行优化。
预测结果全部为同一类别1. 模型训练不充分,陷入局部最优。
2. 类别极度不平衡,某个类别的样本数占绝对优势。
3. 数据泄露,验证集和训练集有大量重复。
1. 查看训练过程中的Loss和Acc曲线,是否很早就不变了。
2. 计算每个类别的样本数量。
3. 检查数据集划分的代码,确保没有重复。
1. 增加训练轮数(epochs),尝试不同的学习率。
2. 对样本少的类别进行过采样,或使用加权的损失函数(nn.CrossEntropyLoss(weight=class_weights))。
3. 重新划分数据集,确保独立。

8. 最佳实践与工程建议

将原型推进到可用的工程系统,需要注意以下关键点:

1. 数据是王道

  • 质量高于数量:1000张标注准确的图片,远胜于10000张标注混乱的图片。在项目初期,花时间清洗和校验数据,回报率最高。
  • 代表性:你的训练数据必须覆盖实际应用场景中可能遇到的各种情况。例如,垃圾图片可能在不同光照、角度、背景、新旧程度下拍摄。数据增强可以模拟一部分,但源头数据的多样性更重要。
  • 划分严谨:务必严格区分训练集、验证集和测试集。测试集应在整个模型开发完成后才使用一次,以评估最终性能,避免“偷看”测试集导致过拟合。

2. 模型选择与优化

  • 从轻量模型开始:不要一上来就用ResNet152。先从MobileNetV2/V3、EfficientNet-B0等轻量模型开始。它们速度快,参数量少,在数据量不大时更容易训练,且便于后续部署到边缘设备。
  • 渐进式解冻:在迁移学习中,一种高级技巧是先冻结所有层,只训练最后的分类层。训练几轮后,逐步解冻更靠近输出的卷积层进行微调。这有助于稳定训练过程。
  • 使用早停(Early Stopping):监控验证集损失,当其在连续多个epoch不再下降时,就停止训练,避免过拟合。可以手动实现,或使用PyTorch的torch.early_stopping回调(需额外安装)。

3. 工程化部署

  • 模型导出:训练完成后,考虑将模型导出为TorchScript(model.script()) 或ONNX格式。这能脱离Python环境运行,便于在C++、Java等环境中部署,并且通常有更好的推理优化。
  • API设计:除了返回最可能的类别,像示例中那样返回所有类别的置信度会更有用。前端可以据此展示一个概率条形图,提升用户体验和可信度。
  • 日志与监控:在生产API中,记录每一次预测的请求、响应时间、结果和置信度。这有助于后续分析模型在真实场景中的表现,发现bad case(例如,哪些图片总是分错)。
  • 异常处理:API必须健壮。要处理各种异常输入:非图片文件、超大文件、空文件、网络超时等,并返回友好的错误信息。

4. 持续迭代

  • 分析错误:定期查看模型预测错误的样本。这些样本是改进模型最宝贵的资料。是某一类特定物体总是分错?还是背景干扰太大?根据分析结果,有针对性地补充训练数据或调整数据增强策略。
  • 考虑更复杂的任务:如果简单的单标签分类效果遇到瓶颈,可以考虑:
    • 目标检测:如果图片中可能包含多个垃圾物体,使用YOLO、Faster R-CNN等检测模型,先定位再分类。
    • 多标签分类:一个物品可能同时属于多个类别(如“纸盒”既是“可回收”也是“纸类”)。
  • 探索新模型:关注学术界和工业界的新进展,如Vision Transformer (ViT) 系列模型,在某些任务上可能比传统CNN有优势。

通过遵循以上流程和建议,你不仅能够完成一个“垃圾自动分类”的项目,更能掌握一套解决实际计算机视觉问题的标准方法论。这套方法同样适用于零件缺陷检测、农作物病害识别、商品自动盘点等众多领域。技术的价值在于解决真实世界的问题,现在,你已经拥有了开始探索的工具和地图。