做深度学习这几年我越来越觉得“模型精度”不是一个孤立的指标它背后永远是算力、显存、响应时间、开发周期这些现实成本的加权。EfficientNet 这个名字凡是搞过图像分类的应该都不陌生它当年最大的贡献不是在某一个榜单上把 Top-1 推高了多少而是把“如何在有限算力预算内拿到更好的精度”这件事系统化、工程化了。这篇文章会用做项目踩过坑的视角把 EfficientNet 的模块设计、复合缩放原理、B0 到 B7 的选型方式以及真正跑训练和部署时容易遇到的问题都梳理一遍适合打算把它用进实际业务的同学。1. 复合缩放为什么是 EfficientNet 的灵魂1.1 效率这件事FLOPs、参数量、延迟到底谁最重要讨论模型效率之前先要把三个概念掰开FLOPs、参数量、延迟。FLOPs 是理论计算量表示模型做一次前向要多少次浮点运算参数量表示模型占多少存储。这两个指标最容易获取论文里到处都是但实际部署时真正卡脖子的是延迟和峰值内存。延迟取决于算力、内存带宽、算子和硬件调度。同样一个模型在不同硬件上的表现差异可能超过一倍甚至更多。有些网络在 GPU 上跑得飞快一到某些移动端芯片上就原形毕露原因往往是深度可分离卷积在这些平台上缺少底层优化看似计算量小实际kernel调度开销巨大。所以选 EfficientNet 的哪个版本不能只看 FLOPs 表得结合自己目标设备的实测速度来定。我自己的判断标准是这样的如果做移动端或边缘设备延迟和内存占用优先级最高如果做服务端离线推理FLOPs 反而是次要的吞吐量和显存利用率更关键如果做学术对比或刷点精度永远是第一位。EfficientNet 的巧妙之处在于它用一套统一的方法约束了参数量和 FLOPs 的增长让选择模型变成了选择算力预算的问题。1.2 单独调宽度、深度、分辨率都有瓶颈在 EfficientNet 之前改进卷积网络的主流思路是加深或者加宽。ResNet 系列靠增加层数拿到了红利但网络深到一定程度后梯度回传愈发困难单纯堆层数会撞上收益递减的墙。加宽度也就是增加每层通道数能更快让模型“看到更多特征”但很多实验证明当通道数膨胀到一定规模后增益会快速下降同样存在饱和问题。提高输入分辨率其实是最直觉的提升手段因为图片更清楚模型能看到更细的纹理。代价则非常直接分辨率是平方关系图像边长增大一倍计算量直接翻四倍。在算力有限的前提下盲目提高分辨率会很快吃掉所有预算。更关键的是这三个维度不是独立的。深层网络需要更宽的特征通道来表达更丰富的信息宽网络也需要足够的分辨率来保留细节分辨率提高后又需要更深的结构去提炼高级语义。单独调任何一个维度都会让网络在某个方面成为瓶颈。这就是为什么以往那种只加深、只加宽或者只提分辨率的做法很快遇到天花板的原因。1.3 复合缩放的数学约束是怎么推出来的EfficientNet 提出用一组系数同时缩放宽度、深度和分辨率。假设初始网络深度为 d、宽度为 w、分辨率为 r复合缩放后分别变成 d^φ、w^φ、r^φ。其中深度按 α 的 φ 次方缩放宽度按 β 的 φ 次方缩放分辨率按 γ 的 φ 次方缩放公式写作深度 d α^φ宽度 w β^φ分辨率 r γ^φ约束条件为 α、β、γ 都不小于 1且 α·β²·γ² ≈ 2。为什么是平方项原因在于计算量对不同维度的敏感程度不一样深度对 FLOPs 的影响是线性的宽度和分辨率则是平方关系。卷积层的计算量大约是输出通道数乘输入通道数乘以卷积核大小和空间分辨率。宽度增加一倍所有卷积层的输入输出通道同时增加所以计算量变成原来四倍分辨率增加一倍空间尺寸变成原来四倍计算量也变成四倍深度增加一倍计算量线性增加一倍。为了让总体 FLOPs 按 2^φ 的倍数增长就需要把每个维度的增长指数压制到使总和满足 2^φ。用网格搜索在基线网络上找一组可行解作者给出的经验值是 α 约为 1.2、β 约为 1.1、γ 约为 1.15。B0 是未缩放的基线模型B1 到 B7 就是依次增加 φ 得到的结果。这种方法确保模型变大时每个维度都在同步提升而不是让某一块被拖后腿。2. 三个关键结构部件MBConv、SE、Swish2.1 MBConv 模块里的倒残差和深度卷积EfficientNet 的基线网络由一系列 MBConv 模块堆叠而成。MBConv 全称是 Mobile Inverted Bottleneck Convolution是从 MobileNetV2 那块引入的结构但 EfficientNet 对它的使用更系统。拆开看一个 MBConv 模块流程大概是这样先做一个 1×1 卷积做通道扩张通常把通道数放大六倍然后接一个 3×3 或 5×5 的深度卷积也就是每个通道单独做空间卷积接着是一个 SE 注意力模块最后用 1×1 卷积把通道数压缩回原来的规模。如果输入输出形状一致就加一条残差捷径如果 stride 为 2则跳过残差连接并对空间尺寸下采样。这个结构的设计动机很明确深度卷积的计算量比普通卷积小得多因为它不跨通道。普通卷积是每个输出通道要对所有输入通道做加权求和深度卷积则完全没有跨通道交互计算量约为普通卷积的九分之一。倒残差的含义是先压缩再扩张再压缩中间的深度卷积在高维空间里做特征变换两头用低维通道控制整体计算量。这种设计和经典残差块的先降维再升维正好相反所以叫倒残差。实际使用中B0 到 B7 的 MBConv 配置并不完全一样。越靠后的阶段会用更大的 expansion 系数和更大的卷积核同时通道数也随缩放系数增长。如果自己重新实现最需要注意的就是 stride 为 2 时残差连接的处理以及 SE 模块在两边的分支形状是否匹配。2.2 SE 注意力为什么会提升准确率SE 模块全称 Squeeze-and-Excitation它做的事情可以概括成一句话让网络在通道维度上学会“注意该注意的”。具体操作是对特征图做全局平均池化把每层通道压缩成一个数值然后经过两个全连接层中间经过一次非线性激活最后用 sigmoid 输出一组 0 到 1 之间的权重把原来特征图的每个通道按权重重新标定。这个设计初看很朴素但它抓住了卷积网络的一个弱点默认情况下每个通道对输出的贡献是一样的不管这个通道捕获的特征是否有用。SE 相当于是给每个通道装了一个可学习的阀门模型训练时自动学会哪些通道重要、哪些通道可以不要。它的计算开销很小全连接层把通道数先压到原来的四分之一或八分之一只增加少量参数和 FLOPs但带来的精度提升通常非常可观。在 EfficientNet 里SE 模块被放在深度卷积之后、最后那个 1×1 投影卷积之前。这个位置很讲究深度卷积已经把空间信息提取了一轮SE 根据全局信息调整通道权重再做通道混合信息流更顺畅。如果你把 SE 放到模块最前面或最后面效果都会打折扣这是我在实验中试过的一个细节。2.3 Swish 激活函数带来的一点点甜头EfficientNet 的另一个细节是激活函数用了 Swish。Swish 的公式是 x 乘以 sigmoid(x)它跟 ReLU 最大的区别是ReLU 在输入为负时硬截断为 0Swish 在输入为负时仍保留一个很小的负梯度ReLU 曲线是不光滑的折线Swish 是光滑且非单调的曲线。光滑和非单调的特性理论上能让梯度在负区间也保持流动对深层网络的训练更友好。很多实验发现在相同条件下把 ReLU 换成 Swish精度会小幅提升而且很少出现因为激活函数导致的训练动荡。代价是 sigmoid 的计算量略高但在现代框架中Swish 被融合成了 SiLU在算子层面几乎不增加额外开销。如果你要复现 EfficientNet推荐直接使用框架里已经实现的 SiLU不需要手动拼一个 Swish。手动实现除了慢还容易因为数值稳定性问题在低精度训练时出现 NaN。3. B0 到 B7模型家族参数分析与选型思路3.1 官方模型参数速查下表列出 EfficientNet B0 到 B7 的主要公开参数和 ImageNet 上的 Top-1 精度。注意这些数字是在特定训练策略和数据增强下得到的实际复现会有小幅浮动。模型输入分辨率参数量理论FLOPsImageNet Top-1B02245.3M0.39B77.1%B12407.8M0.70B79.1%B22609.2M1.0B80.1%B330012M1.8B81.6%B438019M4.2B82.9%B545630M9.9B83.6%B652843M19B84.0%B760066M37B84.3%这里有个观察很有意思B5 到 B7 的参数量和 FLOPs 都在大幅增长但精度只涨了不到一个点。这说明靠无脑提高分辨率和网络宽度来刷精度收益会越来越小这恰恰也印证了复合缩放思路本身的边界。实际业务里B4 以上的版本往往只在精度要求极高的场景出现大部分项目集中在 B0 到 B4 之间。3.2 边缘端、服务端、高精度任务各自怎么选选型第一步不是看精度表而是定算力预算。边缘端设备如果只允许几百兆 FLOPs 的内存和延迟预算B0 或 B1 是合理选择它们的参数量只有 5M 到 8M量化到 INT8 后体积很小可以跑在低功耗芯片上。服务端推理如果瓶颈是 GPU 显存B4 到 B5 更合适因为它们在 19M 到 30M 参数量下就能达到 83% 以上的精度。如果追求极致精度且不在乎推理时长B7 是官方常规版本里最强的一个。但实际项目中B7 的 600 输入分辨率对数据预处理和显存占用压力很大通常需要混合精度和模型并行才能舒服地跑起来。还有一类场景是实验室刷分。如果只看榜单数字直接上 B7 甚至更激进的变体会显得很能打但这背后大量训练资源被牺牲掉容易得不偿失。我通常建议先摸清自己的显存是否装得下对应分辨率的 batch再决定上哪档。一个稳妥的操作是先用 B0 跑通全流程再慢慢往上换更大的版本这样调试成本最低。3.3 当 Backbone 用在检测和分割里时的注意事项EfficientNet 不仅在分类任务里发光作为检测和分割的主干网络也很常见。把它用作 Backbone 时有几个点必须想清楚。第一个是下采样节奏。EfficientNet 的每个 stage 输出不同分辨率的特征图拿到检测框架里做 FPN 时通常要选 P3、P4、P5 这几层。不同缩放的 stage 结构和通道数不一样需要和 FPN 的通道映射对齐这在自定义实现里特别容易出错。第二个是输入分辨率。检测任务里 Backbone 的输入分辨率不等于分类任务的标准分辨率。很多检测框架会用更大的输入比如 800×1333这时如果直接把分类预训练权重拿过来某些 stage 的池化层或 padding 方式可能出现对齐问题。EfficientNet 没有全局池化所以对输入尺寸的容错性整体还行但下采样过程对奇数尺寸不友好输入尺寸最好都能被 32 整除。第三个是训练成本。EfficientNet 系列在检测任务里收敛速度往往比 ResNet 慢因为深度可分离卷积和 SE 模块带来更复杂的梯度流。一般建议加载 ImageNet 预训练权重同时把训练轮数适当拉长。如果数据量很小从零训练 EfficientNet 主干经常会吃亏不如先用 ResNet 练个基线再考虑切换。4. 实战EfficientNet 图像分类的完整复现4.1 数据准备与环境配置假设手头是一个十类的小型图像数据集每类几千张图。这种规模下我不会从零训练 EfficientNet而是直接加载预训练权重做迁移学习这也是绝大多数业务场景的真实做法。环境方面PyTorch 是目前最顺手的框架因为它生态里既有官方实现又有大量预训练权重直接调用就行。显存 8G 以下建议用 B08G 到 16G 可以跑 B216G 以上再考虑更大版本。如果显存紧张最优先做的是开启混合精度训练这一项能直接省掉将近一半显存而且对精度几乎没有损伤。数据处理部分主要做几件事图片解码后缩放到训练分辨率随机裁剪、随机翻转、颜色抖动最后归一化。EfficientNet 论文里还用了 RandAugment 一类的自动增强策略对小数据集很有帮助因为它相当于免费扩大了数据分布。代码实现上直接用现成的图像增强库就能完成。4.2 模型加载、微调训练的实现示例加载模型时可以直接从 torchvision 里导入 EfficientNet B0替换最后的分类头。核心代码长这样import torch import torch.nn as nn from torchvision import models # 加载预训练权重 model models.efficientnet_b0(weightsmodels.EfficientNet_B0_Weights.DEFAULT) # 替换分类层适配自己的类别数 num_classes 10 in_features model.classifier[1].in_features model.classifier[1] nn.Linear(in_features, num_classes)替换分类头时要注意EfficientNet 的分类器前还有一层 Dropout通常默认是 0.2小数据集可以保留或适当调低。如果数据集只有几百张我建议前几十个 epoch 冻结主干只训练分类头因为这时候骨干网络的大梯度很容易破坏预训练特征。等分类头收敛后再解冻主干用较小的学习率全部微调几个 epoch。训练时使用交叉熵损失优化器选 AdamW初始学习率设置在 1e-3 到 3e-3 之间weight decay 给到 1e-4 左右。batch size 按显存能装下的最大程度来设一般 32 到 128 之间。学习率用余弦退火调度配合 warmup可以在刚开始训练时避免大幅度震荡。4.3 训练细节学习率、EMA、正则化训练 EfficientNet 和训练 ResNet 最大的区别在于它对正则化更敏感。因为深度可分离卷积的参数量相对少SE 模块又引入了注意力机制模型在中小数据集上更容易过拟合。我踩过的一个典型坑是不开正则化时训练集精度很高验证集精度在二十个 epoch 后开始下滑明显是过拟合。解决办法有三个方向。第一是把数据增强力度拉大尤其是随机裁剪比例和翻转概率RandAugment 也可以上它的强度系数从小到大调观察验证集变化。第二是把 dropout 概率提高尤其是在分类头前面的那层0.3 到 0.4 都算合理范围对于特别小的数据集可以加到 0.5。第三是用 EMA 指数移动平均维护一份参数的历史均值验证时使用这份平均参数通常能稳定提升两三个点的精度。EMA 的另一个好处是让训练过程更平滑。我在多个数据集上对比过开了 EMA 的模型在训练后期几乎没有振荡验证曲线更稳定这对最终挑选 checkpoint 很有帮助。实现起来也不复杂只需要在每个 step 结束后按动量系数更新 shadow 参数等训练结束再从中取值做验证即可很多训练框架都有现成接口。4.4 推理阶段的优化与导出训练完的模型要落地还需要经过转换和优化。最快的转换路径是导出 ONNX再用推理引擎的优化后端加快速度。导出前要注意输入尺寸固定因为 ONNX 对动态尺寸支持虽有但会引入额外算子影响性能。如果业务要求多分辨率输入建议分别导出几个静态尺寸的模型或者直接选择所有场景中最大的分辨率。如果目标是边缘设备INT8 量化几乎是绕不开的一步。量化方式有两种训练后量化和量化感知训练。数据分布和原始 ImageNet 差异大时训练后量化容易掉点这时推荐做量化感知训练也就是在微调阶段模拟低精度误差让模型学着自己适应量化噪声。这一步通常能挽回大部分精度损失。还有一个容易被忽略的是 BatchNorm 折叠。ONNX 导出时框架一般会自动把 BatchNorm 折叠进前面的卷积层。如果发现导出的模型在推理引擎里速度变慢可以先检查是否做了折叠再检查深度卷积算子是否被映射到高效内核。不同推理后端的算子支持差异挺大换一个后端可能速度就完全不同这些都需要提前做 benchmark。5. 实战中的坑与排查技巧5.1 复现精度对不上的各种原因我自己复现 EfficientNet 时最常见的困惑就是为什么论文里数据那么漂亮自己训出来就少了一两个点。这里有一个非常现实的原因ImageNet 的训练是一套极其复杂的流程包含几百轮的训练、多种增强策略、EMA 和精细调参。业务数据上的微调与从头训练的精度没有直接可比性不要被绝对数字绑架。如果你确实在小数据集上发现模型不收敛首先检查输入分辨率。EfficientNet 的预训练权重对应某个固定分辨率微调时如果擅自改成别的分辨率精度会受影响。相对安全的做法是沿用预训练时对应的分辨率或者至少保证统一裁剪比例训练和验证都用同样的预处理逻辑。另外一个常踩的坑是标签不平衡。EfficientNet 在分类头上没有做任何特殊处理如果类别分布差异很大模型会倾向预测多数类。这时候需要在损失函数里加类别权重或者用 Focal Loss 替代交叉熵。单纯加大学习率并不能解决这个问题。5.2 显存不足和训练不收敛的解法显存不够是最常见的硬性问题。优先开启混合精度训练这一步能省一半显存代价几乎为零。如果还不够可以缩减 batch size 并配合梯度累积用多个小 batch 的梯度累加来模拟大 batch 的效果模型质量不会明显下降只是训练时间会变长。还有一个技巧是把 EfficientNet 的输入分辨率做阶梯式提升。先在小分辨率下跑通全流程当时不炸再逐步提高分辨率。这个思路跟论文里的渐进式训练类似实测既能省显存又能稳定训练过程尤其适合那些数据分布不太规整的项目。训练不收敛时不要急着调模型结构先看两件事预训练权重的加载是否成功以及学习率是否过大。B0 的参数量虽小但 SE 模块会让梯度分布不太均匀学习率稍大就容易震荡。用 AdamW 时可以从 1e-3 起步调低到 3e-4 试试有时候只是这一个参数就把问题解决了。5.3 这些场景下别执着于 EfficientNetEfficientNet 不是万能的。有些场景下它的收益其实不明显甚至还会拖后腿。第一种是超低延迟场景。深度可分离卷积在部分硬件上可能没有优化实际耗时反而大于普通卷积。这时候要么换一个为硬件深度优化的精简模型要么选择把 MBConv 替换成普通卷积的变体后者并不罕见比如后来的 Fused-MBConv 就是因为这个原因被提出的。第二种是超大 batch 分布式训练场景。EfficientNet 的参数量小但训练内存开销并不低因为深度卷积中间特征图很大batch 增大后通信和显存压力反而比同精度的其他结构高。这种情况建议先用 ResNet 或更简结构跑通再考虑上 EfficientNet。第三种是数据量特别大的场景。如果你有上千万张图且计算资源充足EfficientNet 的复合缩放收益会被更通用的训练策略覆盖很多后续工作也证明了这一点。不用为了“更先进”而强行使用它模型只是工具目标是精度和成本的最优解。6. 从 EfficientNet 到 EfficientNetV2设计语言的演进6.1 EfficientNetV2 改了什么EfficientNetV2 是在原版基础上对训练效率和结构细节做了进一步优化的版本它最重要的改动有两个。第一个改动是引入 Fused-MBConv。Fused-MBConv 把原版里的 1×1 扩张卷积和 3×3 深度卷积合并成一个普通的 3×3 卷积。这个改动看起来是“倒退”但它解决了深度可分离卷积在 GPU 和移动端上运算效率不高的问题。普通卷积在大多数硬件上有现成的高度优化实现数据复用也好得多所以实际延迟反而更低。第二个改动是渐进式训练策略。EfficientNetV2 在训练过程中动态调整输入分辨率、数据增强强度和网络正则化强度。刚开始用小分辨率和弱增强让模型快速学习数据分布后期逐步加大分辨率和增强强度模拟从粗到精的学习过程。这个策略能在更少训练轮数内达到同等精度是一个工程价值很高的思路。6.2 这套设计思想对后来模型的影响从 EfficientNet 流行起来之后复合缩放和 SE 模块几乎成了新一代高效网络的标准配料。许多后续模型都会在缩放策略上做文章要么把搜索空间改得更贴合硬件性能要么把复合缩放扩展到宽度、深度、分辨率之外的维度比如核大小、注意力头数等。这种“把缩放变成一个可优化问题”的思路影响力超出了 EfficientNet 本身的成绩。同时EfficientNet 也让大家重新意识到 NAS 的价值。它证明在一个受限但设计良好的搜索空间里结构搜索得到的网络可以比人工设计网络更均衡。后来很多这类工作都会引用这个结论把搜索结果用于实际部署场景。6.3 我对这套模型的一些个人经验与使用心得项目里用过 EfficientNet 一年之后我最大的体会是“效率”不是只看推理一次要多少 FLOPs而是整个数据链路从训练到部署的性价比。EfficientNet 最让我佩服的不是某一个结构细节而是它把模型设计变成了一个有约束的优化问题让后来做模型选型的人有了一个明确预算表。如果现在启动一个新项目我会这样用 EfficientNet数据量小、算力有限时直接用 B0 或 B1 做迁移学习把验证集结果跑出来再说数据量大、显卡多时把 B4 当作默认主力版本来对比对目标和极端尺寸有要求时考虑用更大版本的家族同时配合 EMA 和量化感知训练保精度。EfficientNet 未必在每个任务里都是最优的但它一定是那个能让你快速建立 baseline、心里有底气的选择。
阅读完成 · 觉得有帮助?