十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

MobileNet V2水果识别实战:从训练到部署全流程解析

MobileNet V2水果识别实战:从训练到部署全流程解析 MobileNet V2做水果识别这件事我前后折腾了大概三周。最初的想法很简单在树莓派或者手机上跑一个能认水果的模型十几二十个品种覆盖常见超市货架拍一张照片就能告诉我这是什么、大概是什么状态。试过直接用大模型比如ResNet50、EfficientNet准确率确实高但模型体积和推理延迟直接劝退。后来把目光转向轻量级神经网络MobileNet V2成了最终选择。这篇文章不做理论搬运只讲我在整个项目里怎么把 MobileNet V2 落地的涉及数据准备、训练细节、调参心得、部署要点顺便把踩过的坑一并交代清楚。先说结论用 MobileNet V2 做水果识别在 Fruits-360 这类公开数据集上Top-1 准确率能做到 92% 以上模型文件压在 15MB 以内树莓派 4B 上 CPU 单帧推理 20ms 量级。如果换成工业场景的自采数据准确率会受数据质量影响但模型本身的设计能力足够覆盖大部分需求。1. 项目愿景与技术选型思路1.1 为什么选择MobileNet V2而不是其他模型在目标检测和分类任务里模型选型往往不是“谁精度高选谁”而是“在约束条件下谁能用”。我当时有三条硬性约束第一不能依赖独立显卡目标设备是 CPU 或嵌入式板卡第二模型文件不能太大最好不超过 50MB第三推理延迟要控制在一帧 100ms 以内否则交互体验就是灾难。在候选模型里我做了个横向比较MobileNet V1速度够快但结构相对粗暴Depthwise 卷积之后直接接 1x1 卷积信息流容易丢失准确率一般。MobileNet V2在 V1 基础上引入了倒残差结构和线性瓶颈能在更窄的通道数下保住精度综合表现最均衡。ShuffleNet V2速度实测和 MobileNet V2 接近但在一些设备上的算子实现支持不够完整容易踩坑。EfficientNet-Lite精度更高但模型尺寸和推理时延都偏大除非精度要求极高否则性价比不高。考虑兼容性、生态成熟度、移植难度我最终选了 MobileNet V2。它的训练技巧公开资料多PyTorch 里可以直接调用官方预训练权重适合快速验证。1.2 水果识别场景的特殊性水果识别和普通物体分类不太一样。水果种类多、外观相似度高比如红富士苹果和蛇果、青提和红提、柠檬和青柠颜色和纹理差异往往只在一两个通道上体现。再加上光照、遮挡、拍摄角度等问题模型需要在有限的视觉线索里做精确判断。另外水果识别有一个普通分类没有的难点类间相像但类内差异也大。同样是苹果一个青苹果和一个红苹果可能被归为两类同样是香蕉熟透的和带青皮的外观差距也很大。这种情况下模型需要学习的不是“某个品种的模板”而是“某个品种的特征分布”。所以在数据准备阶段就必须把每个品种的多状态、多角度样本都收进来不能只放“标准照”。这个点我会在后面数据集部分详细展开。2. MobileNet V2核心原理拆解从结构设计到场景适配2.1 深度可分离卷积省算力的第一步MobileNet 系列的基石是深度可分离卷积。标准卷积在做特征提取时会同时考虑通道维度和空间维度计算量是(输入通道数 x 输出通道数 x 卷积核尺寸 x 空间尺寸)。而深度可分离卷积把这个过程拆成两步。第一步是深度卷积对输入的每个通道分别做卷积相当于每个通道用独立的卷积核去提取空间特征。第二步是逐点卷积用 1x1 卷积去融合不同通道的信息。两个步骤的计算量加在一起只有标准卷积的1/输出通道数 1/卷积核尺寸平方左右。以3x3卷积、输入输出通道均为 64 为例标准卷积的单次计算量是64 x 64 x 9 36864次乘法而深度可分离卷积是64 x 9 64 x 64 4672次只占标准卷积的12.7%。这个计算量的缩减能直接影响 CPU 上的推理速度也是 MobileNet V2 能在边缘设备上用的根本原因。2.2 倒残差结构与线性瓶颈V2的进阶之处MobileNet V2 相比 V1 最大的改动在于引入了倒残差结构。一般的残差结构比如 ResNet是“先压缩通道再卷积再扩展”把输入输出都保持在较窄的通道数上。MobileNet V2 的倒残差是反过来的先用 1x1 卷积把通道数升上去再做深度卷积最后再用 1x1 卷积把通道压缩回来。为什么要“倒着”做因为深度卷积只能在低维空间里提取特征如果把特征直接交给它去处理信息瓶颈会导致精度下降。先升维再降维等于先让模型有足够宽的特征空间去“思考”然后再把思考结果压缩保存下来。同时V2 还在最后一个 1x1 卷积之后取消了 ReLU 激活改成线性输出。因为高维特征被压缩到低维时ReLU 会直接把负半轴清零这个操作会不可逆地损失信息。线性瓶颈在这个场景下反而能保留住更多特征。这两个设计的直接结果就是MobileNet V2 在同样的算力预算下精度比 V1 提升了一个档次。这也在我的实测中得到了验证同样的训练集V1 的 Top-1 是 88% 左右V2 能做到 92% 以上。2.3 与目标检测方案如YOLOv5的选择边界必须承认YOLOv5 这类目标检测模型在水果识别场景下也很流行。它能同时定位和分类直接框出画面里的水果用户体验更好。我在项目初期也试过 YOLOv5s检测效果确实不错尤其是当画面里有多个水果、有遮挡的情况。但我后来还是回归了分类模型原因有两点。第一我的应用场景是“拍一张照片识别一个水果”属于单目标场景不需要同时框出多个目标。用目标检测模型属于杀鸡用牛刀模型体积更大、推理更慢、标注成本也更高。分类模型只需要输入一张图输出一个类别标签正好满足需求。第二部署条件受限。YOLOv5s 的权重文件在 14MB 以上CPU 推理一帧要 50ms 以上MobileNet V2 的分类模型 10MB 左右CPU 推理只要 20ms。如果你的场景是多目标识别、需要定位那直接上 YOLOv5单目标分类的话MobileNet V2 是更轻的选择。3. 数据集准备与增强策略模型再好喂给它的数据也得对3.1 数据来源与类别设计我用的公开数据集是 Fruits-360里面包含上百种水果的图片每种按品种分开存放。它的问题在于太“干净”了每张图都是单个水果放在白色背景上旋转角度基本固定没有光照变化。用这个数据集训练出来的模型在真实环境里会“水土不服”。所以我的做法是把 Fruits-360 的数据作为基础再用手机拍了大约 2000 张真实环境照片覆盖室内灯光、自然光、逆光、阴影等场景水果的摆放角度也多样化。最终数据集包含了 18 个类别每类 800 到 1500 张图。类别设计上要注意宁可数量少而精也不要盲目追求多而杂。我在项目初期把类别扩到 30 种结果“芒果”和“木瓜”反复混淆准确率一直卡在 85% 上不去。后来检查数据发现有些芒果样本的成熟度和木瓜相似度极高人类肉眼都容易看错。缩减到 18 类之后效果立竿见影。3.2 数据增强的度怎么把握轻量级模型容易欠拟合但水果识别领域数据相对少又容易过拟合所以增强策略要谨慎。我采用的增强组合是随机水平翻转、随机旋转角度范围 ±25 度、随机亮度饱和度调整、随机缩放剪裁、随机擦除。有一个参数需要特别注意旋转角度。水果不像工业零件那样方向敏感但如果角度范围太大会引入不自然的样本比如香蕉横着往下干扰模型学习。另外对于苹果这类形状接近圆形的物体翻转和旋转的增强增益很小反而容易让模型过拟合到颜色分布上。我最后对苹果类单独降低了旋转角度上限。随机擦除Random Erasing是一个很有用的增强技巧它的原理是随机遮掉图片中的一小部分区域强迫模型学习更全面的特征而不是只依赖某一个局部区域。在水果分类任务里这个技巧能显著提高模型对遮挡情况的鲁棒性尤其是当拍摄时手指偶尔挡住一部分水果时。3.3 类别不平衡的处理Fruits-360 原始数据里有些类别样本特别多有些特别少。如果直接用原始分布训练模型会对样本多的类别产生偏置。我的处理方式是加权采样每个类别的采样权重与样本数成反比训练时按权重取样本而不是按原始数量取。效果好但这种做法也牺牲了一些训练效率——样本少的类别会出现重复。如果发现某个类别的准确率明显低于其他类别优先检查它的样本量和样本质量而不是急着调模型结构。4. 完整训练流程与关键参数配置照着跑就能复现4.1 环境搭建与依赖版本我用的环境是Ubuntu 20.04 PyTorch 1.12.1 torchvision 0.13.1NVIDIA RTX 3060 12GB单卡训练CUDA 11.3 cuDNN 8.2Python 3.8在开始训练之前建议先把数据集的目录结构规范好这样后续写代码时省很多事。我的目录结构是data/ train/ apple/xxx.jpg banana/xxx.jpg val/ apple/xxx.jpg test/ apple/xxx.jpg4.2 超参数设定从默认值到收敛曲线的调优过程在训练超参数的选择上我踩过不少坑最终形成了一套比较可复用的方案。初始学习率我设的是0.001优化器用 AdamW权重衰减设为1e-4。学习率调度用了余弦退火总训练轮数 50 轮。这里有一个关键细节如果你用了预训练权重在 ImageNet 上预训练的 MobileNet V2初始学习率不能太大否则会破坏已经学好的特征。我测试过从0.01开始训结果收敛速度反而更慢准确率最终也低了 2 个百分点左右。批量大小设为 64。这个需要根据显存调整如果显存不够可以把批量大小降为 32同时按比例降低学习率。一般情况下批量大小减半学习率也减半可以保持相对稳定的收敛效果。输入图像尺寸设为224x224。MobileNet V2 默认的输入尺寸就是 224这个尺寸在 ResNet 系列里也是标配。更大的尺寸比如 320可以提高小目标的识别准确率但推理速度会明显下降。在水果这个场景下水果通常占画面的大部分区域224 足够了。4.3 训练代码结构与核心逻辑整个训练代码不算复杂核心部分可以拆成以下几个模块。数据加载部分from torchvision import datasets, transforms from torch.utils.data import DataLoader train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.8, 1.0)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(degrees25), transforms.ColorJitter(brightness0.3, contrast0.3, saturation0.3), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])模型加载部分使用 PyTorch 官方预训练权重from torchvision import models model models.mobilenet_v2(weightsmodels.MobileNet_V2_Weights.IMAGENET1K_V1) num_features model.classifier[1].in_features model.classifier[1] nn.Linear(num_features, 18)训练循环的核心部分采用余弦退火和模型检查点保存criterion nn.CrossEntropyLoss() optimizer optim.AdamW(model.parameters(), lr0.001, weight_decay1e-4) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50) best_acc 0.0 for epoch in range(50): 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() scheduler.step() # 验证 model.eval() acc evaluate(model, val_loader) if acc best_acc: best_acc acc torch.save(model.state_dict(), best_model_mobilenetv2.pth)4.4 训练监控与日志怎么看我使用了 wandb 做实验记录。重点观察的指标有三个训练 loss 曲线、验证 Top-1 准确率、学习率变化曲线。训练 loss 如果一直在下降但验证准确率停滞不前说明模型过拟合了需要加重权重衰减或增强数据增强。验证准确率在一个完整的 epoch 后突然大幅跳变常见原因是数据增强太强导致验证集和训练集分布不一致。学习率变化正常的话准确率曲线应该是平滑上升、后期逐渐趋于平稳如果出现锯齿状波动说明学习率偏大。5. 模型评估与结果分析不能只看一个准确率数字5.1 评估指标的选择与计算水果分类项目常用 Top-1 准确率作为主指标但它会掩盖部分类别混淆的问题。我建议同时关注 Precision、Recall 和 F1 这三个指标尤其是在你对某些特定类别的误判非常关注的情况下。在线下测试集上我的 MobileNet V2 模型最终表现是指标数值Top-1 准确率92.5%平均精确率 Precision92.8%平均召回率 Recall92.1%平均 F1 分数92.4%模型文件大小13.8MBCPU 单帧推理时间树莓派 4B21ms5.2 混淆矩阵的实际意义混淆矩阵能直观展示哪些类别容易混淆。我得到的结果里最严重的混淆出现在柚子和橙子之间其次是青柠和柠檬。这两组水果外观本身就接近在颜色和纹理上只有细微差别靠 RGB 图像很难彻底区分。发现问题后我尝试了两个方案一是增加这两组数据在训练集中的比重二是引入 HSV 色彩空间的特征增强。实际结果是前者有效后者效果不明显。RGB 图像里包含的颜色信息已经足够问题在于样本多样性不足而不是颜色表达方式的问题。5.3 模型轻量化的真实收益模型文件大小 13.8MB在普通的嵌入式设备上完全不是问题。在树莓派 4B 上用 PyTorch 的 CPU 推理接口单帧耗时平均 21ms这个速度对于拍摄后实时识别的场景来说完全够用。如果再用 ONNX 导出、量化到 INT8模型可以进一步压缩到 5MB 左右推理时间能缩短到 10ms 上下但会伴随轻微的精度损失我实测掉 1-2 个百分点。5.4 与热门方案YOLOv5的定量对比网上很多人问“水果识别用 YOLOv5 还是 MobileNet V2 好”。为了有个直观对比我在同一数据集上简单测了下 YOLOv5s 的检测分类效果。YOLOv5s 模型的 mAP 能到 90% 以上但模型体积 14MBCPU 推理一帧约 45ms是 MobileNet V2 分类模型的两倍多。如果场景是画面中有多个水果、需要框出位置YOLOv5 值得用但纯分类场景MobileNet V2 明显更划算。6. 踩坑实录与效率优化技巧这些经验写不进教科书6.1 过拟合与欠拟合怎么判断、怎么治在只有 Fruits-360 干净数据时我遇到了明显的过拟合训练准确率冲到 98%验证准确率却只有 84%。这是典型的模型背了训练集的答案而不是学到了泛化特征。解决步骤有三层先加真实数据手机拍摄再加强数据增强随机擦除、亮度抖动最后调正则把权重衰减从1e-4提高到5e-4。每一步都带来 2-3 个百分点的提升。反过来如果模型在训练集上准确率都不高低于 85%大概率是欠拟合。这时候不要急着加数据先检查学习率是否过大或过小模型是否太小比如用的是 MobileNet V1 而非 V2然后考虑增大模型容量。6.2 数据标注错误的坑我遇到过一个问题验证集准确率一直卡在 90% 上不去检查混淆矩阵时发现“蛇果”和“红富士”这两类反复横跳。排查了几个小时最后发现是数据标注出错一批蛇果的图片被标成了红富士人工检查时才看出来区别。这个教训说明不要迷信标注数据。如果你的模型在某个类别组合上反复出错先人肉检查数据再怀疑模型结构。6.3 训练速度优化的实用技巧训练速度直接决定了你的试错效率。我实测过几个有效的提速方法。混合精度训练AMP能把单轮训练时间缩短将近一半在 PyTorch 里只需要加几行代码就能启用scaler torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): outputs model(inputs) loss criterion(outputs, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()另一个方法是把数据读取改成多进程加载num_workers设为 4pin_memoryTrue这样批量读取数据时不再拖 GPU 的后腿。对于小的数据集这两个优化的启动成本很低收益却很大。6.4 ONNX 导出与部署避坑模型训练完成后部署是另一个技术节点。PyTorch 模型直接部署到生产环境会遇到一些兼容性问题我习惯先转成 ONNX 格式。导出时有一个关键参数要处理opset_version。我用的是11兼容性较好。如果在 TensorRT 上做加速可能需要调到更高的版本。一次导出时我没指定 opset 版本导致在 TensorRT 上加载失败排查了很久。import torch.onnx dummy_input torch.randn(1, 3, 224, 224).to(device) torch.onnx.export( model, dummy_input, mobilenet_v2_fruit.onnx, input_names[input], output_names[output], opset_version11, do_constant_foldingTrue )导出后建议先用onnxruntime在 CPU 上跑一遍对比 PyTorch 的输出。官方模型导出后意外出错的原因大部分在于某些算子转换不完全提前用 onnxruntime 做一次推理验证可以提前发现问题。7. 扩展方向与个人经验总结在基础分类模型之上还可以往几个方向扩展把单分类模型换成多标签模型识别“一个画面里既有苹果又有香蕉”加入目标检测模块定位水果位置用知识蒸馏技术把大模型的知识迁移到 MobileNet V2 的量化版本里。这些方向我在后续的迭代中都有尝试都值得单独开一篇来写。最后说一点我的实际体会轻量级模型做水果识别真正的瓶颈往往不在模型结构而在数据质量。MobileNet V2 的设计已经足够强大在干净数据上轻松跑到 90% 以上但在真实场景中光照、角度、遮挡带来的数据多样性问题才是准确率高低的决定因素。所以与其花大量时间去堆模型参数、试各种结构不如先把数据做扎实把数据分布摸清楚。如果你正在做类似的轻量级识别项目我建议你从数据出发把模型当作一个高效的“特征提取器”而不是“魔法黑盒”你会发现很多问题其实出在数据上而不是参数上。
返回列表