☰
ADown高效下采样:提升YOLOv26小目标检测精度的实战记录
2026/10/20 22:06:43 网站建设 项目流程

做目标检测的人,几乎每天都要和下采样打交道。YOLOv26的主干网络每经过一次stride=2的卷积,特征图就矮一半、瘦一半,计算量确实降下来了,但小目标的纹理信息也正在悄悄流失。ADown高效下采样模块,就是专门来治这个问题的:它把“全局语义”和“局部细节”拆成两条路分开处理,压缩分辨率的同时尽量保住关键高频信息,把它接进YOLOv26之后,mAP50-95能有实打实的提升。下面这篇文章不是教科书式原理,也不是网上那些只给代码不给思路的搬运稿,而是我在一个工业质检场景数据上跑完整个替换、训练、排查过程的完整记录,适合正在做YOLO系列改进、卡在性能瓶颈上、又不想给网络增加太多计算量的读者。

1. 为什么要把下采样单独拿出来做文章

1.1 下采样是检测网络的“信息分水岭”

很多朋友一上来就盯着注意力模块、损失函数、NMS策略这些“看起来更高级”的地方,却忽略了一个每天都在发生的事:网络每进行一次下采样,特征图分辨率直接砍半。YOLOv26从输入到P5层,一共要经过好几次stride=2的操作,输入640分辨率的图片,到最后特征图只有20×20。这中间任何一次下采样设计得不够好,后续的FPN、检测头再怎么努力,也很难把已经丢掉的空间细节找回来。

为什么检测任务对下采样这么敏感?因为检测和纯分类不一样。分类只需要知道“这是一只猫”,但检测还要求模型回答“猫在哪、框该画多宽多高”。框的回归需要像素级的空间信息,小目标可能本身就只有二三十个像素,中间某一次下采样稍微粗糙一点,小目标的边缘、纹理、局部对比度就可能被抹平。这时候模型不是“看不准”,而是“根本没看见”。所以下采样在整个检测网络里相当于一道分水岭:处理得好,浅层细节能顺利留到深层;处理不好,后边的融合结构只能在一个残缺的信息基础上做文章。

1.2 普通卷积和池化下采样到底差在哪

YOLOv26里最常见的下采样方式就是3×3、stride=2的卷积。它的问题在于,卷积核一次性扫过感受野内的多个像素,然后只输出一个值,本质上是在做有损压缩。虽然卷积核参数是学出来的,但采样瞬间,高频细节和低频背景被强行压成一个数值,一部分信息必然丢失。而且普通卷积的通道处理是“一锅烩”,没有区分哪些信息该保留、哪些该舍弃,所有特征图都走同一条路,精度全靠网络自己硬学。

MaxPool的问题更明显,只取区域内最大值,亮度最高的响应留下来了,但纹理分布、边缘走向这些信息几乎全部丢掉。AvgPool相对平稳,可它会把边缘平滑掉,小目标本来就边界模糊,再被平均池化一糊,基本就融化在背景里了。有的实现会先Conv再MaxPool,本质上还是没解决“单路压缩”的局限。

有人可能会说,后来不是有FPN、PAN、BiFPN这些跨层融合结构吗?它们确实能把深层语义和浅层细节拼在一起,但前提是浅层特征里还得保留那些细节。如果某次下采样已经把高频信息压没了,FPN只是把一张“信息残缺”的特征图往上送,融合得再多,也只是把残缺信息重复利用。这也是为什么我后来愿意专门去替换YOLOv26里的下采样层,而不是只在颈部和检测头上堆模块。

2. ADown模块的设计思路与结构拆解

2.1 两条信息通路的互补逻辑

ADown是Adaptive Downsampling的缩写,中文一般叫自适应下采样。它的核心思想很简单:不要用一个卷积把所有信息强行压成一个输出,而是把输入特征拆成两条信息通路,分别处理后再拼起来。

一条通路负责“全局语义”。它更关心这个区域整体是什么,比如背景、大目标的轮廓、上下文关系,对细节不敏感,所以可以用池化或者较小计算量的卷积来做空间压缩。另一条通路负责“局部细节”,重点保留边缘、角点、纹理跳跃这些高频信息,这些信息对小目标识别和框回归至关重要,处理时用步长卷积或者带可学习参数的卷积来做。两条路输出的特征图空间尺寸都是H/2×W/2,输出通道各占一半,最后直接拼接回目标通道数。

这样设计的价值在于:普通卷积下采样是一步到位,信息选择和学习压缩同时发生,负担很重;ADown则是让不同性质的信号走不同的路,全局信息不要干扰局部细节,局部细节也不要拖累全局建模。这有点像团队分工,两个人各擅长一件事,合作效果自然好过一个人硬扛所有任务。

