[文献阅读] 可变形卷积DCN - Deformable Convolutional Networks

news2025/3/16 18:58:58

**文献信息:**Deformable Convolutional Networks arxiv.org/abs/1703.06211
发表于ICCV 2017,提出了可变形卷积DCN(Deformable ConvNets)

摘要

卷积神经网络(CNN)由于其构建模块固定的几何结构天然地局限于建模几何变换。

为了提高CNN的转换建模能力,作者提出了可变形卷积可变形RoI池化。两者都基于这样的想法:增加模块中的空间采样位置以及额外的偏移量,并且从目标任务中学习偏移量。并且新的模块可以很方便的替换普通的CNN模块,并且可以通过标准反向传播便易地进行端对端训练。

实验证明了在深度CNN中学习密集空间变换对于复杂的视觉任务(如目标检测和语义分割)是有效的。

Abstract

This week’s report examines Deformable Convolutional Networks (DCN). DCN introduce deformable convolutions and deformable RoI pooling, which add adaptive offsets to the standard grid sampling positions in convolutional and pooling layers. These offsets are learned from the data, allowing the network to capture complex spatial variations. The report explains how deformable convolutions and pooling layers are integrated into CNN and trained end-to-end via standard backpropagation. Experiments on tasks like object detection and semantic segmentation demonstrate significant improvements in performance, highlighting the effectiveness of DCN in handling geometric deformations in visual data.

可变形卷积

CNN本质上局限于建模大量,未知的数据转换。该限制源于CNN模块的固定几何结构:卷积单元在固定位置对输入特征图进行采样;池化层以一个固定的比例降低空间分辨率;一个RoI(感兴趣区域)池化层把RoI分成固定的空间组块等等。

而缺乏处理几何变换的内部机制。这会导致明显的问题。举一个例子,同一CNN层中所有激活单元的感受野大小是相同的。对于在空间位置上编码语义的高级CNN层来说,这是不可取的。由于不同的位置可能对应不同尺度或形变的目标,所以对于具有精细定位的视觉识别来说,例如使用全卷积网络的语义分割,尺度或感受野大小的自适应确定是理想的情况。

在这里插入图片描述

可变形卷积将2D偏移添加到标准卷积中的常规网格采样位置上。它可以使采样网格自由形变。

(a)标准卷积的定期采样网格(绿点)。(b)变形的采样位置(深蓝色点)和可变形卷积中增大的偏移量(浅蓝色箭头)。©(d)是(b)的特例,表明可变形卷积推广了尺度、长宽比和旋转的各种变换。

可变形卷积层

定义标准的卷积过程,对输入的2Dfeature map y上的每一个位置 P 0 P_0 P0,进行以下卷积操作
y ( P 0 ) = ∑ P n ∈ R w ( P n ) ⋅ x ( P 0 + P n ) y(P_0)=\sum_{P_n\in R}w(P_n)\cdot x(P_0+P_n) y(P0)=PnRw(Pn)x(P0+Pn)
其中,P_n是卷积核的每一个位置,w是卷积核.

网格R定义了感受野的大小和扩张,如:定义了一个扩张大小为1的3×3卷积核
R = { ( − 1 , − 1 ) , ( − 1 , 0 ) , . . . , ( 0 , 1 ) , ( 1 , 1 ) } R = \{(−1, −1),(−1, 0), . . . ,(0, 1),(1, 1)\} R={(1,1),(1,0),...,(0,1),(1,1)}
在标准卷积操作中,对每一个位置 P 0 P_0 P0,对其与它在R中的所有偏移位置(上下左右及对角线)的特征点与卷积核对应的位置进行加权求和,得到新特征图上对应的P_0点。例如 R [ 0 ] = ( − 1 , − 1 ) R[0]=(-1,-1) R[0]=(1,1),就是对应 P 0 P_0 P0点的左上角的点。

