【卷积集】近万字长文总结了日常使用的“卷积”特征,内附代码实现送给准备面试的你

简介: 【卷积集】近万字长文总结了日常使用的“卷积”特征,内附代码实现送给准备面试的你

前言

  卷积神经网络(Convolutional Neural Networks,CNNs) 是深度学习中最常用的神经网络之一。CNNs 在图像处理、自然语言处理、语音识别等领域都取得了重大的进展和成就。卷积神经网络的核心是卷积操作,它是通过卷积核与输入数据进行卷积运算从而提取特征。而卷积核的不同尺寸、形状和参数设置,以及不同的卷积结构组合,可以产生不同的特征提取效果,从而应对不同的应用需求。

普通卷积

  普通卷积(Convolutional Layer):对输入数据进行卷积运算,提取特征信息,通常包括卷积核、步长、填充等参数设置。后续的变形卷积结构都是基于下述的特性进行拓展。

  普通卷积作为卷积神经网络中最基本的卷积结构之一,具有灵活性、可调性和可解释性等优点。同时,普通卷积也存在一些缺点,例如在处理大尺寸输入数据时会导致计算量过大,同时也难以捕捉到全局上下文信息。针对这些问题,我们可以结合其他卷积结构进行改进和优化。

  1. 卷积核:普通卷积的核心是卷积核,卷积核可以看作是一组可学习的参数,通过对输入数据进行卷积运算,可以从输入数据中提取出一些特定的特征。卷积核的尺寸通常是正方形或矩形,可以通过设置不同的卷积核大小,从而提取出不同的特征信息。
  2. 步长:步长是卷积核在对输入数据进行卷积操作时移动的步长。通常情况下,步长的大小可以根据需求进行设置,较小的步长可以提取更为精细的特征,而较大的步长可以减少计算量。
  3. 填充:填充是在输入数据的周围添加一圈像素,以便使得卷积核可以在输入数据边缘进行卷积操作。填充可以分为两种类型,一种是“valid”(不填充),一种是“same”(填充至输出数据的尺寸与输入数据尺寸相同),可以根据需求进行选择。

特点

  1. 多通道输入:普通卷积可以接受多通道的输入数据。在卷积过程中,每个卷积核会分别与输入数据的每个通道进行卷积运算,最终将得到每个卷积核的输出,再将多个卷积核的输出进行合并。
  2. 参数共享:普通卷积的另一个重要特性是参数共享,即在卷积运算中,每个卷积核的参数都是共享的。这种共享参数的机制使得卷积操作具有一定的平移不变性,从而可以在输入数据的不同位置检测到相同的特征。

分组卷积

  分组卷积(Grouped Convolution)是一种常用的卷积结构,它将输入数据分成若干个组,每个组内部进行卷积操作,最后将结果进行拼接,可以有效减少参数量和计算量。

  分组卷积将输入数据按照通道数均分为若干个组,每个组内部使用一个独立的卷积核进行卷积操作,得到一个部分特征图。所有的部分特征图再进行拼接,得到最终的输出特征图。这样做的好处是,每个卷积核只需要处理部分输入数据,从而减少了参数量和计算量,同时还可以增加网络的并行度和计算效率。

特点

  1. 减少参数量和计算量:由于每个卷积核只需要处理部分输入数据,所以可以减少参数量和计算量,从而提高计算效率。
  2. 增加网络并行度:由于每个组内部的卷积操作是独立的,所以可以增加网络的并行度,从而加快计算速度。
  3. 保持空间信息:由于每个组内部的卷积操作只涉及部分通道,所以可以保持空间信息,避免了全局信息的混淆,有利于提高网络的特征表达能力。
  4. 容易造成信息瓶颈:分组卷积虽然减少了参数量和计算量,但也容易造成信息瓶颈,因为每个卷积核只能处理部分输入数据,无法利用其他通道的信息,从而降低了特征表达能力。
  5. 对卷积核数量的要求较高:由于每个卷积核只处理部分输入数据,所以需要增加卷积核的数量才能保证特征提取能力。

  分组卷积常用于对计算资源有限的设备中,如移动端、嵌入式设备等,可以通过减少参数量和计算量来满足计算资源的限制。同时,分组卷积也经常与其他卷积结构进行组合使用,如深度可分离卷积、注意力机制卷积等,以进一步提高网络的性能。