2.2 通道拆分与3×3步长卷积的组合逻辑

以输入通道为C、输出通道也是C为例,ADown通常的做法是把计算拆到两条路径上。局部细节路径一般用3×3、stride=2卷积,因为步长卷积在一步之内就能完成空间分辨率压缩和通道变换,中间不需要额外保存一张大特征图,对推理速度和显存都比较友好。全局路径则可以用池化把分辨率降下来,再接1×1卷积做通道压缩,这样不会引入过多参数。

这里有个关键点:为什么两条路径的卷积核尺寸不都用5×5?因为5×5卷积参数量和计算量比3×3大很多,但感受野增益并没有想象中明显,尤其在浅层,小目标的细节反而更容易被大卷积核“抹平”。我自己试过把局部路径改成5×5,mAP没有明显提升,推理延迟反而增加了不少,后来还是换回3×3。

通道拆分也不是随便拆的。实际操作里,输出通道C会分成两份,每条路径承担C/2个输出通道,拼接后正好是C。如果你把两条路径都设计成输出C,拼接后通道就变成2C,后接的模块如果没做适配,要么报错,要么参数爆炸。这是我第一次接入YOLOv26时长错的地方,后面会详细讲。

2.3 “高效”到底高效在哪

很多第一次接触ADown的人会问:这不就是加了一条旁路吗,参数和计算量不是更大了吗?要回答这个问题,得算一笔账。

普通3×3、stride=2卷积,假设输入是256通道、160×160特征图,输出128通道、80×80特征图,它的FLOPs大约是:2×256×128×3×3×80×80,算下来接近37.7亿次乘加。ADown如果按两条路径设计,每条路径的输入都是256通道、输出只有64通道,两个3×3卷积加两个1×1卷积,总FLOPs并不会比普通卷积翻倍,往往还更少或者基本持平。

但ADown真正的“高效”不是只看FLOPs,而是看单位计算量换来了多少有效信息。普通卷积把输入一次性压成输出,很大一部分计算其实浪费在“学习如何丢弃信息”上;ADown让两条路径各学各的保留策略,本质上是用更少的冗余计算换更充足的特征表达。在我自己的对比实验里,替换ADown后YOLOv26的推理速度几乎没有变化,但mAP表现提升了一个档次,这就是“高效”二字的真实含义。

3. 把ADown接进YOLOv26:替换位置与训练配置

3.1 替换主干还是替换颈部

用ADown改进YOLOv26,第一个问题往往是:到底替换哪些层?我见过有人一股脑把所有下采样层全换成ADown,结果小目标指标不升反降,因为第一层下采样就从640×640降到320×320,过早在空间层面压缩信息,反而把小目标的基础特征给破坏了。

我的实践建议是:主干前几次下采样不要动,优先替换P3进入P4、P4进入P5的下采样卷积,也就是网络后半段的分辨率压缩节点。原因很简单,前两层还处在高分辨率阶段,原始像素信息比较完整,一次简单卷积足够处理;到了后半段,特征图已经很小,每丢一点信息影响都会被放大,这时候ADown的“信息分流保留”作用体现得最充分。

颈部结构里也有一些尺寸减半的卷积,同样可以替换。但需要注意,颈部本身就是在做跨层融合,如果把所有下采样都改成ADown,可能会改变特征对齐方式,反而影响PAN的融合效果。稳妥的做法是先替换主干后两个下采样,跑一轮实验确认涨点,再考虑颈部。

3.2 核心接入代码与shape对齐

我复现ADown时,用的是一版简化实现,只保留核心逻辑。这里放一段示意代码,不同开源版本的ADown细节略有差异,但核心结构都一样:

import torch import torch.nn as nn class ADown(nn.Module): def __init__(self, c1, c2): super().__init__() c = c2 // 2 # 全局语义路径:先用AvgPool压缩空间,再用1x1卷积调整通道 self.global_path = nn.Sequential( nn.AvgPool2d(kernel_size=3, stride=2, padding=1), nn.Conv2d(c1, c, 1, bias=False), nn.BatchNorm2d(c), nn.SiLU(inplace=True) ) # 局部细节路径:用3x3步长卷积保留高频信息 self.local_path = nn.Sequential( nn.Conv2d(c1, c, 3, stride=2, padding=1, bias=False), nn.BatchNorm2d(c), nn.SiLU(inplace=True) ) def forward(self, x): return torch.cat([self.global_path(x), self.local_path(x)], dim=1)

这段代码里最容易被忽略的是通道数对齐。如果原下采样层输入是256、输出是128,那么ADown的c1必须是256,c2必须是128。因为c = c2 // 2,两条路径各输出64通道,拼接后才等于128。如果你把c2写错成256,拼接后就是256通道,和下一层期望的128对不上,训练时大概率直接崩。

