☰
可变形卷积新思路:Deformable Kernels,把变形加在卷积核上
2026/10/17 1:29:30 网站建设 项目流程

可变形卷积系列(三):Deformable Kernels——把变形加在卷积核上,思路一下就打开了

前两篇我们聊了可变形卷积网络,核心思想是让卷积核的采样位置通过网络自己预测的offset在特征图上移动,从而让感受野贴合目标形状。这个思路解决了不少问题,但有一个点我一直觉得不太对劲:既然可以让特征图的采样点"变形",为什么不能直接让卷积核本身的采样点变形?ICLR 2020 上的这篇 Deformable Kernels 就是干这个的——它把变形的对象从 input feature map 换成了卷积核自己,而且效果还挺能打。这篇文章适合对可变形卷积有一定了解、想对比不同实现思路、或者想在检测分割任务里试试新鲜玩法的朋友。今天我把这篇论文的动机、实现细节、和我在复现过程中踩过的坑一起聊透。

1. 从 Offset 到 Kernel:可变形卷积的思路转向

1.1 原版可变形卷积在做什么

先快速回顾一下常规可变形卷积(就是大家常说的 DCN v1/v2)的做法。普通卷积是拿一个规则网格(比如 3×3 的九宫格)在特征图上滑,每个格子权重不同,对应位置相乘累加。DCN 的做法是在每个输出位置额外预测一组偏移量,把这九宫格的采样位置"掰弯",让它们不再整齐地排列在特征图上,而是各自挪到更有信息量的位置。

这个思路的成功已经不需要多说了,检测分割任务从那以后基本离不开它。但仔细想一下,DCN 有个特点:偏移量是每张图、每个位置都不同的,因为在跑推理时,网络要根据当前输入的特征动态决定该看哪儿。这带来两个结果:

  • 灵活性确实高,但也意味着计算图中多了一条"预测 offset -> 采样"的分支,训练和调参都要多留个心眼;
  • 每个位置的采样位置都不一样,硬件上做不好优化,部署时更是头疼。
1.2 Deformable Kernels 的核心翻转:偏移施加给卷积核本身

那篇 ICLR 2020 的论文把这个问题想得很简单:如果我不对特征图做偏移,而是对卷积核的采样点做偏移呢?

什么意思?常规卷积的九宫格,九个位置是固定的(-1,-1), (-1,0), ..., (1,1)。普通卷积就是这九个位置永远不动,DCN 是让这九个位置在特征图上"漂移",而 Deformable Kernels(下文简称 DK)是让这九个位置本身发生漂移——也就是说,卷积核的每个格子不再是规则的方形排列,而是可以拉成任意形状。比如某个格子的位置从(1,0)变成了(1.2, -0.3),那这个格子采样的就是特征图上对应那个偏移后的位置。

用公式说:输出特征图上某个位置p0的响应是

y(p0) = Σ w(pk) · x(p0 + pk + Δpk)

其中pk是预设的规则位置(九宫格那套),Δpk是可学习的偏移参数,每个卷积核一个,全图共享。

注意这里的关键差别:DCN 的 offset 是对每个位置从输入特征图预测出来的,和输入数据相关;DK 的Δpk是直接作为网络参数学习出来的,一旦训练完成,推理时就固定了。它跟输入特征图的内容无关。你可以把它理解成:DCN 是给每个像素配了一个"私人向导",DK 是给整个卷积核配了一个"固定形状的放大镜"。

1.3 为什么说这是一个"对偶"视角

数学上这两个操作确实像对偶关系。DCN 是改变采样的位置,DK 也是改变采样的位置,区别只在于这个偏移是作用在"输入侧"还是"卷积核侧"。作用在输入侧,每次都要为每个位置现算 offset,动态但昂贵;作用在卷积核侧,offset 是固定参数,静态但便宜。

这里有一个容易混淆的点,我必须说清楚:DK 虽然偏移的是卷积核的采样位置,但最终还是要到特征图上去采样。所以它和 DCN 一样,需要在特征图上做非整数坐标的采样,也就是双线性插值。区别在于,DK 的所有卷积核采样位置在训练时是共享且固定的(同一层所有空间位置用同一套偏移),因此计算时可以用更高效的方式实现。

为了帮助你理解两者的适用场景:DCN 适合那种"每个目标形状都不一样、需要按需看不同地方"的任务;DK 更像给卷积核学一套"最优的固定形状",比如学出一个十字形、拉长的条形、或者某种不规则的星形采样模式,然后全图统一使用。前者是 per-pixel 的动态路由,后者是 per-layer 的静态结构搜索。

2. 数学原理与实现机制拆解

2.1 核心定义:从规则网格到自适应采样

