ARTICLE DETAIL

资讯详情

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

ShuffleNet实战:菠萝8类成熟度分类与边缘端部署

ShuffleNet实战:菠萝8类成熟度分类与边缘端部署 简介针对经典轻量级CNN网络ShuffleNet的菠萝成熟度分类实战项目包含完整可运行的代码、已标注的8类菠萝成熟度数据集与训练好的权重文件适合希望快速上手图像分类或轻量级网络应用的开发者。压缩包共2000个文件其中1992张JPG图片构成训练集4808张与测试集806张的成熟度样本另有4个Python脚本负责训练与预测等流程readme及txt/json文件提供说明和类别配置整体大小201MB。模型参数量约一百万采用cos学习率衰减训练50个epoch测试集上达到87%准确率run_results目录中存有最优权重、训练日志及loss/精度曲线便于复盘与调优。运行predict.py即可自动完成inference目录下所有图片推理并将概率最高的三个类别标注在左上角如需训练自定义数据readme已说明自动适配类别数的方法。已有125人学习下载是入门CNN图像分类且需要完整项目参考的实用资源。1. 菠萝成熟度分类为什么选中 ShuffleNet 这条轻量级路线做过图像分类落地的工程师都清楚产线上的分类任务跟打竞赛是两码事。菠萝从开花到采收按颜色和硬度能拆出多个成熟阶段分拣端不可能每台设备都塞一块 A100常见的做法是用一块嵌入式板卡或工控机拖着工业相机跑实时推理。我最早拿 ResNet 试过一轮8 类成熟度确实能训到 95% 以上精度但模型体积 40 多 MB推理延迟在低成本设备上压不下去这就是典型的“性能达标部署翻车”。换成 ShuffleNet 这类轻量级 CNN 之后参数量只有 ResNet 的零头精度经过调参也能回到 90% 以上单张推理时间降到几十毫秒级别。这篇实战笔记就围绕“8 种不同阶段的菠萝成熟度分类”这个具体任务把 ShuffleNet 的选型理由、数据准备、模型搭建、训练调参与部署排错完整过一遍适合正在做农产品分拣或边缘端图像分类的工程师参考。2. 8类成熟度的定义与数据准备标注边界比网络结构更决定精度上限2.1 成熟度不是拍脑袋分的先定“可标注”的视觉标准菠萝成熟度分级在不同的产区有不同说法六分熟、八分熟这种词在分拣线上其实很难执行因为人工目测都有歧义。做分类项目时标注标准定得越主观模型的上限就越低这不是玄学是数据噪声决定的。常见做法是把可观察的视觉特征拆成几个维度果皮主色、果眼颜色、表面光泽、褐变面积、果肩饱满度。把这五个维度组合起来再映射到 8 个阶段从全绿、微黄、半黄、黄绿交错一直到全黄带褐斑。每一步都要有参考图最好拍一组标准样本放在标注界面里标注员每次判断前先看一眼边界图。这一步做完分类问题就从一个模糊的感官判断变成了一个相对稳定的视觉匹配任务。我在实际项目里发现只要标注指导书写得够细两个标注员的标注一致率能从 70% 提到 85% 以上。反之如果直接甩给标注员一句“按成熟度分 8 类”那后期清洗数据的时间会比训练时间还长。建议在项目启动前把分级标准做成一张表每类附 5 到 10 张典型样本图并且只允许标注员在这张表给出的参考框架内做判断。2.2 数据采集的三个硬性要求光照、遮挡与果串隔离菠萝是圆柱形果实表面有凹凸的果眼不同角度拍出来颜色差异很大。采集数据时最容易犯的错误是固定一个角度拍摄这样模型学到的是“这个角度的菠萝”而不是“这个成熟度的菠萝”。我一般要求采集时转动果实至少覆盖正面、侧面、顶部、底部四个视角同时把相机白平衡固定不要在自动白平衡模式下采集否则同一种颜色在不同时刻拍出来偏色很重。另外要注意的是果叶和果柄的遮挡问题。菠萝冠芽很大如果画面里冠芽占比过高模型很可能学到用冠芽颜色来判断成熟度而不是果皮。数据清洗时把冠芽遮蔽果皮超过三分之一的样本直接去掉或者单独裁切出果皮区域再训练。还有一个非常关键的点同一个果串上的菠萝不能同时出现在训练集和验证集里否则验证集会透题导致精度虚高。处理办法是按“采收批次”或“植株编号”做分组划分而不是按单张图随机划分。2.3 data_split 脚本按批次分组切分数据集下面是一个按目录分组的切分脚本假设数据目录结构是data/class_01/..到data/class_08/每个类别目录下有batch_01/、batch_02/等批次子目录。脚本会先按批次读取图片路径再把整个批次分进训练集或验证集保证同一批次的图片不会跨集合出现。import os import random import shutil random.seed(42) src_root data # 数据根目录下一层是类别 dst_root split_dataset # 划分后的输出目录 val_ratio 0.2 # 验证集比例按批次计算 for class_name in sorted(os.listdir(src_root)): class_path os.path.join(src_root, class_name) if not os.path.isdir(class_path): continue batch_list os.listdir(class_path) # 每个批次是一个子目录 random.shuffle(batch_list) # 打乱批次顺序 split_idx int(len(batch_list) * (1 - val_ratio)) train_batches batch_list[:split_idx] val_batches batch_list[split_idx:] for mode, batches in [(train, train_batches), (val, val_batches)]: out_class_dir os.path.join(dst_root, mode, class_name) os.makedirs(out_class_dir, exist_okTrue) for batch_name in batches: src_batch_dir os.path.join(class_path, batch_name) for img_name in os.listdir(src_batch_dir): shutil.copy( os.path.join(src_batch_dir, img_name), os.path.join(out_class_dir, batch_name _ img_name) )这里有个容易忽略的参数是val_ratio。8 类成熟度中中间阶段的样本数量往往偏少如果按批次切分后某一类验证集只有一个批次类内差异会很大验证集指标波动也大。我一般会在切分后统计一下每个类别在训练集和验证集中的样本数比例明显失衡时把极端类别单独拿出来重新手动分配。不要全自动跑完就开训花十分钟看一眼分布比后面调试一个下午更值。2.4 数据增强不能只做随机翻转要模拟产线真实光照菠萝果皮表面是蜡质层在强光下会产生高光反射反射区域的颜色会发白模型很容易把高光误判成成熟过渡色。随机翻转这类基础增强解决不了这个问题我一般会在增强管线里加亮度扰动、对比度扰动和轻微高斯噪声。增强别做太狠尤其是颜色类增强如果饱和度扰动范围过大会把半黄和全黄这两个相邻类的边界抹掉。推荐用 Albumentations 库定义增强管线时把亮度范围控制在 ±30对比度控制在 ±20再加一点随机阴影模拟果实被叶片遮挡的情况。这里强调一点训练集增强的强度要和部署场景匹配产线光照稳定的就少做增强户外移动采集就多做亮度扰动。增强不是越猛越好过度增强会让模型在真实样本上欠拟合。3. 搭一个 ShuffleNet v2 分类器核心模块和 PyTorch 实现3.1 从 ShuffleNet v1 到 v2为什么换掉 1×1 group 卷积ShuffleNet v1 的核心是 pointwise group convolution 加 channel shuffle用 channel shuffle 解决 group 卷积导致的信息流通不畅问题。这个思路在理论上有道理但 v2 的论文指出了 v1 的一个关键问题模型推理速度不是只看 FLOPs内存访问成本 MAC 同样重要。1×1 group 卷积虽然计算量低但分组后内存访问模式不连续在移动端芯片上反而跑不快。所以 ShuffleNet v2 做了一个反直觉的设计用 channel split 替代 1×1 group 卷积把输入通道分成两支一支直连另一支经过深度可分离卷积最后拼接并用 channel shuffle 让两支信息混合。这样一来每个 block 的计算密度更均匀内存访问也更规整。做菠萝成熟度分类这种 8 类小任务ShuffleNet v2 的 0.5x 或 1.0x 宽度就足够了模型参数量只有几 MB训练速度和推理速度都能接受。3.2 channel shuffle 与 depthwise separable 的 pytorch 实现下面给出 ShuffleNet v2 基础模块的 PyTorch 实现。这个模块是搭建整个网络的最小单位理解了它往上堆叠就是照葫芦画瓢。基础模块分为 stride1 和 stride2 两种形态stride2 时不做 channel split逐通道采样后再拼接实现空间下采样。import torch import torch.nn as nn def channel_shuffle(x, groups): batch, channels, height, width x.shape x x.view(batch, groups, channels // groups, height, width) x x.transpose(1, 2).contiguous() x x.view(batch, channels, height, width) return x class ShuffleBlockV2(nn.Module): def __init__(self, in_channels, out_channels, stride1): super().__init__() self.stride stride mid_channels out_channels // 2 if stride 1: self.split True else: self.split False self.downsample nn.AvgPool2d(3, stride2, padding1) branch_channels mid_channels if stride 2: branch_channels in_channels self.conv1 nn.Sequential( nn.Conv2d(branch_channels, branch_channels, 1, 1, 0, biasFalse), nn.BatchNorm2d(branch_channels), nn.ReLU(inplaceTrue) ) self.depth_conv nn.Sequential( nn.Conv2d(branch_channels, branch_channels, 3, stridestride, padding1, groupsbranch_channels, biasFalse), nn.BatchNorm2d(branch_channels) ) self.conv2 nn.Sequential( nn.Conv2d(branch_channels, mid_channels, 1, 1, 0, biasFalse), nn.BatchNorm2d(mid_channels), nn.ReLU(inplaceTrue) ) def forward(self, x): if self.stride 1: x1, x2 torch.chunk(x, 2, dim1) else: x1 self.downsample(x) x2 x x1 self.conv1(x1) x1 self.depth_conv(x1) x1 self.conv2(x1) if self.stride 1: out torch.cat([x1, x2], dim1) else: x2 self.depth_conv(x2) x2 self.conv1[0](x2) x2 self.conv1[1](x2) x2 self.conv1[2](x2) x2 self.depth_conv(x2) x2 self.conv2[0](x2) x2 self.conv2[1](x2) x2 self.conv2[2](x2) out torch.cat([x1, x2], dim1) out channel_shuffle(out, 2) return out这段代码里有三个地方要特别说明。第一channel_shuffle里用了contiguous()因为view之前做过transpose内存不连续直接view会报错或产生错误结果。第二depth_conv的 groups 参数等于输入通道数这是深度可分离卷积的关键写法每个通道单独卷积不做跨通道混合。第三stride2 分支里x2的处理绕了一点实际是走一遍同样的卷积流程代码可以封装成单独函数这里故意展开写是为了看清数据流向。3.3 组装完整网络与 8 分类头的设计基础模块确认无误后组装完整网络就很简单了。先用一个普通 3×3 卷积下采样然后堆叠四个阶段的 ShuffleBlockV2最后一个全局平均池化接全连接分类头。分类头要特别注意输出层初始化8 分类的 last linear 层权重不要用默认的大方差初始化会让训练初期 loss 波动剧烈。class ShuffleNetV2(nn.Module): def __init__(self, num_classes8, width_mult1.0): super().__init__() self.stage_repeats [4, 8, 4] self.stage_out_channels [24, 116, 232, 464] self.stage_out_channels [ int(c * width_mult) for c in self.stage_out_channels ] self.first_conv nn.Sequential( nn.Conv2d(3, self.stage_out_channels[0], 3, 2, 1, biasFalse), nn.BatchNorm2d(self.stage_out_channels[0]), nn.ReLU(inplaceTrue) ) self.maxpool nn.MaxPool2d(3, 2, 1) blocks [] in_channels self.stage_out_channels[0] for stage_idx, repeats in enumerate(self.stage_repeats): for i in range(repeats): stride 2 if i 0 else 1 out_channels self.stage_out_channels[stage_idx 1] blocks.append(ShuffleBlockV2(in_channels, out_channels, stride)) in_channels out_channels self.blocks nn.Sequential(*blocks) self.final_conv nn.Sequential( nn.Conv2d(in_channels, 1024, 1, 1, 0, biasFalse), nn.BatchNorm2d(1024), nn.ReLU(inplaceTrue) ) self.classifier nn.Linear(1024, num_classes) def forward(self, x): x self.first_conv(x) x self.maxpool(x) x self.blocks(x) x self.final_conv(x) x torch.mean(x, dim[2, 3]) x self.classifier(x) return x注意width_mult这个参数它是轻量级网络的灵魂。width_mult1.0 时模型约 2.3M 参数0.5x 时只有 0.6M 左右。菠萝成熟度分类输入图像通常不超 224×2240.5x 精度下降在 1% 以内但推理速度快近一倍。在部署算力吃紧时从 0.5x 开始试不要一上来就用 1.0x。num_classes直接设 8跟标注的 8 个成熟阶段一一对应。4. 训练调参实战让轻量网络在相似成熟度类上不摆烂的 5 个关键设置4.1 预训练权重与冻结策略先用 ImageNet 权重热身轻量网络直接随机初始化训练收敛速度慢且精度容易卡在 85% 左右上不去。常见做法是加载在 ImageNet 上预训练过的 ShuffleNet v2 权重然后做迁移学习。但加载预训练权重后第一件事不是急着全量微调而是先冻结 backbone只训练分类头跑 5 到 10 个 epoch。这一步能让模型先学会利用提取好的特征做成熟度判别而不是让随机分类头的梯度把前面预训练特征冲乱。解冻时也不要一次全解开我一般分成两轮第一轮解冻后面两个 stage第二轮解冻全部层。每轮学习率保持不变或者稍微衰减一点。冻结阶段用 AdamW解冻阶段换成带动量的 SGD后者对 BatchNorm 层的统计量更友好。如果你发现冻住 train 模式下 BN 的 running_mean 不更新导致验证集指标异常可以先把 BN 层设为 eval 模式冻结统计量或者直接用frozen_bnTrue的封装。这个细节很多人不知道属于典型的迁移学习翻车点。4.2 损失函数与样本不均衡标签平滑解决“分不清相邻类”8 类菠萝成熟度不是完全独立的类别它们之间有天然的顺序关系。第五阶段和第四阶段的区别可能只是黄色面积多了一点直接用标准交叉熵会让模型对两类的边界极度敏感训练集上一有噪声标注就剧烈波动。我建议使用标签平滑的交叉熵把 one-hot 目标换成 soft target让模型不需要把某个样本的预测概率推到 1.0而是允许保留一点不确定性给相邻类别。对于样本数少的阶段比如全程很少见的“全绿”和“全黄带褐斑”阶段可以结合焦点损失 Focal Loss让模型把注意力放在难分类样本上。这里给出 PyTorch 中同时做标签平滑的简化实现。class LabelSmoothFocalLoss(nn.Module): def __init__(self, gamma2.0, alpha0.25, smoothing0.1, num_classes8): super().__init__() self.gamma gamma self.alpha alpha self.smoothing smoothing self.num_classes num_classes def forward(self, logits, targets): log_probs torch.log_softmax(logits, dim1) with torch.no_grad(): smooth_targets torch.full_like(log_probs, self.smoothing / self.num_classes) smooth_targets.scatter_(1, targets.unsqueeze(1), 1.0 - self.smoothing) pt torch.exp(log_probs) focal_weight (1 - pt).pow(self.gamma) # alpha 只对少数类放大这里按 one-hot 加权 alpha_weight smooth_targets * self.alpha (1 - smooth_targets) * (1 - self.alpha) loss -alpha_weight * focal_weight * log_probs * smooth_targets return loss.sum(dim1).mean()这段实现的几个参数值得说明gamma2.0是 Focal Loss 的标准配置控制对易分类样本的降权程度alpha0.25对正负类做非对称加权但多分类里直接对平滑后的 target 加权有个副作用会把 0.75 的权重压在大类上所以这个 alpha 值要根据样本比例手动调比例悬殊不大时设成 0.5。smoothing0.1是标签平滑强度太小起不到软化作用太大模型欠拟合。顺带说一句focal 和标签平滑同时用训练初期的 loss 绝对值会比普通交叉熵高一截这是正常的别当 bug 去调。4.3 学习率、batch size 与训练轮次的具体取值ShuffleNet 这类轻量网络对学习率比较敏感初始学习率设在 0.01 到 0.1 之间配合余弦退火调度器效果最好。batch size 受限于显存常用 64 或 128但要注意大的 batch size 会让 BatchNorm 的统计量更稳定菠萝图像的拍摄角度多样如果 batch size 太小BN 的 running_mean 会跟着每个 batch 的色温来回跳训练曲线抖得厉害。轮次方面冻结训练 10 轮解冻后 30 到 50 轮足够。再多没有明显收益反而容易过拟合到菠萝表面的病斑纹理上。每个 epoch 结束记录验证集的准确率取验证集最优的 check point 作为最终模型。早停 patience 设 10 轮连续 10 轮验证集精度不涨就停。4.4 评估指标与混淆矩阵怎么判“熟不到位”8 类成熟度分类不能用单一 accuracy 衡量因为类别有顺序关系分错到相邻类别和分错到跨两级类别的代价完全不同。我一般打印一个 8×8 的混淆矩阵重点看每一行的误分落点。如果大多数误分集中在相邻类别上说明模型学到了合理的颜色渐变规律可接受如果出现第一类和第七类这种隔了很远的混淆说明数据或标注有问题往回查。计算 F1 分数时要用 macro 平均不要用 micro 平均因为 micro 会被大头类别主导。这类任务我更看重“顺序一致性指标”预测类别的绝对值不重要重要的是预测类别和真实类别的差值的绝对值把它降到最低才是这个任务的最终目标。可写一个简单的差值为权重的评估函数真实类别为 3、预测为 4 的惩罚远小于真实为 2、预测为 7 的惩罚。5. 常见问题排查菠萝成熟度分类最常翻车的 7 个小坑5.1 标注边界不一致导致训练 loss 居高不下现象训练集 loss 降到 0.4 左右就稳定不动验证集准确率在 88% 附近反复横跳上不去。原因8 类成熟度中第 4 到第 6 类的边界主观性强不同标注员对同一张图的归类经常差一级。模型同时拟合冲突的标注梯度方向互相抵消loss 无法继续下降。解决重新导出所有标注数据对每个类别随机抽样 20 张图交给两个标注员独立复标计算一致率。不一致率超过 15% 的图片单独挑出来由项目负责人按果皮色卡和参考图仲裁。仲裁后的数据重新加入训练集问题基本能缓解。5.2 训练集准确率高但验证集差过拟合特征不是颜色而是纹理现象训练集准确率到了 99%验证集只有 82%差距明显。原因ShuffleNet 虽然参数量小但训练轮次过长或增强不足时仍然会记住菠萝表面的斑点、病斑和包装泡沫网的纹理模式这些和成熟度无关。解决降低训练轮次早停 patience 从 10 降到 6同时把 ColorJitter 的饱和度扰动从 0.1 提到 0.3让模型不能依赖颜色纹理的组合特征。如果依然过拟合直接在最后一个全局池化后加 dropout 层概率设 0.2注意 PyTorch 的nn.Dropout默认只在训练时生效部署推理时自动关闭。5.3 高光反光误判成熟度现象验证集中带明显高光的菠萝图片几乎全部被预测为更成熟的类别人工复查发现高光区域发白模型把白了当成熟。原因浅色高光区域与成熟菠萝的黄色果皮在 RGB 空间上接近网络只凭局部颜色做判断没有构建“整果颜色一致性”的全局上下文。解决在推理前对图像做高光抑制预处理。常见手法是把图像从 RGB 转到 HSV对 V 通道超过 230 的像素做局部中值滤波或者更简单地在增强阶段加入随机亮斑模拟高光。另一个有效办法是训练时把高光样本手动挑选出来单独建一个类别子集做数据增强让网络看到高光不是成熟。5.4 中间成熟度类别少数类欠拟合现象第 2 类和第 7 类的召回率明显低于其他类验证集混淆矩阵显示这两类的样本大量被分到相邻类。原因菠萝成熟过程不是匀速的第 2 类“微黄”状态持续时间短自然样本少模型学习不充分。这属于典型的长尾分布问题。解决不复制粘贴做简单过采样而是用增强过采样每次读取该类的原图用不同的增强参数生成新样本。也可以引入 MixUp把少数类和相邻类混合让模型学习两者的过渡特征。重点是别把验证集也做同样的过采样否则指标虚高。5.5 训练和推理的图像尺寸不一致导致精度暴跌现象训练时用 224×224部署现场拿到的是工业相机 2592×1944 的原始分辨率推理前 resize 到 224×224 后准确率掉了 7 个百分点。原因resize 方式不一致。训练时用了随机裁剪加 resize部署时直接用 OpenCV 的INTER_LINEAR缩放到正方形导致目标菠萝在画面中的占比和训练分布不一致。解决固定预处理管线。一般做法是在部署代码里完全复用训练时的预处理脚本包括先 resize 到短边 256中心裁剪到 224×224 的流程。如果你不打算保留中心裁剪那训练时也要改成直接 resize 到 224×224不引入裁剪保证两边输入分布一致。5.6 验证集指标高但换一个批次的菠萝就崩现象模型在已有验证集上 F1 达到 0.93拉到果园里拍新一批菠萝测试准确率直接掉到 80% 以下。原因数据划分是按批次做的但同一批次的菠萝可能来自同一个种植地块成熟度分布、光照条件、品种特性都相同验证集与训练集实际上存在隐蔽的数据泄漏。模型学到的是“这个地块的菠萝”而不是通用的成熟度。解决强制要求验证集必须包含至少两个不同地块、不同采收日期的批次。数据划分时按“地块-日期”作为分组单位而不是简单按目录随机分。这类问题在农产品分类里非常常见算得上这个领域最有名的坑之一了。5.7 分类头的 logits 输出在相邻类之间差异太小现象输出的 8 个类概率分布里第 4 和第 5 类概率值都在 0.3 上下模型长期处于“没底气”的状态。原因Softmax 输出的概率差距被过度平滑或者温度参数太高。这是模型在相邻类上特征区分度不足的直接表现。解决训练阶段用标签平滑拉出可靠梯度部署阶段则用温度缩放校准。对验证集统计每个类别的平均置信度用温度系数 T 缩放 logits 后再做 Softmax让置信度分布重新拉开这对分拣产线上设置可信度阈值非常有用。6. 部署落地与模型验证把 ONNX 模型压进边缘设备的常用路径6.1 导出 ONNX 并固定输入尺寸PyTorch 模型训练好之后第一步是导出 ONNX。导出时要注意固定输入尺寸不要用动态轴因为边缘端推理框架对动态 shape 支持不一容易踩坑。输入尺寸建议直接设为部署时的真实输入尺寸如果边缘设备只能用 192×192那就用 192×192 重训或微调不要在导出时临时改。torch.onnx.export( model, torch.randn(1, 3, 224, 224, devicecuda), shufflenet_pineapple_8cls.onnx, input_names[input], output_names[logits], opset_version11, do_constant_foldingTrue, )opset_version11是兼容性较好的选择新版本框架可能提示让你升级到 17但边缘端推理库不一定同步支持。导出后用 ONNX Runtime 加载跑一遍推理对比 PyTorch 输出结果的差异最大误差超过 1e-4 就要查是不是 BatchNorm 折叠的问题。这里说一个血泪经验如果你导出的模型里带有nn.DropoutONNX 里会保留 dropout 节点正常推理时它是不生效的但为了防止优化器不识别导出前把模型切到 eval 模式。6.2 用 OpenVINO、NCNN 或 TensorRT 压进边缘端的取舍ONNX 只是中间格式真正跑推理要根据目标设备选择引擎。Intel 平台的工控机用 OpenVINO 是最省事的路径它支持 CPU 和集成显卡INT8 量化做得比较成熟。NVIDIA 的 Jetson 系列则适合用 TensorRTFP16 精度下 ShuffleNet 推理能达到不错的速度。纯 ARM 移动端或国产板卡上NCNN 和 RKNN 是常见选择。这里没有万能答案核心原则是让模型在一个固定的预处理 pipeline 和推理引擎下跑满测不要跨引擎比较延迟数据那样没有意义。模型转换后一定要在目标设备上做端到端延迟测试包括图像采集、resize、归一化、推理、后处理全链路。只用推理引擎的 benchmark 工具测出来的数据跟真实产线差距很大。菠萝分类产线通常要求 100 毫秒内完成单帧处理ShuffleNet 在多数边缘设备上做到 20 到 50 毫秒的推理时间不难瓶颈往往在图像采集和 resize 上这块容易被忽略。6.3 最后验证的“三读法”数据、混淆矩阵、置信度阈值部署前的最终验证不是跑一个 accuracy 就完事。我惯用的方法是从三个层面读模型行为。第一层读宏观准确率确认整体在 90% 上下。第二层读混淆矩阵的邻近性统计误分类中相邻类别占比超过 90% 说明模型的错误模式符合人类判断逻辑。第三层读置信度分布统计每个类别正确预测时的平均概率选出置信度低于某个阈值比如 0.7的样本人工复查这批样本是否存在标注错误或超出 8 类定义的特殊状态。如果最后发现某些失败样本来自存储运输过程中出现的机械损伤而那在成熟度分级里确实不包含那就得考虑给系统增加一个“拒识”类别置信度低于阈值时输出“无法判断”让产线把这一颗菠萝导入人工复核通道。这个设计比分硬着头皮增加一个第 9 类更合理。温度缩放的具体做法是用验证集在 ONNX 模型上跑出所有 logits然后网格搜索一个温度系数让校准后的置信度与真实准确率对齐做法不复杂但确实是分拣系统落地时值得多花一小时做的事。说到这想起之前调过的另一个农业分类项目一开始大家追求准确率数字好看忽略了置信度阈值设计上线后一小部分低置信度样本被强行归入某个类别反而不如老实输出“待人工复核”。现在的我在做这类分级任务时第一件事是先设计拒绝策略再谈模型调参。希望这篇围绕 ShuffleNet 做菠萝成熟度分类的实战笔记能帮你在相似项目里少走一段弯路。本文还有配套的精品资源点击获取
返回列表