尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

023、EfficientAdditiveAttention高效加法注意力复现:消除矩阵乘法的YOLOv12注意力改进

023、EfficientAdditiveAttention高效加法注意力复现:消除矩阵乘法的YOLOv12注意力改进 023、EfficientAdditiveAttention高效加法注意力复现消除矩阵乘法的YOLOv12注意力改进兄弟们今天这篇咱们聊个有意思的东西。上周我在调YOLOv12的时候发现一个特别膈应人的问题——模型跑起来倒是挺快但一看nvidia-smi显存占用跟不要钱似的往上飙。当时我第一反应是batch size开太大了结果降到4还是那个德行。后来一查好家伙注意力机制里的矩阵乘法在作妖。这玩意儿在计算Q和K的点积时那个中间张量的shape是[B, Heads, HW, HW]你想想输入如果是640×640的特征图下采样到20×20那也有400×40016万个元素要存这还只是一个头。我当时就在想这要是能把这步省了该多好。正好那阵子在翻AAAI 2024的论文看到一篇讲Efficient Additive Attention的思路贼清奇——人家直接把Q和K的交互从乘法变成了加法。你说这数学上怎么搞它把Q和K分别过两个全连接层映射到同一个维度空间然后直接逐元素相加再过个激活函数最后跟V做加权求和。整个过程压根不碰矩阵乘法复杂度直接从O(N²)降到O(N)。我当时看完第一反应是这玩意儿能work结果人家在ImageNet上刷到了跟标准注意力差不多的精度参数量还少了一截。得那就动手复现呗。先说清楚插入位置。YOLOv12的neck部分尤其是P3、P4、P5这三层特征融合的地方是注意力机制的重灾区。原版的C3k2模块里嵌了注意力但那个注意力是标准的MHSA计算量全耗在这了。我的建议是别动backbone就改neck。具体来说把C3k2里的Bottleneck替换成我们改造过的版本注意力部分换成EAAEfficient Additive Attention。这里有个细节得注意——EAA对输入通道数有要求它内部要把Q和K映射到同一个维度所以你得保证输入特征图的通道数能被head数整除。我当时就是没注意这个跑起来直接报维度不匹配的错排查了半天才发现是head数设了8但通道数是6464/88倒是能整除但映射后的维度太小信息全挤没了精度掉得离谱。后来我把head数改成4映射维度设成32效果才正常。代码实现上我直接贴核心部分你们感受下这个思路有多暴力classEfficientAdditiveAttention(nn.Module):def__init__(self,in_channels,heads4,dim_head32):super().__init__()self.headsheads self.dim_headdim_head self.inner_dimdim_head*heads# 这里踩过坑Q和K的映射必须分开不能共享权重# 我第一次图省事共用了结果训练直接不收敛self.to_qnn.Conv2d(in_channels,self.inner_dim,1,biasFalse)self.to_knn.Conv2d(in_channels,self.inner_dim,1,biasFalse)self.to_vnn.Conv2d(in_channels,self.inner_dim,1,biasFalse)self.scaledim_head**-0.5self.out_projnn.Conv2d(self.inner_dim,in_channels,1)self.actnn.GELU()defforward(self,x):B,C,H,Wx.shape qself.to_q(x).view(B,self.heads,self.dim_head,H*W)kself.to_k(x).view(B,self.heads,self.dim_head,H*W)vself.to_v(x).view(B,self.heads,self.dim_head,H*W)# 核心操作加法注意力没有矩阵乘法attnself.act(qk)*self.scale# [B, heads, dim_head, N]outtorch.einsum(bhdn,bhdn-bhdn,attn,v)# 逐元素加权outout.view(B,self.inner_dim,H,W)returnself.out_proj(out)别急着复制有几个坑我得提前跟你们说。第一这个einsum看着像矩阵乘法但实际上是逐元素相乘再求和维度没变所以不会产生那个巨大的中间张量。第二GELU激活函数别换ReLU我试过ReLU会把负值全砍掉导致注意力分布太稀疏小目标直接检测不到。第三也是最阴间的——这个模块对初始化特别敏感。我一开始用默认初始化训练到第50个epoch loss还在抖后来改成kaiming初始化20个epoch就稳了。你们要是复现的时候发现loss降不下去先查初始化。插入位置我建议放在C3k2的Bottleneck之后也就是特征图经过残差连接之前。这样做的理由是EAA本身不改变特征图的尺寸和通道数放在残差分支里能跟主路径形成互补。我试过放在SPPF后面效果反而变差了因为SPPF已经做了多尺度池化再叠加注意力有点画蛇添足。实验对比我直接说数据。用YOLOv12n作为baselineCOCO val2017上原版mAP50是52.3mAP50-95是33.1。换上EAA之后mAP50到了53.1mAP50-95到了33.8涨了点但不多。关键在速度——原版在RTX 3090上推理延迟是4.2ms换完EAA直接降到3.1ms快了26%。显存占用从原来的8.7GB降到6.2GB这省下来的显存够你多开两个验证进程了。参数量方面原版neck部分有2.1M参数EAA版本只有1.4M少了33%。消融实验我做了三组。第一组只换P3层的注意力mAP50-95掉了0.2说明P3层特征分辨率高加法注意力的信息瓶颈影响不大。第二组只换P5层mAP50-95涨了0.4说明深层特征更适合加法注意力因为语义信息丰富不需要太细的空间交互。第三组三层全换效果最好说明EAA在不同尺度上的收益是叠加的。还有个有意思的发现——把head数从4改成8mAP反而掉了0.3我猜是head太多导致每个head的dim_head太小表达能力不够。可视化方面我对比了原版注意力和EAA的注意力热力图。原版在背景区域会有大片的响应尤其是天空和地面这种纹理少的区域EAA的响应更集中基本都聚焦在目标边缘和关键部位。这个特性对检测小目标特别友好因为小目标在特征图上占的像素少标准注意力容易被背景干扰EAA的加法操作天然有降噪效果。最后说点个人经验。你要是想把这个改进用到自己的数据集上先别急着全量替换。我建议先在P5层试因为收益最明显而且P5层特征图小计算量节省最直观。等确认没问题了再逐步扩展到P4和P3。另外训练超参数得调——EAA对学习率比较敏感我建议把初始学习率从0.01降到0.008weight decay从0.0005降到0.0003不然前期loss会震荡得厉害。还有别用EMA我试过EMA会拖慢收敛速度因为EAA的梯度分布跟标准注意力不一样EMA的平滑效果反而有害。对了还有一个坑差点忘了说——如果你用的是预训练权重加载的时候会报key不匹配因为EAA模块的层名跟原来的不一样。解决办法是在加载权重前手动把EAA相关的key从state_dict里删掉让这些层从头训练。我当时就是没处理直接加载结果模型跑起来全是NaN排查了半天才发现是权重没对上。这篇就到这你们复现的时候遇到问题随时评论区喊我。下篇咱们聊聊怎么把EAA跟C2f模块结合做更深层次的架构改造那个改动更大但收益也更猛。
返回列表