接入点确认完后,建议先用一个随机输入打印模型结构,确认每一层输出shape和原模型一致,再做全量训练。这一步能帮你省掉大量排查时间。

3.3 训练配置与超参数设置

ADown接入YOLOv26之后,训练配置不需要做太大改动。我这次实验用的是COCO格式的工业质检数据集,输入分辨率640,batch size 16,训练300轮,优化器用的是带动量的SGD,动量0.937,初始学习率0.01,warmup 3轮,学习率策略是余弦衰减。EMA权重平均保持默认开启。

这里有几个细节值得说。第一,warmup不要省。ADown路径里有BatchNorm,训练初期BN统计量不稳定,如果一开始就用大学习率,BN的running_mean和running_var会被带偏,后面很难纠正。第二,数据增强里Mosaic和MixUp建议保留,因为它们能模拟更多小目标叠加的场景,正好和ADown的细节保留优势形成互补。第三,如果显存紧张,可以先用batch size 8跑通,再逐步加大,不要为了加大batch把输入分辨率降到480,因为ADown在低分辨率下的收益会明显减弱。

训练过程中我还习惯每50轮存一个checkpoint,方便对比同epoch下baseline和ADown版本的差距。光看训练结束时的最终mAP,有时候看不出是模块真的有效,还是优化运气好。分阶段对比能更早发现问题。

4. 实验结果对比:mAP50、mAP50-95和推理速度

4.1 同一数据集上的A/B对比

我在同一个数据集上分别训练了原始YOLOv26和YOLOv26+ADown,除了下采样层不同,其余结构、超参数、数据增强全部保持一致,训到300轮取最优测试结果。为了保证公平,我用的是同一套权值初始化策略,也关闭了任何会影响随机性的外部因素。

下面是这次对比实验的参考数据,不同数据集、不同硬件环境下数值会浮动,但趋势基本一致:

模型mAP50mAP50-95推理延迟(单卡)参数量变化
YOLOv26 baseline0.7340.5123.2 ms-
YOLOv26 + ADown0.7520.5343.3 ms基本持平

mAP50提升了1.8个点,mAP50-95提升了2.2个点。注意一个细节:mAP50-95的涨幅比mAP50更大。mAP50只看IoU=0.5的框准不准,mAP50-95要跨0.5到0.95多个阈值,更反映定位框的精细程度。说明ADown带来的不只是“能不能检测到”,还包括“框得更准”。这一点在需要精确框选的场景里特别重要。

推理延迟从3.2ms变成3.3ms,算是噪声级别的波动。我在TensorRT FP16下又重新测了一遍,两者基本持平,说明ADown确实没有拖慢速度。

4.2 小目标和遮挡场景的改善

整体指标涨了,但更值得关心的是涨在哪里。我把测试结果按目标尺寸分组统计,发现大目标区域mAP基本没变化,小目标区域的提升非常明显。我的数据集里有大量目标只有20到50个像素,baseline的漏检率在4.7%左右,ADown版本降到了3.1%。这个差距在视觉上最直观的表现是:之前完全没框出来的小零件,现在能稳定给出一个框,虽然偶尔框的位置还是有点偏,但至少“找到了”。

遮挡场景也有改善。工业产线上经常有目标互相堆叠、边缘重叠的情况,普通下采样很容易把重叠区域的边界信息搞糊,ADown的局部路径保留了一部分边缘高频响应,模型能更好地区分两个目标的边界。当然,不要指望ADown能解决所有遮挡问题,它只是把“信息基础”打好了,真正的边界区分还要靠后续的检测头和损失函数。

4.3 从特征图看ADown带来的变化

我习惯把中间层特征图导出来,用可视化确认改进是否真的起作用。对比之下,baseline在P5层的部分特征图有很多通道几乎是黑的,说明那些通道里的信息在下采样过程中被压没了,只剩少数几个通道有响应。ADown版本的特征图在同样通道数下,边缘激活明显更密集,目标中心附近的热响应也更集中。

这种差异不是“看起来更亮”那么简单,它直接影响到检测头的回归分支。回归分支需要空间敏感的特征来微调框的位置和大小,如果特征图太平滑、缺少局部梯度,框的四条边就只能靠着周围几个粗糙的响应去猜。ADown保留的局部细节,正好给回归分支提供了更丰富的“锚点”。这也是为什么mAP50-95涨幅普遍高于mAP50的原因。

5. 常见问题与排查技巧实录

5.1 训练损失不下降