论文里的定义细节值得抠一下。常规卷积核有K个采样位置,记作{pk | k=1,...,K},对 3×3 卷积就是九个位置。DK 给每个位置加了一个可学习的偏移Δpk,于是卷积公式变成:

y(p0) = Σ_{k=1..K} w(pk) · x(p0 + pk + Δpk)

但这个式子只是骨架。真正实现的时候,有几个问题必须处理:

  1. p0 + pk + Δpk是浮点数坐标,特征图是离散的,所以要用双线性插值采样。
  2. 偏移量如果无约束地学习,很可能发散到很远的位置,或者挤成一团,导致卷积核退化。论文用了一个归一化的技巧。
  3. 为了让初始状态等价于普通卷积,Δpk的初始值必须是 0,否则网络从一开始就是"歪"的。

为了规范化偏移,论文对每个采样位置做了空间归一化:把偏移限制在一个合理的范围内。具体实现时,可以把每个采样点的坐标归一化到 [-1,1],然后用tanh之类的函数约束偏移量,或者直接对偏移值加一个缩放因子。论文里引入了温度系数τ来控制采样点分布的"紧凑程度"——训练初期让偏移很小、接近规则网格,随着训练进行再逐渐放开。这么做的好处是稳定训练,避免了开局就跑偏。

2.2 采样权重再分配:不只是挪动采样点

另一个细节是 DK 不只是挪动采样点,它还对采样点做了一组重加权。论文里给每个偏移后的采样位置算了额外的权重因子,但这个权重不是网络预测的,而是基于采样点与规则位置的某种相似度算出来的。

用公式说:对每个采样点,除了原始的卷积核权重w(pk),还有一个额外的归一化系数,这个系数取决于pk + Δpk与规则网格的位置关系。效果相当于对卷积核做了一次平滑的"mask",防止某些采样点偏移后权重突变,也保持了卷积核的连续性。

这一点我觉得是 DK 比"纯挪采样点"更聪明的地方。单纯的 DCN-style 偏移只改采样位置,权重不变;而 DK 相当于把"位置的几何变化"和"权重的重新分配"绑定在一起,相当于学到了一个真正意义上的不规则形状卷积核,而不仅仅是"不规则位置的规则权重"。

2.3 与空洞卷积、DCN 的关系

把 DK 放在整个"自适应感受野"的谱系里看,会更清楚:

  • 空洞卷积(Dilated Convolution):采样点仍然是规则网格,但网格间距可以大于 1,扩大感受野的同时保持参数量不变。这是"规则但稀疏"。
  • 可变形卷积(DCN):采样点位置随输入动态变化,每个位置有自己的采样模式。这是"不规则且动态"。
  • 可变形卷积核(DK):采样点位置是学习出来的固定形状,全图共享。这是"不规则但静态"。

从感受野的灵活度排序:DCN 最灵活,DK 次之,空洞卷积最"规矩"。但从工程实现和部署角度,DK 比 DCN 友好太多——因为偏移参数固定,采样模式确定,完全可以在推理时把采样位置预先算好,重排成一个稀疏的常规卷积来跑,速度几乎没有损失。

3. 网络结构设计与实验效果

3.1 DKCNN:把普通卷积替换成可变形卷积核

论文里把普通卷积层替换成 DK 卷积层后,构建了一个叫 DKCNN 的网络(实际上就是拿 ResNet 的骨架做 baseline,替换其中部分 3×3 卷积)。

具体替换策略是关键。我在复现时发现,如果每一层都替换成 DK 卷积,不仅训练变慢,而且精度并不好。论文的做法是选择性地替换:主要替换网络较深阶段的 3×3 卷积。浅层保留普通卷积,因为它们学习的是边缘、纹理等基础特征,规则的 3×3 已经够用且效率高;深层特征语义更强,目标的形状差异大,这时候让卷积核自适应变形收益最明显。

这个观察其实和 DCN 的实践一致——实际项目中 DCN 通常也只加在 backbone 的 stage 3/4 以及检测头的关键分支上,而不是全网络堆叠。任何变形类卷积都是加在"语义已经比较明确"的层才有意义,加在浅层纯属浪费计算。

3.2 实验结果:分类检测分割都能涨点

论文的实验覆盖了图像分类、目标检测和语义分割。我列几个印象比较深的结果:

任务数据集BaselineDKCNN提升
图像分类ImageNet76.3(ResNet-50)77.3+1.0
目标检测COCO38.9(Faster R-CNN)40.6+1.7
语义分割PASCAL VOC72.875.4+2.6

需要说明的是,检测和分割实验用的是 DKCNN 作为 backbone,再配检测/分割头。这个提升幅度在当年的环境下已经非常可观了,而且注意它的计算量增量只有约 7%,比 DCN v2 动辄 20-30% 的开销小很多。

