011、骨干网络改进(二):MobileNet、ShuffleNet等轻量骨干的适配

张开发
2026/4/14 20:38:36 15 分钟阅读

分享文章

011、骨干网络改进(二):MobileNet、ShuffleNet等轻量骨干的适配
一、从一次部署失败说起上周在 Jetson Nano 上部署 YOLO 检测模型原版 Darknet53 骨干跑起来只有 3 FPS内存直接飙到 90%。客户要求至少 15 FPS 且功耗不能太高。试过剪枝、量化效果都不理想最后决定换轻量骨干。本以为把 backbone 替换成 MobileNet 就完事结果训练时 loss 震荡推理时漏检严重——轻量骨干的适配远不是改个网络结构那么简单。二、为什么需要轻量骨干嵌入式端侧部署算力、内存、功耗都是硬约束。Darknet53、CSPDarknet 这些骨干虽然性能强但参数量和计算量对边缘设备不友好。MobileNet、ShuffleNet 这类设计核心思路是用更少的计算代价提取足够的特征。但直接套用到 YOLO 上往往精度掉得厉害原因在于 YOLO 对空间信息和多尺度特征非常敏感而轻量骨干为了效率往往会牺牲这部分能力。三、MobileNet 在 YOLO 中的适配陷阱MobileNet 的核心是深度可分离卷积DWPW计算量确实降下来了但感受野和特征表达能力也弱了。直接替换 backbone 后常见的问题小目标漏检浅层特征不够丰富小目标容易丢。大目标定位漂移深层网络感受野不足大物体边界框不准。训练不稳定BatchNorm 层和 YOLO 的检测头配合不好容易梯度震荡。我常用的改进策略是混合使用标准卷积和深度可分离卷积。比如在 stem 层输入附近和最后输出到检测头的层保留标准卷积保证特征提取的稳定性。中间层大量使用 DW 卷积这样既能省计算又不至于伤筋动骨。# 一个常见的 MobileNet 混合卷积块示例classMobileMixedBlock(nn.Module):def__init__(self,in_c,out_c,stride1):super().__init__()# 第一个标准卷积不动它保证输入特征稳定性self.conv1nn.Conv2d(in_c,in_c,3,stride,1,groups1,biasFalse)# 这里别用 groupsin_cself.bn1nn.BatchNorm2d(in_c)# 中间深度可分离卷积省计算self.dw_convnn.Conv2d(in_c,in_c,3,1,1,groupsin_c,biasFalse)# groupsin_c 就是 DWself.bn2nn.BatchNorm2d(in_c)# 最后的 PW 卷积self.pw_convnn.Conv2d(in_c,out_c,1,1,0,biasFalse)self.bn3nn.BatchNorm2d(out_c)self.actnn.SiLU()# YOLO 常用 SiLU比 ReLU 稍好defforward(self,x):# 先走标准卷积outself.act(self.bn1(self.conv1(x)))# 再走 DWPWoutself.act(self.bn2(self.dw_conv(out)))outself.bn3(self.pw_conv(out))returnout注意看第一个卷积我故意没用 DW这里踩过坑——输入通道少的时候 DW 问题不大但通道数一多开头就用 DW 容易丢失细节信息尤其是小目标。四、ShuffleNet 的通道洗牌与检测头兼容ShuffleNet 的亮点是通道洗牌Channel Shuffle让组卷积之间能信息交流。但它在 YOLO 里最大的问题是检测头通道数对齐麻烦。YOLO 的检测头通常假设 backbone 输出是连续通道但 ShuffleNet 的输出通道是“洗过”的直接接上去会信息错乱。我的做法是在 ShuffleNet 最后加一个Channel Re-Align 层其实就是个 1x1 卷积 BN把通道重新整理成检测头期望的布局。classShuffleNetWithHead(nn.Module):def__init__(self,num_classes80):super().__init__()# 假设这是 ShuffleNet 骨干self.backbonebuild_shufflenet()# 通道重对齐层self.realignnn.Conv2d(1024,1024,1,1,0)# 1x1 卷积整理通道self.bnnn.BatchNorm2d(1024)# YOLO 检测头self.headYOLOHead(1024,num_classes)defforward(self,x):featsself.backbone(x)# 必须加这个重对齐不然检测头学不动aligned_featsself.bn(self.realign(feats))outputsself.head(aligned_feats)returnoutputs另外ShuffleNet 的组数groups设置要注意别为了轻量盲目开大 groups。groups 太大每个组的通道数太少提取的特征会太“窄”检测效果下降明显。我一般建议 groups2 或 4 就够了别超过 8。五、轻量骨干的训练技巧学习率要调小轻量骨干参数少容易训飞。初始学习率可以设为原版的 0.5~0.8 倍用 warmup 慢慢起来。数据增强要谨慎Mosaic、MixUp 这类强增强可以保留但概率降低一点。轻量模型容量小太复杂的数据变换学起来吃力。多尺度训练别丢这是保证检测性能的关键。输入尺寸可以从小一点开始比如 320x320逐步增加到 640x640让模型慢慢适应多尺度。检测头别乱剪有人为了轻量连检测头也剪这是大忌。检测头是任务相关的剪了精度掉得厉害。省计算主要在 backbone 上做文章。六、部署时的实际考量训练完了部署到设备上还有几个坑INT8 量化友好性MobileNet 的 DW 卷积对量化比较友好但 ShuffleNet 的通道洗牌操作在某些推理框架上可能没有优化好的量化算子实测速度可能不如预期。部署前最好用目标框架如 TensorRT、ONNX Runtime先跑一遍性能分析。内存布局对齐边缘设备上内存对齐影响很大。DW 卷积的 memory access pattern 比较规整通常比标准卷积更省内存带宽这也是 MobileNet 在实际设备上跑得不错的原因之一。功耗平衡轻量骨干计算量小但不一定最省电。有些操作如频繁的通道重排会增加内存访问功耗反而上去。实测比理论计算量更重要。七、个人经验与建议轻量骨干适配本质是效率与精度的平衡游戏。我的几条经验别追求极致的轻量参数量压得太狠精度掉到没法用等于白做。先保证精度在可接受范围比如比原版低 3% 以内再优化速度。骨干和检测头要协同设计只改骨干不动检测头往往效果不好。可以适当减少检测头的通道数但别动结构。嵌入式部署先做 profiling训练阶段的 FLOPs 只是参考实际设备上的 latency、内存占用、功耗才是关键。早点上真机测试避免后期返工。保留可配置性在代码里做好骨干切换的接口方便快速对比 MobileNet、ShuffleNet、GhostNet 等不同方案。同一个任务不同设备可能最优骨干不一样。轻量不代表简单轻量骨干的设计往往更精巧调试起来更麻烦。多看看训练曲线第一个 epoch 的 loss 下降情况就能看出骨干是否合适。最后说一句轻量骨干不是银弹它解决的是计算约束下的部署问题。如果设备算力足够用原版骨干省心省力如果不够轻量骨干是必须走的路但每一步都要踩实从训练到部署每个环节都可能藏坑。多实验多调参多上真机这才是工程落地的正道。

更多文章