输出特征映射y上的每个位置 p 0 p_0 p0,我们有
y ( P 0 ) = ∑ P n ∈ R w ( P n ) ⋅ x ( P 0 + P n + Δ P n ) y(P_0)=\sum_{P_n\in R}w(P_n)\cdot x(P_0+P_n+\Delta P_n) y(P0)=PnRw(Pn)x(P0+Pn+ΔPn)
其中, { Δ P n ∣ n = 1 , . . . , N } \{\Delta P_n|n=1,...,N \} {ΔPnn=1,...,N}, N = ∣ R ∣ N = |R| N=R,对应着图中的offsets的每一个位置。

在可形变卷积的操作中,在原来R的偏移量的基础上又加入了一个二维偏移 Δ P n \Delta P_n ΔPn(x、y轴上的偏移),这个 Δ P n \Delta P_n ΔPn的值对应图1中offsets对应位置的值。

由于offsets要通过学习得到,所以是一个浮点值,因此对应的不是特征图上一个真实的位置,如果直接使用取整函数的话无法反向传播,因此该位置的值是通过计算周围4个真实值的双线性插值得到的。
x ( p ) = ∑ q G ( q , p ) ⋅ x ( q ) x(p)=\sum_{q}G(q,p) \cdot x(q) x(p)=qG(q,p)x(q)
其中, g ( a , b ) = m a x ( 0 , 1 − ∣ a − b ∣ ) g(a,b)=max(0,1-|a-b|) g(a,b)=max(0,1ab)

对每一个 P 0 P_0 P0 P n P_n Pn有N个值,对应着卷积核的大小, Δ P n \Delta P_n ΔPn同样也有N个值,对应上图中offset field特征图的N个通道,对于输出的特征图(对应下图中的output feature map)上的每个点,可以单独决定他在原图上采样的3x3的特征点的空间位置。

在这里插入图片描述

可变形池化

对一个输入特征图x和一个 w × h w \times h w×h的RoI,RoI pooling将RoI分割成一个 k × k k \times k k×k的区域(bin),并输出一个 k × k k \times k k×k的特征图y。对于第(i,j)的区域:

1)标准RoI 池化的操作过程:
y ( i , j ) = ∑ p ∈ b i n ( i , j ) x ( P 0 + P ) / n i j y(i,j)=\sum_{p \in bin(i,j)} x(P_0+P)/n_{ij} y(i,j)=pbin(i,j)x(P0+P)/nij

2)可形变 RoI 池化的操作过程:
y ( i , j ) = ∑ p ∈ b i n ( i , j ) x ( P 0 + P + Δ P i j ) / n i j y(i,j)=\sum_{p \in bin(i,j)} x(P_0+P+\Delta P_{ij})/n_{ij} y(i,j)=pbin(i,j)x(P0+P+ΔPij)/nij
这个操作过程跟可形变卷积的基本一样。

讲下 Δ P i j \Delta P_{ij} ΔPij的计算。

对输入特征图x先做一次标准的RoI池化,然后通过一个全连接层,输出一个标准的 k × k k \times k k×k 的offsets Δ P i j ^ \Delta \hat{P_{ij}} ΔPij^,然后根据公式:
Δ P i j = γ ⋅ Δ P i j ^ ⋅ ( w , h ) \Delta P_{ij}=\gamma \cdot \Delta \hat{P_{ij}} \cdot (w,h) ΔPij=γΔPij^(w,h)计算出 Δ P i j \Delta P_{ij} ΔPij。其中, γ \gamma γ是一个超参数设置为0.1。

在这里插入图片描述

感受野的变化

当可变形卷积叠加时,复合变形的影响是深远的。标准卷积中的感受野和采样位置在顶部特征映射上是固定的(左)。它们在可变形卷积中(右)根据目标的尺寸和形状进行自适应调整。

在这里插入图片描述

标准卷积(a)中的固定感受野和可变形卷积(b)中的自适应感受野的图示,使用两层。顶部:顶部特征映射上的两个激活单元,在两个不同尺度和形状的目标上。激活来自3×3滤波器。中间:前一个特征映射上3×3滤波器的采样位置。另外两个激活单元突出显示。底部:前一个特征映射上两个3×3滤波器级别的采样位置。突出显示两组位置,对应于上面突出显示的单元。