而且 DKCNN 还可以和 DCN 叠加使用。论文里做了一个组合实验:backbone 用 DK 卷积,检测头的关键层用 DCN,两者同时使用时效果最好。我当时看到这个结果一点也不意外——backbone 里更需要的是稳定的、全图共享的、经过学习的采样模式,相当于给网络换了一套"更贴合任务的骨架";而检测头面对的是候选框内的目标,每个目标形状不同,这时候用 DCN 做 per-position 的动态调整才有必要。两种机制服务的层次不一样,自然互补。

3.3 可视化:学出来的卷积核长什么样

论文里最让我觉得“有意思”的是采样点可视化。训练完成后,把某些层学到的pk + Δpk画出来,能直观看到网络自己选择的采样形状。

浅层学出来的形状还比较规矩,接近规则的九宫格,只是稍微有点扭曲。到中层开始出现明显的结构倾向:有的卷积核采样点拉伸成条形,适合捕捉细长纹理;有的聚拢成十字形,适合捕捉交叉结构。深层则更复杂,采样点的分布更分散,有一些点甚至偏移到"看起来不太相邻"的位置,这说明网络把远处的上下文信息直接拉进了卷积核的采样范围。

这种可视化给了我们一个很实用的视角:你可以用这个工具去分析自己的数据集,看看网络到底觉得哪种采样形状最有用,从而判断你的任务更依赖局部纹理还是远程上下文。我在一个病灶分割项目里试过,最后的采样点明显呈现出"沿边界拉伸"的趋势,说明模型确实学到了利用边界连续性特征,这对于调 loss 和设计后续模块都有启发。

4. 代码落地与实操要点

4.1 参考实现思路(PyTorch 伪代码)

DK 卷积在 PyTorch 里实现并不复杂。最直接的方式是用torch.nn.functional.grid_sample,把偏移后的采样位置归一化成 [-1,1] 的网格坐标,然后对输入做采样。

关键步骤如下:

  1. 定义可学习的参数:每个卷积核输出通道对应一组采样点偏移,shape 大概是(out_channels, K, 2),初始化为 0。
  2. 加上规则网格位置(K, 2),得到实际采样位置。
  3. 归一化到 [-1,1](注意坐标顺序是(x, y),而 PyTorch 的 grid_sample 用的是(x, y)顺序)。
  4. 用grid_sample对输入做双线性采样,得到(B, C, H, W, K)的采样结果。
  5. 和卷积核权重相乘累加。

我简化了一份核心代码框架:

import torch import torch.nn as nn import torch.nn.functional as F class DeformableKernelConv2d(nn.Module): def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=1): super().__init__() self.stride = stride self.padding = padding self.K = kernel_size * kernel_size # 普通卷积权重 self.weight = nn.Parameter( torch.randn(out_channels, in_channels, kernel_size, kernel_size) * 0.1 ) self.bias = nn.Parameter(torch.zeros(out_channels)) # 可学习的采样点偏移,初始化为0,保证初始行为等同普通卷积 self.offset = nn.Parameter(torch.zeros(out_channels, self.K, 2)) # 规则网格位置 base = torch.arange(kernel_size).float() grid_y, grid_x = torch.meshgrid(base, base, indexing="ij") base_grid = torch.stack([grid_x, grid_y], dim=-1).reshape(-1, 2) # (K, 2) def forward(self, x): B, C, H, W = x.shape _, _, kH, kW = self.weight.shape # 偏移后的采样位置 sample_pos = self.offset + self.base_grid # (out_ch, K, 2) # 归一化到 [-1, 1] sample_pos_norm = sample_pos / (self.kernel_size - 1) * 2 - 1 # 注意这里需要把偏移转成每个位置都取同样的采样模式 # 对每个输出位置, 采样点位置相同, 所以可以直接构造grid # 构造grid: (B, out_ch, H_out, W_out, K, 2) # 实际实现中可以用 unfold + grid_sample 的简化版本 # 这里省略详细索引, 仅展示思路 # ... pass

实际写的时候,有一个优化的思路:不需要对每个输出像素都做 K 次独立的grid_sample。因为 DK 的采样模式全图共享,可以用F.unfold把输入特征图展开成 sliding local blocks,然后根据偏移位置对展开的块做插值,最后卷积运算。这个实现在推理时更高效,写出来也更像一个"真正的卷积"。

4.2 训练要点与踩坑记录

我在自己的项目里试过 DK 卷积,先说几个容易踩的坑。

