代码详解——可变形卷积(DCNv3)

news2024/9/29 9:38:58

文章目录

  • 概述
  • dcnv3.py
    • to_channels_first
    • to_channels_last
    • build_norm_layer
    • build_act_layer
    • _is_power_of_2
    • CenterFeatureScaleModule
    • DCNv3_pytorch
    • DCNv3
  • dcnv3_func.py
    • DCNv3Function
    • dcnv3_core_pytorch
    • _get_reference_points
    • _generate_dilation_grids

可变形卷积DCNv1 & DCNv2
✨✨✨论文及代码详解——可变形卷积(DCNv1)
✨✨✨论文及代码详解——可变形卷积(DCNv2)
DCNv3 是InternImage中提出的,DCNv3在DCNv2版本上进行了改进。
✨✨✨论文详解——《InternImage: Exploring Large-Scale Vision Foundation Models with Deformable Convolutions》
InterImage官方代码: https://github.com/OpenGVLab/InternImage

概述

如下图,首先下载InterImage官方代码,然后在segmentation、detection、classification文件夹下均可以找到ops_dcnv3文件夹,该文件夹下的内容就是实现DCNv3算子的核心代码。

在这里插入图片描述

  • modules
    如下图所示, modules文件夹中的dcnv3.py文件主要定义了DCNv3模块。
    其中DCNv3_pytorch是DCNv3的pytorch实现版本,DCNv3是DCNv3的C++实现版本。
    在这里插入图片描述
  • functions
    如下图所示,function文件夹中的dcnv3_func.py文件定义了DCNv3的一些核心操作。
    其中黄色部分的DCNv3Function类被c++版本的DCNv3 调用。
    其中红色部分的dcnv3_core_pytorch方法被pytorch版本的DCNv3_pytorch调用。
    在这里插入图片描述
  • src
    src下的代码是用C++来实现DCNv3中核心操作,其下的cpucuda分别表示cpu和cuda编程两种实现版本。c++实现的版本需要去编译,否则如上图所示,黄色箭头指向的import DCNv3 有红色波浪线,无法正常导入。
    如果想import DCNv3成功,有两种解决办法:
    (1)需要编译:DCNv3具体编译方法是直接运行make.sh文件(但是这种方法很容易编译失败,对于pytorch,cuda的版本以及c++编译器的配置都有要求)
    (2)不需要编译:去官网上下载轮子https://github.com/OpenGVLab/InternImage/releases/tag/whl_files (更推荐这种方法,但是也需要注意cuda和pytorch的版本)
    在这里插入图片描述
    本文重点介绍DCNv3的pytorch实现部分。

dcnv3.py

to_channels_first

  • 功能:将通道维度放在前面,输入(b,h,w,c),输出(b,c,h,w)
# 将通道维度放在前面:(b,h,w,c)->(b,c,h,w)
class to_channels_first(nn.Module):

    def __init__(self):
        super().__init__()

    def forward(self, x): # (b,h,w,c)
        return x.permute(0, 3, 1, 2) #(b,c,h,w)

to_channels_last

  • 功能:将通道维度放在后面,输入 (b,c,h,w),输出(b,h,w,c)
# 将通道维度放在后面 (b,c,h,w)->(b,h,w,c)
class to_channels_last(nn.Module):

    def __init__(self):
        super().__init__()

    def forward(self, x): # (b,c,h,w)
        return x.permute(0, 2, 3, 1) # (b,h,w,c)

build_norm_layer

  • 功能:构建归一化层,可以选择Batch Norm或者Layer Norm
def build_norm_layer(dim,
                     norm_layer,
                     in_format='channels_last',
                     out_format='channels_last',
                     eps=1e-6):
    layers = []
    if norm_layer == 'BN':
        if in_format == 'channels_last':
            layers.append(to_channels_first())
        layers.append(nn.BatchNorm2d(dim))
        if out_format == 'channels_last':
            layers.append(to_channels_last())
    elif norm_layer == 'LN':
        if in_format == 'channels_first':
            layers.append(to_channels_last()) # (b,c,h,w)->(b,h,w,c)
        layers.append(nn.LayerNorm(dim, eps=eps))
        if out_format == 'channels_first':
            layers.append(to_channels_first())
    else:
        raise NotImplementedError(
            f'build_norm_layer does not support {norm_layer}')
    return nn.Sequential(*layers)

build_act_layer

  • 功能:构建激活函数层,可选择RELU / SiLU / GELU激活函数
def build_act_layer(act_layer):
    if act_layer == 'ReLU':
        return nn.ReLU(inplace=True)
    elif act_layer == 'SiLU':
        return nn.SiLU(inplace=True)
    elif act_layer == 'GELU':
        return nn.GELU()

    raise NotImplementedError(f'build_act_layer does not support {act_layer}')

_is_power_of_2

  • 功能:检查n是否是2的某次方,且n不为0
    如果n & (n - 1)为0,则说明n是2的某次方。
