IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 人工智能 -> 可变形卷积:DCNv1and DCNv2 -> 正文阅读

[人工智能]可变形卷积:DCNv1and DCNv2

1.DCNv1

可变形卷积顾名思义就是卷积的位置是可变形的,并非在传统的N × N的网格上做卷积,这样的好处就是更准确地提取到我们想要的特征(传统的卷积仅仅只能提取到矩形框的特征)
DCN v1的核心思想在于它认为卷积核不应该是一个简简单单的矩形
在不同的阶段,不同的特征图,甚至不同的像素点上都可能有其最优的卷积核结构。
因此DCN v1提出在方形卷积核上的每个点学习一个偏移(offset),卷积核可以根据不同的数据学习不同的卷积核结构,如图1所示。

在这里插入图片描述
图1:可变形卷积核。(a)是标准的3×3卷积。(b),( c),(d)是给普通卷积加上偏移之后形成的可变形的卷积核,其中蓝色的是新的卷积点,箭头是位移方向。

在这里插入图片描述
可变形卷积的结构可以分为上下两个部分:

  • 上面那部分是基于输入的特征图生成x,y方向的offset
  • 下面那部分是基于特征图和offset通过可变形卷积获得输出特征图

假设输入的特征图宽高分别为w,h,下面那部分的卷积核尺寸是 k h k_h kh? k w k_w kw?,那么上面那部分卷积层的卷积核数量应该是 2 ? k h ? k w 2*k_h*k_w 2?kh??kw?,其中2代表x,y两个方向的offset。

并且,这里输出特征图的维度和输入特征图的维度一样,那么offset的维度就是[batch, k h k_h kh?, k w k_w kw?,h,w]

假设下面那部分设置了group参数(代码实现中默认为4),那么第一部分的卷积核数量就是 2 ? k h ? k w ? g r o u p 2*k_h*k_w*group 2?kh??kw??group,即每一个group共用一套offset。下面的可变形卷积可以看作先基于上面那部分生成的offset做了一个插值操作,然后再执行普通的卷积。

可变形卷积示意图2,来自三景页三景页 博主
具体实现的流程大概如下:

  • step1:求输入特征图 U U U(维度=[b,h,w,c])每个像素的偏移量:经过一个普通卷积,padding设置为same,对应的输出结果维度是[b,h,w,2c],记作 V V V

  • 将输入特征图 U U U中的像素索引值与V相加,得到偏移后的position(即在原始图片U中的坐标值),需要将position值限定为图片大小以内。position的大小为(bhw*2c),但position只是一个坐标值,而且还是float类型的,我们需要这些float类型的坐标值获取像素(双线性差值)。

    • 举个例子:我们取一个坐标值,将其转换为四个整数floor(a), ceil(a), floor(b), ceil(b)。
    • 将这四个整数进行整合,得到四对坐标(floor(a),floor(b)), ((floor(a),ceil(b)), ((ceil(a),floor(b)), ((ceil(a),ceil(b))。
    • 这四对坐标每个坐标都对应 U U U中的一个像素值,而我们需要得到(a,b)的像素值,这里采用双线性差值的方式计算(一方面是因为获得的像素更精确,另外一方面是因为可以进行反向传播)。
  • 在得到position的所有像素后,即得到了输出特征图 M M M,将这个新图片M作为输入数据输入到别的层中,如普通卷积。

2.DCNv2

  1. 增加更多的可变形卷积层
  2. 除了让模型学习采样点的偏移,还要学习每个采样点的权重,这是对减轻无关因素干扰的最重要的工作
  3. 使用R-CNN对Faster R-CNN进行知识蒸馏
  4. 目前只能实现3*3大小的卷积

更多的可变形卷积:

  • 在DCN v2将conv3到conv5 block的 3*3卷积全部替换为了可变形卷积,因此可变形卷积层数达到了 12个。这一操作在场景更复杂的COCO数据集有着比较明显的性能提升。

加权采样点偏移:
在这里插入图片描述

  • DCNv2在DCNv1的基础上增加了一个权重系数 m m m,对卷积的在输入特征图上的采样点的偏移量计算权重,去除无关的上下文信息,对于某些不想要的采样点权重可以直接学习成0。

使用R-CNN对Faster R-CNN进行知识蒸馏:

  • 在DCN v2中,作者使用了特征模仿(Feature Mimicking)的方式来用R-CNN对Faster R-CNN的优化。特征模仿是一个比较高级的模型迁移策略,它主要用在知识蒸馏中。
  • 它先训练一个准确率比较高但是速度慢的大型的Teacher网络,然后再训练一个速度快但是泛化能力略差的小的Student网络。
  • 在训练Student网络的过程中,模型不仅要优化它本身任务的损失函数,还要以Teacher网络学习到的特征为目标,优化它的中间的特征层。
  • 因为Student网络的参数量是固定的,所以通过这个策略优化之后的Student网络不仅速度很快,而且能够达到了Teacher类似的准确率。
  • DCN v2使用R-CNN来指导加了了可变形卷积的Faster R-CNN训练的目的是使可变形卷积能够自行的学到更好的采样点的偏移和权重,从而减少无关因素对模型的负面影响。

代码:

import torch
from torch import nn

from yolox.models.network_blocks import get_activation


class DeformConv2d(nn.Module):
    def __init__(self, inc, outc, kernel_size=3, stride=1, padding=1, bias=None, modulation=False,act="silu"):
        """
        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.bn = nn.BatchNorm2d(outc)
        self.act = get_activation(act, inplace=True)
#------------------------------------------------------------------
        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)
        out = self.act(self.bn(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

参考文献1
参考文献2

  人工智能 最新文章
2022吴恩达机器学习课程——第二课(神经网
第十五章 规则学习
FixMatch: Simplifying Semi-Supervised Le
数据挖掘Java——Kmeans算法的实现
大脑皮层的分割方法
【翻译】GPT-3是如何工作的
论文笔记:TEACHTEXT: CrossModal Generaliz
python从零学(六)
详解Python 3.x 导入(import)
【答读者问27】backtrader不支持最新版本的
上一篇文章      下一篇文章      查看所有文章
加:2022-05-12 16:27:40  更:2022-05-12 16:28:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 6:24:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码