4.4 轻量化网络 MobileNet、ShuffleNet与EfficientNet
1.为什么需要轻量化:参数和算力都得省着用
说起来,前面3.3 节我们聊过卷积神经网络,4.2 节又顺着捋了一遍经典分类网络,4.3 节讲了目标检测的R-CNN家族。这些网络越做越深、越做越宽,精度是一路往上走的。可你大概也注意到了,它们都有个共同的脾气,就是吃算力、吃内存、吃电量。ResNet也好,Faster R-CNN也好,离了显卡跑起来都费劲。问题是真到了现实里,好多设备根本塞不进一张显卡。
举几个例子。小明前阵子做了一个手机APP,想拍一张菜就识别是什么菜,结果把一个标准分类模型塞进去,预览画面卡得像幻灯片,手机背面烫得能煎蛋。我有个朋友在工厂里给流水线装了一台工业摄像头做表面缺陷检测,那台小盒子大小的边缘小电脑连独立显卡都没装,全靠一块低功耗芯片硬撑。再想想无人机、智能门铃、助听器、手腕上的运动手环,这些设备算力小、电池小、散热还差,根本扛不动几十层上百兆参数的大模型。
所以轻量化网络应运而生。它的目标很明确,在精度尽量不掉的前提下,把参数量和计算量都压下去。这里有两个常用指标先说清楚。一个是参数量,单位是个,就是模型里一共要学多少个权重。另一个是FLOPs,全称是浮点运算量,单位常写成M(百万次)或者G(十亿次),衡量模型跑一次要做多少次浮点运算。比方说一个FLOPs=500M的模型,跑一次大约做五亿次浮点运算。轻量化网络通常把FLOPs压在几百M以内,参数量压在几M以内,才能在小设备上跑得动、跑得快,还不怎么发烫。
2.MobileNet v1:把卷积一拆为二
Google在2017年提出的MobileNet v1,是轻量化的开山之作之一。它的核心招式叫深度可分离卷积(depthwise separable convolution),思路其实很朴素,就是把一个标准卷积拆成两步来做。
我们一步步拆。一个普通的标准卷积层,输入是一个 通道的特征图,输出是 通道,这里 表示输入通道数, 表示输出通道数。卷积核的空间大小记作 ,输出特征图的高和宽分别记作 和 。这一层要做的浮点运算量大约是 。
深度可分离卷积把这件事拆成两步。第一步叫逐通道卷积(depthwise),每个通道单独用一个小卷积核去卷,通道之间互不混合。这一步用到的卷积核大小是 ,每个通道只跟自己卷一次,运算量大约是 。第二步叫逐点卷积(pointwise),其实就是一个 卷积,专门负责把不同通道的信息混合起来,输出通道数凑到 ,运算量大约是 。
把两步加起来,跟原来的标准卷积比一下,运算量的比值大约是:
举个常见的数,,,那这个比值大约是 ,也就是说只花原来九分之一左右的算力,效果却接近原来。这一招真是巧妙,说穿了就是把空间上的混合和通道间的混合分开来做,省下大量重复的乘加。我记得有本讲工厂管理的书里提过,流水线效率提升的关键往往在把一道复杂的工序拆成两道简单的,道理是相通的。
MobileNet v1还配了两个调节旋钮。一个叫宽度乘子,记作 ,作用是按比例把每一层的通道数缩放, 取值通常在 到 之间, 就是把每层通道数砍到原来的四分之三。另一个叫分辨率乘子,记作 ,作用是按比例缩放输入图像的分辨率, 取值也在 到 之间。把 调小,参数和算力都省了,精度也跟着掉一点。把 调小,输入图更小,算力再省一截,精度同样会掉。这两个旋钮就像相机上的光圈和快门,可以按设备的具体情况搭配,找到对当下最划算的那个平衡点。
3.MobileNet v2:倒过来反倒更好
v1效果不错,但训起来有个老毛病。网络后半段特征图维度偏低的时候,ReLU激活函数会把不少信息直接抹掉,最后那层线性特征变得特别稀疏。MobileNet v2针对这个问题,提出了两个相互配合的新设计,一个叫倒残差结构(inverted residual),一个叫线性瓶颈(linear bottleneck)。
我们先回顾一下残差结构。4.2 节讲ResNet的时候提过,残差块的做法是先用 卷积把通道压低,再做 卷积,再用 把通道升回去,整体是个两头宽、中间窄的沙漏形状。倒残差刚好反过来,先用 卷积把通道升上去(升得还挺高,比方说升到原来的六倍),再做 的逐通道卷积做空间混合,最后再用 卷积把通道降回来,整体是个两头窄、中间胖的纺锤形状。
为什么非要倒过来呢,是因为低维特征经过ReLU这种非线性激活的时候,损耗特别大。倒残差先把通道数升上去,让信息在高维空间里转一圈再降到低维保留下来,损耗就小多了。线性瓶颈这个名字指的是最后那一步降通道的时候,作者建议不再加ReLU之类的非线性激活,而是直接输出,也就是让这一段保持线性。这么一来,瓶颈处(也就是通道数被压低的那一段)的信息能保得更完整。
我有个做翻译的朋友跟我说过,他译一篇文章前会先把原文每句话的隐含意思摊开来充分理解,再凝练成简洁的译文。倒残差的工作方式有点像这个,先把信息升到一个宽裕的高维空间里慢慢处理,再压回精炼的低维表示,中间那段宽裕的空间才是表达力的来源。倒残差加线性瓶颈一组合,v2在同等算力下精度比v1高出一截,一度是手机端推理的事实标准。
4.ShuffleNet:让分组卷积的信息跨组流动
同样是2017年,旷视提出了ShuffleNet。它走的是一条更狠的省算力路线,核心是把 卷积这种最占算力的部分换成分组卷积(group convolution)。分组卷积的想法是把通道切成若干组,组内自己卷自己,组间互不干扰,这样一来 卷积的算力大幅下降。
可这里有个新问题。组之间一直不交流,信息就被困在了各自的通道组里,几个组各学各的,特征没法融合,精度肯定要掉。ShuffleNet的关键操作叫通道重排(channel shuffle),专门来解决这个麻烦。具体做法是,假设我们把通道分成 组, 表示分组数,每一层卷积算完之后,把输出通道整体打乱重新排列,让原本属于同一组的通道被打散到不同的新组里。下一层卷积的时候,新组里就混着上一层好几个老组的通道,信息就又流通起来了。
打个比方,这有点像学校重新排座位。要是一群学生按入学成绩分班之后三年不变,那班级之间彼此老死不相往来。可要是每隔一段时间,学校就把所有学生重新打散分班,原本一个班里的同学就分散到各处,新班级里又能交到新朋友。通道重排做的就是这件事,让分组卷积省下来的算力不白省,模型还能继续学得到跨通道的信息。
ShuffleNet在算力极低的设备上表现尤其好,十几兆参数就能做出一个像样的小模型。它在不少工业相机的边缘模块里都有应用,做产品外观检测、流水线分拣,跑起来又快又省电,工人师傅不用再围着显卡机箱转。
5.EfficientNet:深度、宽度、分辨率一起按比例放大
聊到这儿你大概也看出来了,调一个网络其实有三个可以拨动的方向。一是深度,也就是网络有多少层,二是宽度,也就是每层有多少通道,三是输入分辨率,也就是送进来的图有多大。之前大家的做法多半是单独调一个,要么把网络做深一点,要么把通道做宽一点,要么把图开大一点,三个方向各自为政。
Google在2019年提出的EfficientNet给出了一个更系统的方法,叫复合缩放(compound scaling)。它的核心想法是,这三个方向是有机联系的整体,单纯放大其中一个会很快碰天花板。比方说网络一味加深,精度提升越来越慢,训练却越来越难。要想用最少的算力换最大的精度提升,三个维度得协同放大,按一个固定的比例一起变。
EfficientNet用一个统一的复合系数 (读作phi)来控制整体放大的力度, 就是我们要指定的总缩放系数。再用三个固定的小系数 、、 分别决定深度、宽度、分辨率各自的放大比例(这里特意换成拉丁字母,是为了不和上面MobileNet的宽度乘子 混淆)。设基础模型的三项分别是 、、,那放大之后的深度、宽度、分辨率满足:
约束条件是 ,意思是 每增加 ,总浮点运算量大约翻一倍。这一约束是有讲究的,因为卷积层的总运算量大致跟深度成正比,跟宽度的平方成正比(输入和输出通道一起翻倍),又跟分辨率的平方成正比(高和宽一起翻倍),所以三个系数按这个约束搭配,才能保证 增加一时算力恰好翻一倍。三个常数 、、 的具体数值,是作者先在一个小基准上做网格搜索定下来的。
作者还先用神经网络架构搜索(NAS)找到了一个小而精的基础模型EfficientNet-B0,再用复合缩放一路放大到B7。从B0到B7,精度一路涨,最厉害的B7在ImageNet上的精度做到了当时同量级里的最高,算力却比同精度的前辈们省得多。我记得有个长跑运动员叫基普乔格,他第一次把马拉松跑进两小时那次,靠的是配速、步频、补给、路线、天气整套系统配合到位,单点再怎么发力也补不齐别处的短板。EfficientNet的思路其实跟这个挺像,三个维度协同调好,整体效率自然就上来了。
6.FLOPs低不等于真机跑得快
讲到这儿还有一个特别容易踩的坑,必须提醒你。FLOPs低,只代表理论计算量小,并不直接等于真机上跑得快。实际推理延迟还要看很多东西,包括内存访问的快慢、缓存命中率、专用指令能不能用上、并行度够不够,甚至散热不行被降频也算一份。
举个真实的例子。小张当年做毕业设计,挑了一个FLOPs特别漂亮的小模型部署到手机上,指望做到实时识图。结果一上真机,每帧还是要八十多毫秒,离实时差得远。后来他用手机自带的推理框架做了一次profiling(性能剖析),发现瓶颈根本不在浮点运算上。这个模型用了大量零散的小算子,每个小算子都要单独启动、单独读写内存,光开销就把算的功夫淹了。后来他换了一个算子更规整、内存访问更连续的模型,FLOPs反倒稍高一点,真机延迟却降到了三十毫秒以内,这才算跑顺。
还有嵌入式设备上,有时候要的是稳定的低延迟,有时候要的是低功耗,有时候要的是高吞吐,三样东西往往不可兼得。比方说一台电池供电的野外摄像头,一周才充一次电,那它最在乎的倒是单张图能耗,单张图延迟倒在其次。可一台自动驾驶车上的实时检测模块,最在乎的就是单帧延迟必须低于某个硬上限,超了就算事故。同一种轻量化模型,放到这两种场景下,结论可能完全相反。
所以选轻量化模型,不妨记住三步。第一步,在FLOPs相近的若干候选里挑出几个,初筛一遍。第二步,务必把这几个候选拿到目标真机上实测延迟和能耗,这一步省不了。第三步,结合自己的硬约束(电池、散热、延迟上限)做最终选择。光看论文里报的FLOPs和ImageNet精度就拍板,十有八九要踩坑。4.3 节讲检测的时候我们提过工程化和论文指标之间的差距,到了轻量化这边,这个差距只会更大。
按惯例这里该收个尾。MobileNet的两代做的是结构上的精打细算,ShuffleNet做的是分组之间信息的灵活流转,EfficientNet做的是缩放方法上的整体规划,三条路各有各的巧思。下一章我们继续聊视觉里的其他话题,回见。
练习
Q1. MobileNet v1 的深度可分离卷积把标准卷积拆成了哪两步?为什么能省算力?
拆成两步:第一步逐通道卷积(depthwise),每个通道单独用一个小卷积核去卷,通道之间互不混合;第二步逐点卷积(pointwise),用 1×1 卷积把不同通道的信息混合起来凑到输出通道数。说穿了是把空间上的混合和通道间的混合分开来做,省下大量重复的乘加。比值约为 ,常见取 、 时约 ,只花原来九分之一的算力。
Q2. 取 、,深度可分离卷积相对于标准卷积的算力比值是多少?又,MobileNet v2 的"倒残差"为什么倒过来反而更好?
比值约为 ,约九分之一。倒残差先 1×1 把通道升上去、再 3×3 逐通道卷积、最后 1×1 降回来,整体是两头窄中间胖的纺锤形。因为低维特征过 ReLu 这种非线性激活损耗特别大,先升到高维空间转一圈再降到低维,信息损耗小得多;最后降通道那段还建议不加非线性(线性瓶颈),让瓶颈处信息保得更完整。
Q3. 小张挑了个 FLOPs 特别漂亮的小模型部署到手机,结果每帧还是八十多毫秒,为什么?选轻量化模型该怎么避坑?
因为 FLOPs 低只代表理论计算量小,不等于真机跑得快。实际延迟还看内存访问快慢、缓存命中率、专用指令能不能用上、并行度够不够。那个模型用了大量零散小算子,每个都要单独启动、单独读写内存,光开销就把算的功夫淹了。避坑三步:先在 FLOPs 相近的候选里初筛,再把候选拿到目标真机实测延迟和能耗(这步省不了),最后结合电池、散热、延迟上限等硬约束做选择,别光看论文里的 FLOPs 和精度就拍板。