4、MobileNet与EfficientNet:CNN轻量化的思路如何启发Transformer?
说实话,很多做Transformer部署的朋友,一开始都看不上CNN那套轻量化方法。我当年也是。总觉得Transformer是“新贵”,CNN是“老将”,思路应该完全不同。但真正踩过坑之后才发现——嗯,轻量化这件事,底层逻辑是相通的。
今天我们就来聊聊,MobileNet和EfficientNet这两个CNN轻量化的代表作,到底给了Transformer哪些启发。我个人觉得,理解了这个,你才算真正入门了边缘部署。
4.1 深度可分离卷积:把标准卷积拆开
先说说MobileNet的核心贡献——深度可分离卷积。说白了,就是把一个标准卷积拆成两步:
- 第一步:深度卷积——每个通道单独做卷积,不跨通道
- 第二步:逐点卷积——用1x1卷积把通道信息融合起来
你想想看,标准卷积一次要处理所有通道,计算量是 K×K×C_in×C_out×H×W。拆开之后呢?
深度卷积:K×K×C_in×H×W
逐点卷积:1×1×C_in×C_out×H×W
总计算量 ≈ K²×C_in×H×W + C_in×C_out×H×W
对比一下,计算量直接降到了原来的 1/C_out + 1/K²。我用3x3卷积举个例子,输出通道是64的话,计算量只有原来的 1/64 + 1/9 ≈ 0.126,也就是12.6%。
关键点:这个思路后来被用在了MobileViT、EdgeNeXt这些轻量Transformer上。它们把自注意力也做了类似的“拆解”——先做局部注意力,再做全局融合。
我在项目中遇到过一个问题:直接套用深度可分离卷积到Transformer的FFN层,精度掉了3个点。后来发现,是因为Transformer的FFN层本身就有两个全连接层,再拆就过度了。所以,不是所有地方都适合拆。
4.2 宽度乘数与分辨率乘数:两个调优旋钮
MobileNet还引入了两个超参数:
- 宽度乘数 α:控制每层的通道数,取值范围0.25~1.0
- 分辨率乘数 ρ:控制输入图像的分辨率,常见224、192、160、128
这两个参数,说白了就是让你在精度和速度之间做权衡。我习惯的做法是:
- 先固定分辨率,调宽度乘数,找到精度不掉点的最小值
- 再固定宽度,调分辨率,看能不能进一步压缩
- 最后两个一起调,做网格搜索
我的经验:对于Transformer模型,宽度乘数的效果比分辨率乘数更明显。因为Transformer的参数量主要集中在Embedding和FFN层,通道数一减,效果立竿见影。
我曾经在一个视觉Transformer项目里,把宽度乘数从1.0降到0.75,模型大小从85MB降到了48MB,推理速度提升了40%,精度只掉了0.8%。这个trade-off,我觉得很值。
4.3 EfficientNet的复合缩放:三个维度一起调
EfficientNet提出了一个更系统的思路——复合缩放。它认为,网络的深度、宽度、分辨率这三个维度不是独立的,而是相互影响的。所以它用一个统一的系数 φ 来控制三个维度:
深度:d = α^φ
宽度:w = β^φ
分辨率:r = γ^φ
其中 α·β²·γ² ≈ 2
这个公式看着有点复杂,但核心思想很简单:你加大分辨率,就要同时加深网络、加宽通道,否则模型会“消化不良”。
这个思路对Transformer的启发非常大。你看现在的轻量Transformer,比如MobileViT、EfficientFormer,都在做类似的事情:
| 维度 | CNN中的对应 | Transformer中的对应 |
|---|---|---|
| 深度 | 网络层数 | Transformer Block数量 |
| 宽度 | 通道数 | Embedding维度 / Head数量 |
| 分辨率 | 输入图像大小 | Patch大小 / 序列长度 |
注意:Transformer的序列长度和CNN的分辨率不是完全等价的。序列长度增加,自注意力的计算量是平方增长的。所以调整分辨率乘数时,要特别小心序列长度不要超过模型的设计上限。
4.4 从CNN到Transformer:哪些思路可以直接搬?
我总结了一下,CNN轻量化的思路,大概有这几条可以直接用在Transformer上:
- 深度可分离卷积 → 分解自注意力:把全局注意力拆成局部注意力+全局注意力,或者用轴向注意力替代全连接注意力
- 宽度乘数 → 调整Embedding维度:直接减少每个Token的向量长度,效果明显但要注意精度损失
- 分辨率乘数 → 调整Patch大小:增大Patch大小可以缩短序列长度,但会损失细节信息
- 复合缩放 → 多维度联合优化:不要只调一个参数,要同时考虑深度、宽度、分辨率三个维度
举个例子,我最近在部署一个视觉Transformer到树莓派上。原始模型有12层,Embedding维度384,Patch大小16。我做了这样的调整:
# 原始配置
layers = 12
embed_dim = 384
patch_size = 16
# 轻量化配置
layers = 8 # 减少深度
embed_dim = 256 # 减少宽度
patch_size = 32 # 降低分辨率(增大Patch)
结果模型大小从86MB降到了22MB,推理速度从2.3秒降到了0.7秒。精度从82.1%降到了79.3%,但在这个应用场景下,完全够用。
核心启发:CNN轻量化的本质是“在计算量和精度之间找平衡”。这个思路完全适用于Transformer。不要觉得Transformer是“新东西”就要另起炉灶,很多老方法换个马甲就能用。
4.5 避坑指南:我踩过的几个坑
最后分享几个我实际踩过的坑,希望能帮你少走弯路:
- 坑一:直接套用MobileNet的宽度乘数到Transformer。Transformer的Embedding维度通常和Head数量绑定,你减了维度,Head数量也要跟着调,否则注意力头数不对。
- 坑二:过度压缩FFN层。Transformer的FFN层有个扩展因子(通常是4),你把这个因子从4降到2,模型确实小了,但精度掉得很快。我建议至少保留3倍。
- 坑三:忽略了位置编码。你调整了Patch大小或者序列长度,位置编码也要重新训练或者做插值。我曾经直接复用原来的位置编码,结果模型完全学不进去。
嗯,今天就聊到这里。下一章我们会具体讲怎么用这些思路,把一个标准的ViT模型改造成适合边缘部署的轻量版本。到时候我会带着你一步步操作,包括代码实现和性能对比。