第一次把ADown接进去时,我遇到过训练损失一直不降的情况。排查后发现不是ADown本身的问题,而是我把某个下采样层的c2写错了,导致下一层输入的通道对不上。这类问题最麻烦的地方在于:不一定立即报错,有些框架会自动广播或者偷偷修改shape,网络照样能前向,但内部计算已经完全错了。

排查方法很简单,训练前打印模型summary,逐层对照输入输出shape。如果shape和baseline不一致,肯定哪里写错了。另外一个常见坑是BatchNorm初始化。ADown里的BN如果承接了不稳定的输入分布,前几十轮损失可能会出现一个很大的尖峰,这时先跑一个小数据集过拟合,确认模型有学习能力,再上全量训练。

5.2 推理时报shape错误

训练好好的,导出或者推理时报shape错误,这是另一个高频问题。大多数时候出在AvgPool2d的参数上。kernel_size=3、stride=2、padding=1,对偶数分辨率输入输出是H/2,但对奇数分辨率输入,输出会变成(H+1)/2,和另一条路径的拼接结果就对不齐。

YOLOv26标准输入是640,内部特征图基本都是偶数尺寸,所以平时不会触发。但如果你做了多尺度推理,比如输入变成672或736,某些层就可能出现奇数尺寸。解决办法有两个:一是给ADown加一个自适应尺寸对齐,比如对输出做一次插值;二是干脆在推理时固定输入尺寸,不开放任意尺度。我后来选了后者,稳定省事。

5.3 速度反而变慢怎么办

正常情况下ADown不会明显拖慢速度,但如果你用的是某些魔改版本,两条路径都堆了多个卷积,速度就会变慢。我见过有人把全局路径写成了AvgPool + 3×3卷积 + 1×1卷积,局部路径又用两个3×3卷积堆叠,计算量直接翻倍,速度掉了15%以上。

性能优先的做法是:全局路径保持AvgPool + 1×1卷积,局部路径保持一个3×3步长卷积就够了。先用ONNX导出看单层耗时,再用TensorRT FP16加速,确认慢在哪一层。如果所有优化做完还慢,那就得接受一个事实:ADown的收益主要体现在精度上,速度和精度的权衡需要你自己按业务场景决定。

5.4 与其他涨点模块的叠加顺序

很多人在ADown之前已经往YOLOv26里加过其他模块,比如注意力机制、可变形卷积、更深的C2f结构。它们能不能和ADown共存?能,但顺序有讲究。我的建议是先把ADown接进去,把模型训稳、确认涨点,再考虑插注意力模块。一次只改一个变量,这样训练效果可回溯,出问题也好定位。

注意力模块最好插在ADown之后,而不是之前。因为注意力本质上是“选择性放大某些通道”,如果放在ADown前面,可能会提前压制掉局部细节路径里的高频信息,让ADown白忙一场。放在后面,注意力可以基于ADown保留的更丰富的特征做筛选,效果反而更好。我实际测下来,ADown加一个轻量注意力模块,mAP50-95还能再涨0.3到0.5个点。

6. 经验总结与后续扩展

6.1 在自定义数据集上的落地建议

不是所有数据集都能从ADown里受益。如果你的目标都是大目标,图片背景干净、遮挡少,ADown带来的提升可能只有零点几个点,甚至没有提升。如果目标偏小、分布密集、边缘纹理重要,那ADown就是性价比很高的选择。

落地之前,我建议先用1000到2000张图做一个快速对比实验,300轮全量训练太费时间,可以先训80到100轮看趋势。如果这时候mAP50-95已经出现0.3个点以上的优势,再拉全量训练。这种做法能让你在半天之内判断ADown适不适合你的场景,不用每次都做长周期试错。

6.2 从ADown延伸出的几个可选优化方向

如果你已经成功复现并且确认涨点,下一步可以考虑几个方向。一是把全局路径的AvgPool换成可学习的BlurPool,能进一步抑制下采样时的混叠效应,代价是代码多几十行。二是在局部路径中加入可变形卷积的变体,让采样位置更贴合目标形状,这个涨点幅度很可观,但推理速度会受影响。三是把ADown和更细粒度的损失函数配合使用,比如在回归分支上加强小目标权重。

我个人在实际项目中还发现一个细节:ADown对模型缩放因子比较敏感。同样的结构,用在tiny类模型上提升幅度往往比用在large类模型上更大,因为小模型本身参数少,更依赖特征提取环节的质量,下采样设计的优劣会被放大。如果你在做轻量化检测设备,这个模块值得优先试。

最后分享一个小经验。ADown改动本身不大,但它的价值在于提醒我们:网络里的每个基础操作都值得重新审视。不要只盯着注意力、Transformer这些热点,下采样这种“最普通”的模块,反而是很多时候真正卡住性能上限的地方。把这个思路带到其他模块上,你会发现改进空间比想象中大得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询