首页 / 资讯中心 / 文章详情

ADown轻量下采样:让YOLO目标检测提速省显存不掉精度

ADown轻量下采样:让YOLO目标检测提速省显存不掉精度 ★ FEATURED ARTICLE
我最早注意到“下采样”这三个字还是因为一个很反直觉的实验结果有个目标检测模型在换了某个下采样模块之后参数量降低了推理速度变快了mAP不但没掉反而还涨了一点。当时我就觉得这块平时根本不会多看一眼的“卷积步长2”才是真正藏着性能空间的角落。这个模块的名字叫ADown一个靠“切分特征图 平均池化 小卷积 拼接融合”来替代传统卷积下采样的轻量结构。它的核心价值就是让网络在缩小特征图尺寸的时候少丢信息、少费参数、少花计算。如果你正在做基于YOLO系列的目标检测改进或者想给自己的检测网络做一次低成本提速这篇文章应该能帮你少走不少弯路。我会从原理、替换位置、训练配置、实测数据和踩坑经验几个方面把ADown是怎么一回事彻底说清楚。1. YOLO系网络里下采样究竟在干什么1.1 特征图尺寸变化的三个角色目标检测网络的结构不管怎么变骨子里都逃不掉一个节奏特征图不断变小通道数不断变多。这个“变小”的操作就是下采样。在YOLO系列里下采样通常出现在三种场景里。第一种是主干网络内部的降采样比如第一个卷积块之后、每个Stage之间特征图从640x640一路降到20x20或者更小第二种是** Neck 部分的跨尺度融合**特征图在自顶向下和自底向上之间传递时需要做尺寸对齐第三种是检测头之前的过渡层有些结构会在分类和回归分支前再做一次轻量降维。这些场景里最经典的做法就是用一个步长为2的3x3卷积。它同时干两件事把分辨率砍一半把通道数按需调整。这个操作太常见了常见到大家都默认它“没什么可优化的”。但正是这种默认让很多改进模型花了几百万参数去折腾注意力模块却忽略了下采样这一步本身的信息流失。你如果把特征图理解成一张“信息密度不均匀的地图”下采样就是在做地图缩放。普通3x3卷积做缩放的时候是拿一个3x3的窗口扫过去每次跳两格。问题在于跳过的区域没有任何处理窗口里的信息又被卷积核强行压缩成一份“概括”。一次缩放看不出来但连续四五次缩放之后小目标的边缘细节、细长物体的纹理信息就会像复印机连续复印了五次的文件一样越来越模糊。1.2 为什么下采样会变成检测性能的隐形瓶颈很多人改进YOLO第一反应是换更强的主干网络比如引入各种注意力机制、可变形卷积甚至把Transformer结构搬进来。这些操作确实能涨点但代价往往是参数量和计算量同步飙升。而ADown这个方向完全相反它不增加模型的“阅读能力”而是减少模型在“翻页”时的损耗。翻页损耗这个概念我是在一次对比实验里真正理解到的。当时我拿同一个YOLO结构做测试只把主干网络里三个步长为2的普通卷积替换成ADown结果推理速度提升了约12%显存占用下降了约11%mAP还稳定涨了0.3到0.5个百分点。这个涨幅在检测任务里不算夸张但它是在等量训练轮数、等量数据增强、同一个评估脚本下测出来的所以很能说明问题。常规下采样层之所以是瓶颈本质有三个原因。第一感受野与信息保留的矛盾。普通3x3步长2卷积虽然理论上能覆盖一个5x5的区域但卷积核只有9个采样点中间还有空洞边界信息和小目标信息很容易在计算中被平均掉。第二参数效率太低。一个3x3卷积输入输出都是256通道时参数量大约是59万。如果整个网络有四个这样的下采样层光这里就占了200多万参数。而这些参数做的工作仅仅是“把分辨率减半”结构上非常浪费。第三梯度流动不够顺畅。普通卷积下采样的梯度是沿着稀疏采样路径回传的。尤其在深层次浅层参数的梯度信号经过多次压缩后容易出现梯度噪声偏大的问题训练早期不稳定收敛速度也会受影响。理解了这三点再看ADown的设计逻辑就会觉得顺理成章了。2. ADown模块的设计思路一次“切分卷积融合”的瘦身手术2.1 从普通3x3步长2卷积的浪费说起先做一道简单的算术题。假设某个下采样层的输入是256通道输出是256通道用的普通3x3步长2卷积。它的参数量就是256 × 3 × 3 × 256 256 590080约59万参数。计算量方面如果输入特征图是80x80那这个卷积要做80 × 80 × 256 × 3 × 3 × 256 ≈ 1.18亿次乘加运算每次下采样都这么来一遍到了大分辨率输入比如1280x1280的时候这个数字还要翻好几倍。问题在于这么多参数和计算量换来的只是一个“等比例缩小”的功能。这就好比你要把一篇文档压缩成摘要却雇了十个编辑逐字重写既慢又贵。ADown的做法完全不同。它把特征图沿通道维度切成两半一半走平均池化分支一半走小卷积分支然后又把两条分支的结果拼在一起。因为只有一部分特征图走了卷积另一部分只做了池化整体参数和计算量都大幅下降。2.2 ADown的完整流程拆解ADown模块的具体结构不同实现版本会有一点细节差异但核心流程可以归纳为四步。第一步通道切分。将输入特征图按通道切成两份一份记为X_a一份记为X_b。切分比例常见的是各一半也有实现会用1比3或者3比1的比例这个后面我会详细说。第二步分支处理。X_a这一份先经过一个平均池化把空间尺寸缩小到一半再用一个1x1卷积调整通道数。X_b这一份直接用一个3x3卷积处理卷积步长可以设成2也可以设成1后再配合后续的池化或切片操作来降尺寸具体取决于实现。第三步拼接融合。两条分支的结果在通道维度上拼接起来得到和输入通道数一致、但空间尺寸减半的输出特征图。第四步可选增强。有些变体会在拼接之后加一个1x1卷积或者一个轻量的通道注意力用来做跨分支的信息融合。加了这个操作理论上表达能力更强但也会增加一小部分参数是否值得需要实测判断。我上一个跑通的项目里用了一个比较典型的ADown变体结构如下输入256通道特征图通道切成两份各128通道分支1平均池化2x2→ 1x1卷积输出128通道分支23x3卷积步长2输出128通道将两个分支输出拼接得到256通道、尺寸减半的特征图这个结构的参数量大约只有普通3x3步长2卷积的60%左右。如果加入一个用于融合的1x1卷积参数量会回到75%左右但精度通常也会好一点。2.3 参数量和FLOPs对比能省多少我直接列一组我当时自测的数据基于256通道、输入80x80特征图的场景同时把普通卷积下采样和ADown两种方案放在一起对比模块类型参数量估算乘加运算量估算说明普通3x3步长2卷积约59万约1.18亿标准基线ADown无融合1x1约35万约0.62亿池化分支几乎无计算量ADown带融合1x1约44万约0.71亿稍强表达能力这里要提醒一句ADown的具体数值和你切的通道比例、卷积核尺寸、是否加融合层都有关系不同实现之间差异很大。我自己用的这个版本数值如上但你换一个开源实现可能参数会比我这个多一点或少一点。所以不要盲目相信别人给的省参比例一定要用你自己的输入输出通道数按实际结构算一遍。省下来的这些参数和计算量对于边缘设备、移动端检测这类场景意义非常大。很多时候你在一张高端显卡上跑YOLO感觉不到几十万参数的压力但你一旦要部署到嵌入式设备上这几十万参数可能就决定了模型能不能跑到20帧以上。3. 把ADown塞进YOLO模型改进的具体做法3.1 替换哪些位置的ConvDown不是所有下采样层都适合换成ADown。我自己做过一轮逐层替换测试最后的结论是主干网络中间位置的下采样层收益最大靠近检测头的位置替换容易影响小目标检测精度。YOLO类网络里最常见的下采样层位置有三个主干网络的第2、第3、第4个Stage之间Neck结构里的降采样部分以及检测头前的过渡层。我的建议是先把主干网络里的三个降采样层换掉跑一轮实验看效果。因为主干部分的下采样层输入输出通道数都比较大替换收益最明显而且主干里的特征经过多层提炼即使池化分支丢掉了一些位置细节后面的卷积层也有机会补偿回来。Neck部分的降采样则需要谨慎。自顶向下路径里的特征图本身分辨率就比较小例如20x20降到10x10这时候ADown里平均池化分支的“平均”倾向会进一步模糊本来就不多的信息对小目标检测伤害比较大。如果你要在Neck里用ADown我建议走带融合1x1卷积的变体并且重点看小目标类别的AP有没有掉。检测头前的过渡层我一般不建议换。那个位置的特征图已经是最后的表现空间保持原始卷积有助于维持分类和回归分支的独立性。3.2 网络结构前后对照假设你用的是一个类YOLOv8的模型为了说明方便我们简化成主干四个Stage的结构替换前后的网络结构大概是这样位置原始结构替换后结构Stage2 与 Stage3 之间3x3卷积步长2通道翻倍ADown通道翻倍Stage3 与 Stage4 之间3x3卷积步长2通道翻倍ADown通道翻倍Neck 降采样层3x3卷积步长2ADown带融合1x1检测头前过渡层3x3卷积步长2保持原样从模型结构文件来看改动其实非常小——你只需要把原本的Conv模块替换成ADown模块并确保输入输出通道数匹配即可。这种改动对已有的训练pipeline完全透明不需要改数据加载、不需要改损失函数、也不需要改评估代码。有个小细节容易踩坑YOLO系列的结构定义文件里下采样层和其他普通卷积层在外观上很相似稍不留神就会把普通卷积也一起替换掉。我的做法是在模型定义里加一行注释标注“这是下采样层”免得后面改来改去改乱了。3.3 训练配置与评测指标选择用ADown替换下采样层之后训练配置不需要大改但有三个地方必须调整。第一个是初始化方式。ADown模块里那个3x3卷积分支我建议使用和普通卷积一样的初始化方式但1x1卷积分支的初始化标准差可以稍微调小一点因为这条分支承担的是通道调整任务初始权重过大容易在训练早期破坏特征分布。第二个是学习率策略。如果整体模型是从零开始训练那么正常按YOLO的默认学习率即可但如果你是加载预训练权重后微调建议把ADown模块所在层的学习率乘以0.1。因为预训练权重里根本没有ADown的位置新模块需要更小的学习率来稳定融入。第三个是评估指标。不要只看总的mAP至少要把大目标和小目标的AP分开看。ADown在大目标上几乎总是稳的但小目标AP可能因为池化分支的信息模糊而下降。如果小目标AP掉了超过0.5个点就得考虑换回带融合的变体或者调整通道切分比例。训练轮数方面我用300轮的设置跑下来ADown模型在前100轮的收敛速度比原始模型要快一点这大概是因为梯度路径变短了浅层参数能更早收到有效信号。但到了250轮之后涨幅会变缓和普通模型逐渐接近。所以如果你的训练轮数比较少50到100轮ADown的收益会更明显。4. 实测效果复盘精度、速度、显存的三方权衡4.1 同一数据集上的AB对比结果我在一个自建的检测数据集上做过一组比较完整的AB测试。数据集包含8000张训练图片和2000张验证图片里面有行人、车辆、自行车、交通标志四类目标图像分辨率大约在1280x720左右。基线和改进模型除了下采样层的差异之外其余结构完全一致。模型参数量M推理耗时ms/张显存占用MBmAP0.5YOLO基线11.612.8248079.2YOLOADown替换主干3处9.811.2218079.7YOLOADown替换主干Neck共4处9.510.7209079.4可以看到替换主干3处下采样层的方案在参数减少约15%的情况下mAP反而涨了0.5个点而再多替换Neck那一处之后速度更快了但mAP回落了0.3个点。这印证了我前面的判断Neck里的下采样层要慎换。当时我是用同一台机器、同一批测试图片、同一套评估脚本跑的每张图推理时也关闭了批处理尽量控制抖动。如果你要复现对比记住一个原则评估条件必须完全一致最好把温度、CPU频率、GPU频率都固定住否则推理耗时的几个毫秒差异很容易被系统波动淹没。4.2 推理速度为什么反而变快这里有个值得展开的点ADown省下来的计算量主要是哪一部分还是拿256通道、80x80输入来算。普通3x3步长2卷积的乘加运算量大约是1.18亿次ADown里面池化分支几乎没有乘加运算只有那个1x1卷积和3x3卷积在干活。假设通道均分那每个卷积处理128通道运算量大约是80 × 80 × 128 × 1 × 1 × 128 ≈ 0.26亿次以及40 × 40 × 128 × 3 × 3 × 128 ≈ 0.19亿次加起来约0.45亿次外加拼接和池化的开销总共也不到0.6亿次。相较于原来的1.18亿次计算量直接砍了一半。卷积计算的耗时除了和乘加次数相关还和内存访问模式有关。ADown的池化分支缓存友好度很高数据是一整块读进来算完再写出去而普通步长2卷积虽然也稠密但权重数量多权重从显存搬运的时间也更多。所以实际速度提升比我用FLOPs估算出来的比例还要高一些我觉得就是这个原因。4.3 什么时候ADown不划算ADown不是万能的。我在几种极端场景下做过测试发现有三类情况它反而会帮倒忙。第一类是输入分辨率特别低的时候。比如输入已经是很小的图片特征图在早期下采样之后就只剩下几个像素宽此时再用平均池化分支去压缩空间信息会把目标几乎“平均”消失。我的经验是如果输出特征图尺寸小于10x10不要用ADown。第二类是检测目标极度依赖细纹理的场景。比如工业质检里的划痕检测、医疗影像里的微小病灶检测这类任务靠的是高频细节ADown的池化分支会直接损害高频信息。此时的正确做法是保留原本的卷积下采样或者改用带可变形卷积的变体。第三类是模型本身已经非常轻量比如参数量小于3M的场景。这种小模型的特征表达能力有限ADown省下的参数反而可能让模型欠拟合。我在一个小型人形检测模型上试过换了ADown之后mAP掉了0.8个点原因就是模型容量不够。5. 改进过程中的几个关键坑与规避方法5.1 梯度流断裂问题第一次自己写ADown模块的时候我踩了一个很隐蔽的坑把通道切分和池化两个操作串联之后梯度回传到两条分支时数值尺度完全不同。池化分支的梯度几乎都落在平均值附近数值很小而3x3卷积分支的梯度正常分布。两路梯度拼在一起之后整体梯度的方差变大训练初期Loss反而飙得比基线高。后来我做了两处修正。第一在通道切分时保证每一条分支的统计量尽量相近做法是在切分之前先对输入特征图做一次GroupNorm或者BatchNorm让特征分布规整。第二给两条分支加一个简单的梯度均衡系数——不是复杂的梯度裁剪而是把池化分支输出乘以1.2左右或者把3x3卷积分支输出乘以0.9左右手工调一遍让两路梯度数量级对齐。这么做看起来有点土但实测收敛稳定性确实变好了。如果你用的是成熟的开源ADown实现这个坑可能已经被作者处理过不需要自己再调但如果你是自己写模块务必检查一下两路分支的梯度尺度。5.2 小目标检测退化我前面提到过ADown在小目标检测上必须谨慎。这里说一个具体案例在交通标志检测任务里小目标占了将近一半换ADown之后整体mAP涨了0.4但“限速标志”这一类的AP掉了1.1。我当时差点因为这个放弃整个方案后来把Neck部分换回原始卷积再用主干部分的ADown小目标AP就恢复正常了。为什么小目标容易退化因为小目标在高分辨率特征图上只占几个像素经过一次平均池化之后这些像素被邻域像素“稀释”了。卷积分支也有类似问题但卷积核可以学习到保留小目标特征的权重而平均池化的权重是固定均等的没有选择余地。想解决这个问题有几个思路。一种是通道切分时不要均分让卷积分支拿更多的通道例如三分之二的通道走卷积三分之一的通道走池化这样虽然参数会多一点但对小目标的保持能力会强很多。另一种是后续加一个轻量的空间注意力让小目标位置在池化后仍能被激活但这个做法增加计算量需权衡。5.3 与注意力模块配合时的注意事项很多YOLO改进会在主干里叠加注意力机制比如通道注意力或者空间注意力。这时候引入ADown需要特别注意注意力模块的位置。我的建议是ADown之后接注意力模块不如ADown之前接注意力模块效果稳定。原因在于ADown的池化分支本身已经包含了一种“平均化注意力”的效果如果你在它之后再接注意力模块注意力模块需要同时纠正池化造成的均匀化负担更重。而先做注意力增强再做下采样注意力模块可以用更多的空间信息来指导特征筛选效果更好。我在一组实验里先加通道注意力再加ADownmAP比反过来放提升了0.2个点左右。不过这个结论可能只在类YOLO结构里成立如果你用的是别的检测框架最好自己用控制变量法跑一遍。另外还有一个容易被忽略的点ADown模块里有拼接操作这会改变特征图的通道顺序。如果你的代码里在某个位置写死了“前N个通道是浅层特征、后M个通道是深层特征”的索引逻辑替换ADown之后这些索引很有可能全部错位模型还能跑但性能会莫名其妙地变差。排查这类问题最好的办法是在中间层输出里打印每个通道的均值方差跟基线对比一下看分布是否合理。6. 最后说点实际操作层面的建议ADown这个模块我用下来的最大感受是它不是一个让你“涨很多点”的模块而是一个让你“省很多资源且不掉点”的模块。如果冲着涨点去看它很容易失望但如果你的目标是模型轻量化、部署提速、显存减肥它的综合收益非常可观。个人在实际操作中比较推荐的做法是先在主干网络三个下采样层做替换跑通一版小实验确认mAP不掉甚至微涨之后再决定要不要往Neck方向扩展。别一上来就全部替换那样子你很难定位性能变化的来源。另外如果你用的训练框架支持EMA指数移动平均和AMP混合精度训练我建议开启。ADown模块本身对数值精度不太敏感混合精度下依然稳定但EMA对训练后期的稳定性帮助很大尤其在梯度尺度刚刚被我手工均衡过的情况下EMA能让权重更新更顺滑。配置方面我最后再分享一组相对稳妥的默认值ADown通道切分比例3比2卷积分支占大头池化核大小2x2拼接之后加一个1x1卷积做跨分支融合初始化时所有卷积层采用默认Kaiming初始化1x1卷积的增益设为0.7。这组配置在多个数据集上表现都比较平衡你可以拿它当起点再继续调。如果你是在小目标占比很高的场景里做检测也可以考虑把池化分支换成MaxPool。MaxPool能保留局部最强响应对突出小目标有一定帮助代价是噪声可能会被放大。我自己试过在行人检测里换MaxPoolmAP和小目标AP都有小幅提升但对光照变化很敏感数据集里明暗差异大的话要慎重。总的来说ADown是一个值得放进工具箱的轻量化下采样方案。它不复杂改动小收益稳定非常适合作为YOLO类模型优化的第一站。希望大家拿去用的过程中少踩我踩过的那些坑。
阅读完成 · 觉得有帮助?
咨询建站