代码实现

ini

复制代码

import numpy as np
def grouped_convolution(x, w, b, groups, stride=1, padding=0):
    """
    分组卷积实现
    Parameters:
        x: 输入数据,shape 为 (batch_size, in_channels, height, width)
        w: 卷积核,shape 为 (out_channels, in_channels/groups, kernel_size, kernel_size)
        b: 偏置,shape 为 (out_channels,)
        groups: 分组数
        stride: 步长,默认为 1
        padding: 填充大小,默认为 0
    Returns:
        out: 卷积结果,shape 为 (batch_size, out_channels, out_height, out_width)
    """
    batch_size, in_channels, height, width = x.shape
    out_channels, _, kernel_size, _ = w.shape
    # 计算输出尺寸
    out_height = (height + 2 * padding - kernel_size) // stride + 1
    out_width = (width + 2 * padding - kernel_size) // stride + 1
    # 对输入数据进行填充
    x_padded = np.pad(x, ((0,), (0,), (padding,), (padding,)), mode='constant')
    # 分组卷积
    out = np.zeros((batch_size, out_channels, out_height, out_width))
    for i in range(groups):
        start = i * in_channels // groups
        end = (i + 1) * in_channels // groups
        x_group = x_padded[:, start:end, :, :]
        w_group = w[i * out_channels // groups: (i + 1) * out_channels // groups, :, :, :]
        b_group = b[i * out_channels // groups: (i + 1) * out_channels // groups]
        out_group = np.zeros((batch_size, out_channels // groups, out_height, out_width))
        for j in range(out_channels // groups):
            out_group[:, j, :, :] = np.sum(
                x_group * w_group[j, :, :, :],
                axis=(1, 2, 3)
            )
            out_group[:, j, :, :] += b_group[j]
        out[:, i * out_channels // groups: (i + 1) * out_channels // groups, :, :] = out_group
    return out

深度可分离卷积

  深度可分离卷积(Depthwise Separable Convolution)是一种卷积神经网络中常用的卷积结构,它将普通卷积拆分为深度卷积和逐点卷积两个步骤,从而减少了模型的参数数量和计算复杂度,同时保持了较好的性能。

实现步骤:

  1. 深度卷积(Depthwise Convolution):对输入的每个通道进行单独的卷积操作,使用相同的卷积核,从而生成与输入相同数量的输出通道。该步骤只包含输入通道的卷积操作,不涉及输出通道的计算。
  2. 逐点卷积(Pointwise Convolution):在深度卷积的基础上,使用1×1的卷积核对每个通道的结果进行卷积操作,从而将不同通道的信息进行融合,并生成最终的输出特征图。

特点:

  1. 减少参数数量:相比于普通卷积,深度可分离卷积的参数数量大幅度减少,因为深度卷积只对每个通道进行卷积操作,而逐点卷积只包含1×1的卷积核。
  2. 减少计算复杂度:深度可分离卷积的计算复杂度也较低,因为深度卷积只进行输入通道的卷积操作,而逐点卷积的卷积核尺寸较小,计算量也较小。
  3. 保持较好的性能:尽管深度可分离卷积减少了参数数量和计算复杂度,但它仍然可以保持较好的性能,因为深度卷积和逐点卷积都具有一定的特征提取能力,并且可以更好地适应不同的输入数据。

  深度可分离卷积常用于轻量化的卷积神经网络中,例如MobileNet和Xception等网络。它们在计算量和参数数量都较小的情况下,可以达到与传统卷积神经网络相近的性能,因此在移动设备和嵌入式设备等资源受限的环境下具有重要的应用价值。

代码实现

  在下面的代码中,DepthwiseSeparableConv 类定义了深度可分离卷积层,其中包括深度卷积层和逐点卷积层。在 __init__ 方法中,我们首先定义了深度卷积层,它的输入通道数和输出通道数都是 in_channels,卷积核的大小为 kernel_size,步长为 stride,填充为 padding,并设置了 groups=in_channels,这样就可以将每个通道独立处理,相当于对输入数据的每个通道分别进行卷积操作。

  接着我们定义了逐点卷积层,它的输入通道数为深度卷积层的输出通道数,输出通道数为 out_channels,卷积核大小为 1x1,步长为 1,填充为 0。

  在 forward 方法中,我们首先将输入数据 x 输入到深度卷积层中进行卷积操作,然后使用 ReLU 激活函数进行非线性变换。接着将结果输入到逐点卷积层中进行卷积操作,再次使用 ReLU 激活函数进行非线性变换,并返回最终的输出结果。

python

复制代码

import torch
import torch.nn as nn
class DepthwiseSeparableConv(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):
        super(DepthwiseSeparableConv, self).__init__()
        self.depthwise = nn.Conv2d(in_channels, in_channels, kernel_size, stride, padding, groups=in_channels)
        self.pointwise = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0)
        self.relu = nn.ReLU(inplace=True)
    def forward(self, x):
        x = self.depthwise(x)
        x = self.relu(x)
        x = self.pointwise(x)
        x = self.relu(x)
        return x

  使用代码可以方便地构建深度可分离卷积层,并且可以根据具体的输入通道数、输出通道数、卷积核大小、步长和填充等参数进行灵活的配置和使用。

空洞卷积

  空洞卷积(Dilated Convolution),也称为膨胀卷积或扩张卷积,是一种卷积操作,通过在卷积核的空间采样点之间插入零值来扩展卷积核的有效感受野,从而增加了卷积核感受野的大小。与传统的卷积相比,空洞卷积在卷积核内部插入一定的间隔,使得卷积核在进行卷积操作时跨越更大的距离,从而可以扩大感受野,提高特征提取能力。

特点:

  1. 扩大感受野:由于在卷积核的采样点之间插入零值,空洞卷积可以使卷积核覆盖更大的感受野,从而更好地捕捉图像中的长距离关系和上下文信息。
  2. 保持分辨率:空洞卷积可以通过调整卷积核的扩张率来控制输出特征图的尺寸,从而保持输入特征图的分辨率不变。
  3. 减少参数:相对于普通卷积,空洞卷积可以通过增加卷积核的感受野而不增加卷积核的大小,从而减少参数量和计算量,提高网络的计算效率。
  4. 可并行化:由于在空洞卷积中卷积核的采样点之间存在固定的空隙,因此可以通过并行计算来加速卷积运算,提高计算效率。
  5. 多尺度特征提取:空洞卷积可以通过调整卷积核的扩张率来提取不同尺度的特征,从而适应不同大小的目标,可以应用于图像分割、语义分割、目标检测等领域。

  空洞卷积可以增加卷积核的感受野,提高特征提取能力,同时可以保持输入特征图的分辨率不变,减少参数量和计算量,提高计算效率。因此,空洞卷积在深度学习中得到了广泛的应用。

代码实现

python

复制代码

import torch
import torch.nn as nn
# 定义一个包含空洞卷积的网络模块
class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        # 定义一个空洞卷积层,输入通道数为3,输出通道数为16,卷积核大小为3x3,空洞参数为2
        self.conv = nn.Conv2d(3, 16, kernel_size=3, padding=2, dilation=2)
    def forward(self, x):
        # 前向传播
        x = self.conv(x)
        return x
# 创建一个随机输入数据
x = torch.randn(1, 3, 28, 28)
# 创建一个空洞卷积模块并进行前向传播
net = MyModule()
out = net(x)
# 打印输出数据的形状
print(out.shape)

反卷积

  反卷积(Transposed Convolution),也称作转置卷积或上采样卷积,是一种用于对输入数据进行上采样的卷积操作。它可以将输入数据的尺寸进行扩大,从而使得网络输出的特征图的尺寸和输入数据的尺寸保持一致,这在图像分割、语义分割等任务中非常常见。

特点:

  1. 上采样:反卷积的主要作用是对输入数据进行上采样,即将输入数据的尺寸扩大,以便于更好地适应输出特征图的尺寸。与下采样相对应,上采样可以增加特征图的分辨率,提高模型的准确度和精度。
  2. 可逆性:反卷积是一种可逆的操作,即输入特征图可以通过反卷积操作被还原成更高分辨率的特征图。因此,反卷积可用于图像重建和合成等任务中。
  3. 无法恢复细节:虽然反卷积可以增加特征图的分辨率,但是它无法恢复输入数据中丢失的细节信息。因此,反卷积常常与编码器-解码器结构一起使用,以便在解码器中使用反卷积扩大特征图尺寸的同时,利用编码器提取的低分辨率特征信息来恢复更多的细节信息。
  4. 计算量大:由于反卷积需要进行大量的计算,因此在实际应用中,需要考虑计算效率和模型大小等因素。为了减少计算量和模型大小,可以采用其他的上采样方式,如双线性插值、最近邻插值等。

  反卷积是一种重要的卷积结构,可以用于增加特征图的分辨率,从而提高模型的准确度和精度。但是需要注意的是,反卷积无法恢复输入数据中丢失的细节信息,并且计算量较大,需要综合考虑计算效率和模型大小等因素。

python

复制代码

import torch
import torch.nn as nn
# 定义反卷积层
class Deconvolution(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, stride, padding):
        super(Deconvolution, self).__init__()
        self.deconv = nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride, padding)
    def forward(self, x):
        out = self.deconv(x)
        return out
# 定义网络结构
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, 3, 1, 1)
        self.conv2 = nn.Conv2d(32, 64, 3, 1, 1)
        self.deconv = Deconvolution(64, 32, 3, 2, 1)
        self.conv3 = nn.Conv2d(32, 3, 3, 1, 1)
    def forward(self, x):
        x = nn.functional.relu(self.conv1(x))
        x = nn.functional.relu(self.conv2(x))
        x = nn.functional.relu(self.deconv(x))
        x = self.conv3(x)
        return x
# 测试反卷积
input_data = torch.randn(1, 3, 256, 256)
model = Net()
output = model(input_data)
print(output.shape)

双线性卷积

  双线性卷积(Bilinear Convolution)是一种卷积神经网络中常用的卷积结构,它是通过对输入数据进行两次卷积操作,分别使用不同的卷积核,从而能够学习到输入数据中不同区域的关系和相互作用。

特点:

  1. 能够捕捉输入数据中的高阶特征。双线性卷积结构能够在不增加太多参数和计算量的情况下,学习到输入数据中不同区域之间的复杂关系,从而捕捉到更高阶的特征信息。
  2. 能够生成高质量的图像。由于双线性卷积结构能够学习到输入数据中的空间关系,因此可以应用于图像生成、图像编辑等任务中,生成高质量的图像。
  3. 参数共享。在双线性卷积的第二次卷积操作中,使用的卷积核与第一次卷积操作中使用的卷积核共享参数,因此可以有效减少参数量。
  4. 模型可解释性。双线性卷积结构的参数直接对应于输入数据中的空间关系,因此可以通过分析参数的取值,了解输入数据中不同区域之间的关系和相互作用。

  虽然双线性卷积结构在图像生成、图像编辑等任务中表现出了良好的性能,但其计算量较大,且需要消耗大量的计算资源。此外,双线性卷积结构还存在着一些问题,例如无法处理多尺度输入数据,容易过拟合等。因此,在实际应用中需要根据具体任务的需求,结合其它卷积结构进行选择和组合。

代码实现

  这里定义了一个名为 BilinearConv 的类,继承自 nn.Module,实现了双线性卷积的前向计算逻辑。在 __init__ 方法中,定义了卷积核的尺寸、步长、填充等参数,以及卷积核的权重和偏置,其中权重使用了 nn.Parameter 封装,以便于后续在训练过程中进行优化。在 reset_parameters 方法中,使用了 kaiming 初始化方法对权重进行初始化。在 forward 方法中,先使用 PyTorch 内置的 F.conv2d 函数进行普通卷积操作,然后对卷积结果进行 ReLU 激活和双线性插值操作,最终得到输出结果。

  这里的 F.interpolate 函数实现了双线性插值,其中 scale_factor 参数指定了插值的比例,即输出尺寸相对于输入尺寸的缩放比例。mode 参数指定了插值的模式,这里使用了双线性插值模式。align_corners 参数指定了插值操作是否要对齐角点,这里设置为 True,即按照角点对齐的方式进行插值。

python

复制代码

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
class BilinearConv(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):
        super(BilinearConv, self).__init__()
        self.in_channels = in_channels
        self.out_channels = out_channels
        self.kernel_size = kernel_size
        self.stride = stride
        self.padding = padding
        self.weight = nn.Parameter(torch.Tensor(out_channels, in_channels, kernel_size, kernel_size))
        self.bias = nn.Parameter(torch.Tensor(out_channels))
        self.reset_parameters()
    def reset_parameters(self):
        nn.init.kaiming_uniform_(self.weight, a=np.sqrt(5))
        if self.bias is not None:
            fan_in, _ = nn.init._calculate_fan_in_and_fan_out(self.weight)
            bound = 1 / np.sqrt(fan_in)
            nn.init.uniform_(self.bias, -bound, bound)
    def forward(self, x):
        out = F.conv2d(x, self.weight, bias=self.bias, stride=self.stride, padding=self.padding)
        out = F.relu(out)
        out = F.interpolate(out, scale_factor=self.stride, mode='bilinear', align_corners=True)
        return out
if __name__ == "__main__":
    x = torch.zeros(1, 3, 640, 640)
    model = BilinearConv(3, 64, 1)
    y = model(x)
    print(y.shape)

可变性卷积

  可变形卷积(Deformable Convolution)是一种基于普通卷积(Convolution)的卷积操作,它引入了可变形卷积核(Deformable Convolution Kernel),使卷积核可以自适应地改变形状,从而更好地适应输入数据的复杂形状。可变形卷积最初是由Facebook AI Research在2017年提出的,目前已被广泛应用于图像语义分割、目标检测、姿态估计等领域。

特点:

  1. 自适应感受野:可变形卷积核可以根据输入数据的不同形状自适应地调整形状和大小,从而具有自适应的感受野(Receptive Field),能够更好地适应不同尺度的物体和不规则形状的场景。
  2. 空间变形:可变形卷积核可以对输入数据进行空间变形(Spatial Deformation),从而更好地适应输入数据的非刚性变形,能够更好地处理图像中的姿态变化和形变。
  3. 参数可学习:可变形卷积核的形状和大小是可以通过反向传播来学习的,可以自适应地学习到输入数据中的局部特征和形状变化,从而提高特征提取和表示的能力。
  4. 多分辨率特征:可变形卷积可以同时利用不同尺度和不同层次的特征信息,从而提高特征的多样性和丰富性,能够更好地处理复杂场景和多尺度物体。

  可变形卷积是一种非常有用的卷积操作,它可以更好地适应输入数据的复杂形状和姿态变化,能够更好地提取图像中的局部特征和细节信息,从而提高图像分割、目标检测、姿态估计等任务的精度和鲁棒性。

ini

复制代码

from torch.autograd import Variable, Function
import torch
from torch import nn
import numpy as np
class DeformConv2D(nn.Module):
    def __init__(self, inc, outc, kernel_size=3, padding=1, bias=None):
        super(DeformConv2D, self).__init__()
        self.kernel_size = kernel_size
        self.padding = padding
        self.zero_padding = nn.ZeroPad2d(padding)
        self.conv_kernel = nn.Conv2d(inc, outc, kernel_size=kernel_size, stride=kernel_size, bias=bias)
    def forward(self, x, offset):
        dtype = offset.data.type()
        ks = self.kernel_size
        N = offset.size(1) // 2
        # Change offset's order from [x1, x2, ..., y1, y2, ...] to [x1, y1, x2, y2, ...]
        # Codes below are written to make sure same results of MXNet implementation.
        # You can remove them, and it won't influence the module's performance.
        offsets_index = Variable(torch.cat([torch.arange(0, 2*N, 2), torch.arange(1, 2*N+1, 2)]), requires_grad=False).type_as(x).long()
        offsets_index = offsets_index.unsqueeze(dim=0).unsqueeze(dim=-1).unsqueeze(dim=-1).expand(*offset.size())
        offset = torch.gather(offset, dim=1, index=offsets_index)
        # ------------------------------------------------------------------------
        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 = Variable(p.data, requires_grad=False).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:]], -1)
        q_rt = torch.cat([q_rb[..., :N], q_lt[..., N:]], -1)
        # (b, h, w, N)
        mask = torch.cat([p[..., :N].lt(self.padding)+p[..., :N].gt(x.size(2)-1-self.padding),
                          p[..., N:].lt(self.padding)+p[..., N:].gt(x.size(3)-1-self.padding)], dim=-1).type_as(p)
        mask = mask.detach()
        floor_p = p - (p - torch.floor(p))
        p = p*(1-mask) + floor_p*mask
        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
        x_offset = self._reshape_x_offset(x_offset, ks)
        out = self.conv_kernel(x_offset)
        return out
    def _get_p_n(self, N, dtype):
        p_n_x, p_n_y = np.meshgrid(range(-(self.kernel_size-1)//2, (self.kernel_size-1)//2+1),
                          range(-(self.kernel_size-1)//2, (self.kernel_size-1)//2+1), indexing='ij')
        # (2N, 1)
        p_n = np.concatenate((p_n_x.flatten(), p_n_y.flatten()))
        p_n = np.reshape(p_n, (1, 2*N, 1, 1))
        p_n = Variable(torch.from_numpy(p_n).type(dtype), requires_grad=False)
        return p_n
    @staticmethod
    def _get_p_0(h, w, N, dtype):
        p_0_x, p_0_y = np.meshgrid(range(1, h+1), range(1, w+1), indexing='ij')
        p_0_x = p_0_x.flatten().reshape(1, 1, h, w).repeat(N, axis=1)
        p_0_y = p_0_y.flatten().reshape(1, 1, h, w).repeat(N, axis=1)
        p_0 = np.concatenate((p_0_x, p_0_y), axis=1)
        p_0 = Variable(torch.from_numpy(p_0).type(dtype), requires_grad=False)
        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
if __name__ == "__main__":
    x = torch.zeros(1, 3, 640, 640)
    model = DeformConv2D(3, 64)
    y = model(x)
    print(y.shape)

注意力卷积

  注意力机制卷积(Attention Convolution)是一种在卷积神经网络中引入注意力机制的卷积结构,其核心思想是对输入数据中不同区域进行加权,以便网络更加关注重要的信息,从而提高特征提取和分类的精度。

特点:

  1. 加权特征:注意力机制卷积在卷积操作之前先对输入数据进行加权,将不同区域的信息按照其重要性进行加权,从而更加关注重要的特征信息,提高特征提取的精度。
  2. 动态调整:注意力机制卷积中的注意力权重是可以动态调整的,能够根据不同的输入数据自适应地学习不同的特征,从而提高分类精度。
  3. 可嵌入性:注意力机制卷积可以与其他卷积结构相结合,形成更加复杂的卷积网络,同时也可以作为其他卷积结构的子模块使用。
  4. 适应不同应用场景:由于注意力机制卷积能够动态调整注意力权重,因此可以应用于各种不同的应用场景,例如图像分类、图像分割、目标检测等。

  注意力机制卷积是一种非常有用的卷积结构,它能够根据不同的输入数据自适应地学习不同的特征,并且可以与其他卷积结构相结合,形成更加复杂的卷积网络,因此在各种不同的应用场景中都有着广泛的应用前景。

代码实现

python

复制代码

import torch
import torch.nn as nn
class AttentionConv2d(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):
        super(AttentionConv2d, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride=stride, padding=padding)
        self.attention = nn.Sequential(
            nn.Conv2d(out_channels, 1, kernel_size=1),
            nn.Sigmoid()
        )
    def forward(self, x):
        feature = self.conv(x)
        attention = self.attention(feature)
        out = feature * attention.expand_as(feature)
        return out

1x1卷积

  1x1卷积是卷积神经网络中的一种特殊卷积结构,它的卷积核大小为1x1,即只包含一个元素。

特点:

  1. 降维和升维:1x1卷积可以通过调整卷积核的数量和通道数,实现对输入数据的降维和升维操作。通过降维,可以减少网络中的参数数量,降低计算复杂度;通过升维,可以增加网络的特征表达能力,提高特征提取效果。
  2. 信息融合:1x1卷积可以用于特征的信息融合,即将多个通道的特征进行融合,生成新的特征表示。例如,在ResNet中,1x1卷积被用于跨层连接的信息融合,提高了网络的表达能力和稳定性。
  3. 空间信息保持:1x1卷积不改变输入数据的空间尺寸,保持了输入数据的空间信息。这对于一些需要保持图像细节信息的任务(如图像分割、目标检测)非常重要,可以避免信息的丢失和模糊化。
  4. 参数共享:与普通卷积类似,1x1卷积也具有参数共享的特性。在网络中,多个位置和通道的特征可以共享同一组1x1卷积的参数,从而减少了网络的参数数量,提高了模型的泛化能力。

  1x1卷积是一种非常有用的卷积结构,可以用于特征的降维和升维、信息融合、空间信息保持和参数共享等操作。在卷积神经网络中广泛应用,可以提高网络的性能和效率。

代码实现

ini

复制代码

import torch
import torch.nn as nn
# 定义输入数据
x = torch.randn(1, 32, 16, 16)
# 定义1x1卷积层
conv = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=1)
# 对输入数据进行卷积操作
y = conv(x)
# 打印输出结果
print(y.shape)

图卷积

  图卷积(Graph Convolution)是一种应用于图像处理、社交网络分析、推荐系统等领域的新兴卷积技术。与传统的卷积神经网络中的卷积操作不同,图卷积是在图结构上进行的。

特点

  1. 输入不再是张量:在传统的卷积神经网络中,输入数据通常是一个张量,但在图卷积中,输入数据是一个图结构,由节点和边构成。每个节点表示一个特征向量,每条边表示两个节点之间的关系。
  2. 邻域信息定义不唯一:在图结构中,每个节点的邻域不一定是固定的,可以根据不同的任务和场景定义邻域。例如,在社交网络分析中,可以将一个用户的邻域定义为他的朋友圈子。
  3. 卷积核的形状不固定:在传统的卷积神经网络中,卷积核的形状通常是固定的,但在图卷积中,卷积核的形状是可以变化的,因为邻域信息不是固定的。因此,卷积核的形状需要根据不同的邻域进行调整。
  4. 模型结构的灵活性高:在图卷积中,可以根据不同的任务和场景设计不同的模型结构。例如,可以设计多层图卷积来提取不同层次的特征,或者使用池化层来降低图的复杂度。
  5. 可以处理任意尺寸的输入:在传统的卷积神经网络中,输入数据的尺寸通常是固定的,但在图卷积中,可以处理任意尺寸的输入,因为图的大小和结构是不固定的。

   图卷积具有较高的灵活性和适应性,可以处理多种类型的输入数据,包括社交网络、推荐系统、生物信息学、化学分子等等。

代码实现

python

复制代码

import torch
import torch.nn as nn
import torch.nn.functional as F
class GraphConv(nn.Module):
    def __init__(self, in_features, out_features):
        super(GraphConv, self).__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.weight = nn.Parameter(torch.FloatTensor(in_features, out_features))
        self.bias = nn.Parameter(torch.FloatTensor(out_features))
    def forward(self, x, adj):
        x = torch.matmul(adj, x)
        x = torch.matmul(x, self.weight)
        return x + self.bias
class GCN(nn.Module):
    def __init__(self, nfeat, nhid, nclass):
        super(GCN, self).__init__()
        self.conv1 = GraphConv(nfeat, nhid)
        self.conv2 = GraphConv(nhid, nclass)
    def forward(self, x, adj):
        x = F.relu(self.conv1(x, adj))
        x = self.conv2(x, adj)
        return F.log_softmax(x, dim=1)

深度卷积

  深度卷积(Dense Convolutional Network)是一种密集连接的卷积神经网络,与传统的卷积神经网络相比,其最大的特点是在网络中引入了密集连接(Dense Connection)。

  在传统的卷积神经网络中,每一层的输出仅作为下一层的输入,而在DenseNet中,每个卷积层的输出都会直接连接到网络的后续层。因此,DenseNet可以更好地利用前面层的特征信息,增强了网络的特征重用能力和梯度传播效率。

特点:

  1. 特征重用:每个卷积层的输出都连接到网络的后续层,从而使得前面层的特征信息可以被后续层充分利用,减少了特征信息的丢失。
  2. 参数共享:每个卷积层的输出不仅连接到后续层,还连接到同一层的其他卷积层,从而实现了参数共享,减少了参数数量。
  3. 梯度传播:由于每个卷积层的输出都连接到网络的后续层,梯度可以直接沿着这些连接反向传播,从而加速了梯度的传播。
  4. 防止过拟合:DenseNet在每个卷积层后引入了批量归一化和非线性激活函数,可以有效地防止过拟合。
  5. 减少梯度消失:由于每个卷积层的输出都连接到网络的后续层,可以减少梯度消失问题,使得网络更加容易训练。

总结

  金三银四不易,聊以此文献给诸君,愿君长风破浪!


相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
4月前
|
SQL JSON 网络协议
告警流量特征分析(护网蓝初面试干货)
告警流量特征分析(护网蓝初面试干货)
147 0
卷积面试题(最重要)
卷积面试题(最重要)
187 0
|
存储 Java C#
OOP 三大特征和文件编程面试题 | 学习笔记
快速学习OOP 三大特征和文件编程面试题,课程揭秘Java面向对象编程的奥秘,深入解析Java经典数据类型,多线程编程详解,带你全面探索多线程编程的世界,攻克Java面向对象编程疑难点!
OOP 三大特征和文件编程面试题 | 学习笔记
|
29天前
|
Java 程序员
java线程池讲解面试
java线程池讲解面试
52 1
|
2月前
|
存储 关系型数据库 MySQL
2024年Java秋招面试必看的 | MySQL调优面试题
随着系统用户量的不断增加,MySQL 索引的重要性不言而喻,对于后端工程师,只有在了解索引及其优化的规则,并应用于实际工作中后,才能不断的提升系统性能,开发出高性能、高并发和高可用的系统。 今天小编首先会跟大家分享一下MySQL 索引中的各种概念,然后介绍优化索引的若干条规则,最后利用这些规则,针对面试中常考的知识点,做详细的实例分析。
251 0
2024年Java秋招面试必看的 | MySQL调优面试题
|
2月前
|
存储 算法 Java
铁子,你还记得这些吗----Java基础【拓展面试常问题型】
铁子,你还记得这些吗----Java基础【拓展面试常问题型】
46 1
|
2月前
|
NoSQL Java 关系型数据库
凭借Java开发进阶面试秘籍(核心版)逆流而上
最近参加了面试或者身边有朋友在面试的兄弟有没有发现,现在的面试不仅会问八股文,还会考察框架、项目实战、算法数据结构等等,需要准备的越来越多。 其实面试的时候,并不是要求你所有的知识点都会,而是关键的问题答到点子上!这份《Java 开发进阶面试秘籍(核心版)》由 P8 面试官整体把控,目前已经更新了 30 万字! 资料中涵盖了一线大厂、中小厂面试真题,毕竟真题都是技术领域最经典的基础知识和经验沉淀的汇总,非常有必要学习掌握!双重 buff 叠加,offer 接到手软~ 点击此处取,这可能是你到目前为止领取的最具含金量的一份资料! 整套资料涵盖:Spring、Spring
|
2月前
|
存储 缓存 Java
面试官:什么是Java内存模型?
面试官:什么是Java内存模型?
112 0
面试官:什么是Java内存模型?
|
1月前
|
消息中间件 NoSQL 网络协议
Java面试知识点复习​_kaic
Java面试知识点复习​_kaic
|
3天前
|
Java 调度
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
31 1