4、模型训练基础:轻量化模型设计原则(MobileNet、ShuffleNet、EfficientNet-Lite),训练时如何为部署做准备。
各位同学,咱们今天聊点实在的。
模型训练,很多人觉得就是调个参数、跑个loss。但如果你想把模型塞进嵌入式设备里,那训练阶段就得开始“动心思”了。我见过太多人,训练时精度刷得飞起,一到部署就傻眼——模型太大、推理太慢、算子不支持。说白了,训练和部署之间,隔着一道“轻量化”的鸿沟。
今天这一讲,我就带你看看,怎么在训练阶段就为部署铺好路。核心就三个字:轻量化。
4.1 轻量化模型设计原则:少即是多
嵌入式平台资源有限,CPU慢、内存小、带宽窄。所以模型设计的第一原则就是:用最少的计算量,达到最好的效果。
我个人习惯把轻量化设计拆成三个维度:
- 减少参数量:参数少了,模型文件就小,内存占用也低。
- 减少计算量:用FLOPs(浮点运算次数)衡量,计算量小了,推理就快。
- 减少内存访问:这个容易被忽略。有些模型虽然FLOPs低,但频繁读写内存,实际速度反而慢。
嗯,这里要注意,参数量和计算量并不完全等价。比如全连接层参数量大,但计算量不一定比卷积高。你得根据硬件特性来权衡。
4.2 三大经典轻量化网络:MobileNet、ShuffleNet、EfficientNet-Lite
这三兄弟,是嵌入式部署的“常青树”。我挨个给你讲讲它们的设计思路,以及我在项目中踩过的坑。
4.2.1 MobileNet:深度可分离卷积的鼻祖
MobileNet的核心思想,就是把标准卷积拆成两步:深度卷积(Depthwise Conv) 和 逐点卷积(Pointwise Conv)。
你想想看,标准卷积要同时处理空间信息和通道信息,计算量很大。而深度卷积只处理空间信息,逐点卷积只处理通道信息。一拆开,计算量直接降到原来的1/8到1/9。
举个例子,输入是 112x112x32,输出是 112x112x64,卷积核大小 3x3:
- 标准卷积:3x3x32x64x112x112 ≈ 2.3亿次运算
- 深度可分离卷积:3x3x32x112x112 + 1x1x32x64x112x112 ≈ 0.3亿次运算
差距一目了然。
4.2.2 ShuffleNet:通道混洗的妙用
ShuffleNet的思路更巧妙。它用了分组卷积(Group Conv),但分组卷积有个问题:组与组之间信息不流通。怎么办?它加了一个“通道混洗(Channel Shuffle)”操作。
说白了,就是把不同组的通道打乱重新分配。这样既保持了分组卷积的低计算量,又保证了信息交互。
我记得有一次,我在一个资源极紧的MCU上做图像分类。MobileNet V2的模型大小还是超了,换成ShuffleNet V2,参数量直接砍半,精度只掉了1%。那一刻我真是拍大腿——早该用它的。
4.2.3 EfficientNet-Lite:为部署而生的“瘦身版”
EfficientNet本身是通过NAS(神经架构搜索)找到的最优结构,但它的原始版本用了Swish激活函数和SE模块,在嵌入式设备上跑不动。所以Google推出了EfficientNet-Lite,专门为移动端和边缘设备优化。
它做了三件事:
- 把Swish换成ReLU6(很多芯片对ReLU有硬件加速)
- 去掉SE模块(减少计算量)
- 调整分辨率缩放策略(让模型更“瘦”)
嗯,这里要注意,EfficientNet-Lite虽然精度比原版低一点,但推理速度快了不止一倍。我一般在做“精度-速度”权衡时,会优先选它。
| 模型 | 参数量 | FLOPs | 适合场景 |
|---|---|---|---|
| MobileNet V2 | 3.4M | 300M | 通用嵌入式设备 |
| ShuffleNet V2 | 2.3M | 146M | 超低资源设备 |
| EfficientNet-Lite0 | 4.7M | 400M | 精度优先的部署 |
4.3 训练时如何为部署做准备:从源头开始优化
很多同学训练模型时,只关注训练集上的精度。但部署时你会发现,训练时的很多“习惯”到了推理时就是灾难。下面我列出几个关键点。
4.3.1 使用量化感知训练(QAT)
量化是嵌入式部署的必经之路。但直接训练后量化(PTQ),精度往往会掉。我建议你在训练时就加入伪量化节点,让模型学会适应低精度。
代码示例(PyTorch):
import torch
import torch.quantization as quant
# 定义模型
model = MobileNetV2()
# 开启量化感知训练
model.qconfig = quant.get_default_qat_qconfig('fbgemm')
model = quant.prepare_qat(model, inplace=True)
# 正常训练
for epoch in range(epochs):
for data, target in train_loader:
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
# 训练完成后,转换为量化模型
model = quant.convert(model, inplace=True)
4.3.2 冻结BN层
训练时,Batch Normalization(BN)层会统计均值和方差。但部署时,这些统计量应该是固定的。如果你在训练时让BN层继续更新,部署后的推理结果可能会漂移。
我的做法是:在训练的最后几个epoch,冻结BN层,让模型适应固定的统计量。
def freeze_bn(model):
for module in model.modules():
if isinstance(module, torch.nn.BatchNorm2d):
module.eval() # 冻结BN
module.requires_grad_(False)
4.3.3 输入尺寸对齐
很多轻量化模型对输入尺寸有要求。比如MobileNet V2默认是224x224,但你的摄像头可能输出的是320x240。如果你在训练时用了随机裁剪,部署时也要保持一致。
我曾经犯过一个错:训练时用了224x224,部署时忘了做resize,直接喂了320x240。结果模型输出全是错的。排查了半天才发现是尺寸问题。
4.3.4 算子兼容性检查
不是所有算子都能在嵌入式设备上跑。比如某些激活函数(Swish、Mish)、某些池化方式(自适应池化),在硬件上可能没有实现。
我建议你在训练前,先查一下目标平台的算子支持列表。比如用TensorRT部署,就避免使用不支持的自定义算子。用TFLite Micro,就只用标准算子。
嗯,这里有个笨办法:训练完后,先用目标平台的推理引擎跑一遍。如果报错,再回头改模型。虽然麻烦,但最保险。
4.4 总结:训练时多想一步,部署时少踩一坑
轻量化模型设计,不是把模型变小那么简单。它需要你从训练阶段就开始思考:
- 选对网络结构(MobileNet、ShuffleNet、EfficientNet-Lite)
- 用对训练技巧(QAT、冻结BN、尺寸对齐)
- 查清硬件限制(算子兼容性、内存带宽)
说白了,嵌入式部署是一场“预谋”。你训练时多花一点心思,部署时就能少掉几根头发。
下一讲,我们会深入模型剪枝与蒸馏,看看怎么把一个大模型“压缩”成小模型。到时候再聊。