def _is_power_of_2(n):
    if (not isinstance(n, int)) or (n < 0): # 如果n不为整数或者n小于0
        raise ValueError(
            "invalid input for _is_power_of_2: {} (type: {})".format(n, type(n)))

    return (n & (n - 1) == 0) and n != 0

CenterFeatureScaleModule

  • 功能:生成缩放系数。
    F.linear的输出取决于传入的实参weightbias,然后再通过sigmod函数归一化。
class CenterFeatureScaleModule(nn.Module):
    def forward(self,
                query,
                center_feature_scale_proj_weight, # (group,channels)
                center_feature_scale_proj_bias):
        center_feature_scale = F.linear(query,
                                        weight=center_feature_scale_proj_weight,
                                        bias=center_feature_scale_proj_bias).sigmoid() # 全连接层+sigmod
        # F.linear的输出取决于传入的实参weight和bias
        # 输入:(*,channels) -> 输出:(*,group)
        return center_feature_scale

DCNv3_pytorch

class DCNv3_pytorch 是分组可变形卷积DCNv3的pytorch的实现版。

'''分组可变形卷积 pytorch实现版'''
class DCNv3_pytorch(nn.Module):
    def __init__(
            self,
            channels=64, # 通道数
            kernel_size=3, # 卷积核大小
            dw_kernel_size=None, # 深度可分离卷积核大小
            stride=1, # 步长
            pad=1, # 填充
            dilation=1, #空洞率
            group=4,# 分组数
            offset_scale=1.0, 
            act_layer='GELU', # 激活函数
            norm_layer='LN', # 归一化层
            center_feature_scale=False):

        super().__init__()
        if channels % group != 0: # 分组卷积必须保证通道数可以被组数整除
            raise ValueError(
                f'channels must be divisible by group, but got {channels} and {group}')
        _d_per_group = channels // group # 每个组数拥有的通道数
        dw_kernel_size = dw_kernel_size if dw_kernel_size is not None else kernel_size # 设置深度可分离卷积核的大小
        # 最好把_d_per_group设置为2的某次方,方便cuda的具体实现
        if not _is_power_of_2(_d_per_group):
            warnings.warn(
                "You'd better set channels in DCNv3 to make the dimension of each attention head a power of 2 "
                "which is more efficient in our CUDA implementation.")

        self.offset_scale = offset_scale
        self.channels = channels
        self.kernel_size = kernel_size
        self.dw_kernel_size = dw_kernel_size
        self.stride = stride
        self.dilation = dilation
        self.pad = pad
        self.group = group
        self.group_channels = channels // group
        self.offset_scale = offset_scale
        self.center_feature_scale = center_feature_scale
        
        # 深度可分离卷积
        self.dw_conv = nn.Sequential(
            # depth-wise 逐通道卷积
            # H_out=H_in+2*padding-(kernel_size-1) 
            nn.Conv2d(
                channels,
                channels, # 输入输出的通道数不变
                kernel_size=dw_kernel_size,
                stride=1,
                padding=(dw_kernel_size - 1) // 2, # 输出大小不变
                groups=channels), # 分组数和输入通道数相等,也就实现了逐通道的卷积 !!!
            # 归一化层
            build_norm_layer(
                channels,
                norm_layer,
                'channels_first', #如果是LN, (b,c,h,w)->(b,h,w,c)
                'channels_last'),
            # 激活层
            build_act_layer(act_layer))
            
        self.offset = nn.Linear( # offset: 偏移量
            channels,
            group * kernel_size * kernel_size * 2) # 输出通道数:group*2*kernel_size*kernel_size
            
        self.mask = nn.Linear( # mask: 调制标量
            channels,
            group * kernel_size * kernel_size) # 输出通道数:group*kernel_size*kernel_size
            
        self.input_proj = nn.Linear(channels, channels) # Linear层,输入输出通道数不变
        self.output_proj = nn.Linear(channels, channels) # Linear层,输入输出通道数不变
        self._reset_parameters() # 参数初始化
        
        if center_feature_scale: # 如果需要对特征图进行缩放
            self.center_feature_scale_proj_weight = nn.Parameter(
                torch.zeros((group, channels), dtype=torch.float)) # weighit:大小为(group,channels)的全0 Tensor
            self.center_feature_scale_proj_bias = nn.Parameter(
                torch.tensor(0.0, dtype=torch.float).view((1,)).repeat(group, )) # bias: 大小为(group)的全0 tensor
            self.center_feature_scale_module = CenterFeatureScaleModule()# (*,channels)->(*,groups)

