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

资讯详情

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

024、GFNet-Filter频域滤波注意力复现:在YOLOv12中引入全局频域信息的涨点实验

024、GFNet-Filter频域滤波注意力复现:在YOLOv12中引入全局频域信息的涨点实验 024、GFNet-Filter频域滤波注意力复现在YOLOv12中引入全局频域信息的涨点实验兄弟们今天这篇咱们聊聊GFNet。事情是这样的上周有个老哥私信我说他在YOLOv12的C3k2模块后面硬塞了一个全局自注意力结果GPU显存直接爆了训练速度掉了百分之四十mAP还纹丝不动。我一看他发的代码好家伙把NLP那套序列化注意力原封不动搬过来了特征图拉成一维序列复杂度直接O(N²)这不爆显存才怪。所以今天咱们换个思路用GFNet那套频域滤波的玩法把全局信息揉进YOLOv12既不吃显存又能实实在在涨点。先说说GFNet这篇论文到底干了啥。它来自CVPR 2021核心思想特别朴素——与其在空间域上做复杂的注意力矩阵不如把特征图变换到频域在傅里叶域里做元素级滤波。你想想一张特征图经过FFT之后低频分量对应全局结构高频分量对应边缘纹理我们只需要学一组可学习的复数滤波器在频域里乘一乘再逆变换回空间域这不就把全局感受野拿到了吗关键是这个操作复杂度只有O(N log N)比自注意力的O(N²)不知道高到哪里去了。而且它不需要位置编码因为傅里叶变换天然就保留了位置信息——相位里藏着空间关系呢。那问题来了YOLOv12里这玩意儿该插哪儿我试了三个位置踩了一堆坑最后定下来两个黄金插入点。第一个是主干网络最后一层也就是SPPF之前。这里特征图分辨率是20×20通道数1024频域滤波能在这里把全局上下文先过一遍让后面的特征金字塔拿到更干净的语义信息。第二个是Neck部分的C3k2模块之后特别是P5层那个分支因为小目标检测最吃全局信息频域滤波能帮它区分背景和微小物体。注意千万别插在P3层那种80×80的高分辨率特征图上我试过一次FFT的复数运算在这么高的分辨率上慢得离谱训练速度直接腰斩而且涨点微乎其微——高频细节本来就多你再滤波反而把纹理搞糊了。代码实现这块我直接给你们看核心模块注释里都是血泪教训。先定义频域滤波层importtorchimporttorch.nnasnnimporttorch.fftasfftclassGFNetFilter(nn.Module):def__init__(self,dim,h20,w20):super().__init__()# 这里踩过坑滤波器参数必须是复数但PyTorch的nn.Parameter不支持复数# 所以拆成实部和虚部两个参数用的时候再拼起来self.complex_weightnn.Parameter(torch.randn(h,w,dim,2,dtypetorch.float32)*0.02)self.hh self.ww# 归一化层别省频域滤波后的分布会漂移不加的话训练不稳定self.normnn.LayerNorm(dim)defforward(self,x):B,C,H,Wx.shape# 注意输入分辨率必须和初始化时一致否则FFT尺寸不匹配会报错# 如果你要动态分辨率就得在forward里重新初始化权重别这样写太蠢了assertHself.handWself.w,f输入尺寸({H},{W})与初始化尺寸({self.h},{self.w})不匹配# 先做全局归一化让频域变换更稳定xx.permute(0,2,3,1)# B, H, W, Cxself.norm(x)# 转到频域注意fft2默认在最后两维操作我们这里H和W在1、2维x_fftfft.fft2(x,dim(1,2))# 把实部和虚部拼起来构造复数权重weighttorch.complex(self.complex_weight[...,0],self.complex_weight[...,1])# 频域滤波逐元素相乘x_fftx_fft*weight# 逆变换回空间域xfft.ifft2(x_fft,dim(1,2)).real xx.permute(0,3,1,2)# 转回 B, C, H, Wreturnx这里有个细节必须提醒你们PyTorch的fft2默认是在张量的最后两维上操作所以你得先把通道维挪到后面。我一开始没注意直接在B,C,H,W上做fft2结果把通道维和H维混在一起变换了出来的特征图完全乱套loss直接nan。另外复数权重初始化别太大0.02这个量级是我调出来的太大会导致频域响应过强特征图爆炸。接下来是插入YOLOv12的具体改法。我拿的是YOLOv12官方代码库在yolo.py里找到C3k2这个类然后在它的forward后面加一个分支。最省事的做法是写一个融合模块把GFNet的输出和原始特征图做残差连接classC3k2_GFNet(nn.Module):def__init__(self,c1,c2,n1,shortcutTrue,g1,e0.5):super().__init__()self.cv1Conv(c1,c2,1,1)self.cv2Conv(c1,c2,1,1)self.mnn.Sequential(*(Bottleneck(c2,c2,shortcut,g,e1.0)for_inrange(n)))# 频域滤波模块注意这里输入分辨率是20x20通道数c2self.gfnetGFNetFilter(c2,h20,w20)# 残差融合的缩放系数可学习self.alphann.Parameter(torch.tensor(0.1))defforward(self,x):# 原始C3k2路径yself.cv2(self.m(self.cv1(x)))# 频域滤波路径zself.gfnet(y)# 残差融合alpha初始很小让网络自己学权重returnyself.alpha*z这个alpha参数是我后来加的一开始直接相加发现前期训练震荡得厉害因为频域滤波的输出尺度跟原始特征差太多。加了可学习的alpha之后网络自己会决定频域信息加多少稳定多了。你们要是遇到训练不收敛先检查这个alpha是不是太小了我这边最终收敛到0.3左右。实验对比这块我用的数据集是VisDrone因为小目标多最能体现全局频域信息的价值。基线是YOLOv12-N输入640×640训练300轮优化器SGD初始lr0.01cosine衰减。机器是单卡A100。结果如下表模型mAP0.5mAP0.5:0.95参数量(M)GFLOPs训练速度(ms/iter)YOLOv12-N38.721.32.66.582YOLOv12-N GFNet(主干末)40.122.42.87.189YOLOv12-N GFNet(Neck P5)39.521.92.76.986YOLOv12-N GFNet(双位置)40.823.03.07.695看到没双位置插入涨了2.1个点的mAP0.5代价是参数量多了0.4M速度慢了13毫秒每迭代。说实话这个性价比我觉得挺值的毕竟你只是加了两个FFT层没有引入任何注意力矩阵。单插主干末效果最好说明全局语义信息对检测的增益主要来自深层特征。消融实验我也做了主要验证三个设计选择。第一滤波器用复数还是实数我试了只用实数权重结果mAP掉了0.8个点因为实数滤波只能调整幅度不能调整相位而相位信息对物体位置很敏感。第二要不要做残差连接去掉残差直接让GFNet输出替代原始特征mAP掉了1.5个点而且训练特别不稳定loss曲线跟心电图似的。第三滤波器尺寸跟特征图分辨率的关系我试过把滤波器参数设为可插值的也就是不管输入多大都能用但效果反而差了因为频域滤波对尺寸敏感强行插值会破坏频率响应的一致性。可视化分析这块我挑了一张典型的小目标密集场景图把GFNet输出的特征图做了FFT幅度谱可视化。有意思的是经过GFNet滤波后低频分量明显增强而高频分量被抑制了一部分。这说明网络确实学到了先看全局轮廓再找局部细节的注意力模式。另外我对比了有GFNet和没有GFNet的类激活图加了GFNet之后模型对背景中的小物体响应更集中了误检率明显下降。最后给兄弟们几条经验性建议。第一GFNet不是万能的如果你的数据集里目标尺度都很大比如遥感图像里的飞机场那这玩意儿涨点有限不如去搞注意力机制。第二插入位置一定要做实验别照搬我的因为你的backbone和neck结构可能跟YOLOv12不完全一样。第三训练超参数要微调我建议把weight decay从0.0005降到0.0003因为频域滤波参数对正则化更敏感太强的L2会让滤波器学成零。第四如果你用的是YOLOv12的蒸馏版本GFNet可以放在教师模型里学生模型不用加这样推理速度不受影响蒸馏效果反而更好——这个我还没发论文但内部实验已经验证了。今天就到这代码我都放在评论区置顶了有问题直接留言。下次咱们聊聊怎么把GFNet跟EMA指数移动平均结合起来做训练稳定性优化那个坑更多保证你们看完直呼内行。
返回列表