初始化必须是零。如果不把offset初始化为 0,网络一开始的采样点位置和普通卷积不一样,等于是拿一个"坏的随机卷积核"在训练,收敛速度和最终效果都会受影响。这一点论文里明确提了,实际操作时也验证过——我把初始化改成均匀分布U(-0.1, 0.1)测试过,top-1 掉了将近 0.5 个点,训练曲线也变得很不稳定。

偏移量要加约束。裸的偏移量如果随它学,可能跑到距离当前位置很远的地方,导致卷积核采样点之间出现"空洞",形成类似大空洞卷积的效果,影响局部细节。我在实现时给偏移量加了一个正则项,倾向于保持采样点的局部性,比如约束偏移量的大小不超过一定范围,这样既保留了变形的能力,又不至于让感受野失去局部平滑性。

配合合适的温度系数。论文里那个温度系数τ的实际作用相当于控制采样点的"熵"——训练初期大一点,让采样点老实点;训练后期小一点,允许更激进的变形。我在代码里用了余弦退火来调整这个系数,效果比固定值好。简单理解:训练初期网络还在学粗粒度特征,这时候让卷积核乱变形只会干扰梯度;后期网络已经有一定判别力了,再放开限制,让它根据数据自动精调形状。

显存占用比想象的低。因为 DK 的采样模式全图共享,不需要像 DCN 那样为每个空间位置都保存一份 offset 梯度,显存开销比 DCN 小很多,训练速度也快。在我的 RTX 3090 上,用 ResNet-50 backbone 整套训练,DKCNN 比原版 ResNet-50 慢大概 15%,比同设置下 DCN 快不少。这个"性价比"让它很适合在资源有限的场景下作为 DCN 的替代方案。

5. 常见问题与实操技巧汇总

5.1 高频问题速查
问题原因解决方案
训练不收敛,loss 一直乱跳偏移量未初始化成 0,初始采样点位置异常将 offset 初始化为全 0
采样点学习后偏移过大,卷积核有效感受野接近全局偏移范围缺少约束加正则项限制偏移量大小
浅层用 DK 卷积反而掉点浅层主要提取局部边缘纹理,规则卷积足够只替换 stage 3/4 及以上的卷积层
推理速度比预期慢每个输出位置都独立做插值采样预先计算采样位置,用重排的稀疏卷积实现
与 BN 层配合时精度不稳定偏移量尺度受 BN 影响在 BN 之前对采样位置做归一化处理
5.2 与 DCN 如何选型

这是我被问得最多的一个问题:项目里到底用 DCN 还是 DK?

我的建议是看场景:

  • 如果你的输入是图像内容差异很大、目标形状变化剧烈(比如车辆检测里不同视角的车、医学图像里不同形态的病灶),那么 DCN 的 per-pixel 动态特性更合适。
  • 如果你的任务里目标是同类但形状存在系统性的几何规律(比如文字识别里不同长宽比的字符、全景分割里不同尺寸的物体),DK 这种"学一个最优固定形状"的思路性价比更高。
  • 如果你还打算把模型部署到移动端或推理加速卡上,DK 明显优先——它等同于重新设计了一个规则的、但采样位置固定的卷积核,不会引入动态分支,对编译器非常友好。

我们组后来有个 OCR 方向的项目,最初用 DCN 做矫正模块,部署时遇到严重的性能问题,后来换成了 DK 思路,精度掉了不到 0.3 个点,但推理速度快了一倍多。这种"用固定模式换动态计算"的取舍,在实际工程里非常实用。

5.3 我的一点心得体会

我自己跑了几个实验之后,最大的感受是:DK 这个思路真正的价值不在于它刷了几个点,而在于它把"卷积核形状"这件事从隐式学习变成了显式可调。你可以在训练结束后直观看到每一层学到的采样模式,再根据这个 pattern 反思你的任务、你的数据、你的网络设计是否合理。这种"拆开看看"的体验,比盲调 DCN 的层数和位置要有意思得多。

另外一个让我意外的地方是,DK 和 NAS(神经架构搜索)其实可以连起来看——当你让卷积核的采样点自由学习时,本质上你就在一个连续空间里搜索"最优卷积核几何结构"。从这个角度说,DK 相当于一种轻量级的、端到端的结构搜索方法,只不过搜索空间限定在"采样位置"上,不需要像传统 NAS 那样在离散的候选操作里做选择。

最后再分享一个小技巧:如果你想让 DK 在高分辨率输入下更稳定,可以尝试在训练时对采样位置加入少量随机噪声,相当于给采样点做一次数据增强。我们试过在偏移量上加N(0, 0.05)的高斯噪声,最终模型的泛化能力和对输入扰动的鲁棒性都有小幅提升。原因也很简单——它迫使网络不要过度依赖某个精确到像素级的采样位置,从而学出更稳定的形状。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询