(即插即用模块-Convolution部分) 十五、(2024) LDConv 线性可变形卷积

在这里插入图片描述

paper:LDConv: Linear deformable convolution for improving convolutional neural networks

Code:https://github.com/CV-ZhangXin/LDConv


1、Linear Deformable Convolution

标准卷积操作通常使用固定形状的正方形核,无法适应目标形状的变化。例如,对于长条形目标,使用正方形核可能会导致信息丢失。而可变形卷积虽然可以调整采样形状,但其参数数量随核大小平方增长,灵活性不足。这种限制使得网络难以根据资源情况进行灵活的参数和计算量调节。所以这篇论文提出一种 线性可变形卷积(Linear Deformable Convolution),其源于对标准卷积操作的局限性认识,旨在提升卷积神经网络在目标检测等任务中的性能。

LDConv 提出了一个算法来生成任意大小卷积核的初始采样坐标,并通过偏移量调整采样形状,使其能够适应目标形状的变化。同时,LDConv 允许卷积核具有任意数量的参数,例如 1, 2, 3, 4, 5, 6, 7 等,从而为网络设计提供了更多灵活性。最后,LDConv 将参数数量的增长趋势从平方变为线性,从而降低了对硬件环境的要求。

对于输入X,LDConv 的实现过程:

  1. 生成初始采样坐标:根据卷积核大小,使用算法生成初始采样坐标,这些坐标可以是任意形状,例如三角形、矩形、菱形等。
  2. 计算偏移量:通过卷积操作学习偏移量,并将其添加到初始采样坐标上,生成新的采样坐标,从而调整采样形状。
  3. 提取特征:通过对特征图进行插值和重采样,获得对应于新采样坐标的特征,并使用相应的卷积操作提取特征。

LDConv 相比于标准卷积和可变形卷积具有以下优势:

  • 更强的适应性:LDConv 能够根据目标形状的变化动态调整采样形状,从而更好地捕捉目标特征。
  • 更高的灵活性:LDConv 允许卷积核具有任意数量的参数和采样形状,从而为网络设计提供了更多灵活性。
  • 更低的计算开销:LDConv 将参数数量的增长趋势从平方变为线性,从而降低了对硬件环境的要求。

Linear Deformable Convolution 结构图:
在这里插入图片描述

2、代码实现

import torch
from torch import nn
from einops import rearrange
import math


class LDConv(nn.Module):
    def __init__(self, inc, outc, num_param=5, stride=1, bias=None):
        super(LDConv, self).__init__()
        self.num_param = num_param
        self.stride = stride
        self.conv = nn.Sequential(nn.Conv2d(inc, outc, kernel_size=(num_param, 1), stride=(num_param, 1), bias=bias),
                                  nn.BatchNorm2d(outc),
                                  nn.SiLU())  # the conv adds the BN and SiLU to compare original Conv in YOLOv5.
        self.p_conv = nn.Conv2d(inc, 2 * num_param, kernel_size=3, padding=1, stride=stride)
        nn.init.constant_(self.p_conv.weight, 0)
        self.p_conv.register_full_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):
        # N is num_param.
        offset = self.p_conv(x)
        dtype = offset.data.type()
        N = offset.size(1) // 2
        # (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:]))

        # resampling the features based on the modified coordinates.
        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)

        # bilinear
        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

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

        return out

    # generating the inital sampled shapes for the LDConv with different sizes.
    def _get_p_n(self, N, dtype):
        base_int = round(math.sqrt(self.num_param))
        row_number = self.num_param // base_int
        mod_number = self.num_param % base_int
        p_n_x, p_n_y = torch.meshgrid(
            torch.arange(0, row_number),
            torch.arange(0, base_int))
        p_n_x = torch.flatten(p_n_x)
        p_n_y = torch.flatten(p_n_y)
        if mod_number > 0:
            mod_p_n_x, mod_p_n_y = torch.meshgrid(
                torch.arange(row_number, row_number + 1),
                torch.arange(0, mod_number))

            mod_p_n_x = torch.flatten(mod_p_n_x)
            mod_p_n_y = torch.flatten(mod_p_n_y)
            p_n_x, p_n_y = torch.cat((p_n_x, mod_p_n_x)), torch.cat((p_n_y, mod_p_n_y))
        p_n = torch.cat([p_n_x, p_n_y], 0)
        p_n = p_n.view(1, 2 * N, 1, 1).type(dtype)
        return p_n

    # no zero-padding
    def _get_p_0(self, h, w, N, dtype):
        p_0_x, p_0_y = torch.meshgrid(
            torch.arange(0, h * self.stride, self.stride),
            torch.arange(0, w * self.stride, 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

    #  Stacking resampled features in the row direction.
    @staticmethod
    def _reshape_x_offset(x_offset, num_param):

        x_offset = rearrange(x_offset, 'b c h w n -> b c (h n) w')
        return x_offset


if __name__ == '__main__':
    x = torch.randn(1, 64, 224, 224)
    model = LDConv(inc=64, outc=64)

    output = model(x)
    print(output.shape)

### 一维卷积即插即用模块实现 在一维信号处理领域,通常会使用 `scipy` 或者 `numpy` 提供的功能来快速构建一维卷积操作。以下是基于 Python 的一种常见实现方式。 #### 使用 NumPy 和 SciPy 进行一维卷积 可以利用 `scipy.signal.convolve` 函数完成一维卷积的操作[^2]。下面是一个简单的代码示例: ```python import numpy as np from scipy import signal def one_dimensional_convolution(signal, kernel): """ 对输入的一维信号执行卷积操作。 参数: signal (array-like): 输入的一维信号数组。 kernel (array-like): 卷积核数组。 返回: array: 输出的卷积结果。 """ result = signal.convolve(signal, kernel, mode='same') return result # 测试数据 input_signal = np.array([1, 2, 3, 4, 5]) convolution_kernel = np.array([0.25, 0.5, 0.25]) output_result = one_dimensional_convolution(input_signal, convolution_kernel) print("原始信号:", input_signal) print("卷积核:", convolution_kernel) print("卷积结果:", output_result) ``` 上述代码展示了如何通过调用 `signal.convolve` 方法轻松实现一维卷积功能。其中参数 `mode='same'` 表明输出长度与输入相同。 #### PyTorch 中的一维卷积实现 如果希望在深度学习框架中使用更高效的 GPU 加速版本,可以选择 PyTorch 自带的一维卷积层 `nn.Conv1d` 来替代手动计算的方式[^1]。以下是一段完整的代码实例: ```python import torch import torch.nn as nn class OneDConvModule(nn.Module): def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=0): super(OneDConvModule, self).__init__() self.conv_layer = nn.Conv1d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, stride=stride, padding=padding) def forward(self, x): return self.conv_layer(x) # 初始化模型并设置超参 model = OneDConvModule(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=1) # 构造测试张量 test_tensor = torch.tensor([[1., 2., 3., 4., 5.]]).unsqueeze(0) # shape: [batch_size, channels, length] # 执行前向传播 with torch.no_grad(): conv_output = model(test_tensor) print("输入张量形状:", test_tensor.shape) print("卷积后输出张量形状:", conv_output.shape) print("卷积后的结果:\n", conv_output.squeeze()) ``` 此部分实现了基于 PyTorch 的一维卷积神经网络模块,并允许灵活调整诸如步幅 (`stride`)、填充 (`padding`) 等重要属性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

御宇w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值