ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于深度学习的垃圾分类模型实战:从数据采集到部署全流程

基于深度学习的垃圾分类模型实战:从数据采集到部署全流程 简介一份面向深度学习与图像识别方向的本科毕业设计论文完整呈现生活垃圾分类识别算法的研究过程与实验结论适合计算机、电子信息类专业学生用于课题参考或论文写作模板。论文以带标记的垃圾图像为原始数据利用降维生成128×128尺寸的新图像兼顾低算力硬件下的训练需求与质量经神经网络调参和多轮实验分类准确率达93%以上。资源包仅1个docx文件大小631KB包含中英文摘要、目录、正文及附录代码正文涵盖引言、课题背景、国内外研究现状、基于机器学习的图像识别等章节。下载后可在附录直接查看代码便于对照算法流程理解。已有82人学习下载可作为垃圾分类图像识别从数据预处理到模型调优的完整案例。1. 基于深度学习网络的生活垃圾分类为什么通用模型在垃圾桶前翻车只要开始做基于深度学习网络的生活垃圾分类项目几乎所有人都会下意识拿现成的卷积神经网络在公开数据集上跑一遍。在 TrashNet 这类千张级基准上ResNet50 跑到 90% 以上的 Top-1 准确率并不难于是很多人直接把模型搬到小区垃圾分类房结果摄像头一开就露馅外卖盒被识别成纸碗玻璃瓶和陶瓷来回跳动傍晚光照一变化准确率掉得比换模型还快。这种翻车不是某一个网络结构的问题而是垃圾分类任务对类目粒度、拍摄角度和背景干扰的敏感程度远超普通 ImageNet 分类。真正能推到落地阶段的算法要把注意力从“换个更强网络”挪开分散到数据体系、迁移学习参数、训练评估指标和部署后处理这几段。我按自己实际走过的路线来拆类目怎么定、骨干网络怎么选、训练流程怎么搭、哪些坑连老手都会踩最后落到部署时的后处理。适合有 Python 基础、想一周内看到可演示原型的工程师代码和参数尽量给到能直接抄的程度。2. 垃圾分类数据集与标签体系类目粒度决定算法上限2.1 分几类才合理四分类直接做还是细粒度再映射垃圾分类落到场景最常提的是四分类可回收物、有害垃圾、厨余垃圾、其他垃圾。但千万别直接拿这四个标签当模型输出。模型面对的是一张图神经网络学的是类内一致性。“可回收物”里面装着塑料瓶、玻璃瓶、纸箱、金属罐、旧衣物这些子类颜色、纹理、形状差异太大放同一个类里网络找不到稳定的公共视觉特征训练过程容易原地打转最后被其中一个占多数的子类带走。我一般建议用两层标签体系第一层是细粒度标签比如 plastic_bottle、glass_bottle、carton_box、metal_can、battery、food_waste、dirty_tissue第二层是业务层推断后再把细粒度结果映射到四分类。细粒度训练有两个好处一是标注一致性更容易保证“外卖盒”标成 dirty_paper 还是 plastic 是清晰的标成四分类里的“其他”反而模糊二是以后业务调整比如街道改要求把纸盒单独分出来只需要改映射和后处理不用重训模型。业务类别细粒度标签模型可学的视觉特征可回收物plastic_bottle瓶身弧度、透光、表面高光可回收物glass_bottle厚重高光、瓶口螺纹、通体透明可回收物carton_box瓦楞纹理、黄褐色可回收物metal_can金属漫反射、侧壁压痕有害垃圾battery圆柱外形、金属正负极厨余垃圾food_waste湿润表面、颜色杂乱其他垃圾dirty_tissue褶皱、柔软、无固定形态# label_map.py # 细粒度类目到业务类目的映射 core_to_major { plastic_bottle: 0, # 0: 可回收物 glass_bottle: 0, carton_box: 0, metal_can: 0, battery: 1, # 1: 有害垃圾 food_waste: 2, # 2: 厨余垃圾 dirty_tissue: 3, # 3: 其他垃圾 }这段映射的逻辑很简单模型输出层预测细粒度类目业务层再把 7 个输出折叠成 4 类。这样做还有个隐性好处——数据不平衡时可以单独针对某个细粒度类目补拍不用重做全量标注。标注规范里还要写一条硬规则所有被食物污染的纸类、木质餐具一律归入 dirty_tissue 或单独标 food_related_waste不要标成干净的 paper 或 carton。道理很直接算法只能看到颜色和纹理看不到“边界是被油浸过还是被咖啡染过”如果标注里干净纸和脏纸混在一个标签里模型再怎么调参也没救。2.2 自建数据集现场采集、清洗去重与按场景划分公开数据集只能帮你跑通流程真正要形成可维护的算法必须自建现场数据。采集时不要只坐在工位上拍手机图垃圾房的灯光是顶灯还是侧灯摄像头装在桶上方还是侧面都会改变图像分布。我一般用市售采集支架固定三个拍摄高度——俯视 45 度、水平视角、顶部俯视每个物体至少拍 3 到 5 张覆盖不同角度和不同光照时段。文件名里带上场景 ID 和采集批次例如scene_07_batch_20240512_023.jpg后面做数据集划分会省很多麻烦。采集完第一件事不是标注而是清洗。手机拍出来的照片里总有大量模糊、重复同一物体连续拍了十来张和损坏的图直接进标注流程会白白消耗人力。我在训练前会跑一遍去模糊和去重脚本用全图标准差筛掉低信息量的模糊图用 MD5 去重完全相同的文件。# clean_duplicates.py import os, hashlib from PIL import Image, ImageStat def clean_duplicates(root_dir, stddev_threshold8.0): seen_hashes set() for dirpath, _, filenames in os.walk(root_dir): for name in filenames: if not name.lower().endswith((.jpg, .jpeg, .png)): continue path os.path.join(dirpath, name) try: img Image.open(path).convert(RGB) stat ImageStat.Stat(img) stddev sum(stat.stddev) / 3 if stddev stddev_threshold: # 方差太小图像基本是纯色或严重模糊 os.remove(path) continue h hashlib.md5(img.tobytes()).hexdigest() if h in seen_hashes: os.remove(path) # 完全重复保留第一份 else: seen_hashes.add(h) except Exception: os.remove(path)这里的stddev_threshold不是玄学它是一个经验值纯色背景垃圾房的图片方差通常在 5 到 7正常带物体和纹理的图片方差在 20 以上。先拿 50 张手工确认一次阈值再批量处理。去重之后划分数据集必须按场景文件夹而不是按文件随机 split。我在这上面吃过亏同一只塑料瓶的三个角度照片一张进了训练集一张进了验证集验证准确率跑到 0.97上线立刻跌到 0.6。原因就是验证集里出现了训练集物体的“近似分身”高指标是记忆出来的不是泛化出来的。# build_split.py import os, random random.seed(42) root data/raw scene_dirs [d for d in os.listdir(root) if os.path.isdir(os.path.join(root, d))] random.shuffle(scene_dirs) val_num max(1, int(len(scene_dirs) * 0.2)) val_scenes set(scene_dirs[:val_num]) with open(train.txt, w) as ft, open(val.txt, w) as fv: for scene in scene_dirs: out (fv if scene in val_scenes else ft) for f in os.listdir(os.path.join(root, scene)): out.write(f{scene}/{f}\n)划分之后在训练文件里留一行注释同一场景所有图片属于同一集合。后续难例回灌时也要沿用这个原则否则数据泄漏问题会反复出现。2.3 数据增强塑料反光、油渍和遮挡最需要什么垃圾图像和普通商品图最大的不同是状态脏乱差塑料瓶有反光易拉罐有凹陷厨余垃圾表面带油渍垃圾桶边缘还会遮挡物体。数据增强要做的是模拟这些扰动而不是把图像变成艺术风格。我常用的增强组合如下。# augmentations.py import albumentations as A from albumentations.pytorch import ToTensorV2 train_transform A.Compose([ A.RandomRotate90(p0.5), A.HorizontalFlip(p0.5), A.ColorJitter(brightness0.2, contrast0.2, saturation0.2, hue0.05, p0.7), A.RandomResizedCrop(size(224, 224), scale(0.7, 1.0), p0.8), A.GaussNoise(var_limit(10, 50), p0.3), A.CoarseDropout(max_holes4, max_height24, max_width24, p0.3), ToTensorV2(), ])逐个说参数RandomRotate90模拟瓶子横着竖着出现ColorJitter控制亮度和对比度模拟早上和傍晚的色温差异RandomResizedCrop的scale(0.7, 1.0)不会切太狠避免把瓶子切成一截看不出整体形状GaussNoise模拟手机摄像头低照度噪点CoarseDropout随机抹掉几个小方块模拟树枝、桶盖遮挡。验证集不要加任何增强只做 Resize 和 Normalize否则验证损失不再反映真实分布。注意增强力度不是越大越好。我见过把GaussNoise拉到 80 导致模型把噪点当成纹理特征的。每次调增强只改一个参数用验证集召回率说话不要凭感觉堆叠。增强后的图像仍然要保持“垃圾的材质感”。玻璃瓶的高光区域对分类很重要如果增强把高光冲掉模型就学不到玻璃和塑料的区别。遇到这种情况我会专门加一张带人造光源的采集批次而不是靠颜色抖动硬补。3. 骨干网络怎么选ResNet50、MobileNetV3 与预训练权重的取舍3.1 残差结构为什么能撑住垃圾识别的底层特征ResNet50 当基线不是因为它新而是它的残差结构很适合垃圾分类这种“材质敏感”任务。垃圾分类的视觉难点通常不在形状而在材质玻璃和塑料都是透明或半透明金属和陶瓷都有高光纸张靠纹理分辨。卷积神经网络的前几层提取边缘、颜色、纹理基元后几层组合成物体语义。ResNet 的 shortcut 把浅层信息直接加到深层等于让分类头在做最终判断时还能同时看到“玻璃瓶的高光”和“玻璃瓶的轮廓”这两层证据不会因为网络加深就把底层纹理信息冲淡。从训练稳定性看50 层 CNN 如果没有残差连接反向传播时梯度容易在深层消失这是 2015 年之前很深网络很难训起来的主要原因。ResNet 的恒等映射让梯度反向传播多了一条直通路径训练时收敛速度更快。垃圾分类数据集普遍只有几千到几万张比 ImageNet 小两三个数量级训练不稳定的问题会被放大所以选带残差结构的骨干对起步阶段更友好。3.2 三种骨干的横向对比部署设备决定选型骨干网络不能只看精度排行榜先看推理时跑在什么设备上。智能垃圾分类桶一般有两种部署形态一是 n 摄像头接一块开发板模型本地跑二是摄像头采集后传到服务端推理。第一种形态对 CPU 延迟和内存敏感第二种可以任性一点。骨干网络参数量约CPU 推理相对耗时适合设备我这边实际取舍ResNet5025.6M高服务端、NVIDIA Jetson精度上限高对功耗不敏感MobileNetV3-Large5.4M低树莓派 4、边缘盒子精度损失可控硬件容忍度低就选它ShuffleNetV22.3M更低单片机、低端 SoC延迟最卷但需要优质数据兜底ResNet50 在服务端推理没问题但如果现场是树莓派 4 连续跑视频流它会吃满 CPU 且延迟超过几百毫秒。MobileNetV3-Large 的参数量只有 ResNet50 的五分之一精度在垃圾分类这类中等难度的任务上通常只掉两三个点换来的是实时视频流能跑得动。ShuffleNetV2 更适合几十块钱的低端 RV1126 之类方案但它的容量有限玻璃瓶和陶瓷这类细类目误判率会明显上升数据质量必须补得更勤。我一般建议先拿 ResNet50 把标注数据和训练流程跑通确认精度和失败模式再换 MobileNetV3-Large 压到边缘设备上。反过来做很容易把大量时间耗在调参上因为轻量网络对数据噪声更敏感很难判断是网络不行还是数据不行。3.3 迁移学习冻结层数、学习率与权重初始化垃圾分类没有 ImageNet 那么大的公开预训练库所以几乎都从 ImageNet 预训练权重开始。预训练卷积核已经掌握了边缘、纹理、颜色分布这些通用低层特征我们要改的只是最后的分类头以及让深层特征适配“瓶子、纸箱、厨余”这些本领域语义。# model_factory.py import torch.nn as nn import torchvision.models as models def build_model(num_classes, backboneresnet50, pretrainedTrue): if backbone resnet50: model models.resnet50( weightsmodels.ResNet50_Weights.IMAGENET1K_V1 if pretrained else None ) in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) elif backbone mobilenet_v3_large: model models.mobilenet_v3_large( weightsmodels.MobileNet_V3_Large_Weights.IMAGENET1K_V1 if pretrained else None ) in_features model.classifier[-1].in_features model.classifier[-1] nn.Linear(in_features, num_classes) else: raise ValueError(funsupported backbone: {backbone}) return model替换分类头的关键是要先读in_features再实例化新的Linear。pretrainedTrue在旧版 PyTorch 可以写新版 torchvision 会提示改用weights参数上面写法两种都兼容。PyTorch 2.0 以后models.resnet50(pretrainedTrue)会出现弃用告警但不报错升级代码时顺手换成 weights 写法就行。替换完新头下一步是冻结策略。我第一轮训练通常只让新分类头学骨干参数全冻结等 val 损失不再下降再解冻骨干最后两个 stage。def freeze_up_to(model, backbone_name, unlock_headTrue, unlock_last_stageFalse): for name, param in model.named_parameters(): param.requires_grad False if unlock_head: if backbone_name resnet50: for name, param in model.named_parameters(): if name.startswith(fc): param.requires_grad True else: for name, param in model.named_parameters(): if name.startswith(classifier): param.requires_grad True if unlock_last_stage: for name, param in model.named_parameters(): if backbone_name resnet50 and name.startswith(layer4.): param.requires_grad True if backbone_name mobilenet_v3_large and name.startswith(features.12.): param.requires_grad True return modellayer4对应 ResNet 的最后一个残差 stagefeatures.12对应 MobileNetV3-Large 的最后几个卷积层。这两个位置离分类头最近解冻它们能让模型学本领域的语义组合而前面的 stage 保持 ImageNet 初始化起正则化作用。优化器要为“新头”和“解冻层”配不同学习率这是迁移学习最容易出效果的参数设计。# optimizer_groups.py import torch base_params, head_params [], [] for name, param in model.named_parameters(): if not param.requires_grad: continue if fc in name or classifier in name: head_params.append(param) else: base_params.append(param) optimizer torch.optim.AdamW([ {params: base_params, lr: 1e-4}, {params: head_params, lr: 1e-3}, ], weight_decay1e-4)新头从随机初始化开始学习率给 1e-3预训练骨干权重已经足够好只能用小步慢走给 1e-4。两个数量级就这样分开避免新头学太慢、骨干又被大梯度扰动。weight_decay1e-4对几万张规模的数据集是稳妥的起点数据量更小时可以提到 1e-3。4. 用 PyTorch 跑通垃圾分类训练数据加载、训练循环与评估指标4.1 数据加载与划分同场景同集合的 Dataset 封装第 2 章已经把数据划分成train.txt和val.txt现在需要一个 Dataset 类把它们读进来。用文本文件记录样本比直接用文件夹目录更灵活难例回灌时只要往文件里追加一行。# dataset.py import torch from torch.utils.data import Dataset from PIL import Image class GarbageDataset(Dataset): def __init__(self, txt_path, label_to_id, transformNone): self.samples [] self.label_to_id label_to_id self.transform transform for line in open(txt_path): line line.strip() if not line: continue rel_path, label line.split() self.samples.append((rel_path, self.label_to_id[label])) def __len__(self): return len(self.samples) def __getitem__(self, idx): rel_path, label self.samples[idx] img Image.open(rel_path).convert(RGB) if self.transform: img self.transform(imageimg)[image] return img, labellabel_to_id是第 2 章里细粒度标签到整数编号的映射写在训练配置里不要散落各脚本。DataLoader 设置num_workers4, pin_memoryTrueWindows 下num_workers设 2 或 0避免子进程报错Linux 上 4 到 8 都可以。4.2 训练循环混合精度、梯度缩放与学习率调度训练循环建议一次性写对混合精度否则后面加数据、调参数都要回来改。# train.py import torch from torch.cuda.amp import GradScaler, autocast def run_epoch(model, loader, criterion, optimizer, scheduler, scaler, device, trainTrue): total_loss, correct, total 0.0, 0, 0 model.train() if train else model.eval() torch.set_grad_enabled(train) for images, labels in loader: images, labels images.to(device), labels.to(device) if train: optimizer.zero_grad() with autocast(): outputs model(images) loss criterion(outputs, labels) if train: scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() total_loss loss.item() * images.size(0) preds outputs.argmax(1) correct (preds labels).sum().item() total labels.size(0) return total_loss / total, correct / totalautocast让前向和反向在 fp16 下进行显存省一半速度也能上来。GradScaler 解决 fp16 梯度过小可能下溢的问题它是配套出现的单用autocast不管 GradScaler训练早期容易出现 loss 不下降。PyTorch 2.x 中也可写torch.amp.autocast(cuda)效果一致。学习率调度器我习惯用余弦退火按 epoch 数设T_max。如果训练轮数不超过 30CosineAnnealingLR(optimizer, T_max30)足够。不要在训练循环里随机调整学习率先固定整体策略再看 val 损失决定要不要重启一个更长 schedule。4.3 评估指标只看准确率会把玻璃瓶全部漏掉模型训完先别急着上线先看评估报告。垃圾分类场景里Top-1 准确率会骗人如果“其他垃圾”占样本一半模型全部猜“其他”都能到 50% 准确率而玻璃瓶这类少数类完全没学到。# evaluate.py from sklearn.metrics import confusion_matrix, classification_report def evaluate_model(model, loader, device, class_names): model.eval() y_true, y_pred [], [] with torch.no_grad(): for images, labels in loader: images images.to(device) outputs model(images) y_true.extend(labels.cpu().numpy()) y_pred.extend(outputs.argmax(1).cpu().numpy()) print(confusion_matrix(y_true, y_pred)) print(classification_report( y_true, y_pred, target_namesclass_names, digits3))classification_report里的 precision 表示“模型说它是玻璃瓶时有多少次说对了”recall 表示“所有真玻璃瓶里模型找回来多少”。垃圾分类桶实战中玻璃瓶漏放比误报更严重漏放意味着碎玻璃直接进入其他垃圾后续处理设备容易出问题。所以我评估时盯 recall 比盯总体准确率更严尤其是玻璃、电池这两个细粒度类目。注意混淆矩阵打印出来后先找对角线之外最大的数字。它指向的那一对类目就是你下一步要补数据或调后处理的位置不要凭印象猜。训练过程中保存权重也有讲究。不要等最后一个 epoch 才保存也不要每轮都保存覆盖。我一般用 val loss 最低的那一轮作为 best checkpoint后面无论调多久这个模型就是“后悔药”永远不删。5. 避坑指南垃圾分类模型训练中的五个翻车现场5.1 类别不平衡模型把所有垃圾都当成“其他”现象验证集整体准确率 88%看起来不错但展开每个类目的报告发现玻璃瓶精确率只有 15%召回率 20%模型几乎把所有玻璃瓶都猜成了“其他垃圾”。原因是玻璃瓶样本太少而“其他垃圾”在采集现场天然占比高Softmax 分类器在样本不均衡时会被先验频率带走。解决优先补拍是第一位实在补不齐就用采样器加权。WeightedRandomSampler让少数类每个 epoch 被抽到的概率更高。from torch.utils.data import WeightedRandomSampler def make_weighted_sampler(dataset): counts {} for _, label in dataset.samples: counts[label] counts.get(label, 0) 1 weights [1.0 / counts[label] for _, label in dataset.samples] return WeightedRandomSampler(weights, len(dataset.samples), replacementTrue)采样的代价是训练更慢而且少数类可能被重复记忆。使用加权采样时把训练 epoch 数减半或观察 val 损失是否比原来更快反弹两者配套调整。5.2 数据泄漏同一件垃圾出现训练和验证集现象离线评估 0.97现场实测 0.6差异大得像两个模型。原因是划分数据时直接随机打散文件名同一个物体不同角度的照片被拆成了两半验证集变成了开卷考试。原因本质是“近似重复”。手机连拍同一只瓶子变化很小模型看一遍训练集相当于见过验证集答案。解决就是我第 2 章说的按场景批次划分同一轮采集的照片去同一个集合。以后每次加新数据也要先判断它和旧数据是不是“同一物体多个角度”宁可把整批数据划到训练集也不要把同物体拆进验证集拉高虚假指标。5.3 模型学的是背景而不是垃圾现象识别准确率在固定背景的测试集上很好换到小区垃圾房不同位置的摄像头立刻掉十几个点。用 Grad-CAM 把注意力图打出来发现高亮区域全部集中在图像边缘或角落根本没对准杯子、瓶子这类主体。原因卷积神经网络抓“捷径”的能力比人强。数据里纸箱样本大量在库房纸堆前拍摄模型学到的是“背景里有瓦楞纸纹理就猜纸箱”不是“轮廓像盒子的物体大概率是纸箱”。解决分两步先给现有数据做背景变化比如把单一背景替换成 4 到 5 种真实场景再用 Grad-CAM 做一轮医疗确认模型重新把注意力放回物体中心。如果背景交叉采集做不到至少要把不同背景下的照片按场景拆开训练而不是全部混在一堆。5.4 过拟合和欠拟要不要先看训练损失现象train loss 一路下降val loss 先降后升这是过拟合train 和 val 都降不到期望值这是欠拟合。很多人遇到第一个情况就急着加增强结果越加越差因为过拟合可能是解冻层太多造成的。我排查的顺序是先固定 backbone 冻到只剩分类头跑到 val 损失瓶颈然后一次只解冻一层或一个 stage观察 val 变化。每次只改一个变量把freeze_stage、lr、weight_decay、epoch、val_loss记成一个表对比时看相邻两行差异。这个习惯能帮你省掉大量盲目调参时间。欠拟合时优先怀疑学习率太低或数据增强过猛把增强先全关掉跑到一个干净的基线再逐步加回来。5.5 训练时预处理和部署时不统一现象离线评估 0.9部署后只有 0.5而且不管怎么换模型都没好转。检查代码才发现训练时用 224x224 加 Normalize(0.485, 0.456, 0.406)部署端直接 OpenCV 读图 resize 成 416 就送进网络还因为 OpenCV 默认读 BGR和训练时的 RGB 完全反了。解决写一个独立的preprocess函数训练和部署共用。import numpy as np from PIL import Image MEAN [0.485, 0.456, 0.406] STD [0.229, 0.224, 0.225] def preprocess(path, size224): img Image.open(path).convert(RGB) img img.resize((size, size), Image.BILINEAR) arr np.array(img).astype(np.float32) / 255.0 arr (arr - MEAN) / STD return arr.transpose(2, 0, 1)[None] # (1, 3, 224, 224)用PIL.Image打开而不是cv2.imread就绕开了 BGR 通道顺序问题。导出模型后拿同一张图分别跑 PyTorch 和推理引擎输出差值应小于 1e-4否则优先检查预处理是否被推理端偷偷改过。这是部署环节最隐蔽也最容易翻车的点。6. 部署与迭代ONNX 导出、置信度阈值与难例回灌6.1 用 ONNX 把 PyTorch 模型导出现在要把训练好的模型送到现场设备上。常见做法是先把模型导出成 ONNX再用 ONNX Runtime 或各家推理框架加载。导出前记得把model.eval()和torch.no_grad()状态设置好不然某些算子会带上 Dropout 或 BatchNorm 的更新路径。import torch model build_model(num_classes7, backbonemobilenet_v3_large) model.load_state_dict(torch.load(best.pth, map_locationcpu)) model.eval() dummy torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy, garbage_model.onnx, input_names[image], output_names[logits], dynamic_axesNone, opset_version17, )dynamic_axesNone表示固定 batch 为 1动态 batch 会增加部署端开发和算子兼容性难度单摄像头场景没必要。如果部署芯片老遇到算子不支持把opset_version退到 12 或改用 torch 自带 exporter 的简化版本。6.2 置信度阈值与“拒绝类”智能分类桶不能硬着头皮分。我一般把输出层接一个置信度阈值低于阈值的直接转人工复核或让桶不动作等待重新投递。阈值不是越高越好我这里用的是 0.6当部署现场有灯光反光时调到 0.75 更稳。import numpy as np def decide(logits, threshold0.6): # 稳定 softmax 计算 shift logits - np.max(logits) probs np.exp(shift) probs / probs.sum() idx int(np.argmax(probs)) if probs[idx] threshold: return -1 # 低置信度请人工确认或退回重新投放 return idxthreshold的取值不能靠感觉要拿一段现场灰度数据跑一遍统计被正确分类样本的置信度分布选取覆盖 90% 以上的最低值。6.3 难例回灌与长尾优化把现场低置信度的图像保存下来每周请人复核标记一次然后回灌训练集。回灌时仍然遵循“按场景划分”原则新数据全部进训练集等下一轮重新评估时验证集要保证没有和它同场景的图像。我操作上就是让现场设备每天自动导出置信度在 0.4 到 0.7 之间、且被人工修正过的图片放进hard_examples/目录累积一到两周后统一增量微调一次。长尾类目从不靠一两次调参翻身靠的就是这种“失败样本进数据、再重训”的循环。这套流程走下来模型本身反而是项目里最不神秘的部分。我自己培养的第一个习惯是每次改动数据或网络后固定跑一遍完整评估把混淆矩阵和几张代表性误判图截图存进实验记录没有这个记录你会分不清到底是新数据起了作用还是学习率调度在捣乱。最初我拿到 94% 准确率时以为接近上线水平看到玻璃瓶召回率只有 0.3 才清醒过来。后来我把每个细粒度类目的召回率当成第一指标所有数据迭代都朝这个方向努力。垃圾分类不是靠一个大模型包办的任务而是数据、训练和部署后处理配合的结果希望这些经验能帮你把算法从演示推到可持续维护的状态。本文还有配套的精品资源点击获取
返回列表