_reset_parameters

  • 功能:初始化参数,将bias设置为0,weights采用xavier_uniform_ 分布进行初始化。
    # 参数初始化
    def _reset_parameters(self):
        constant_(self.offset.weight.data, 0.)
        constant_(self.offset.bias.data, 0.)
        constant_(self.mask.weight.data, 0.)
        constant_(self.mask.bias.data, 0.)
        xavier_uniform_(self.input_proj.weight.data)
        constant_(self.input_proj.bias.data, 0.)
        xavier_uniform_(self.output_proj.weight.data)
        constant_(self.output_proj.bias.data, 0.)

forward
功能:前向传播

    def forward(self, input): # (N,H,W,C)
        N, H, W, _ = input.shape
        x = self.input_proj(input) #Linear层,通道不变 x: (N,H,W,C)
        x_proj = x
        x1 = input.permute(0, 3, 1, 2) # x1: (N,C,H,W)
        '''
        改进点1:将原始卷积权值w_k分离为depth-wise和point-wise两部分,其中depth-wise部分由原始的location-aware modulation scalar m_k负责,point-wise部分是采样点之间的共享投影权值w。
        '''
        # DW_conv(逐通道卷积)+Norm+Act
        x1 = self.dw_conv(x1) # (N,H,W,C)
        ''' 
        改进点2:我们将空间聚集过程分成G组,每个组都有单独的采样偏移offset和调制规模mask
        因此在一个卷积层上的不同组可以有不同的空间聚集模式,从而为下游任务带来更强的特征。
        '''
        offset = self.offset(x1) #生成偏移 (N,H,W,group*2*kernel_size*kernel_size)
        mask = self.mask(x1).reshape(N, H, W, self.group, -1) # mask 表示调制标量
        # mask后:(N,H,W,group*kernel_size*kernel_size)
        # reshape后:(N,H,W,group,kernel_size*kernel_size)
        mask = F.softmax(mask, -1).reshape(N, H, W, -1)  # (N,H,W,group*kernel_size*kernel_size)
        # softmax的dim=-1,表示在kernel_size*kernel_size个样本点进行归一化,其和等于1。
        '''
        改进点3:我们将基于element-wise的sigmoid归一化改为基于样本点的softmax归一化。这样,将调制标量的和限制为1,使得不同尺度下模型的训练过程更加稳定。
        '''
        x = dcnv3_core_pytorch( # 可变形卷积的核心代码
            x, offset, mask, 
            # x: (N,H,W,C) 
            # offset: (N,H,W,group*2*kernel_size*kernel_size)
            # mask: (N,H,W,group*kernel_size*kernel_size)
            self.kernel_size, self.kernel_size,
            self.stride, self.stride,
            self.pad, self.pad,
            self.dilation, self.dilation,
            self.group, self.group_channels,
            self.offset_scale) # (N_, H_out, W_out,group*group_channels)

        if self.center_feature_scale: # 如果需要特征缩放
            center_feature_scale = self.center_feature_scale_module(
                x1, self.center_feature_scale_proj_weight, self.center_feature_scale_proj_bias)
            # x1:(N,H,W,C) ->(N,H,W,groups)
            center_feature_scale = center_feature_scale[..., None].repeat(
                1, 1, 1, 1, self.channels // self.group).flatten(-2)
            #  (N, H, W, groups) -> (N, H, W, groups, 1) -> (N, H, W, groups, _d_per_group) -> (N, H, W, channels)
            x = x * (1 - center_feature_scale) + x_proj * center_feature_scale
            # x_proj和x按照一定的系数相加
        x = self.output_proj(x) # (N,H,W,C)
        return x

在forward函数中体现了InterImage论文中提出的三个改进点。

  • 改进点1:将原始卷积权值w_k分离为depth-wise和point-wise两部分,其中depth-wise部分由原始的location-aware modulation scalar m_k负责,point-wise部分是采样点之间的共享投影权值w。
    如下图所示,DCNv3的实现代码offsetmask是由经过dw_conv的depth-wise convolution得到的x1生成,但是一同输入dcnv3的是原始的x_proj
    在这里插入图片描述
    如下图是DCNv2的实现代码,其中它的m(mask)和offset 是原始输入x分别通过普通卷积p_convm_conv生成。
    在这里插入图片描述
    假设卷积核的大小是(kernel_h,kernel_w), 输入的大小是(c_in,h,w)。卷积核需要滑动t次。
    那么普通卷积需要的总乘法运算数是c_out*kernel_h*kernel_w*c_in*t
    而depth-wise 卷积需要的总乘法数是kernel_h*kernel_w*c_in*t, 相比下减少了c_out倍。
    因此用dw_conv生成offset和mask,可以提升模型的效率。

  • 改进点2:我们将空间聚集过程分成 G G G组,每个组都有单独的采样偏移 ∆ p g k ∆p_{gk} pgk和调制规模 m g k m_{gk} mgk
    DCNv3生成的mask大小是(N,H,W,group*kernel_size*kernel_size), offset的大小是(N,H,W,2*group*kernel_size*kernel_size),每个组都有不同的采样偏移和调制规模。
    而DCNv2生成的mask大小是(N,H,W,kernel_size*kernel_size), offset的大小是(N,H,W,2*kernel_size*kernel_size)

  • 改进点3:将基于element-wise的sigmoid归一化改为基于样本点的softmax归一化。
    如下图,DCNv3的softmax函数在最后一个维度上进行归一化,保证了最后一个维度的kernel_size*kernel_size个元素的和为1。
    在这里插入图片描述
    如下图所示,DCNv2的softmax函数只是将所有的element通过sigmod 函数,保证每个element的值在[0,1]范围内。
    在这里插入图片描述

DCNv3

DCNv3是C++实现版本,唯一和DCNv3_pytorch不同的是, dcnv3_core_pytorch 被替换成了DCNv3Function类中的apply函数(这个是dcnv3_func.py中定义的autograd function)。
在这里插入图片描述

'''分组可变形卷积 C++实现版'''
class DCNv3(nn.Module):
    def __init__(
            self,
            channels=64,
            kernel_size=3,
            dw_kernel_size=None,
            stride=1,
            pad=1,
            dilation=1,
            group=4,
            offset_scale=1.0,
            act_layer='GELU',
            norm_layer='LN',
            center_feature_scale=False):
        """
        DCNv3 Module
        :param channels
        :param kernel_size
        :param stride
        :param pad
        :param dilation
        :param group
        :param offset_scale
        :param act_layer
        :param norm_layer
        """
        super().__init__()
        if channels % group != 0:
            raise ValueError(
                f'channels must be divisible by group, but got {channels} and {group}')
        _d_per_group = channels // group
        dw_kernel_size = dw_kernel_size if dw_kernel_size is not None else kernel_size
        # you'd better set _d_per_group to a power of 2 which is more efficient in our CUDA implementation
        if not _is_power_of_2(_d_per_group):
            warnings.warn(
                "You'd better set channels in DCNv3 to make the dimension of each attention head a power of 2 "
                "which is more efficient in our CUDA implementation.")

        self.offset_scale = offset_scale
        self.channels = channels
        self.kernel_size = kernel_size
        self.dw_kernel_size = dw_kernel_size
        self.stride = stride
        self.dilation = dilation
        self.pad = pad
        self.group = group
        self.group_channels = channels // group
        self.offset_scale = offset_scale
        self.center_feature_scale = center_feature_scale
        
        self.dw_conv = nn.Sequential(
            nn.Conv2d(
                channels,
                channels,
                kernel_size=dw_kernel_size,
                stride=1,
                padding=(dw_kernel_size - 1) // 2,
                groups=channels),
            build_norm_layer(
                channels,
                norm_layer,
                'channels_first',
                'channels_last'),
            build_act_layer(act_layer))
        self.offset = nn.Linear(
            channels,
            group * kernel_size * kernel_size * 2)
        self.mask = nn.Linear(
            channels,
            group * kernel_size * kernel_size)
        self.input_proj = nn.Linear(channels, channels)
        self.output_proj = nn.Linear(channels, channels)
        self._reset_parameters()
        
        if center_feature_scale:
            self.center_feature_scale_proj_weight = nn.Parameter(
                torch.zeros((group, channels), dtype=torch.float))
            self.center_feature_scale_proj_bias = nn.Parameter(
                torch.tensor(0.0, dtype=torch.float).view((1,)).repeat(group, ))
            self.center_feature_scale_module = CenterFeatureScaleModule()

    def _reset_parameters(self):
        constant_(self.offset.weight.data, 0.)
        constant_(self.offset.bias.data, 0.)
        constant_(self.mask.weight.data, 0.)
        constant_(self.mask.bias.data, 0.)
        xavier_uniform_(self.input_proj.weight.data)
        constant_(self.input_proj.bias.data, 0.)
        xavier_uniform_(self.output_proj.weight.data)
        constant_(self.output_proj.bias.data, 0.)

    def forward(self, input):
        """
        :param query                       (N, H, W, C)
        :return output                     (N, H, W, C)
        """
        N, H, W, _ = input.shape

        x = self.input_proj(input)
        x_proj = x
        dtype = x.dtype

        x1 = input.permute(0, 3, 1, 2)
        x1 = self.dw_conv(x1)
        offset = self.offset(x1)
        mask = self.mask(x1).reshape(N, H, W, self.group, -1)
        mask = F.softmax(mask, -1).reshape(N, H, W, -1).type(dtype)

        x = DCNv3Function.apply(  # !!!
            x, offset, mask,
            self.kernel_size, self.kernel_size,
            self.stride, self.stride,
            self.pad, self.pad,
            self.dilation, self.dilation,
            self.group, self.group_channels,
            self.offset_scale,
            256)
        
        if self.center_feature_scale:
            center_feature_scale = self.center_feature_scale_module(
                x1, self.center_feature_scale_proj_weight, self.center_feature_scale_proj_bias)
            # N, H, W, groups -> N, H, W, groups, 1 -> N, H, W, groups, _d_per_group -> N, H, W, channels
            center_feature_scale = center_feature_scale[..., None].repeat(
                1, 1, 1, 1, self.channels // self.group).flatten(-2)
            x = x * (1 - center_feature_scale) + x_proj * center_feature_scale
        x = self.output_proj(x)

        return x

dcnv3_func.py

DCNv3Function

DCNv3Function是一个自定义的autograd function, 用于C++实现的DCNv3。

class DCNv3Function(Function): # 自定义 autograd function
    @staticmethod
    @custom_fwd
    def forward( # 前向传播
            ctx, input, offset, mask,
            kernel_h, kernel_w, stride_h, stride_w,
            pad_h, pad_w, dilation_h, dilation_w,
            group, group_channels, offset_scale, im2col_step):
        ctx.kernel_h = kernel_h
        ctx.kernel_w = kernel_w
        ctx.stride_h = stride_h
        ctx.stride_w = stride_w
        ctx.pad_h = pad_h
        ctx.pad_w = pad_w
        ctx.dilation_h = dilation_h
        ctx.dilation_w = dilation_w
        ctx.group = group
        ctx.group_channels = group_channels
        ctx.offset_scale = offset_scale
        ctx.im2col_step = im2col_step
        output = DCNv3.dcnv3_forward(
            input, offset, mask, kernel_h,
            kernel_w, stride_h, stride_w, pad_h,
            pad_w, dilation_h, dilation_w, group,
            group_channels, offset_scale, ctx.im2col_step)
        ctx.save_for_backward(input, offset, mask)
        # # 保存所需内容,以备backward时使用,所需的结果会被保存在saved_tensors元组中;
        # 此处仅能保存tensor类型变量,若其余类型变量(Int等),可直接赋予ctx作为成员变量,也可以达到保存效果
        return output

    @staticmethod
    @once_differentiable
    @custom_bwd
    def backward(ctx, grad_output): # 反向传播
        input, offset, mask = ctx.saved_tensors  # 取出forward中保存的result
        # 计算梯度并返回
        grad_input, grad_offset, grad_mask = \
            DCNv3.dcnv3_backward(
                input, offset, mask, ctx.kernel_h,
                ctx.kernel_w, ctx.stride_h, ctx.stride_w, ctx.pad_h,
                ctx.pad_w, ctx.dilation_h, ctx.dilation_w, ctx.group,
                ctx.group_channels, ctx.offset_scale, grad_output.contiguous(), ctx.im2col_step)

        return grad_input, grad_offset, grad_mask, \
            None, None, None, None, None, None, None, None, None, None, None, None

    @staticmethod
    def symbolic(g, input, offset, mask, kernel_h, kernel_w, stride_h,
                 stride_w, pad_h, pad_w, dilation_h, dilation_w, group,
                 group_channels, offset_scale, im2col_step):
        """Symbolic function for mmdeploy::DCNv3.

        Returns:
            DCNv3 op for onnx.
        """
        return g.op(
            'mmdeploy::TRTDCNv3',
            input,
            offset,
            mask,
            kernel_h_i=int(kernel_h),
            kernel_w_i=int(kernel_w),
            stride_h_i=int(stride_h),
            stride_w_i=int(stride_w),
            pad_h_i=int(pad_h),
            pad_w_i=int(pad_w),
            dilation_h_i=int(dilation_h),
            dilation_w_i=int(dilation_w),
            group_i=int(group),
            group_channels_i=int(group_channels),
            offset_scale_f=float(offset_scale),
            im2col_step_i=int(im2col_step),
        )

dcnv3_core_pytorch

def dcnv3_core_pytorch(
        input, offset, mask, kernel_h,
        kernel_w, stride_h, stride_w, pad_h,
        pad_w, dilation_h, dilation_w, group,
        group_channels, offset_scale):
    # for debug and test only,
    # need to use cuda version instead
    '''
    输入参数说明:
    input: (N,H,W,C)
    offset: (N,H,W,group*2*kernel_size*kernel_size)
    mask: (N,H,W,group*2*kernel_size*kernel_size)
    pad_h=pad_w 在h,w方向的填充(默认是1)
    dilation_h,dilation_w : 空洞卷积率
    group:分组数
    group_channels: 每个组中的通道数
    '''
    input = F.pad(
        input,
        [0, 0, pad_h, pad_h, pad_w, pad_w]) # 用0填充 大小变为 (N_,H_in,W_in,C)
    N_, H_in, W_in, _ = input.shape 
    _, H_out, W_out, _ = offset.shape # (N_,H_out,W_out,C)=(N,H,W,C)

    ref = _get_reference_points(
        input.shape, input.device, kernel_h, kernel_w, dilation_h, dilation_w, pad_h, pad_w, stride_h, stride_w)
    # ref (1,H_out_cov,W_out_cov,1,2)
    grid = _generate_dilation_grids(
        input.shape, kernel_h, kernel_w, dilation_h, dilation_w, group, input.device)
    # grid (1,1,1,group * kernel_h * kernel_w,2)
    spatial_norm = torch.tensor([W_in, H_in]).reshape(1, 1, 1, 2).\
        repeat(1, 1, 1, group*kernel_h*kernel_w).to(input.device)
    # spatial_norm (1,1,1,group*kernel_h*kernel_w)
    sampling_locations = (ref + grid * offset_scale).repeat(N_, 1, 1, 1, 1).flatten(3, 4) + \
        offset * offset_scale / spatial_norm # 得到最终的采样点位置
    # repeat 后(N_,H_out,W_out,group*kernel_h*kernel_w,2)
    # flatten 后(N_,H_out,W_out,group*kernel_h*kernel_w*2)
    # offset: (N_,H_out,W_out,group*kernel_h*kernel_w*2)
    P_ = kernel_h * kernel_w
    sampling_grids = 2 * sampling_locations - 1 # 把大小规范化到[-1,1]之间,方便F.grid_sample函数进行采样

    input_ = input.view(N_, H_in*W_in, group*group_channels).transpose(1, 2).\
        reshape(N_*group, group_channels, H_in, W_in)
    # input: (N_,H_in,W_in,group*group_channels)
    # view后:(N_, H_in*W_in, group*group_channels)
    # transpose后:(N_, group*group_channels, H_in*W_in)
    # reshape后:(N_*group, group_channels, H_in, W_in)

    
    sampling_grid_ = sampling_grids.view(N_, H_out*W_out, group, P_, 2).transpose(1, 2).\
        flatten(0, 1)
    # sampling_grids:(N_, H_out, W_out, group*P_*2)
    # transpose后:(N_, H_out*W_out, group, P_, 2)
    # flatten后:( N_*group, H_out*W_out, P_, 2)

    
    sampling_input_ = F.grid_sample( # 偏移后的采样位置
        input_, sampling_grid_, mode='bilinear', padding_mode='zeros', align_corners=False) # 双线性插值
    # input_: (N_*group, group_channels, H_in, W_in)
    # sampling_grid_: (N_*group, H_out*W_out, P_, 2)
    # sampling_input_: (N_*group, group_channels, H_out*W_out, P_)
    
    mask = mask.view(N_, H_out*W_out, group, P_).transpose(1, 2).\
        reshape(N_*group, 1, H_out*W_out, P_) # 调制标量
    # mask: (N_, H_out, W_out, group*P_)
    # view后: (N_, H_out*W_out, group, P_)
    # transpose后:(N_, group, H_out*W_out, P_)
    # reshape后:(N_*group, 1, H_out*W_out, P_)

    output = (sampling_input_ * mask).sum(-1).view(N_,group*group_channels, H_out*W_out)
    # *后:(N_*group,  group_channels, H_out*W_out, P_)
    # sum(-1)后:(N_*group,  group_channels, H_out*W_out) 
    # view后:(N_,group*group_channels, H_out*W_out)

    return output.transpose(1, 2).reshape(N_, H_out, W_out, -1).contiguous()
    # transpose后:(N_, H_out*W_out,group*group_channels)
    # reshape后:(N_, H_out, W_out,group*group_channels)=(N,H,W,C)

初始设置:
在这里插入图片描述
这里的sum(-1)是将kernel_size*kernel_size个像素的值简单求和,并没有和对应的weight相乘后再求和,因为此处对应的是上文所述=改进点1中的point-wise的操作。
在这里插入图片描述

_get_reference_points

  • 功能:生成大小为(1,H_out,W_out,1,2) 的reference_points, 其中H_out和W_out 和生成的offset的高宽相同。
def _get_reference_points(spatial_shapes, device, kernel_h, kernel_w, dilation_h, dilation_w, pad_h=0, pad_w=0, stride_h=1, stride_w=1):
	# spatial_shapes: 原始输入pad后大小 (N_,H_in,W_in,C)
    _, H_, W_, _ = spatial_shapes  
    # 等于offset的宽和高(pad=1,stride=1)
    H_out = (H_ - (dilation_h * (kernel_h - 1) + 1)) // stride_h + 1 # H_out
    W_out = (W_ - (dilation_w * (kernel_w - 1) + 1)) // stride_w + 1 # W_out

    ref_y, ref_x = torch.meshgrid(
        torch.linspace(
            (dilation_h * (kernel_h - 1)) // 2 + 0.5,
            (dilation_h * (kernel_h - 1)) // 2 + 0.5 + (H_out - 1) * stride_h,
            H_out,
            dtype=torch.float32,
            device=device),
        # 在[pad_h + 0.5,H_ - pad_h - 0.5]范围内,生成H_out个等分点
        torch.linspace(
            (dilation_w * (kernel_w - 1)) // 2 + 0.5,
            (dilation_w * (kernel_w - 1)) // 2 + 0.5 + (W_out - 1) * stride_w,
            W_out,
            dtype=torch.float32,
            device=device))
        # 在[pad_w + 0.5,W_ - pad_w - 0.5]范围内,生成W_out个等分点
    # ref_y:(H_out,W_out)
    # ref_x:(H_out,W_out)
    ref_y = ref_y.reshape(-1)[None] / H_ # 归一化
    # reshape后(H_out*W_out)
    # None后(1,H_out*W_out)
    ref_x = ref_x.reshape(-1)[None] / W_
    # (1,H_out*W_out)

    ref = torch.stack((ref_x, ref_y), -1).reshape(
        1, H_out, W_out, 1, 2)
    # stack后 (1,H_out*W_out,2)
    # reshape 后 (1,H_out,W_out,1,2)

    return ref # (1,H_out_cov,W_out_cov,1,2)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

_generate_dilation_grids

  • 功能:用于生成大小为(1,1,1,group * kernel_h * kernel_w,2)的采样点。
def _generate_dilation_grids(spatial_shapes, kernel_h, kernel_w, dilation_h, dilation_w, group, device):
    _, H_, W_, _ = spatial_shapes #(N_,H_in,W_in,C)
    points_list = []
    x, y = torch.meshgrid(
        torch.linspace(
            -((dilation_w * (kernel_w - 1)) // 2),
            -((dilation_w * (kernel_w - 1)) // 2) +
            (kernel_w - 1) * dilation_w, kernel_w,
            dtype=torch.float32,
            device=device),
        torch.linspace(
            -((dilation_h * (kernel_h - 1)) // 2),
            -((dilation_h * (kernel_h - 1)) // 2) +
            (kernel_h - 1) * dilation_h, kernel_h,
            dtype=torch.float32,
            device=device))
    # x: (kernel_w,kernel_h)
    # y: (kernel_w,kernel_h)
    points_list.extend([x / W_, y / H_])
    grid = torch.stack(points_list, -1).reshape(-1, 1, 2).\
        repeat(1, group, 1).permute(1, 0, 2)
    # stack后:(kernel_w,kernel_h,2)
    # reshape后:(kernel_w*kernel_h,1,2)
    # repeat后:(kernel_w*kernel_h,group,2)
    # permute后:(group,kernel_w*kernel_h,2)
    grid = grid.reshape(1, 1, 1, group * kernel_h * kernel_w, 2)
    #(1,1,1,group * kernel_h * kernel_w,2)
    return grid

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

华为OD机试 - 出错的或电路 - 二进制 - (Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

Vue2.0+webpack 引入字体文件(eot,ttf,woff)

webpack.base.config.js 需要配置 {test:/\/(woff2?|eot|ttf|otf)(\?.*)?$/,loader: url-loader,options: {limit: 10000,name: utils.assetsPath(fonts/[name].[hash:7].[ext])}} 如果 Vue2.0webpack3.6引入字体文件&#xff08;eot&#xff0c;ttf&#xff0c;woff&…

成都爱尔林江院长解析离焦眼镜为何与众不同

近视是影响我国国民尤其是青少年眼健康的重大公共卫生问题。因病因不明确&#xff0c;且尚无有效的治疗方法&#xff0c;如何有效控制近视发生和增长备受关注。国家出台了儿童近视防控方案&#xff0c;社会上也出现了各种近视防控方法及策略。周边离焦技术&#xff0c;算得上近…

快速上手Linux核心命令:Linux的文本编辑器vi和vim

前言 上一篇中已经预告&#xff0c;我们这篇主要说Linux中vi/vim 编辑器。它是我们使用Linux系统不可缺少的工具&#xff0c;学会了&#xff0c;你就可以在Linux世界里畅通无阻&#xff0c;学废了&#xff0c;常用操作你也会了&#xff0c;也是够用了&#xff0c;O(∩_∩)O 简…

javascript初学者可以做些什么小东西或者项目来练手?

前言 可以试一下面的一些项目&#xff0c;可能有一些比较复杂&#xff0c;可以学习一下代码的结构思路&#xff0c;希望对你有帮助~ 实用工具向 1.Exchart Star&#xff1a;55.6k Exchart提供了大量精美的图表&#xff0c;只有你想不到&#xff0c;没有你在它上面找不到的&…

了解 JSON 格式

一、JSON 基础 JSON&#xff08;JavaScript Object Notation&#xff0c;JavaScript 对象表示法&#xff09;是一种轻量级的数据交换格式&#xff0c;JSON 的设计目的是使得数据的存储和交换变得简单。 JSON 易于人的阅读和书写&#xff0c;同时也易于机器的解析和生成。尽管 J…

8月17日上课内容 LVS+Keepalived群集

本章结构 Keepalived概述 keepalived工作原理 Keepalived 是一个基于VRRP协议来实现的LVS服务高可用方案&#xff0c;可以解决静态路由出现的单点故障问题 在一个LVS服务集群中通常有主服务器 (MASTER)和备份服务器(BACKUP)两种角色的服务器&#xff0c;但是对外表现为一个虚拟…

为什么选择elasticsearch分布式搜索引擎

文章目录 &#x1f52d;什么是elasticsearch&#x1f320;ELK技术栈&#x1f320;elasticsearch和lucene&#x1f320;为什么不是其他搜索技术&#xff1f; &#x1f52d;总结 &#x1f52d;什么是elasticsearch elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常…

mini木马实践和防护方法

一、实验目的&#xff1a; 1.了解木马的实现原理 2.了解基本的防护方法 二、预备知识&#xff1a; 木马通常有两个可执行程序&#xff1a;一个是客户端&#xff0c;即控制端&#xff1b;另一个是服务端&#xff0c;即被控制端。植入被种者电脑的是“服务器”部分&#xff0c;而…

linux如何使用keepalived配置VIP

VIP常用于负载均衡的高可用&#xff0c;使用VIP可以给多个主机绑定一个IP&#xff0c;这样&#xff0c;当某个负载应用挂了之后&#xff0c;可以自动切到另一个负载。 我这里是在k8s环境中做的测试&#xff0c;集群中有6个节点&#xff0c;我给140和141两个节点配置VIP。 1. 安…

「2024」预备研究生mem-立体几何截面模型代绝对值得一次和二次函数

一、立体几何截面模型&代绝对值得一次和二次函数 二、练习题 凑配换元

Mysql5.7.36主从同步实操

主库创建同步账户 #创建备份的账户 CREATE USER backup192.168.32.1 IDENTIFIED BY backup123; #给账户授予备份的权限 GRANT REPLICATION SLAVE ON *.* TO backup192.168.32.1; #刷新权限 FLUSH PRIVILEGES;停止主库 配置主库需要的备份参数 打开my.ini文件&#xff0c;配置…

Unity C# 之 Task、async和 await 、Thread 基础使用的Task的简单整理

Unity C# 之 Task、async和 await 、Thread 基础使用的Task的简单整理 目录 Unity C# 之 Task、async和 await 、Thread 基础使用的Task的简单整理 一、Task、async和 await 、Thread 基础概念 1、线程&#xff0c;多线程 2、Task 3、async &#xff08;await &#xff09;…

第四章:树形结构的关联式容器(map+set)

系列文章目录 文章目录 系列文章目录前言1、关联式容器与序列式容器1.1 键值对 2、set的介绍3、multiset的介绍3.1 接口count与容器multiset 4、map的介绍4.1 接口insert4.2 operator[]和at 5、multimap的介绍 前言 根据应用场景的不桶&#xff0c;STL总共实现了两种不同结构的…

PBI 之 Query数据导入、处理

PBI梳理数据流程 首先通过Excel 插件 Power Query、Power Pivot处理数据&#xff0c;然后使用Power Desktop导入处理好的数据进行展示。最后发布到在线网站。 一、导入数据 二、处理数据 如下&#xff0c;进入到Power Query编辑器界面 数据--查询&连接 界面区 界面区右键…

ChatGPT产品发布时间表-了解别人家的创业节奏

ChatGPT产品节点-2023年7月末-长期更新 ChatGPT风靡全球&#xff0c;创造了科技史上的发展奇迹。它可以根据简短的提示生成文章、代码等&#xff0c;极大地提高了生产力。许多大品牌正在尝试利用它来生成广告和营销文字。OpenAI也在此技术上大举投资。以下是ChatGPT产品发布时间…

共创无线物联网数字化新模式|协创数据×企企通采购与供应链管理平台项目成功上线

近日&#xff0c;全球无线物联网领先者『协创数据技术股份有限公司』&#xff08;以下简称“协创数据”&#xff09;SRM采购与供应链项目全面上线&#xff0c;并于近日与企企通召开成功召开项目上线总结会。 基于双方资源和优势&#xff0c;共同打造了物联网特色的数字化采购供…

运动耳机哪款好用、适合运动的耳机推荐

如今&#xff0c;蓝牙耳机不仅是手机的最佳伴侣&#xff0c;也成为了运动爱好者的必备装备。但是&#xff0c;在如此众多的蓝牙耳机中&#xff0c;你是否对选购感到困惑呢&#xff1f;实际上&#xff0c;选择适合运动的蓝牙耳机需要考虑许多因素&#xff0c;如舒适度、稳固性、…

STM32L151C8T6 芯片数据手册

1. 芯片型号含义 2. Flash&#xff1a; 64KB 3. keil 配置ST-Link 烧录程序 4. keil 选择Flash 烧录算法 5. 系统主频 32Mhz 6. 时钟树 clock tree

2009年下半年 软件设计师 上午试卷3

博主介绍&#xff1a;✌全网粉丝3W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…