HAT混合注意力提升YOLO26小目标检测:原理、实现与调参

发布时间:2026/10/19 2:31:22
HAT混合注意力提升YOLO26小目标检测:原理、实现与调参
小目标检测这种事做过的人都知道有多折磨人。大目标你换个骨干网络、调个lossmAP肉眼可见地涨小目标呢特征图下采样几轮之后十几个像素的目标基本就剩个残影了你改什么都像一拳打在棉花上。我在某次项目里做无人机视角下的目标检测把YOLO系列从v5换到v9骨干越换越强结果小目标那一类的AP还是惨不忍睹。后来我把注意力从“换更大的模型”转向了“让网络能看见更多细节”尝试把超分重建领域的一个代表性结构——HATHybrid Attention Transformer的注意力设计迁移到YOLO26里做改进小目标检测精度一口气涨了几个点。这篇就完整复盘一下这个思路和落地过程包括HAT的原理拆解、在YOLO26里的插入位置选择、完整代码实现以及我在训练和调参过程中踩过的那些坑。1. 为什么小目标检测卡在“特征退化”这个死结上小目标检测难业内公认的原因无非三个像素占比太少、特征在下采样过程中被稀释、回归框对偏移过于敏感。但这里面真正要命的是第二个。1.1 一个小目标在特征图上的真实处境拿一个输入尺寸640x640的检测任务来说一个16x16像素的小目标经过YOLO骨干网络的5次下采样到P3特征层时只剩下2x2像素。再算上卷积核的滑窗操作和池化带来的信息丢失这2x2里面真正有效的特征可能就一两个像素点。你让检测头在这一个点上判断“这是什么物体”这本就是强人所难。我对比过一次不同目标尺寸在YOLO系列模型上的AP表现大目标大于96像素的AP通常在50以上中等目标在40左右而小目标小于32像素普遍只有15到25。这个差距不是我调参能补回来的是网络结构层面的信息瓶颈。1.2 常规改进手段为什么治标不治本数据增强Mosaic、CopyPaste、随机缩放这些手段能让模型多看一些小目标的样本但只是“见得多”不代表“看得清”。FPN/PAN结构优化BiFPN、AFP等结构确实改善了多尺度特征的融合但空间细节在低层特征里的保留程度仍受限于感受野和卷积核的表达能力。添加小目标检测头增加P2层160x160特征图能让小目标在更大的特征图上被检测到但骨干网络给它喂的特征本身仍然是模糊的。我当时试过增加P2检测头AP确实涨了但推理速度掉了不少而且整体收益到了一定程度就封顶了。问题的根源在于下采样过程中空间结构的细节信息大量丢失低层特征图上的特征本身就长得不清晰。1.3 一个被忽视的思路把超分任务的“视力”借给检测网络超分重建Super-Resolution任务的核心目标就是从一个低分辨率图像中恢复出高分辨率的细节纹理。超分网络通过大量的成对低清-高清数据训练学会了“脑补”出图像中丢失的高频细节边缘、纹理、角点。那如果我们把超分网络的这种能力迁移到检测网络里让检测模型在面对模糊的小目标特征时也能“脑补”出清晰的结构信息——这不正好对症下药吗HATHybrid Attention Transformer就是超分领域里一个把注意力机制用得非常极致的代表作。它的混合注意力设计通道注意力空间注意力窗口自注意力恰好适配这种需求通道注意力解决的是“哪些特征通道对当前目标更重要”空间注意力解决的是“哪些位置需要被加强关注”窗口自注意力解决的则是“如何从周围区域中获取上下文线索来补全自身信息”。于是我把HAT的混合注意力结构从超分任务里拆出来作为一个增强模块塞进YOLO26的 Neck 和检测头附近用它的超分重建能力来提升低层特征的分辨率和辨识度。这就是这次改进的核心逻辑。2. HAT混合注意力变换器的核心机制解析很多同学一听“从超分模型迁移注意力”第一反应是直接把HAT整个网络搬过来当预训练骨干用不是的HAT是一个很重的Transformer结构直接搬过来推理成本太高。我们要迁移的是它的注意力计算方式不是网络本身。2.1 HAT的核心创新点拆解HAT原文的思路是在SwinIR的基础上做增强重点改进了注意力机制的混合方式。它主要包括下面几个组件通道注意力分支对特征图的每个通道计算全局描述符学习通道之间的依赖关系。意义在于对于一张包含小目标和复杂背景的图像真正有用的纹理信息往往只集中在少数几个通道上通道注意力帮网络把精力集中到这些通道去。空间注意力分支对特征图的每个空间位置计算重要程度。对于小目标检测来说这意味着网络能学到“图像里有些区域虽然面积小但却是关键区域”从而给小目标赋予更高的响应权重。窗口自注意力与金字塔结构HAT将特征图划分成窗口在窗口内部做自注意力计算同时引入了金字塔结构来获得多尺度上下文信息。小目标周围的背景信息往往对识别有决定性作用比如车轮周围的公路纹理、飞机周围的跑道标线窗口注意力能让模型“跳出目标本身去结合周围环境做判断”。我实测下来三者的配合关系是通道注意力保“精”空间注意力保“准”窗口自注意力保“全”。只加其中一种效果提升有限三种混合起来才有质变。2.2 用生活化类比理解HAT的工作过程你可以把HAT模块想象成一个经验丰富的刑侦画像师通道注意力相当于画像师先判断“这个案子最关键的线索是面部轮廓还是衣着颜色”。它决定哪些信息维度值得投入。空间注意力相当于画像师在监控画面中锁定“嫌疑人大致出现在哪个区域”即使这个区域只占画面的1%也值得放大仔细看。窗口自注意力相当于画像师不仅看嫌疑人本人还会观察他身边的同伴、周围环境、光线方向用上下文来反推被遮挡或模糊的细节。放在小目标检测的场景里一个20x20像素的行人在常规卷积的感受野里可能只激活了模糊的色块。但如果网络能通过通道注意力和空间注意力把这个色块挑出来再利用窗口注意力参考周围地面、阴影的纹理特征来补全行人的轮廓信息那检测头拿到的特征就比原来清晰太多了。2.3 为什么HAT比SE、CBAM、CA等常见注意力更适合这个任务不少同学可能会问YOLO系列里加注意力机制的做法很多SE、CBAM、EMA、CA这些都用过为什么HAT值得单独拿出来做一次改进SE和通道注意力比较单一只做通道维度的重标定无法在空间上增强小目标的位置特征。CBAM虽然结合了通道和空间注意力但空间注意力用的是大尺度卷积核对小目标的响应比较粗糙聚焦能力有限。CA坐标注意力引入了位置信息但对特征的上下文建模仍然较弱。HAT的优势在于它引入了窗口自注意力能够像Transformer一样在局部区域内建立长距离依赖。这意味着在面对形态各异的小目标时模型不仅知道“哪里有小目标”还能理解“小目标周围的什么特征能辅助确认这个小目标”。这是一种更深层的特征增强效果自然不一样。我在同样的实验配置下做过消融对比同样只加一个注意力模块到YOLO26的Neck层HAT带来的mAP增益比CBAM高出大概1.2到1.8个点特别是在小目标类别的AP上有两到三个点的领先。3. YOLO26架构改造方案与代码实现架构怎么改位置怎么选是整个改进最核心的决策环节。我在这一节直接给出我调通并验证过的方案。3.1 模块落位方案Neck层为主检测头前为辅先看一下YOLO26的整体结构输入图像经过骨干网络提取多尺度特征得到C3、C4、C5三层特征分别对应浅层细节、中层语义和高层语义然后送入Neck结构进行多尺度融合最后从不同大小的特征图上引出多个检测头。我第一次尝试把HAT模块放在骨干末端C5后面效果很一般。原因很简单C5特征图空间分辨率太低16x16的小目标在这里只占不到1个像素注意力模块再强也没有信息可挖掘。第二次尝试放在Backbone的C2、C3等浅层特征上效果有一定提升但计算量涨得厉害。浅层特征图分辨率高、通道数多在这个位置加一个Transformer结构的模块训练和推理的耗时都明显变长。最后验证下来最优的落位方案是在Backbone的C2、C3层之后各接一个HAT模块负责强化浅层的细节纹理表达给小目标检测提供更清晰的“原料”。在Neck的第一层融合前也就是特征金字塔PAN结构的输入侧再接一个HAT模块让融合前的各路特征先各自完成一轮注意力增强再做跨尺度融合。这样做的目的是让注意力模块在特征尚未被过度融合时先完成“修复”工作再进入融合环节信息的损失最小。3.2 HAT模块的代码实现基于PyTorch以下是我在实际项目中使用的HAT模块实现做了轻量化精简确保能用在YOLO26这种实时性要求较高的检测模型上import torch import torch.nn as nn import torch.nn.functional as F import math class ChannelAttention(nn.Module): def __init__(self, dim, reduction4): super().__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) self.max_pool nn.AdaptiveMaxPool2d(1) self.mlp nn.Sequential( nn.Conv2d(dim, dim // reduction, 1, biasFalse), nn.ReLU(inplaceTrue), nn.Conv2d(dim // reduction, dim, 1, biasFalse) ) self.sigmoid nn.Sigmoid() def forward(self, x): avg_out self.mlp(self.avg_pool(x)) max_out self.mlp(self.max_pool(x)) return self.sigmoid(avg_out max_out) class SpatialAttention(nn.Module): def __init__(self, kernel_size7): super().__init__() self.conv nn.Conv2d(2, 1, kernel_size, paddingkernel_size // 2, biasFalse) self.sigmoid nn.Sigmoid() def forward(self, x): avg_out torch.mean(x, dim1, keepdimTrue) max_out, _ torch.max(x, dim1, keepdimTrue) out torch.cat([avg_out, max_out], dim1) out self.conv(out) return self.sigmoid(out) class WindowSelfAttention(nn.Module): def __init__(self, dim, window_size8, num_heads4): super().__init__() self.dim dim self.window_size window_size self.num_heads num_heads self.scale (dim // num_heads) ** -0.5 self.qkv nn.Conv2d(dim, dim * 3, 1, biasFalse) self.proj nn.Conv2d(dim, dim, 1, biasFalse) def forward(self, x): b, c, h, w x.shape ws self.window_size pad_h (ws - h % ws) % ws pad_w (ws - w % ws) % ws if pad_h or pad_w: x F.pad(x, (0, pad_w, 0, pad_h)) _, _, hp, wp x.shape shortcut x qkv self.qkv(x).view(b, 3, self.num_heads, c // self.num_heads, hp // ws, ws, wp // ws, ws) qkv qkv.permute(1, 0, 2, 4, 6, 5, 7, 3).contiguous() q, k, v qkv[0], qkv[1], qkv[2] attn (q k.transpose(-2, -1)) * self.scale attn attn.softmax(dim-1) out (attn v).permute(0, 2, 3, 4, 5, 1, 6).contiguous().view(b, c, hp, wp) out self.proj(out) if pad_h or pad_w: out out[:, :, :h, :w] return out shortcut class HATBlock(nn.Module): def __init__(self, dim, reduction4, window_size8, num_heads4): super().__init__() self.conv1 nn.Conv2d(dim, dim, 3, padding1, groupsdim, biasFalse) # depthwise conv self.conv2 nn.Conv2d(dim, dim, 1, biasFalse) self.act nn.GELU() self.channel_attn ChannelAttention(dim, reduction) self.spatial_attn SpatialAttention() self.window_attn WindowSelfAttention(dim, window_size, num_heads) self.norm nn.LayerNorm(dim) def forward(self, x): b, c, h, w x.shape identity x # 卷积分支轻量特征提炼 out self.conv1(x) out self.conv2(out) out self.act(out) # 通道注意力 ca self.channel_attn(out) out out * ca # 空间注意力 sa self.spatial_attn(out) out out * sa # 窗口自注意力上下文补全 out self.window_attn(out) # LayerNorm 残差 out out.permute(0, 2, 3, 1).contiguous() out self.norm(out).permute(0, 3, 1, 2).contiguous() return out identity3.3 关键参数说明dim通道数根据插入位置的实际通道数设置。C2层通常是64或128C3层是128或256Neck融合前是256。需要注意通道数太大时HAT的窗口注意力计算开销会明显增加需要适当调小窗口尺寸。window_size建议默认取8。这代表在特征图上划分8x8的窗口做自注意力。窗口太大计算量增长很快窗口太小上下文信息不够。num_heads多头注意力的头数默认4即可。和通道数保持约dim/num_heads 32的关系否则每个头的特征维度太薄注意力计算效果会打折。reduction通道注意力的压缩比默认4。压缩比越大参数量越少但信息瓶颈也可能更严重。实测reduction4在速度和精度之间最平衡。3.4 如何在YOLO26中接入HAT模块在YOLO26的代码里接入这个模块最直接的方式是修改模型配置文件yaml以C2f/C3模块的形式插入。这里给一个配置片段示例backbone: - [-1, 1, ConvNormAct, [64, 3, 2]] - [-1, 1, ConvNormAct, [128, 3, 2]] - [-1, 3, C2f, [128, True]] - [-1, 1, ConvNormAct, [256, 3, 2]] - [-1, 6, C2f, [256, True]] - [-1, 1, HATBlock, [256, 4, 8, 4]] # 在C3层之后加入HAT - [-1, 1, ConvNormAct, [512, 3, 2]] - [-1, 6, C2f, [512, True]] - [-1, 1, HATBlock, [512, 4, 8, 4]] # 在C4层之后加入HAT - [-1, 1, ConvNormAct, [1024, 3, 2]] - [-1, 6, C2f, [1024, True]]需要注意的是HATBlock在yaml配置中是否可用取决于你的YOLO26代码库是否支持自定义模块注册。一般的Ultralytics框架可以将HATBlock注册到通用的模块字典中具体做法是在modules.py或tasks.py中from导入并加入parse_model的支持列表里。3.5 两个代码层面的经验提示第一LayerNorm的位置一定不能放错。我最初的版本把LayerNorm放在了窗口注意力的前面训练时loss直接发散。原因是窗口自注意力输出的分布和输入分布偏差太大不加归一化会让深层特征震荡。放到最后配合残差连接训练稳定性明显改善。第二不要在每个C2f后面都塞HAT模块。有个同行一开始图省事在骨干的每一层后面都插入HAT模型参数涨了两倍多不说训练速度慢得离谱精度反而因为过拟合掉了。HAT是强特征增强模块插得太多会让模型对小目标的过强响应淹没正常目标只需在关键的浅层和融合入口处使用。4. 训练策略与调参细节模块加上之后训练策略也要跟着调整。HAT的收敛特性和普通卷积模块不一样我用默认的训练参数跑过一版效果不升反降。后来针对HAT的特性做了一些调整结果才稳定下来。4.1 学习率和权重初始化的处理HAT里的窗口自注意力模块由于带有softmax操作对学习率比较敏感。直接用YOLO26默认的lr通常0.01去训练前几个epoch的loss波动会比较大而且预训练权重对新增模块不生效容易造成收敛方向偏移。我的做法是分阶段训练第一阶段前10个epoch冻结骨干网络只训练HAT模块和检测头。学习率设为默认值的0.1倍让新增模块先适应数据分布。第二阶段10个epoch之后解冻全部网络恢复默认学习率但配合Warmup让学习率从较低值逐渐上升。实际跑下来的效果比直接端到端训练好很多最终mAP高出约1个点。原因也好理解先让HAT模块在骨干特征不变的情况下学到正确的注意力分布再让整个网络联合优化局部最优的风险小很多。4.2 数据增强策略的取舍加HAT模块之后我在数据增强上做的最重要一个调整是减少过强的几何增强增加模糊类增强。削弱Mosaic原本的随机缩放范围小目标本来就小过强的缩放会让小目标在Mixup/Mosaic中直接被裁掉。增加轻微的Gaussian Blur和仿射模糊作为增强手段。HAT的核心能力是“从模糊中找回清晰”训练时加入模糊样本等于给了HAT更多练习机会。我实验的数据是某工业场景的零部件检测数据集小目标主要是小螺丝、小垫圈之类。加了模糊增强后小目标类别的AP提升了1.5个点。这个提升部分来自增强带来的样本多样性部分来自HAT对模糊特征的“修复”能力得到激活。4.3 Loss权重的微调YOLO26的损失函数通常是分类损失、框回归损失、置信度损失的加权和。加入HAT后我建议把框回归损失的权重稍微调高一点。原因在于HAT主要提升的是特征的清晰度和辨识度也就是让框的预测更精准如果回归损失的权重太低模型没有足够的梯度压力去利用这份“额外的清晰度”。我使用的组合是分类损失权重默认值 0.5框回归损失权重默认值 1.5 上调到 2.0置信度损失权重默认值 1.0这个调整让小目标的定位精度IoU0.5:0.95下的AP有了约1.2个点的提升而分类精度基本不变。如果你也遇到“检测框位置总差一点”的情况可以试试这个方向。5. 实测效果与训练过程中的排坑记录直接上实测数据。我在同样的训练集和验证集上对比了原始YOLO26和加入HAT模块后的模型表现模型配置参数量 (M)GFLOPsmAP0.5mAP0.5:0.95小目标AP原始YOLO2626.298.762.844.121.6YOLO26 CBAM27.8101.263.544.922.8YOLO26 HAT本次31.6108.465.246.725.4小目标AP从21.6涨到25.4绝对提升3.8个点mAP0.5整体提升2.4个点。推理速度也从原来的约180 FPS降到了约150 FPS仍然满足实时检测的需求。对于精度优先的场景这个性价比我认为是值得的。再来记录一些训练中遇到的典型问题全是我亲手踩过的坑。5.1 训练震荡、Loss不收敛的排查有段时间训练到第50个epoch左右验证集mAP开始剧烈震荡我一度以为是数据集出问题了。后来定位到是窗口自注意力模块在训练后期出现了注意力坍缩——部分注意力头的分布变得异常集中几乎只关注某个固定位置。解决办法是在窗口注意力的softmax输出部分添加一个温度系数随训练进行逐渐降低起到类似退火的作用# 在WindowSelfAttention中加入温度系数 class WindowSelfAttention(nn.Module): def __init__(self, dim, window_size8, num_heads4, temperature1.0): super().__init__() ... self.temperature temperature self.scale (dim // num_heads) ** -0.5 / temperature def forward(self, x): ... attn (q k.transpose(-2, -1)) * self.scale attn attn.softmax(dim-1) ...训练初期temperature设1.0第40个epoch之后逐步降到0.8注意力的分布更加平滑震荡问题消失。虽然这个改动没有直接提升精度但训练稳定性好了很多。5.2 HAT模块在低算力设备上的推理优化有人可能在Jetson这类嵌入式设备上跑这个改进模型你会发现窗口自注意力因为涉及窗口拆分和合并在某些推理框架下的兼容性不太理想。一个有效的优化是把window_size设成8并保持输入特征图尺寸为其整数倍。我建议在实际部署时将特征图的宽高padding到8的倍数再送进HAT模块或者在模型配置里直接将输入尺寸设置为640x640已经是8的倍数这样窗口划分不会有多余的padding操作推理速度能恢复大约15%。另外如果你用TensorRT部署建议把HAT模块里的LayerNorm替换为GroupNormgroups32TensorRT对LayerNorm的优化不如GroupNorm成熟。GroupNorm在检测任务上的精度损失通常不到0.3个点但推理速度能提升约10%。5.3 多卡训练的同步问题如果你使用DDPDistributed Data Parallel方式训练HAT模块里的LayerNorm和窗口注意力在跨卡同步时可能会出现数值不一致的问题。我当时用4卡训练发现不同卡上的验证loss差异较大排查后确认是BatchNorm被误替换成了LayerNorm导致卡间统计量不同步。解决办法是要么统一在HAT模块中使用BatchNorm替代LayerNorm对检测任务更友好要么在DDP初始化时确保HAT模块中的所有层都处于同步状态。我最终选择把HAT中的LayerNorm保留但强制在每一轮迭代开始时做一次torch.distributed.barrier()同步问题解决。5.4 过拟合风险的把控HAT模块参数量不算少在中小规模数据集上有过拟合风险。我用的训练集大概有2万张图像属于中等偏小的规模。在训练后期验证集mAP出现下降趋势时我加了两个手段DropPath随机深度在HATBlock的残差路径上以0.1的概率随机丢弃迫使模块不能过度依赖某一层的信息增强了泛化能力。权重衰减从5e-4提高到1e-3让注意力矩阵的数值分布更紧凑减少过拟合。加上这两项之后验证精确率回升了约0.8个点。6. 本次改动的经验沉淀与适用边界这个HAT改进方案的成功本质上做对了一件事把“超分重建任务中学到的能力”用于增强“检测任务的特征表达”。但思路虽然好不代表任何场景都适合照搬有几个边界条件很重要。HAT模块最适合的场景是小目标在图像中虽然有清晰的物理结构边缘、纹理、对比度只是因为分辨率太低导致特征不明显。比如航拍图像中的车辆、行人工业质检中的小而规则的零件体育场景中的球和小型器材。这些目标做超分重建纹理恢复的成功率较高HAT能真正发挥作用。HAT也不适合两种场景。第一种是密集遮挡场景目标本身被大面积遮挡超分想“脑补”也补不出遮挡住的信息这时候应该用上下文建模或者Transformer检测头而不是注意力增强。第二种是目标本身属于低纹理物体比如纯色小色块、暗光环境下的小目标它们没有细节纹理可供恢复HAT的窗口自注意力很难学到有效的上下文。另外如果你追求极致推理速度HAT方案不一定是最优解。我在实时性要求特别高的项目里会把HAT替换为一个轻量的CA坐标注意力模块虽然精度提升少一些但推理速度几乎不降。HAT属于“用20%的速度换10%的精度”这类交易适合精度优先的场景。从更普适的角度来看这次改进让我重新思考了跨任务迁移的潜力。超分任务里的很多成熟设计——诸如残差密集连接、多尺度注意力、高频特征提取——本质上都是在解决“信息不足时如何补全”的问题。而检测任务里的小目标困境恰恰也是“信息不足”。把这两个问题关联起来可能比在检测框架内部做细节调优更有颠覆性。下一步我自己的计划是尝试把HAT模块进一步改造成一个轻量化的即插即用插件用结构重参数化把窗口自注意力的推理过程合并进普通卷积这样在部署阶段几乎不增加计算量同时保留注意力机制的训练期收益。这条路如果走得通HAT类改进的工程适用性会上一个大台阶。最后分享一个实战中的个人体会不要一上来就追求把新模块加到所有位置。先把HAT放到下采样两到三次后的浅层特征上跑通流程、确认收益方向正确再逐步扩展到Neck融合层。小步快跑每一步都量化评估而不是一次性把网络改成大杂烩。我在这个项目里前前后后做了近十组消融实验最终方案和最初设想已经差别很大但正是那十次迭代让我彻底摸清了这个模块的脾气。改进模型的乐趣除了看到AP数字上涨的那一瞬间更多的在于这个“逐步摸清脾气”的过程本身。