042、DConv-Former可变形卷积与Transformer融合注意力在YOLOv12中的复现——动态特征交互
042、DConv-Former可变形卷积与Transformer融合注意力在YOLOv12中的复现——动态特征交互
昨天调模型的时候又碰到那个老问题——小目标漏检。coco val上AP50都0.72了,AP_s还是卡在0.31上不去。我盯着tensorboard里那几条loss曲线发呆,突然想到之前读CVPR那篇Deformable Attention的文章,思路其实可以换个姿势塞进YOLOv12的neck里。今天折腾了一整天,把DConv-Former这个融合模块在YOLOv12上跑通了,记录一下踩坑过程。
先说清楚DConv-Former到底在干什么。传统Transformer的attention是在全局或固定窗口内算相似度,但目标检测里物体尺度变化太剧烈,固定感受野要么漏掉大目标的上下文,要么被小目标的背景噪声干扰。可变形卷积的思路是让采样点跟着目标形状走,但DCNv2本身没有全局建模能力。DConv-Former就是把这两者拧在一起——先用可变形卷积生成一组偏移量,这些偏移量告诉Transformer的attention模块"你应该往哪些位置去看",然后attention只在偏移后的采样点上做交互。说白了就是让注意力自己学会"该看哪里",而不是傻乎乎地在整张feature map上均匀撒网。
插入位置我试了三个地方:backbone的C3k2后面、neck的PANet上采样之前、还有detect头前面。最后发现放在neck的top-down路径里效果最稳。具体来说,我把它插在YOLOv12的nn.C3k2模块之后、Concat操作之前。这个位置刚好是浅层特征和深层特征交汇的地方,语义信息和空间细节都还在,可变形偏移量能学到的东西最多。你要是放在backbone太深的位置,偏移量基本学不动,因为特征图分辨率太低;放在detect头前面又太浅,全局上下文不够。
代码实现上有个大坑必须提醒。PyTorch里torchvision.ops.deform_conv2d的输入格式是(N, C, H, W)的input加上(N, 2*kH*kW, H, W)的offset,但YOLOv12的feature map是(B, C, H, W),batch size和通道顺序没问题,关键是offset的通道数计算。我一开始偷懒直接用了offset = self.conv_offset(x)输出64通道,结果deform_conv2d报错说offset尺寸不匹配。后来老老实实按公式算:offset_channels = 2 * kernel_size * kernel_size,kernel_size=3就是18通道。别想着省事,这个错报得你怀疑人生。
再说attention部分。我用的不是标准MHSA,而是简化版的线性注意力——把Q和K映射到低维空间再算相似度,这样配合可变形偏移量,计算量能压住。具体实现里,我先用DCNv2对输入x做一次特征重采样,得到x_deformed,然后把这个重采样后的特征同时作为Q和K的输入,V还是用原始x。这样做的直觉是:偏移量已经告诉模型"哪些位置重要",Q和K在重要位置上做交互,V保留原始信息用于输出。你要是把V也换成deformed版本,梯度会变得很不稳定,训练前期loss直接nan,我试过,别问怎么知道的。
代码结构上,我写了一个DConvFormer类,里面包含三个子模块:offset_conv(生成偏移量)、deform_conv(可变形重采样)、linear_attn(线性注意力)。forward流程是:x进来先过offset_conv得到offset,然后deform_conv用这个offset对x重采样,得到x_deformed;接着x_deformed过layer_norm,分别映射成Q和K,x映射成V;attention score = softmax(Q @ K^T / sqrt(d) ),然后和V做加权求和;最后接一个残差连接和FFN。FFN我用了两个1x1卷积,中间加GELU激活,比标准Transformer的FFN更适合检测任务。
有个细节值得注意:offset_conv的初始化。我一开始用默认的kaiming初始化,训练到第20个epoch时发现offset的值域已经跑到±50去了,feature map才32x32,这偏移量直接把采样点甩出图外了。后来改成用零初始化offset_conv的权重,bias也设成0,这样初始状态下可变形卷积退化成普通卷积,让模型自己慢慢学偏移量。这个trick在DCNv2原论文里提过,但很多人实现时忽略了。你要是发现训练初期loss不降,先检查这个。
实验对比我跑了三组:baseline YOLOv12、YOLOv12+DConvFormer(neck插入)、YOLOv12+DConvFormer(backbone插入)。数据集用的VisDrone,因为小目标多,能看出差异。结果如下表:
| 模型配置 | mAP50 | mAP50-95 | AP_s | AP_m | 参数量(M) | GFLOPs |
|---|---|---|---|---|---|---|
| YOLOv12 baseline | 0.523 | 0.318 | 0.214 | 0.402 | 20.1 | 71.3 |
| +DConvFormer(neck) | 0.547 | 0.336 | 0.248 | 0.421 | 22.8 | 79.6 |
| +DConvFormer(backbone) | 0.531 | 0.324 | 0.229 | 0.408 | 22.8 | 79.6 |
neck插入比backbone插入在AP_s上高了近2个点,说明动态特征交互确实更适合放在特征融合阶段。参数量增加了2.7M,GFLOPs多了8.3,换来AP50提升2.4个点,性价比还行。你要是部署在边缘设备上,可以考虑把linear_attn的维度从128降到64,能省不少计算,代价是AP_s掉0.5左右。
消融实验我拆了三个组件:只保留DCN(去掉attention)、只保留attention(去掉DCN)、完整DConvFormer。结果很有意思——只保留DCN时AP_s提升0.8个点,只保留attention时提升0.5个点,但两者合起来提升了3.4个点。不是简单的加法效应,说明DCN生成的偏移量确实给attention提供了更好的采样位置先验,而attention反过来又让偏移量的学习有了全局梯度信号。这种协同效应是DConv-Former的核心价值,单独用任何一个都差点意思。
可视化分析我看了几个典型样本。小目标密集的场景(比如停车场里的车),baseline的attention map基本是均匀分布的,DConvFormer的attention map会集中在车辆轮廓边缘,而且偏移量把采样点拉到了相邻车辆的间隙位置——这说明模型学到了"看目标要看边界,但判断类别要看周围环境"这种策略。还有个有意思的现象:在遮挡场景下,偏移量会把采样点绕到遮挡物后面去,相当于模型自己学会了"透视"。
最后说点经验性的东西。第一,DConvFormer的offset_conv不要加BN,加了BN之后偏移量的分布会被强行归一化,导致可变形卷积失去"变形"能力,我试过加了BN之后AP直接掉1.5个点。第二,训练时学习率要调低一点,我用的初始lr从0.01降到0.008,因为DCN的梯度比普通卷积大一个量级,lr太猛容易震荡。第三,如果你用的是YOLOv12的官方代码库,记得在yaml配置文件里把DConvFormer的depth参数设成1就够了,堆多了反而过拟合,我试过depth=2,验证集loss反而升高。
这个模块的改进空间还很大。比如可以尝试把可变形偏移量改成可学习的稀疏采样模式,或者把线性attention换成gated attention。但就目前的效果来看,DConv-Former在YOLOv12的neck里已经能稳定带来小目标检测的提升,而且代码实现不算复杂,适合作为论文的一个创新点。你要是复现过程中遇到问题,优先检查offset的通道数、初始化方式、以及插入位置这三个地方,90%的坑都在这。