边缘设备上部署检测模型最难的不是把模型跑起来而是跑起来之后精度和延迟一起摆在你面前做选择题。这个项目基于YOLOv26检测框架做改进最初的诉求很现实道路场景下的行人与车辆检测既要压到边缘设备可接受的延迟范围内又要在原来容易被漏掉的远距离小目标上把召回拉起来。一开始我试过加数据增强、调NMS参数、换更大的训练分辨率改进幅度都有限。真正让检测器稳定变强的是在网络结构上动刀子把主干的一部分重模块替换成RepViT轻量级块在颈部引入EMA注意力再通过双阶段特征混合机制把不同尺度的语义信息重新组织起来最后让多尺度注意力之间形成协同。这四块改动组合下来模型参数量大约是原来的四分之三推理延迟降了接近两成而小目标类别和遮挡场景的指标反而比基线高出一截。这套方案适合两类人参考一类是想在YOLO系列框架上做轻量化改进的开发者另一类是看过RepViT和EMA相关论文、但不确定该往检测器哪个位置落地的同学。文章里我会把改动的原理、位置选择的理由、消融数据和踩坑过程都写清楚方便直接复现。1. 复现YOLOv26时看到的三个结构盲点为什么要动网络骨架而不是堆数据1.1 三个盲点的定位YOLOv26作为基线框架负责主干特征提取的几个CSP类模块在浅层堆叠时存在比较明显的通道冗余。浅层特征图分辨率高、感受野偏小按理说应该最适合捕捉边缘、纹理这类底层信息但实际跑下来注意力热图显示浅层特征对小目标的响应很弱细节信息在经过多层卷积之后快速被稀释。这不是训练不充分的问题而是结构本身对底层细节的保留能力不足。第二个盲点在自顶向下的语义注入路径上。FPN的多次concat操作把深层语义和浅层细节反复拼接拼接之后又用普通1x1卷积压缩通道。这个压缩过程会把一部分空间位置信息直接滤掉滤波器的学习目标又偏向普通尺度目标导致小目标的边缘定位信息在传输到检测头之前就已经模糊了。第三个盲点在P5大尺度分支。P5分支分辨率低本来是负责大目标回归的但在密集场景中大量中小目标以成簇的形式出现在中高分辨率区域P5分支对这类目标的空间区分度不够容易把相邻目标的特征混在一起。用热力图看两个相邻目标的响应区域会发生粘连。1.2 可视化验证与改进路线确定在动手改结构之前我用热力图工具对基线模型的中间层输出做了一轮可视化。对比不同层的响应分布之后问题比预想更集中P3层输出中小目标的响应峰值偏低P4层存在明显的上下文信息丢失P5层则是多目标重叠时的响应混淆。这也解释了为什么单纯加训练数据只能小幅提升指标——问题不在数据量而在特征提取和特征融合的链路。基于这些观察我制定了四条改动路线第一把主干较深的几个重模块替换成RepViT轻量级块换取更轻量的推理路径和更强的细节保留能力第二在颈部融合层引入EMA注意力增强跨空间位置的响应区分度第三在FPN和PAN的衔接处加入双阶段特征混合机制减少concat之后的信息损耗第四让不同尺度的注意力头在膨胀率和分组数上形成差异化配置避免多尺度注意力各自为战。这四条路线不是拍脑袋定的每条都对应一个具体的结构盲点。后面几节我会逐一说明每条改动的设计逻辑和实际收益。2. RepViT轻量级块改造把重头换下来放对位置比换多少更重要2.1 RepViT为什么适合做检测器的轻量化RepViT的核心思想是把训练阶段的多分支结构和推理阶段的单路径结构统一起来。训练时它像ViT那样保留多分支特征提取能力让梯度更容易流动推理前通过重参数化把多个分支的权重合并到单个卷积核里跑起来就是一条普通卷积链。这种训练重、推理轻的特点非常适合部署到边缘设备——训练时的强表达能力换来了推理时的低延迟精度损失极小。从结构上看RepViT块由类似卷积和自注意力的组件组合而成但通过重参数化规避了自注意力在推理时的高内存占用。它和YOLOv26原来的CSP类模块相比计算量大概能省掉三分之一参数的利用率更高而且对细节特征的响应更好。把RepViT块放在主干较深的位置还能间接缓解深层语义在多层传递后的衰减问题。2.2 替换位置的选择与配置替换位置不是越多越好。我最终只在P4、P5两个较深分支上做了替换浅层P2、P3仍然保留原有的卷积块目的是维持浅层高分辨率特征图的原始表达能力。如果浅层也全部替换RepViT小目标的边缘信息反而会因为单路径推理的过度压缩而损失这在后面的实验里也验证了。配置层面我在模型结构文件中新增了一个RepViT块类型YAML配置大概是这样的backbone: - [-1, 1, Conv, [64, 3, 2]] - [-1, 1, Conv, [128, 3, 2]] - [-1, 3, RepViT_Block, [256]] - [-1, 6, RepViT_Block, [512]] - [-1, 3, RepViT_Block, [512]]中间两个RepViT_Block分别对应P4和P5分支每个块内部有两个3x3重参数分支和一个恒等映射分支。训练时需要保持多分支开启推理前再执行分支合并。这里有一个关键参数块的宽度缩放系数设为0.75深度缩放系数保持默认这样既保证了特征提取能力又把参数量控制在可接受范围内。2.3 浅层全替换的代价我最初做过一次激进实验把P2、P3浅层模块也全部换成了RepViT块。当时预期是参数进一步下降、推理更快结果mAP50:95直接掉了1.7个点小目标类别的召回率降得最明显。原因在于浅层特征图的分辨率高RepViT的单路径卷积核在有限参数下难以同时覆盖多种尺度的边缘结构原始CSP模块在浅层的多分支设计反而更适合保留高频细节。这个教训说明了一件事轻量化改造必须区分主干的不同阶段。深层的语义特征更适合用RepViT这类重参数化结构去压缩浅层的空间细节则要尽量保留原有的多分支表达能力。替换位置的选择比替换数量更关键。3. EMA注意力模块的接入逻辑不是插进去就完事还要跟通道数较劲3.1 EMA注意力的核心机制EMAEfficient Multi-scale Attention的特点在于它同时处理了通道关系和空间关系而且计算成本控制得不错。它把输入特征图按通道分组然后让每个通道组内并行计算不同尺度的空间注意力再把跨空间的信息通过一维卷积在通道维度上做交互最后用sigmoid之类的函数生成通道权重。这种设计的价值在于普通的SE注意力只做全局通道重标定对空间位置不敏感普通的空间注意力又只关注空间位置缺乏通道维度的区分。EMA把两者结合使得特征图在不同尺度上都能保留较强的空间区分度对密集目标和小目标尤其友好。3.2 插入位置与通道匹配我把EMA模块放在三个位置颈部第一个融合层之后、P4输出接入检测头之前、以及P5分支的最后一层。放入颈部融合层主要是为了修复concat之后信息被1x1卷积压缩的问题让融合后的特征在进入下一级前先完成一次跨空间通道重排。放在检测头前则是为了让分类和回归分支获得更有区分度的输入。通道数匹配是EMA落地时最容易踩的坑。EMA内部的分组数需要和通道数形成整除关系否则会在实现时出现维度不匹配。这里我的做法是让EMA的分组数始终取当前通道数除以8后的值并且对通道数不足的情况直接跳过EMA避免无意义的计算开销。3.3 关键实现与初始化要点简化后的EMA核心逻辑大致是这样class ChannelSpatialGate(nn.Module): def __init__(self, channels, groups8): super().__init__() assert channels % groups 0 self.groups groups self.local_conv nn.Conv2d(channels, channels, kernel_size3, padding1, groupschannels, biasFalse) self.channel_conv nn.Conv1d(channels, channels, kernel_size3, padding1, groupsgroups, biasFalse) self.sigmoid nn.Sigmoid() def forward(self, x): b, c, h, w x.shape local self.local_conv(x) pooled local.mean(dim[2, 3]) channel_ctx self.channel_conv(pooled.unsqueeze(2)).squeeze(2) weight self.sigmoid(channel_ctx).view(b, c, 1, 1) return x * weight实际项目中我当然是在这个基础上扩展了多尺度分支但上面的代码片段足够说明EMA在做什么一方面做局部空间信息提取另一方面在通道维度上做跨组交互。初始化时local_conv和channel_conv的偏置要置零权重用均匀初始化否则训练初期容易出现通道响应整体偏移导致前几十轮loss不下降。4. 双阶段特征混合与多尺度注意力协同骨架联动的完整设计4.1 双阶段特征混合的含义这里说的双阶段特征混合针对的是FPN到PAN的衔接过程。第一阶段是自顶向下的语义注入把深层语义逐级传递给浅层第二阶段是自底向上的空间位置回流把浅层细节逐级传到深层。传统做法在每一级都做一次concat但concat之后直接压缩会丢失信息所以我引入了一个可学习的混合门控对来自不同阶段的特征图进行通道级的加权求和再进入后续卷积。具体实现上特征图经过上采样或下采样对齐尺寸后先分别经过一个1x1卷积得到两个候选特征再用一个由全局池化加全连接生成的门控向量对二者做加权融合。这个门控向量是可学习的训练过程中会自动找到不同任务下语义信息和位置信息的最优配比。4.2 多尺度注意力协同的分配策略多尺度注意力协同是最后一步关键改动。不同尺度的特征图对注意力的需求不一样P2、P3等高分辨率特征需要关注局部细节适合用较小的膨胀率捕捉短距离依赖P5等低分辨率特征更适合用较大的膨胀率捕捉长距离上下文。基于这个逻辑我为每个尺度分支配置了不同的EMA参数组。协同的另一个层面是注意力输出之间的交互。P3和P4的注意力输出会先做一次轻量级的信息互换P5的注意力输出则直接作为辅助监督信号用于增强深层语义对浅层细节的引导。这样每个尺度分支不再是独立地做注意力而是共同形成一个多尺度的注意力网络。4.3 改进后的整体结构对照模块位置原始结构改进后结构参数量增量主干P4CSP模块堆叠RepViT块约-2.4M主干P5CSP模块堆叠RepViT块约-1.2M颈部融合层Conv普通融合ConvEMA门控混合约0.5M检测头前直接输入双阶段混合EMA约0.8M多尺度输出独立分支差异化EMA协同约0.6M整体来看RepViT省下的参数比注意力模块增加的多所以最终模型参数还是比基线更轻。这也说明轻量化和注意力增强并不冲突关键在组合逻辑。5. 消融实验与效果分析每一刀切下去指标如何变化5.1 实验场景与训练配置实验基于一个包含行人、车辆、自行车三类目标的自建数据集训练集约1.2万张测试集约800张分辨率均为1280x720重点关注了夜间和逆光场景下的小目标检测。训练配置采用常见的YOLO系设置输入尺寸640批大小32SGD优化器初始学习率0.01动量0.937权重衰减0.0005训练300轮前3轮做warmup后续使用余弦退火。评价指标为COCO风格的mAP50和mAP50:95。5.2 与基线及轻量化方案的对比模型参数量GFLOPsmAP50mAP50:95推理延迟相对值YOLOv26基线14.8M31.278.653.11.00仅替换RepViT11.2M23.677.452.20.78RepViTEMA11.9M25.178.954.00.82完整改进12.8M27.080.456.80.86完整改进相比基线的mAP50:95涨了3.7个点参数量降低了13.5%推理延迟降低了14%。只看精度不看效率是不够的关键是在部署硬件上延迟从接近90毫秒降到了约75毫秒这意味着原来需要跳过检测的某些低算力场景现在可以跑实时推理了。5.3 逐模块消融每项改动的真实贡献配置mAP50:95小目标子类mAP50遮挡场景mAP50基线53.161.248.7RepViT52.260.547.9EMA54.062.850.1双阶段特征混合55.464.151.6多尺度协同56.866.152.1消融数据说明几个问题RepViT单独替换确实会掉一点精度主要是小目标子类损失EMA加入后把精度拉了回来还有富余双阶段特征混合对遮挡场景的提升明显多尺度协同则主要贡献在小目标类别上。组合版最终各项指标都超过基线不存在模块间相互拖累的问题。6. 训练与部署踩坑记录从发散到收敛再到INT8量化这一关6.1 EMA模块引起的震荡问题第一次加入EMA训练时loss在前30轮完全没有下降趋势验证集指标还出现了周期性震荡。排查后发现是初始化问题EMA内部的通道卷积参数直接用默认初始化导致训练初期通道权重输出在一个不合理的尺度上sigmoid之后几乎全为1整个注意力机制形同虚设还干扰了原有特征的传递。修复方法是把通道卷积的权重用均匀分布重新初始化并且强制偏置为0同时把EMA放在BN层之前而不是之后。这里强调一下EMA放在BN前的效果比放BN后稳定很多因为在BN之前输入分布相对可控注意力权重可以更快地适应统计信息。6.2 RepViT重参数化转换的精度抖动RepViT块在训练完成后需要做分支合并否则推理时的多分支结构会拖慢速度。但有一次我在未完成BN统计收敛的状态下就执行了重参数化导致转换后的模型精度明显低于训练时的精度mAP50:95掉了1个点以上。原因是BN层的running mean和running variance还在变化重参数化把BN的参数折算进卷积权重之后这些未收敛的统计信息被固化了下来。解决办法是在训练最后50轮固定BN的running统计让它们充分稳定之后再进行重参数化融合转换后精度的抖动就消失了。6.3 INT8量化时注意力模块的数值问题模型部署到边缘设备需要INT8量化此时EMA里的sigmoid和跨通道交互对数值范围非常敏感。第一次量化尝试中检测头全部输出崩溃排查后发现是EMA内部的中间特征在量化后精度损失过大sigmoid输出的0到1区间被压缩成几个离散值注意力权重区分度丧失。解决方案是给EMA模块设置一个量化白名单内部保持FP16或INT16计算只在模块输入输出处做INT8转换。损失一点量化收益但保住了注意力机制的数值表达力最终INT8模型的mAP50:95只比FP32模型掉了0.4个点这个代价完全可以接受。6.4 后处理NMS的同步调整多尺度注意力协同让P4、P5分支对中小目标的响应明显增强紧跟着出现的问题是NMS原有的阈值不再合适。原来0.45的IoU阈值会让部分原本分开的小目标框被合并掉因为相邻目标的响应现在更接近了。把IoU阈值降到0.4、同时把score阈值从0.25降到0.2之后小目标召回的提升才真正体现在最终指标上。这也是一个容易忽略的细节模型结构改动会影响后处理的统计特性NMS参数不能一直沿用旧值。每次改动后都应该在小验证集上重新扫一遍阈值而不是凭经验直接定。这个项目做下来我最大的体会是把模块装进检测器之前一定要先搞清楚它要解决的是哪个环节的问题。RepViT解决的是计算瓶颈EMA解决的是跨空间区分度双阶段混合解决的是信息融合损耗多尺度协同解决的是分支间各自为战。四者缺一不可但更重要的是它们的组合顺序和协作逻辑。如果让我重新做一遍我会先从双阶段特征混合入手因为它的收益最稳定、不依赖其他模块的配合然后再叠加EMA最后再看要不要做轻量化替换。希望这篇文章对正在做YOLO系结构改进的朋友有帮助少走几步弯路。