在这里插入图片描述

每个图像三元组在三级3×3可变形滤波器中显示了三个激活单元(绿色点)分别在背景(左)、小目标(中)和大目标(右)上的采样位置(每张图像中的93=72993=729个红色点)。

结果

四个模型的backbone层都是使用的ResNet-101,且在相同层将标准卷积替换为可形变卷积。@V和@C分别对应VOC 2012和PASCAl VOC数据集。其余三个网络对应的是目标检测任务,使用VOC2007数据集,并使用mAP作为检验标准,@0.5和@0.7分别对应使用0.5和0.7的IoU。
从图中我们可以看出加入可形变卷积后,每个模型的准确率都得到了提升。DeepLab在加入3层可形变卷积后准确率最高,其余网络在加入6层可形变卷积后准确率最高。

在这里插入图片描述

实验

https://github.com/4uiiurz1/pytorch-deform-conv-v2

可变形卷积的pytorch模块,modulation=True可以设置为DCNv2版本。

偏移量预测卷积层self.p_conv,用于生成偏移量。

对调整后的输入特征图进行卷积操作,普通卷积层self.conv

使用可变形卷积模块可以很方便的替换原有的CNN模块,直接使用。

import torch
from torch import nn


class DeformConv2d(nn.Module):
    def __init__(self, inc, outc, kernel_size=3, padding=1, stride=1, bias=None, modulation=False):
        """
        Args:
            modulation (bool, optional): If True, Modulated Defomable Convolution (Deformable ConvNets v2).
        """
        super(DeformConv2d, self).__init__()
        self.kernel_size = kernel_size
        self.padding = padding
        self.stride = stride
        self.zero_padding = nn.ZeroPad2d(padding)
        self.conv = nn.Conv2d(inc, outc, kernel_size=kernel_size, stride=kernel_size, bias=bias)

        self.p_conv = nn.Conv2d(inc, 2*kernel_size*kernel_size, kernel_size=3, padding=1, stride=stride)
        nn.init.constant_(self.p_conv.weight, 0)
        self.p_conv.register_backward_hook(self._set_lr)

        self.modulation = modulation
        if modulation:
            self.m_conv = nn.Conv2d(inc, kernel_size*kernel_size, kernel_size=3, padding=1, stride=stride)
            nn.init.constant_(self.m_conv.weight, 0)
            self.m_conv.register_backward_hook(self._set_lr)

    @staticmethod
    def _set_lr(module, grad_input, grad_output):
        grad_input = (grad_input[i] * 0.1 for i in range(len(grad_input)))
        grad_output = (grad_output[i] * 0.1 for i in range(len(grad_output)))

    def forward(self, x):
        offset = self.p_conv(x)
        if self.modulation:
            m = torch.sigmoid(self.m_conv(x))

        dtype = offset.data.type()
        ks = self.kernel_size
        N = offset.size(1) // 2

        if self.padding:
            x = self.zero_padding(x)

        # (b, 2N, h, w)
        p = self._get_p(offset, dtype)

        # (b, h, w, 2N)
        p = p.contiguous().permute(0, 2, 3, 1)
        q_lt = p.detach().floor()
        q_rb = q_lt + 1

        q_lt = torch.cat([torch.clamp(q_lt[..., :N], 0, x.size(2)-1), torch.clamp(q_lt[..., N:], 0, x.size(3)-1)], dim=-1).long()
        q_rb = torch.cat([torch.clamp(q_rb[..., :N], 0, x.size(2)-1), torch.clamp(q_rb[..., N:], 0, x.size(3)-1)], dim=-1).long()
        q_lb = torch.cat([q_lt[..., :N], q_rb[..., N:]], dim=-1)
        q_rt = torch.cat([q_rb[..., :N], q_lt[..., N:]], dim=-1)

        # clip p
        p = torch.cat([torch.clamp(p[..., :N], 0, x.size(2)-1), torch.clamp(p[..., N:], 0, x.size(3)-1)], dim=-1)

        # bilinear kernel (b, h, w, N)
        g_lt = (1 + (q_lt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_lt[..., N:].type_as(p) - p[..., N:]))
        g_rb = (1 - (q_rb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_rb[..., N:].type_as(p) - p[..., N:]))
        g_lb = (1 + (q_lb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_lb[..., N:].type_as(p) - p[..., N:]))
        g_rt = (1 - (q_rt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_rt[..., N:].type_as(p) - p[..., N:]))

        # (b, c, h, w, N)
        x_q_lt = self._get_x_q(x, q_lt, N)
        x_q_rb = self._get_x_q(x, q_rb, N)
        x_q_lb = self._get_x_q(x, q_lb, N)
        x_q_rt = self._get_x_q(x, q_rt, N)

        # (b, c, h, w, N)
        x_offset = g_lt.unsqueeze(dim=1) * x_q_lt + \
                   g_rb.unsqueeze(dim=1) * x_q_rb + \
                   g_lb.unsqueeze(dim=1) * x_q_lb + \
                   g_rt.unsqueeze(dim=1) * x_q_rt

        # modulation
        if self.modulation:
            m = m.contiguous().permute(0, 2, 3, 1)
            m = m.unsqueeze(dim=1)
            m = torch.cat([m for _ in range(x_offset.size(1))], dim=1)
            x_offset *= m

        x_offset = self._reshape_x_offset(x_offset, ks)
        out = self.conv(x_offset)

        return out

    def _get_p_n(self, N, dtype):
        p_n_x, p_n_y = torch.meshgrid(
            torch.arange(-(self.kernel_size-1)//2, (self.kernel_size-1)//2+1),
            torch.arange(-(self.kernel_size-1)//2, (self.kernel_size-1)//2+1))
        # (2N, 1)
        p_n = torch.cat([torch.flatten(p_n_x), torch.flatten(p_n_y)], 0)
        p_n = p_n.view(1, 2*N, 1, 1).type(dtype)

        return p_n

    def _get_p_0(self, h, w, N, dtype):
        p_0_x, p_0_y = torch.meshgrid(
            torch.arange(1, h*self.stride+1, self.stride),
            torch.arange(1, w*self.stride+1, self.stride))
        p_0_x = torch.flatten(p_0_x).view(1, 1, h, w).repeat(1, N, 1, 1)
        p_0_y = torch.flatten(p_0_y).view(1, 1, h, w).repeat(1, N, 1, 1)
        p_0 = torch.cat([p_0_x, p_0_y], 1).type(dtype)

        return p_0

    def _get_p(self, offset, dtype):
        N, h, w = offset.size(1)//2, offset.size(2), offset.size(3)

        # (1, 2N, 1, 1)
        p_n = self._get_p_n(N, dtype)
        # (1, 2N, h, w)
        p_0 = self._get_p_0(h, w, N, dtype)
        p = p_0 + p_n + offset
        return p

    def _get_x_q(self, x, q, N):
        b, h, w, _ = q.size()
        padded_w = x.size(3)
        c = x.size(1)
        # (b, c, h*w)
        x = x.contiguous().view(b, c, -1)

        # (b, h, w, N)
        index = q[..., :N]*padded_w + q[..., N:]  # offset_x*w + offset_y
        # (b, c, h*w*N)
        index = index.contiguous().unsqueeze(dim=1).expand(-1, c, -1, -1, -1).contiguous().view(b, c, -1)

        x_offset = x.gather(dim=-1, index=index).contiguous().view(b, c, h, w, N)

        return x_offset

    @staticmethod
    def _reshape_x_offset(x_offset, ks):
        b, c, h, w, N = x_offset.size()
        x_offset = torch.cat([x_offset[..., s:s+ks].contiguous().view(b, c, h, w*ks) for s in range(0, N, ks)], dim=-1)
        x_offset = x_offset.contiguous().view(b, c, h*ks, w*ks)

        return x_offset


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2316206.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【统计学相关笔记】2. 多元正态的Cochran定理

fisher 引理 如何说明一个线性变换和二次型独立: 二次型矩阵和线性变换阵乘积0即可。

蓝桥杯刷题——第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组

一、0握手问题 - 蓝桥云课 算法代码&#xff1a; #include <iostream> using namespace std; int main() {int sum0;for(int i49;i>7;i--)sumi;cout<<sum<<endl;return 0; } 直接暴力&#xff0c;题意很清晰&#xff0c;累加即可。 二、0小球反弹 - 蓝…

Canoe Panel常用控件

文章目录 一、Panel 中控件分类1. 指示类控件2. 功能类控件3. 信号值交互类控件4. 其他类控件 二、控件使用方法1. Group Box 控件2. Input/Output Box控件3. Static Text控件4. Button控件5. Switch/Indicator 控件 提示&#xff1a;Button 和 Switch 的区别参考 一、Panel 中…

【软考-架构】11.3、设计模式-新

✨资料&文章更新✨ GitHub地址&#xff1a;https://github.com/tyronczt/system_architect 文章目录 项目中的应用设计模式创建型设计模式结构型设计模式行为型设计模式 &#x1f4af;考试真题题外话 项目中的应用 在实际项目中&#xff0c;我应用过多种设计模式来解决不同…

【大模型(LLMs)RAG 检索增强生成 面经】

1 RAG 基础面 1.1 为什么大模型需要外挂 (向量) 知识库? 如何将外部知识注入大模型,最直接的方法:利用外部知识对大模型进行微调。 思路: 构建几十万量级的数据,然后利用这些数据 对大模型进行微调,以将 额外知识注入大模型 优点: 简单粗暴 缺点: 这几十万量级的数据…

Centos 7 安装达梦数据库

一、环境准备 1. 确认操作系统的版本和数据库的版本是否一致 cat /etc/redhat-release 2. 关闭防火墙 查看防火墙状态 firewall-cmd --state 停止firewall systemctl stop firewalld.service 禁止firewall开机启动 systemctl disable firewalld.service 3. 修改文件l…

@Autowired 注解在构造器上的使用规则(字段注入也挺好的)

背景 在看Spring Framework官方文档时&#xff0c;看到这样一段描述&#xff1a; As of Spring Framework 4.3, an Autowired​ annotation on such a constructor is no longer necessary if the target bean defines only one constructor to begin with. However, if seve…

深度学习视觉2D检测算法综述

目录 一、两阶段目标检测算法 1.1 R-CNN&#xff08;Region-based CNN&#xff0c;2014&#xff09; 1.2 Fast R-CNN&#xff08;Fast Region-based CNN&#xff0c;2015&#xff09; 1.3 Faster R-CNN&#xff08;Faster Region-based CNN&#xff0c;2016&#xff09; 1…

复试不难,西电马克思主义学院—考研录取情况

01、马克思主义学院各个方向 02、24马克思主义学院近三年复试分数线对比 PS&#xff1a;马院24年院线相对于23年院线增加15分&#xff0c;反映了大家对于马克思主义理论学习与研究的热情高涨&#xff0c;也彰显了学院在人才培养、学科建设及学术研究等方面的不断进步与成就。 6…

【A2DP】深入解读A2DP中通用访问配置文件(GAP)的互操作性要求

目录 一、模式支持要求 1.1 发现模式 1.2 连接模式 1.3 绑定模式 1.4 模式间依赖关系总结 1.5 注意事项 1.6 协议设计深层逻辑 二、安全机制&#xff08;Security Aspects&#xff09; 三、空闲模式操作&#xff08;Idle Mode Procedures&#xff09; 3.1 支持要求 …

分享一个免费的CKA认证学习资料

关于CKA考试 CKA&#xff08;Certified Kubernetes Administrator&#xff09;是CNCF基金会&#xff08;Cloud Native Computing Foundation&#xff09;官方推出的Kubernetes管理员认证计划&#xff0c;用于证明持有人有履行Kubernetes管理的知识&#xff0c;技能等相关的能力…

观成科技:​加密C2框架Platypus流量分析

一、工具介绍 Platypus 是一款支持多会话的交互式反向 Shell 管理器。在实际的渗透测试中&#xff0c;为了解决 Netcat/Socat 等工具在文件传输、多会话管理方面的不足,该工具在多会话管理的基础上增加了在渗透测试中能更好发挥作用的功能&#xff08;如&#xff1a;交互式 Sh…

Jetson Nano NX 重装系统

本篇记录了自己刚拿到Jetson板子后&#xff0c;刻意去学习给板子重刷系统的过程&#xff0c;学会重装系统是玩嵌入式开发板的基操。 注意&#xff1a;我使用的是 Nvidia 官方 SDK Manager 给 Jetson 刷系统的&#xff0c;需要额外准备一台 linux 电脑&#xff08;双系统或者虚拟…

Java数据结构第二十三期:Map与Set的高效应用之道(二)

专栏&#xff1a;Java数据结构秘籍 个人主页&#xff1a;手握风云 目录 一、哈希表 1.1. 概念 1.2. 冲突 1.3. 避免冲突 1.4. 解决冲突 1.5. 实现 二、OJ练习 2.1. 只出现一次的数字 2.2. 随机链表的复制 2.3. 宝石与石头 一、哈希表 1.1. 概念 顺序结构以及平衡树中…

linux系统命令——权限

一、有哪些权限 读&#xff08;r&#xff09;——对应数字4 写&#xff08;w&#xff09;——对应数字2 执行&#xff08;x&#xff09;——对应数字1 二、权限及数字的对应 4对应r-- 2对应-w- 1对应--x 5对应r-x 6对应rw- 7对应rwx 三、文件的基本属性 如图&#…

PentestGPT 下载

PentestGPT 下载 PentestGPT 介绍 PentestGPT&#xff08;Penetration Testing GPT&#xff09;是一个基于大语言模型&#xff08;LLM&#xff09;的智能渗透测试助手。它结合了 ChatGPT&#xff08;或其他 GPT 模型&#xff09;与渗透测试工具&#xff0c;帮助安全研究人员自…

JVM 2015/3/15

定义&#xff1a;Java Virtual Machine -java程序的运行环境&#xff08;java二进制字节码的运行环境&#xff09; 好处&#xff1a; 一次编写&#xff0c;到处运行 自动内存管理&#xff0c;垃圾回收 数组下标越界检测 多态 比较&#xff1a;jvm/jre/jdk 常见的JVM&…

sql靶场-时间盲注(第九、十关)保姆级教程

目录 时间盲注&#xff08;第九、十关&#xff09; 1.判断 2.确认时间盲注 2.手工尝试时间盲注 数据库名长度 数据库名字符 表数 表名长度 表名字符 字段数 字段名长度 字段名字符 4.脚本时间盲注注入 5.第十关 时间盲注&#xff08;第九、十关&#xff09; 1.判…

51c自动驾驶~合集54

我自己的原文哦~ https://blog.51cto.com/whaosoft/13517811 #Chameleon 快慢双系统&#xff01;清华&博世最新&#xff1a;无需训练即可解决复杂道路拓扑 在自动驾驶技术中&#xff0c;车道拓扑提取是实现无地图导航的核心任务之一。它要求系统不仅能检测出车道和交…

大模型推理:LM Studio在Mac上部署Deepseek-R1模型

LM Studio LM Studio是一款支持离线大模型部署的推理服务框架&#xff0c;提供了易用的大模型部署web框架&#xff0c;支持Linux、Mac、Windows等平台&#xff0c;并提供了OpenAI兼容的SDK接口&#xff0c;主要使用LLama.cpp和MLX推理后端&#xff0c;在Mac上部署时选择MLX推理…