深度学习中的卷积操作

本文从信号处理中的互相关运算引入深度学习中的卷积
然后介绍了不同的卷积类型,以及如何在pytorch中使用这些卷积层。

(在看pytorch文档中的Conv1D/2D/3D的时候感到比较困惑,又很好奇深度学习中各种各样的卷积操作。于是结合整理几乎包含深度学习中所有的卷积操作,主要参考的有《Dive into Deep learning》, cs231, pytorch的官网文档,stackoverflow以及csdn和知乎上的介绍…简单记录一下)

一、前言

1.1 数学中的卷积操作

图像中的卷积操作由数学中卷积的演化而来,所以我们先了解下数学中的卷积操作


直观理解
在信号/图像处理中,卷积定义为
两个函数在反转和移位后的乘积的积分,以下可视化展示了这一过程:
在这里插入图片描述


数值理解

  • 连续函数的卷积
    在数学中, 两个函数(比如 f , g f, g f,g : R d → R \mathbb{R}^d \rightarrow \mathbb{R} RdR )之间的 “卷积”被定义为
    ( f ∗ g ) ( x ) = ∫ f ( z ) g ( x − z ) d z . (f * g)(\mathbf{x})=\int f(\mathbf{z}) g(\mathbf{x}-\mathbf{z}) d \mathbf{z} . (fg)(x)=f(z)g(xz)dz.
    也就是说, 卷积是当把函数g “翻转” 并移位 x \mathbf{x} x时, 测量 f f f g g g之间的乘积
  • 离散函数的卷积
    当为离散对象时, 积分就变成求 和。例如:对于由索引为 Z \mathbb{Z} Z的、平方可和的、无限维向量集合中抽取的向量,我们得到以下定义:

( f ∗ g ) ( i ) = ∑ a f ( a ) g ( i − a ) . (f * g)(i)=\sum_a f(a) g(i-a) . (fg)(i)=af(a)g(ia).

  • 二维函数的卷积
    对于二维张量, 则为 f f f的索引 ( a , b ) (a, b) (a,b) g g g的索引 ( i − a , j − b ) (i-a, j-b) (ia,jb)上的对应加和:
    ( f ∗ g ) ( i , j ) = ∑ a ∑ b f ( a , b ) g ( i − a , j − b ) . (f * g)(i, j)=\sum_a \sum_b f(a, b) g(i-a, j-b) . (fg)(i,j)=abf(a,b)g(ia,jb).

1.2 信号处理中的互相关运算

严格来说,卷积层是个错误的叫法,因为它所表达的运算其实是互相关运算(cross-correlation),⽽不是卷积运算。在卷积层中,输⼊张量和核张量通过互相关运算产⽣输出张量。


直观理解
互相关被称为滑动点积或两个函数的滑动内积。互相关的filters不需要反转,它直接在函数f中滑动。f和g之间的交叉区域是互相关,下图显示了相关性和互相关之间的差异:
在这里插入图片描述


数值理解:
在深度学习中,卷积中的filters是不需要反转的。严格来说,它们是互相关的,本质上是执行逐元素的乘法和加法,在深度学习中我们称之为卷积。
在这里插入图片描述

二、深度学习中的卷积

2.1 卷积操作

我们先以最简单的单通道的卷积为例,来讲解深度学习中的卷积操作~


单通道的卷积操作
首先,我们使用3x3滤波器进行二维卷积运算:
左边是卷积层的输入,例如输入图像。右边是卷积滤波器(Filter),也叫核(Kernel)。由于滤波器的形状是3x3,这被称为3x3卷积。
在这里插入图片描述
我们通过在输入上滑动这个滤波器来执行卷积运算。在每个位置,我们都进行逐元素矩阵乘法并对结果求和。这个总和进入特征图(feature map)。卷积运算发生的绿色区域被称为感受野(receptive field)。由于滤波器的大小,感受野也是3x3。
在这里插入图片描述
这里的卷积核在左上角,卷积运算“4”的输出显示在结果的特征图中。
然后我们将滤波器向右滑动并执行相同的操作,将结果也添加到特征映射中。
在这里插入图片描述
我们继续这样做,并在特征图中聚合卷积结果。下面的动画展示了整个卷积运算。
在这里插入图片描述

2.2. 填充和步幅

Padding

在应用多层卷积时,我们常常丢失边缘像素。 由于我们通常使用小卷积核,因此对于任何单个卷积,我们可能只会丢失几个像素。 但随着我们应用许多连续卷积层,累积丢失的像素数就多了。 解决这个问题的简单方法即为填充(padding)

输入图像的边界填充元素(通常填充元素是0)的方法叫做填充。


先看个动画直观感受下~ 灰色的区域是填充的地方

在这里插入图片描述


再来看看带填充的二维互相关运算~
在这里插入图片描述
通常, 如果我们添加 p h p_h ph行填充 (大约一半在顶部, 一半在底部) p w p_w pw列填充(左侧大约 一半, 右侧一半), 则输出形状将为

( n h − k h + p h + 1 ) × ( n w − k w + p w + 1 ) 。 \left(n_h-k_h+p_h+1\right) \times\left(n_w-k_w+p_w+1\right) 。 (nhkh+ph+1)×(nwkw+pw+1)
这意味着输出的高度和宽度将分别增加 p h p_h ph p w p_{w} pw 在许多情况下, 我们需要设置 p h = k h − 1 p_h=k_h-1 ph=kh1 p w = k w − 1 p_w=k_w-1 pw=kw1, 使输入和输出具有相同的高 度和宽度。这样可以在构建网络时更容易地预测每个图层的输出形状。假设 k h k_h kh是奇数, 我们将在高度的两侧填充 p h / 2 p_h / 2 ph/2行。如果 k h k_h kh是偶数, 则一种可能性是在输入顶部填充 ⌈ p h / 2 ⌉ \left\lceil p_h / 2\right\rceil ph/2行, 在底部填充 ⌊ p h / 2 ⌋ \left\lfloor p_h / 2\right\rfloor ph/2行。同理, 我们填充宽度的两侧。

卷积神经网络中卷积核的高度和宽度通常为奇数,例如1、3、5或7。 选择奇数的好处是,保持空间维度的同时,我们可以在顶部和底部填充相同数量的行,在左侧和右侧填充相同数量的列。

此外,对于任何二维张量X,当满足: 1. 卷积核的大小是奇数; 2. 所有边的填充行数和列数相同; 3. 输出与输入具有相同高度和宽度 则可以得出:输出Y[i, j]是通过以输X[i, j]为中心,与卷积核进行互相关计算得到的。

Stride

在计算互相关时,卷积窗口从输入张量的左上角开始,向下、向右滑动。 在前面的例子中,我们默认每次滑动一个元素。 但是,有时候为了高效计算或是缩减采样次数,卷积窗口可以跳过中间位置,每次滑动多个元素。

我们将每次滑动元素的数量称为步幅(stride)


先看个动画直观感受下~ 注意观察stride变大时,输出的特征图变小

  • stride =1
    在这里插入图片描述
  • stride=2
    在这里插入图片描述

再来看看不同步幅的二维互相关运算~
下图是垂直步幅为3,水平步幅为2的二维互相关运算。
着色部分是输出元素以及用于输出计算的输入和内核张量元素:0×0+0×1+1×2+2×3=8、0×0+6×1+0×2+0×3=6。
在这里插入图片描述

可以看到,为了计算输出中第一列的第二个元素和第一行的第二个元素,卷积窗口分别向下滑动三行和向右滑动两列。但是,当卷积窗口继续向右滑动两列时,没有输出,因为输入元素无法填充窗口(除非我们添加另一列填充)
通常, 当垂直步幅为 s h s_h sh 、水平步幅为 s w s_w sw时, 输出形状为

⌊ ( n h − k h + p h + s h ) / s h ⌋ × ⌊ ( n w − k w + p w + s w ) / s w ⌋ . \left\lfloor\left(n_h-k_h+p_h+s_h\right) / s_h\right\rfloor \times\left\lfloor\left(n_w-k_w+p_w+s_w\right) / s_w\right\rfloor . (nhkh+ph+sh)/sh×(nwkw+pw+sw)/sw.

如果我们设置了 p h = k h − 1 p_h=k_h-1 ph=kh1 p w = k w − 1 p_w=k_w-1 pw=kw1, 则输出形状将简化为
⌊ ( n h + s h − 1 ) / s h ⌋ × ⌊ ( n w + s w − 1 ) / s w ⌋ \left\lfloor\left(n_h+s_h-1\right) / s_h\right\rfloor \times\left\lfloor\left(n_w+s_w-1\right) / s_w\right\rfloor (nh+sh1)/sh×(nw+sw1)/sw 。 更进一步, 如果输入的高度和宽度可以 被垂直和水平步幅整除, 则输出形状将为 ( n h / s h ) × ( n w / s w ) \left(n_h / s_h\right) \times\left(n_w / s_w\right) (nh/sh)×(nw/sw)


不同Padding和Stride 的卷积效果

No padding, no stridesArbitrary padding, no stridesHalf padding, no stridesFull padding, no strides
No padding, stridesPadding, stridesPadding, strides (odd)

2.3 多输入通道和多输出通道

多输入通道

在许多应用程序中,我们处理的是具有多个通道的图像。

  • Eg1: RGB图像
    图像被表示为具有高度、宽度和深度的3D矩阵,其中深度对应于颜色通道(RGB)。
    在这里插入图片描述
  • Eg2: 卷积神经网络的图层
    卷积网络层通常包含多个通道(通常为数百个通道),每个通道描述了上一层中不同的特征。

术语解释

在上文中, 我们简单的把filter 和kernel等同。但是本质上有所区别。在介绍多通道之前,我们有必要分清这些术语的差别。

  • kernels”指的是2D-权重矩阵。
  • filters”用于堆叠在一起的多个kernels的3D-结构。

对于2D-filters,filters与kernels相同。 但是对于3D-filters和深度学习中的大多数卷积而言,filters是kernels的集合。 每个kernels都是独一无二的,强调了输入通道的不同特征。
在这里插入图片描述


多输入通道卷积过程如下

  • 每个kernels应用到前一层的每个输入通道上,以生成一个输出通道。
    在这里插入图片描述

  • 我们为所有kernels重复这样的过程以生成多个输出通道。
    在这里插入图片描述

  • 然后将输出通道中的加在一起以形成单个输出通道。
    在这里插入图片描述


计算多输入通道的互相关运算

输入包含多个通道时,需要构造一个与输入数据具有相同输入通道数的卷积核,以便与输入数据进行互相关运算。
假设输入的通道数为 c i c_i ci,那么卷积核的输入通道数也需要为 c i c_i ci。如果卷积核的窗口形状是 k h × k w k_h×k_w kh×kw,那么当 c i = 1 c_i=1 ci=1时,我们可以把卷积核看作形状为 k h × k w k_h×k_w kh×kw的二维张量。
然而,当 c i > 1 c_i>1 ci>1时,我们卷积核的每个输入通道将包含形状为 k h × k w k_h×k_w kh×kw的张量。将这些张量 c i c_i ci连结在一起可以得到形状为 c i × k h × k w c_i×k_h×k_w ci×kh×kw的卷积核。
由于输入和卷积核都有 c i c_i ci个通道,我们可以对每个通道输入的二维张量和卷积核的二维张量进行互相关运算,再对通道求和(将ci的结果相加)得到二维张量。这是多通道输入和多输入通道卷积核之间进行二维互相关运算的结果。
在这里插入图片描述


再来看个动画直观感受下趴~
在这里插入图片描述
假设我们有一个32x32x3的图像,我们使用一个大小为5x5x3的滤波器(注意卷积滤波器的深度与图像的深度相匹配,都是3)。当滤波器位于特定位置时,它覆盖了输入的一小部分,然后我们执行上面描述的卷积操作。唯一不同的是,这次我们在3D而不是2D中做矩阵相乘的和,但结果仍然是一个标量。我们像上面那样在输入上滑动过滤器,并在每个位置执行卷积,将结果聚合在特征图中。该特征映射的大小为32x32x1,如图所示 :
在这里插入图片描述

多输出通道

如果我们使用10个不同的滤波器,我们将得到10个大小为32x32x1的特征映射,并将它们沿着深度维度堆叠,将得到卷积层的最终输出:大小为32x32x10的体积,如右边的大蓝框所示。
下面我们可以看到两个特征映射是如何沿着深度维度堆叠的。每个滤波器的卷积操作是独立执行的,得到的特征映射是不相交的。
在这里插入图片描述


让我们再来看看多输出通道的互相关运算~
如下图,我们采用具有3个输入通道和2个输出通道的1x1卷积核。 (输出通道数和核函数的组数相同,如浅蓝色和深蓝色两组,每一组核函数对应一个输出的2维特征图)
在这里插入图片描述
c i c_i ci c o c_o co分别表示输入和输出通道的数目,并让 k h k_h kh k w k_w kw为卷积核的高度和宽度。为了获得多个通道的输出,我们可以为每个输出通道创建一个形状为 c i × k h × k w c_i×k_h×k_w ci×kh×kw的卷积核张量,这样卷积核的形状是 c o × c i × k h × k w c_o×c_i×k_h×k_w co×ci×kh×kw。在互相关运算中,每个输出通道先获取所有输入通道,再以对应该输出通道的卷积核计算出结果。

三、卷积类型

3.1 1D/2D/3D 卷积

  • 1D卷积: 主要用于输入是连续的,如文本或音频。
  • 2D卷积: 主要用于输入图像的地方。
  • 3D卷积-主要用于三维医学成像或检测视频中的事件。

卷积的维度是怎么定义的呢?这里卷积的维度可不是输入或者卷积核的维度哦~ 而是由卷积核的移动的维度来定的
如下图所示: 当卷积核只能沿着x轴移动时,就是1D卷积; 当沿着x,y两个轴移动时就是2D卷积…以此类推
在这里插入图片描述

1D卷积2D卷积3D卷积
卷积核只能沿着x轴移动卷积核可以沿着x轴,y轴移动卷积核可以沿着x轴,y轴,z轴移动

为了说明卷积的维度和输入、卷积核、输出的维度无关,只和卷积核的移动维度有关,我们来看看几个例子 :


1D卷积-1D输入
在这里插入图片描述

  • 输入:一维
  • 卷积核:一维
  • 输出:一维
  • 卷积核向右移动

1D卷积-2D输入
在这里插入图片描述

  • 输入是二维的: input =[W,L]
  • 卷积核是二维的,卷积核的高度(L)和输入的高度(L)相同:filter = [k,L]
  • 输出时1维的:output = [W]
  • 如果有N个卷积核,则输出的大小是二维的(1DxN但此时依然是1D卷积(因为卷积核的移动方向只有1个维度)

2D卷积-3D输入 (LeNet,VGG都会用到这种卷积)
在这里插入图片描述

  • 输入是3D张量:input = [W,H,L]
  • 卷积核也是3D的,且卷积核的深度(L)和输入的通道数(L)相同: filter = [k,k,L]
  • 输出是一个二维张量: output = [W,H]
  • 此时卷积核只朝着x,y轴两个方向移动,因此是2D卷积。
  • 如果此时有N个卷积核,那么输出是3D张量(2DxN)。

假如有2个卷积核,则输出的大小是2Dx2。如下图所示:
在这里插入图片描述

1D 卷积


直观理解
在这里插入图片描述

在PyTorch中,分别在torch.nntorch.nn.functional两个模块都有conv1dconv2dconv3d;从计算过程来说,两者本身没有太大区别;但是torch.nn下的都是卷积层,conv的参数都是经过训练得到;torch.nn.functional下的都是函数,其参数可以人为设置。本文中,我们以torch.nn为例


torch.nn.Conv1d

# Class
torch.nn.Conv1d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
  • 功能: 对由多个输入平面组成的输入信号应用1D卷积。

输入大小 ( N , C i n , L ) \left(N, C_{\mathrm{in}}, L\right) (N,Cin,L) 输出大小 ( N , C out  , L out  ) \left(N, C_{\text {out }}, L_{\text {out }}\right) (N,Cout ,Lout ) 计算过程如下:

out ⁡ ( N i , C out  j ) = bias ⁡ ( C out  j ) + ∑ k = 0 C i n − 1  weight  ( C out  j , k ) ⋆ input ⁡ ( N i , k ) \operatorname{out}\left(N_i, C_{\text {out }j}\right)=\operatorname{bias}\left(C_{\text {out }j}\right)+\sum_{k=0}^{C_{i n}-1} \text { weight }\left(C_{\text {out }_j}, k\right) \star \operatorname{input}\left(N_i, k\right) out(Ni,Cout j)=bias(Cout j)+k=0Cin1 weight (Cout j,k)input(Ni,k)

其中 ⋆ \star 是互相关(cross-correlation)运算符, N N N 是批大小(batch size), C C C表示通道数, L L L 是信号序列的长度。

  • 参数
    • in_channels (int) 输入图像中的通道数
    • out_channels (int) 由卷积产生的通道数
    • kernel_size (int or tuple) 卷积核的大小
    • stride (int or tuple, optional) 卷积的步幅。默认值:1
    • padding (int, tuple or str, optional) 在输入的两边添加填充。默认值:0
    • padding_mode (str*, optional*)'zeros', 'reflect', 'replicate' 或者 'circular'. 默认:'zeros'
    • dilation (int *or tuple, optional*) 核元素之间的间距。默认值:1 (具体理解见后文的【空洞卷积】)
    • groups (int*, optional*) 从输入通道到输出通道的阻塞连接数。默认值:1
    • bias (bool*, optional*) 如果为True,则向输出添加一个可学习偏差。默认值:True

上述的参数中,比较特殊的参数是:group
groups参数的含义:假设卷积操作的输入通道数是in_channels,输出通道数是out_channles,分组数是groups,分组卷积就是把原本的整体卷积操作分成groups个小组来分别处理,其中每个分组的输入通道数是in_channles / groups,输出通道数是out_channles / groups,最后将所有分组的输出通道数concat,得到最终的输出通道数
groups的值必须能整除in_channelsout_channels (具体理解请看本文【3.7分组卷积】)

  • group=1时,每一层输出由所有输入分别与卷积核卷积的累加得到
  • groups=2时,该操作等价于有两个并行的conv层,每个层看到一半的输入通道并产生一半的输出通道,然后两者都连接起来。
  • group=In_channel时,每个输入通道都与它自己的一组滤波器( s i z e =  out_channels   in_channels  size=\frac{\text { out\_channels }}{\text { in\_channels }} size= in_channels  out_channels )进行卷积
group=1group=3
  • 形状
    • 输入: ( N , C i n , L i n ) \left(N, C_{i n}, L_{i n}\right) (N,Cin,Lin) 或者 ( C i n , L i n ) \left(C_{i n}, L_{i n}\right) (Cin,Lin)
    • 输出: ( N , C out  , L out  ) \left(N, C_{\text {out }}, L_{\text {out }}\right) (N,Cout ,Lout ) 或者 ( C out  , L out  ) \left(C_{\text {out }}, L_{\text {out }}\right) (Cout ,Lout ), 其中

L out  = ⌊ L i n + 2 ×  padding  −  dilation  × (  kernel_size  − 1 ) − 1  stride  + 1 ⌋ L_{\text {out }}=\left\lfloor\frac{L_{i n}+2 \times \text { padding }-\text { dilation } \times(\text { kernel\_size }-1)-1}{\text { stride }}+1\right\rfloor Lout = stride Lin+2× padding  dilation ×( kernel_size 1)1+1

  • 变量

    • weight (Tensor) - 模型的可学习权重,大小为 ( o u t _ c h a n n e l s ,  in_channels   groups  , k e r n e l _ s i z e ) (out\_channels, \frac{\text { in\_channels }}{\text { groups }}, kernel\_size) (out_channels, groups  in_channels ,kernel_size). 这些权重是从 U ( − k , k ) \mathcal{U}(-\sqrt{k}, \sqrt{k}) U(k ,k ) 中采样得到的。其中 k =  groups  C in  ∗  kernel_size  k=\frac{\text { groups }}{C_{\text {in }} * \text { kernel\_size }} k=Cin  kernel_size  groups 
    • bias (Tensor) - 模型的可学习偏置,大小为 ( o u t _ c h a n n e l s ) (out\_channels) (out_channels). 如果biasTrue, 那么这个值是从 U ( − k , k ) \mathcal{U}(-\sqrt{k}, \sqrt{k}) U(k ,k )中采样得到,其中 k =  groups  C in  ∗  kernel_size  k=\frac{\text { groups }}{C_{\text {in }} * \text { kernel\_size }} k=Cin  kernel_size  groups 
  • 例子

m = nn.Conv1d(16, 33, 3, stride=2)
input = torch.randn(20, 16, 50)
output = m(input)

2D卷积


直观理解

  • 单Filter
    如下图所示,可以将这个过程视作将一个3D-filters矩阵滑动通过输入层。注意,这个输入层和filters的深度都是相同的(即通道数=卷积核数)
    这个 3D-filters仅沿着 2 个方向(图像的高和宽)移动(这也是为什么 3D-filters即使通常用于处理3D-体积数据,但这样的操作还是被称为 2D-卷积)。

在这里插入图片描述

  • 多Filters
    多Filters可实现在不同深度的层之间实现过渡
    假设输入层有 Din 个通道,而想让输出层的通道数量变成 Dout,我们需要做的仅仅是将 Dout个filters应用到输入层中。每一个filters都有Din个卷积核,都提供一个输出通道。在应用Dout个filters后,Dout个通道可以共同组成一个输出层。标准 2D-卷积,通过使用 Dout 个filters,将深度为 Din 的层映射为另一个深度为 Dout 的层。
    在这里插入图片描述

数值理解
在这里插入图片描述


torch.nn.Conv2d

CLASS
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
  • 功能: 对由多个输入平面组成的输入信号应用2D卷积。

输入大小 ( N , C i n , H , W ) \left(N, C_{\mathrm{in}}, H,W\right) (N,Cin,HW) 输出大小 ( N , C out  , H out  , W o u t ) \left(N, C_{\text {out }}, H_{\text {out }},W_{out}\right) (N,Cout ,Hout ,Wout) 计算过程如下:

out ⁡ ( N i , C out  j ) = bias ⁡ ( C out  j ) + ∑ k = 0 C i n − 1  weight  ( C out  j , k ) ⋆ input ⁡ ( N i , k ) \operatorname{out}\left(N_i, C_{\text {out }j}\right)=\operatorname{bias}\left(C_{\text {out }j}\right)+\sum_{k=0}^{C_{i n}-1} \text { weight }\left(C_{\text {out }_j}, k\right) \star \operatorname{input}\left(N_i, k\right) out(Ni,Cout j)=bias(Cout j)+k=0Cin1 weight (Cout j,k)input(Ni,k)

其中 ⋆ \star 2D互相关(cross-correlation)运算符, N N N 是批大小(batch size), C C C表示通道数, L L L为输入平面的高度(像素), W W W为宽度(像素)。

  • 参数

    • in_channels (int) 输入图像中的通道数
    • out_channels (int) 由卷积产生的通道数
    • **kernel_size (int or tuple) –** 卷积核的大小
    • stride (int or tuple, optional) 卷积的步幅。默认值:1
    • padding (int, tuple or str, optional) 在输入的两边添加填充。默认值:0
    • padding_mode (str*, optional*)'zeros', 'reflect', 'replicate' 或者 'circular'. 默认:'zeros'
    • dilation (int *or tuple, optional*) 核元素之间的间距。默认值:1
    • groups (int*, optional*) 从输入通道到输出通道的阻塞连接数。默认值:1
    • bias (bool*, optional*) 如果为True,则向输出添加一个可学习偏差。默认值:True
  • 形状

    • 输入: ( N , C i n , H i n , W i n ) \left(N, C_{i n}, H_{in},W_{in}\right) (N,Cin,Hin,Win) 或者 ( C i n , H i n , W i n ) \left(C_{i n}, H_{i n},W_{in}\right) (Cin,Hin,Win)
    • 输出: ( N , C out  , H out  , W o u t ) \left(N, C_{\text {out }}, H_{\text {out }},W_{out}\right) (N,Cout ,Hout ,Wout) 或者 ( C out  , H out  , W o u t ) \left(C_{\text {out }}, H_{\text {out }},W_{out}\right) (Cout ,Hout ,Wout), 其中

H out  = ⌊ H in  + 2 ×  padding  [ 0 ] −  dilation  [ 0 ] × (  kernel_size  [ 0 ] − 1 ) − 1  stride  [ 0 ] + 1 ⌋ W out  = ⌊ W in  + 2 ×  padding  [ 1 ] −  dilation  [ 1 ] × (  kernel_size  [ 1 ] − 1 ) − 1  stride  [ 1 ] + 1 ∣ \begin{aligned}& H_{\text {out }}=\left\lfloor\frac{H_{\text {in }}+2 \times \text { padding }[0]-\text { dilation }[0] \times(\text { kernel\_size }[0]-1)-1}{\text { stride }[0]}+1\right\rfloor \\& W_{\text {out }}=\left\lfloor\frac{W_{\text {in }}+2 \times \text { padding }[1]-\text { dilation }[1] \times(\text { kernel\_size }[1]-1)-1}{\text { stride }[1]}+1 \mid\right.\end{aligned} Hout = stride [0]Hin +2× padding [0] dilation [0]×( kernel_size [0]1)1+1Wout = stride [1]Win +2× padding [1] dilation [1]×( kernel_size [1]1)1+1

  • 变量

    • weight (Tensor) - 模型的可学习权重,大小为 ( o u t _ c h a n n e l s ,  in_channels   groups  , k e r n e l _ s i z e [ 0 ] , k e r n e l _ s i z e [ 1 ] ) (out\_channels, \frac{\text { in\_channels }}{\text { groups }},kernel\_size[0], kernel\_size[1]) (out_channels, groups  in_channels ,kernel_size[0],kernel_size[1]). 这些权重是从 U ( − k , k ) \mathcal{U}(-\sqrt{k}, \sqrt{k}) U(k ,k ) 中采样得到的。其中 k =  groups  C in  ∗  kernel_size  k=\frac{\text { groups }}{C_{\text {in }} * \text { kernel\_size }} k=Cin  kernel_size  groups 
    • bias (Tensor) - 模型的可学习偏置,大小为 ( o u t _ c h a n n e l s ) (out\_channels) (out_channels). 如果biasTrue, 那么这个值是从 U ( − k , k ) \mathcal{U}(-\sqrt{k}, \sqrt{k}) U(k ,k )中采样得到,其中  groups  C i n ∗ ∏ i = 0 1  kernel_size  [ i ] \frac{\text { groups }}{C_{\mathrm{in}} * \prod_{i=0}^1 \text { kernel\_size }[i]} Cini=01 kernel_size [i] groups 
  • 例子

>>> # With square kernels and equal stride
>>> m = nn.Conv2d(16, 33, 3, stride=2)
>>> # non-square kernels and unequal stride and with padding
>>> m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2))
>>> # non-square kernels and unequal stride and with padding and dilation
>>> m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1))
>>> input = torch.randn(20, 16, 50, 100)
>>> output = m(input)

3D卷积

通过将2D-卷积的推广,在3D-卷积定义为filters的深度小于输入层的深度(即卷积核的个数小于输入层通道数),故3D-filters需要在三个维度上滑动(输入层的长、宽、高)。
在filters上滑动的每个位置执行一次卷积操作,得到一个数值。当filters滑过整个3D空间,输出的结构也是3D的。
在这里插入图片描述

3D卷积在执行时不仅在各自的通道中共享卷积核,而且在各帧(连续k帧)之间也共享卷积核

  • 2D convolution: 使用场景一般是单通道的数据(例如MNIST),输出也是单通道,对整个通道同时执行卷积操作;
  • 2D convolution on multiple frames: 使用场景一般是多通道的数据(例如cifar-10),输出也是单通道,对整个通道同时执行卷积操作;2D卷积在执行时是在各自的通道中共享卷积核
  • 3D convolution: 使用场景一般是多帧(单/多通道)的frame-like数据(视频帧),且输出也是多帧,依次对连续k帧的整个通道同时执行卷积操作;

视觉角度: 先看个动画直观感受下~
如下图,一共有4个卷积核,其中frame1和frame2共享一个卷积核,frame2和frame3共享一个卷积核… 每个卷积核对应一个输出通道。
在这里插入图片描述


计算角度
假设现在有一个3帧的画面,且每一帧有2个通道,在时间维度的跨度为2帧,卷积核的宽度为3。
在这里插入图片描述
由于在时间维度的跨度为2帧,且每帧有2个通道,所以从“矩阵”个数来看的话,我们的卷积核应该有4矩阵。
在这里插入图片描述


torch.nn.Conv3d

CLASS
torch.nn.Conv3d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
  • 功能: 对由多个输入平面组成的输入信号应用3D卷积。

输入大小 ( N , C i n , D , H , W ) \left(N, C_{\mathrm{in}},D, H,W\right) (N,Cin,DHW) 输出大小 ( N , C out  , D o u t , H out  , W o u t ) \left(N, C_{\text {out }},D_{out}, H_{\text {out }},W_{out}\right) (N,Cout ,Dout,Hout ,Wout) 计算过程如下:

out ⁡ ( N i , C out  j ) = bias ⁡ ( C out  j ) + ∑ k = 0 C i n − 1  weight  ( C out  j , k ) ⋆ input ⁡ ( N i , k ) \operatorname{out}\left(N_i, C_{\text {out }j}\right)=\operatorname{bias}\left(C_{\text {out }j}\right)+\sum_{k=0}^{C_{i n}-1} \text { weight }\left(C_{\text {out }_j}, k\right) \star \operatorname{input}\left(N_i, k\right) out(Ni,Cout j)=bias(Cout j)+k=0Cin1 weight (Cout j,k)input(Ni,k)

其中 ⋆ \star 是3D互相关(cross-correlation)运算符, N N N 是批大小(batch size), C C C表示通道数, L L L为输入平面的高度(像素), W W W为宽度(像素)。

  • 参数

    • in_channels (int) 输入图像中的通道数
    • out_channels (int) 由卷积产生的通道数
    • kernel_size (int or tuple) 卷积核的大小
    • stride (int or tuple, optional) 卷积的步幅。默认值:1
    • padding (int, tuple or str, optional) 在输入的两边添加填充。默认值:0
    • padding_mode (str*, optional*)'zeros', 'reflect', 'replicate' 或者 'circular'. 默认:'zeros'
    • dilation (int *or tuple, optional*) 核元素之间的间距。默认值:1
    • groups (int*, optional*) 从输入通道到输出通道的阻塞连接数。默认值:1
    • bias (bool*, optional*) 如果为True,则向输出添加一个可学习偏差。默认值:True
  • 形状

    • 输入: ( N , C i n , D i n , H i n , W i n ) \left(N, C_{i n}, D_{in},H_{in},W_{in}\right) (N,Cin,Din,Hin,Win) 或者 ( C i n , D i n , H i n , W i n ) \left(C_{i n}, D_{in},H_{i n},W_{in}\right) (Cin,Din,Hin,Win)
    • 输出: ( N , C out  , D o u t , H out  , W o u t ) \left(N, C_{\text {out }},D_{out}, H_{\text {out }},W_{out}\right) (N,Cout ,Dout,Hout ,Wout) 或者 ( C out  , D o u t , H out  , W o u t ) \left(C_{\text {out }}, D_{out},H_{\text {out }},W_{out}\right) (Cout ,Dout,Hout ,Wout), 其中

D out  = ⌊ D in  + 2 ×  padding  [ 0 ] −  dilation  [ 0 ] × (  kernel_size  [ 0 ] − 1 ) − 1  stride  [ 0 ] + 1 ⌋ H out  = ⌊ H in  + 2 ×  padding  [ 1 ] − dilation ⁡ [ 1 ] × (  kernel_size  [ 1 ] − 1 ) − 1  stride  [ 1 ] + 1 ⌋ W out  = ⌊ W in  + 2 ×  padding  [ 2 ] − dilation ⁡ [ 2 ] × (  kernel_size  [ 2 ] − 1 ) − 1  stride  [ 2 ] + 1 ⌋ \begin{aligned}& D_{\text {out }}=\left\lfloor\frac{D_{\text {in }}+2 \times \text { padding }[0]-\text { dilation }[0] \times(\text { kernel\_size }[0]-1)-1}{\text { stride }[0]}+1\right\rfloor \\& H_{\text {out }}=\left\lfloor\frac{H_{\text {in }}+2 \times \text { padding }[1]-\operatorname{dilation}[1] \times(\text { kernel\_size }[1]-1)-1}{\text { stride }[1]}+1\right\rfloor \\& W_{\text {out }}=\left\lfloor\frac{W_{\text {in }}+2 \times \text { padding }[2]-\operatorname{dilation}[2] \times(\text { kernel\_size }[2]-1)-1}{\text { stride }[2]}+1\right\rfloor\end{aligned} Dout = stride [0]Din +2× padding [0] dilation [0]×( kernel_size [0]1)1+1Hout = stride [1]Hin +2× padding [1]dilation[1]×( kernel_size [1]1)1+1Wout = stride [2]Win +2× padding [2]dilation[2]×( kernel_size [2]1)1+1

  • 变量

    • weight (Tensor) - 模型的可学习权重,大小为 ( o u t _ c h a n n e l s ,  in_channels   groups  , k e r n e l _ s i z e [ 0 ] , k e r n e l _ s i z e [ 1 ] ) (out\_channels, \frac{\text { in\_channels }}{\text { groups }},kernel\_size[0], kernel\_size[1]) (out_channels, groups  in_channels ,kernel_size[0],kernel_size[1]). 这些权重是从 U ( − k , k ) \mathcal{U}(-\sqrt{k}, \sqrt{k}) U(k ,k ) 中采样得到的。其中 k =  groups  C i n ∗ ∏ i = 0 2  kernel_size  [ i ] k=\frac{\text { groups }}{C_{\mathrm{in}} * \prod_{i=0}^2 \text { kernel\_size }[i]} k=Cini=02 kernel_size [i] groups 
    • bias (Tensor) - 模型的可学习偏置,大小为 ( o u t _ c h a n n e l s ) (out\_channels) (out_channels). 如果biasTrue, 那么这个值是从 U ( − k , k ) \mathcal{U}(-\sqrt{k}, \sqrt{k}) U(k ,k )中采样得到,其中  groups  C i n ∗ ∏ i = 0 2  kernel_size  [ i ] \frac{\text { groups }}{C_{\mathrm{in}} * \prod_{i=0}^2 \text { kernel\_size }[i]} Cini=02 kernel_size [i] groups 
  • 例子

>>> # With square kernels and equal stride
>>> m = nn.Conv3d(16, 33, 3, stride=2)
>>> # non-square kernels and unequal stride and with padding
>>> m = nn.Conv3d(16, 33, (3, 5, 2), stride=(2, 1, 1), padding=(4, 2, 0))
>>> input = torch.randn(20, 16, 10, 50, 100)
>>> output = m(input)

3.2 1x1卷积

在这篇论文中 《Network In Network》 首次提出的了1x1的卷积

1×1卷积,即 k h = k w = 1 k_h=k_w=1 kh=kw=1,看起来似乎没有多大意义。 毕竟,卷积的本质是有效提取相邻像素间的相关特征,而1×1卷积显然没有此作用。 尽管如此,1×1仍然十分流行,经常包含在复杂深层网络的设计中。
因为使用了最小窗口,1×1卷积失去了卷积层的特有能力——在高度和宽度维度上,识别相邻元素间相互作用的能力。 其实1×1卷积的唯一计算发生在通道上。


直观理解
下图中描述了:在一个维度为 H x W x D的输入层上的操作方式。经过大小为 1 x 1 x D 的filters的 1 x 1 卷积,输出通道的维度为 H x W x 1。如果我们执行 N 次这样的 1 x 1 卷积,然后将这些结果结合起来,我们能得到一个维度为 H x W x N 的输出层。
在这里插入图片描述


1x1 卷积的优点

从上图来看,1x1的卷积表面上好像只是feature maps中的每个值乘了一个数,但实际上不仅仅如此,首先由于会经过激活层,所以实际上是进行了非线性映射,其次就是可以改变feature maps的channel数目。
在执行计算昂贵的 3 x 3 卷积和 5 x 5 卷积前,往往会使用 1 x 1 卷积来减少计算量。此外,它们也可以利用调整后的非线性激活函数来实现双重用途。

1 x 1卷积的一些优点是:

  • 降维以实现高效计算 (经过1 x 1卷积后,我们在深度方向上减小了尺度即减少通道数)
  • 高效的低维嵌入或特征池(假设原始输入有200个通道,则1 x 1卷积会将这些通道嵌入到单个通道中。)。
  • 卷积后再次应用非线性(在1 x 1卷积之后,可以添加非线性激活函数,例如ReLU,非线性允许网络学习更复杂的功能)。

3.3 转置卷积 (反卷积)

转置卷积 又称 反卷积 (Deconvolution)。与传统的上采样方法相比,转置卷积的上采样方式 并非预设的插值方法,而是同标准卷积一样,具有可学习的参数,可通过网络学习来获取最优的上采样方式。(UpSampling)。

  • 在 DCGAN,生成器将随机值转变为一个全尺寸图片,此时需用到转置卷积。
  • 在语义分割中,会在编码器中用卷积层提取特征,然后在解码器中恢复原先尺寸,从而对原图中的每个像素分类。该过程同样需用转置卷积。经典方法有 FCN 和 U-Net。
  • CNN 可视化:通过转置卷积将 CNN 的特征图还原到像素空间,以观察特定特征图对哪些模式的图像敏感。

反卷积(deconvolution)也可以称为卷积转置转置卷积(transposed convolution),但其并非卷积操作的反向操作。由上边的介绍可以看出,卷积操作会将输入映射到一个更小的特征图中,那么反卷积则可以将这个小的特征图映射为一个大的特征图。我们可以将其理解为上采样。

在这里插入图片描述


直观理解:

  • 为了理解转置卷积,我们从另外一个角度去理解普通卷积
    在这里插入图片描述
    如上图普通卷积核一步一步滑动窗口得到特征输出,但实际上在计算机中并不会如上图所示计算(这样计算效率比较低),计算机会将卷积核转换成等效的矩阵,将输入转换为向量。通过输入向量和卷积核矩阵的相乘获得输出向量输出的向量经过整形便可得到我们的二维输出特征。具体的操作如下图所示。
    由于上图3x3卷积核要在输入上不同的位置卷积4次,所以通过补零的方法将卷积核分别置于一个4x4矩阵的四个角落。这样输入可以直接和这四个4x4的矩阵进行卷积,构成等效矩阵,而舍去了滑动这一操作步骤。
    在这里插入图片描述
    进一步的,将输入拉成长向量,四个4x4卷积核也拉成长向量并进行拼接,如下图。
    在这里插入图片描述
    记向量化的图像为 I I I ,向量化的卷积矩阵为 C C C , 输出特征向量为 O O O ,则有:
    I T × C = O I^T \times C=O IT×C=O
    在这里插入图片描述
  • 模仿上述的等效矩阵的构造方法,我们进一步来理解一下转置卷积

下面我们来举例说明。
对于输入特征矩阵I作为输入特征,3*3的卷积stride=1, 对其进行普通卷积得到输出特征图O,构造等效矩阵
在这里插入图片描述
在这里插入图片描述
将I转变为1行16的矩阵,而卷积等效矩阵的每一个矩阵转化为一个16行1列的向量,再合并为16行4列的矩阵,做乘积就等效与原来的卷积运算,如下图,与上面写的的理论一样
在这里插入图片描述
此时与上面写的一样,我们不要求从O来得到I,我们只要求得到与I相同形状的P,可以看到将O与C的转置确实没有得到I,而是得到了P。
在这里插入图片描述
此时我们将O重新恢复到两行两列的矩阵,同时将C的转置的每一列恢复为两行两列的矩阵,一共有16个。
在这里插入图片描述
我们将每一个O与16个2行2列的矩阵相乘可以得到图中的P,这时我们会发现一个有趣的现象:如下图第一个矩阵[0,0,0,0]与O相乘等于0,会等于图中绿色矩阵(实际上就是转置矩阵,但我们先不用管绿色矩阵怎么来的)与途中蓝色框相乘的值(空白格里面是0),第二个矩阵[1,0,0,0]与O相乘得到2,这会等于绿色矩阵与蓝色框向右一格的框相乘的值,以此类推会发现绿色矩阵(其实就是转置卷积)相当于对O矩阵周围填充2行2列得到的矩阵(也即整个虚线框矩阵)做普通卷积得到P。而P同时也是O矩阵与C矩阵的乘积。这样事实上绿色矩阵就是事实上的转置卷积
在这里插入图片描述
我们这时再来观察绿色矩阵,可以发现它会为原来的卷积上下左右翻转得到,如下图
在这里插入图片描述
因为这个绿色卷积矩阵对做了填充变化的O矩阵做卷积得到的结果,与转置矩阵C对没有填充的O矩阵做矩阵乘法结果相同,所以绿色矩阵称为转置卷积矩阵,操作称为转置卷积。


转置卷积的一般步骤

  1. 在输入特征图元素间填充s-1行、列个数值0(其中s表示转置卷积的步距
  2. 在输入特征图四周填充k-p-1行、列个数值0(其中k表示转置卷积的kernel_size大小,p为转置卷积的padding,注意这里的padding和卷积操作中有些不同)
  3. 将卷积核参数上下、左右翻转
  4. 做正常卷积运算(填充0,步距1)
  • 不同的Padding 和Stride的影响
s=1,p=0,k=3s=1,p=1,k=3s=1,p=2,k=3
s=2,p=0,k=3s=2,p=1,k=3s=2,p=0,k=3
如上图所示可以看出,通过控制stride的大小可以控制上采样的倍率。stride越大,上采样的倍率就越大。

数学理解

  • 卷积矩阵
    先理解一个概念,卷积矩阵:把卷积操作写成一个矩阵的形式,通过一次矩阵乘法就可以完成整个卷积操作。
    卷积矩阵的构造是通过对卷积核的重排列构造的。
    在这里插入图片描述
    例如,对于一个3x3的卷积核
    [ 1 4 1 1 4 3 3 3 1 ] \left[\begin{array}{lll} 1 & 4 & 1 \\ 1 & 4 & 3 \\ 3 & 3 & 1 \end{array}\right] 113443131
    可以重排得到卷积矩阵
    [ 1 4 1 0 1 4 3 0 3 3 1 0 0 0 0 0 0 1 4 1 0 1 4 3 0 3 3 1 0 0 0 0 0 0 0 0 1 4 1 0 1 4 3 0 3 3 1 0 0 0 0 0 0 1 4 1 0 1 4 3 0 3 3 1 ] \left[\begin{array}{llllllllllllllll} 1 & 4 & 1 & 0 & 1 & 4 & 3 & 0 & 3 & 3 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 1 & 4 & 1 & 0 & 1 & 4 & 3 & 0 & 3 & 3 & 1 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 1 & 4 & 1 & 0 & 1 & 4 & 3 & 0 & 3 & 3 & 1 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 & 4 & 1 & 0 & 1 & 4 & 3 & 0 & 3 & 3 & 1 \end{array}\right] 1000410014000100101041413414030130103341133401030030003300130001
    假设输入矩阵是:
    [ 4 5 8 7 1 8 8 8 3 6 6 4 6 5 7 8 ] \left[\begin{array}{llll} 4 & 5 & 8 & 7 \\ 1 & 8 & 8 & 8 \\ 3 & 6 & 6 & 4 \\ 6 & 5 & 7 & 8 \end{array}\right] 4136586588677848

将输入矩阵转换为一个1x16的列向量
[ 4 5 8 7 1 8 8 8 3 6 6 4 6 5 7 8 ] \left[\begin{array}{llllllllllllllll} 4 & 5 & 8 & 7 & 1 & 8 & 8 & 8 & 3 & 6 & 6 & 4 & 6 & 5 & 7 & 8 \end{array}\right] [4587188836646578]
与卷积矩阵相乘后得:
[ 122 148 126 134 ] \left[\begin{array}{llll} 122 & 148 & 126 & 134 \end{array}\right] [122148126134]
再reshape成:
[ 122 148 126 134 ] \left[\begin{array}{ll} 122 & 148 \\ 126 & 134 \end{array}\right] [122126148134]

对比原始的卷积操作,以1为步长没有填充,那么卷积结果也为:
[ 122 148 126 134 ] \left[\begin{array}{ll} 122 & 148 \\ 126 & 134 \end{array}\right] [122126148134]

  • 反卷积的操作

由此,我们可以得出:
当我们将反卷积矩阵进行转置,那么就可以得到一个16x4的转置卷积矩阵,对于输出的2x2的feature map,reshape为4x1,再将二者相乘即可得到一个16x1的转置卷积的结果
[ 1 0 0 0 4 1 0 0 1 4 0 0 0 1 0 0 1 0 1 0 4 1 4 1 3 4 1 4 0 3 0 1 3 0 1 0 3 3 4 1 1 3 3 4 0 1 0 3 0 0 3 0 0 0 3 3 0 0 1 3 0 0 0 1 ] × [ 122 148 126 134 ] = [ 2 9 6 1 6 29 30 7 10 29 33 13 12 24 16 4 ] ,  \left[\begin{array}{llll} 1 & 0 & 0 & 0 \\ 4 & 1 & 0 & 0 \\ 1 & 4 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 1 & 0 & 1 & 0 \\ 4 & 1 & 4 & 1 \\ 3 & 4 & 1 & 4 \\ 0 & 3 & 0 & 1 \\ 3 & 0 & 1 & 0 \\ 3 & 3 & 4 & 1 \\ 1 & 3 & 3 & 4 \\ 0 & 1 & 0 & 3 \\ 0 & 0 & 3 & 0 \\ 0 & 0 & 3 & 3 \\ 0 & 0 & 1 & 3 \\ 0 & 0 & 0 & 1 \end{array}\right] \times\left[\begin{array}{c} 122 \\ 148 \\ 126 \\ 134 \end{array}\right]=\left[\begin{array}{c} 2 \\ 9 \\ 6 \\ 1 \\ 6 \\ 29 \\ 30 \\ 7 \\ 10 \\ 29 \\ 33 \\ 13 \\ 12 \\ 24 \\ 16 \\ 4 \end{array}\right] \text {, } 1410143033100000014101430331000000001410143033100000014101430331 × 122148126134 = 2961629307102933131224164

此时再reshape即可得到一个4x4的输出。
[ 2 9 6 1 6 29 30 7 10 29 33 13 12 24 16 4 ] \left[\begin{array}{cccc} 2 & 9 & 6 & 1 \\ 6 & 29 & 30 & 7 \\ 10 & 29 & 33 & 13 \\ 12 & 24 & 16 & 4 \end{array}\right] 2610129292924630331617134
这样就通过转置卷积将2x2的矩阵反卷为一个4x4的矩阵,但是从结果也可以看出反卷积的结果与原始输入信号不同。只是保留了位置信息,以及得到了想要的形状。


棋盘格效应(Checkerboard Artifacts)

在反卷积生成的图片中经常会产生如下类似于棋盘格的纹理。我们称之为Checkerboard Artifacts
在这里插入图片描述
当输出窗口的大小不能被步幅整除时,反卷积很容易产生unevean overleap
如下图,分别是1D和2D情况下的unevean overleap 。可以看出,2D的uneaven overleap的情况更严重。

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

更多内容参考:Deconvolution and Checkerboard Artifacts

1D反卷积


torch.nn.ConvTranspose1d

CLASS
torch.nn.ConvTranspose1d(in_channels, out_channels, kernel_size, stride=1, padding=0, output_padding=0, groups=1, bias=True, dilation=1, padding_mode='zeros', device=None, dtype=None)
  • 功能: 对由多个输入平面组成的输入信号应用1D反卷积。

  • 参数

    • in_channels (int) 输入图像中的通道数
    • out_channels (int) 由卷积产生的通道数
    • **kernel_size (int or tuple) –** 卷积核的大小
    • stride (int or tuple, optional) 卷积的步幅。默认值:1
    • padding (int, tuple or str, optional) 在输入的两边添加填充。默认值:0
    • padding_mode (str*, optional*)'zeros', 'reflect', 'replicate' 或者 'circular'. 默认:'zeros'
    • dilation (int *or tuple, optional*) 核元素之间的间距。默认值:1
    • groups (int*, optional*) 从输入通道到输出通道的阻塞连接数。默认值:1
    • bias (bool*, optional*) 如果为True,则向输出添加一个可学习偏差。默认值:True
  • 形状

    • 输入: ( N , C i n , L i n ) \left(N, C_{i n}, L_{i n}\right) (N,Cin,Lin) 或者 ( C i n , L i n ) \left(C_{i n}, L_{i n}\right) (Cin,Lin)
    • 输出: ( N , C out  , L out  ) \left(N, C_{\text {out }}, L_{\text {out }}\right) (N,Cout ,Lout ) 或者 ( C out  , L out  ) \left(C_{\text {out }}, L_{\text {out }}\right) (Cout ,Lout ), 其中

L o u t = ( L i n − 1 ) × s t r i d e − 2 × p a d d i n g + d i l a t i o n × ( k e r n e l _ s i z e − 1 ) + o u t p u t _ p a d d i n g + 1 L_{out}=(L_{in}−1)×stride−2×padding+dilation×(kernel\_size−1)+output\_padding+1 Lout=(Lin1)×stride2×padding+dilation×(kernel_size1)+output_padding+1

  • 变量
    • weight (Tensor) - 模型的可学习权重,大小为 ( i n _ c h a n n e l s ,  out_channels   groups  , k e r n e l _ s i z e ) (in\_channels, \frac{\text { out\_channels }}{\text { groups }}, kernel\_size) (in_channels, groups  out_channels ,kernel_size). 这些权重是从 U ( − k , k ) \mathcal{U}(-\sqrt{k}, \sqrt{k}) U(k ,k ) 中采样得到的。其中 k =  groups  C out  ∗  kernel_size  k=\frac{\text { groups }}{C_{\text {out }} * \text { kernel\_size }} k=Cout  kernel_size  groups 
    • bias (Tensor) - 模型的可学习偏置,大小为 ( o u t _ c h a n n e l s ) (out\_channels) (out_channels). 如果biasTrue, 那么这个值是从 U ( − k , k ) \mathcal{U}(-\sqrt{k}, \sqrt{k}) U(k ,k )中采样得到,其中 k =  groups  C out  ∗  kernel_size  k=\frac{\text { groups }}{C_{\text {out }} * \text { kernel\_size }} k=Cout  kernel_size  groups 

2D反卷积


torch.nn.ConvTranspose2d

CLASS
torch.nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride=1, padding=0, output_padding=0, groups=1, bias=True, dilation=1, padding_mode='zeros', device=None, dtype=None)
  • 功能: 对由多个输入平面组成的输入信号应用2D反卷积。

  • 参数

    • in_channels (int) 输入图像中的通道数
    • out_channels (int) 由卷积产生的通道数
    • **kernel_size (int or tuple) –** 卷积核的大小
    • stride (int or tuple, optional) 卷积的步幅。默认值:1
    • padding (int, tuple or str, optional) 在输入的两边添加填充。默认值:0
    • padding_mode (str*, optional*)'zeros', 'reflect', 'replicate' 或者 'circular'. 默认:'zeros'
    • dilation (int *or tuple, optional*) 核元素之间的间距。默认值:1
    • groups (int*, optional*) 从输入通道到输出通道的阻塞连接数。默认值:1
    • bias (bool*, optional*) 如果为True,则向输出添加一个可学习偏差。默认值:True
  • 形状

    • 输入: ( N , C i n , H i n , W i n ) \left(N, C_{i n}, H_{in},W_{in}\right) (N,Cin,Hin,Win) 或者 ( C i n , H i n , W i n ) \left(C_{i n}, H_{i n},W_{in}\right) (Cin,Hin,Win)
    • 输出: ( N , C out  , H out  , W o u t ) \left(N, C_{\text {out }}, H_{\text {out }},W_{out}\right) (N,Cout ,Hout ,Wout) 或者 ( C out  , H out  , W o u t ) \left(C_{\text {out }}, H_{\text {out }},W_{out}\right) (Cout ,Hout ,Wout), 其中

H o u t = ( H i n − 1 ) × s t r i d e [ 0 ] − 2 × p a d d i n g [ 0 ] + d i l a t i o n [ 0 ] × ( k e r n e l _ s i z e [ 0 ] − 1 ) + o u t p u t _ p a d d i n g [ 0 ] + 1 H_{out}=(H_{in}−1)×stride[0]−2×padding[0]+dilation[0]×(kernel\_size[0]−1)+output\_padding[0]+1 Hout=(Hin1)×stride[0]2×padding[0]+dilation[0]×(kernel_size[0]1)+output_padding[0]+1

W o u t = ( W i n − 1 ) × s t r i d e [ 1 ] − 2 × p a d d i n g [ 1 ] + d i l a t i o n [ 1 ] × ( k e r n e l _ s i z e [ 1 ] − 1 ) + o u t p u t _ p a d d i n g [ 1 ] + 1 W_{out}=(W_{in}−1)×stride[1]−2×padding[1]+dilation[1]×(kernel\_size[1]−1)+output\_padding[1]+1 Wout=(Win1)×stride[1]2×padding[1]+dilation[1]×(kernel_size[1]1)+output_padding[1]+1

  • 变量

    • weight (Tensor) - 模型的可学习权重,大小为 ( i n _ c h a n n e l s ,  out_channels   groups  , k e r n e l _ s i z e [ 0 ] , k e r n e l _ s i z e [ 1 ] ) (in\_channels, \frac{\text { out\_channels }}{\text { groups }},kernel\_size[0], kernel\_size[1]) (in_channels, groups  out_channels ,kernel_size[0],kernel_size[1]). 这些权重是从 U ( − k , k ) \mathcal{U}(-\sqrt{k}, \sqrt{k}) U(k ,k ) 中采样得到的。其中 k =  groups  C out  ∗  kernel_size  k=\frac{\text { groups }}{C_{\text {out }} * \text { kernel\_size }} k=Cout  kernel_size  groups 
    • bias (Tensor) - 模型的可学习偏置,大小为 ( o u t _ c h a n n e l s ) (out\_channels) (out_channels). 如果biasTrue, 那么这个值是从 U ( − k , k ) \mathcal{U}(-\sqrt{k}, \sqrt{k}) U(k ,k )中采样得到,其中  groups  C o u t ∗ ∏ i = 0 1  kernel_size  [ i ] \frac{\text { groups }}{C_{\mathrm{out}} * \prod_{i=0}^1 \text { kernel\_size }[i]} Couti=01 kernel_size [i] groups 
  • 例子

>>> # With square kernels and equal stride
>>> m = nn.ConvTranspose2d(16, 33, 3, stride=2)
>>> # non-square kernels and unequal stride and with padding
>>> m = nn.ConvTranspose2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2))
>>> input = torch.randn(20, 16, 50, 100)
>>> output = m(input)
>>> # exact output size can be also specified as an argument
>>> input = torch.randn(1, 16, 12, 12)
>>> downsample = nn.Conv2d(16, 16, 3, stride=2, padding=1)
>>> upsample = nn.ConvTranspose2d(16, 16, 3, stride=2, padding=1)
>>> h = downsample(input)
>>> h.size()
torch.Size([1, 16, 6, 6])
>>> output = upsample(h, output_size=input.size())
>>> output.size()
torch.Size([1, 16, 12, 12])

3D反卷积


torch.nn.ConvTranspose2d

CLASS
torch.nn.ConvTranspose3d(in_channels, out_channels, kernel_size, stride=1, padding=0, output_padding=0, groups=1, bias=True, dilation=1, padding_mode='zeros', device=None, dtype=None)
  • 功能: 对由多个输入平面组成的输入信号应用3D反卷积。

  • 参数

    • in_channels (int) 输入图像中的通道数
    • out_channels (int) 由卷积产生的通道数
    • **kernel_size (int or tuple) –** 卷积核的大小
    • stride (int or tuple, optional) 卷积的步幅。默认值:1
    • padding (int, tuple or str, optional) 在输入的两边添加填充。默认值:0
    • padding_mode (str*, optional*)'zeros', 'reflect', 'replicate' 或者 'circular'. 默认:'zeros'
    • dilation (int *or tuple, optional*) 核元素之间的间距。默认值:1
    • groups (int*, optional*) 从输入通道到输出通道的阻塞连接数。默认值:1
    • bias (bool*, optional*) 如果为True,则向输出添加一个可学习偏差。默认值:True
  • 形状

    • 输入: ( N , C i n , D i n , H i n , W i n ) \left(N, C_{i n}, D_{in},H_{in},W_{in}\right) (N,Cin,Din,Hin,Win) 或者 ( C i n , D i n , H i n , W i n ) \left(C_{i n}, D_{in},H_{i n},W_{in}\right) (Cin,Din,Hin,Win)
    • 输出: ( N , C out  , D o u t , H out  , W o u t ) \left(N, C_{\text {out }},D_{out}, H_{\text {out }},W_{out}\right) (N,Cout ,Dout,Hout ,Wout) 或者 ( C out  , D o u t , H out  , W o u t ) \left(C_{\text {out }}, D_{out},H_{\text {out }},W_{out}\right) (Cout ,Dout,Hout ,Wout), 其中

D o u t = ( D i n − 1 ) × s t r i d e [ 0 ] − 2 × p a d d i n g [ 0 ] + d i l a t i o n [ 0 ] × ( k e r n e l _ s i z e [ 0 ] − 1 ) + o u t p u t _ p a d d i n g [ 0 ] + 1 H o u t = ( H i n − 1 ) × s t r i d e [ 1 ] − 2 × p a d d i n g [ 1 ] + d i l a t i o n [ 1 ] × ( k e r n e l _ s i z e [ 1 ] − 1 ) + o u t p u t _ p a d d i n g [ 1 ] + 1 W o u t = ( W i n − 1 ) × s t r i d e [ 2 ] − 2 × p a d d i n g [ 2 ] + d i l a t i o n [ 2 ] × ( k e r n e l _ s i z e [ 2 ] − 1 ) + o u t p u t _ p a d d i n g [ 2 ] + 1 D_{out}=(D_{in}−1)×stride[0]−2×padding[0]+dilation[0]×(kernel\_size[0]−1)+output\_padding[0]+1\\H_{out}=(H_{in}−1)×stride[1]−2×padding[1]+dilation[1]×(kernel\_size[1]−1)+output\_padding[1]+1\\W_{out}=(W_{in}−1)×stride[2]−2×padding[2]+dilation[2]×(kernel\_size[2]−1)+output\_padding[2]+1 Dout=(Din1)×stride[0]2×padding[0]+dilation[0]×(kernel_size[0]1)+output_padding[0]+1Hout=(Hin1)×stride[1]2×padding[1]+dilation[1]×(kernel_size[1]1)+output_padding[1]+1Wout=(Win1)×stride[2]2×padding[2]+dilation[2]×(kernel_size[2]1)+output_padding[2]+1

  • 变量

    • weight (Tensor) - 模型的可学习权重,大小为 ( o u t _ c h a n n e l s ,  out_channels   groups  , k e r n e l _ s i z e [ 0 ] , k e r n e l _ s i z e [ 1 ] ) (out\_channels, \frac{\text { out\_channels }}{\text { groups }},kernel\_size[0], kernel\_size[1]) (out_channels, groups  out_channels ,kernel_size[0],kernel_size[1]). 这些权重是从 U ( − k , k ) \mathcal{U}(-\sqrt{k}, \sqrt{k}) U(k ,k ) 中采样得到的。其中 k =  groups  C o u t ∗ ∏ i = 0 2  kernel_size  [ i ] k=\frac{\text { groups }}{C_{\mathrm{out}} * \prod_{i=0}^2 \text { kernel\_size }[i]} k=Couti=02 kernel_size [i] groups 
    • bias (Tensor) - 模型的可学习偏置,大小为 ( o u t _ c h a n n e l s ) (out\_channels) (out_channels). 如果biasTrue, 那么这个值是从 U ( − k , k ) \mathcal{U}(-\sqrt{k}, \sqrt{k}) U(k ,k )中采样得到,其中  groups  C o u t ∗ ∏ i = 0 2  kernel_size  [ i ] \frac{\text { groups }}{C_{\mathrm{out}} * \prod_{i=0}^2 \text { kernel\_size }[i]} Couti=02 kernel_size [i] groups 
  • 例子

>>> # With square kernels and equal stride
>>> m = nn.ConvTranspose3d(16, 33, 3, stride=2)
>>> # non-square kernels and unequal stride and with padding
>>> m = nn.ConvTranspose3d(16, 33, (3, 5, 2), stride=(2, 1, 1), padding=(0, 4, 2))
>>> input = torch.randn(20, 16, 10, 50, 100)
>>> output = m(input)

3.4 扩张卷积(空洞卷积)

系统能以相同的计算成本,提供更大的感受野,扩张卷积在实时分割领域特别受欢迎。 在需要更大的观察范围,且无法承受多个卷积或更大的kennels,可以用它。

这篇论文中介绍了扩张卷积: 《Semantic Image Segmentation with Deep Convolutional Nets and Fully Connected CRFs》

在这里插入图片描述


直观理解:
直观上,空洞卷积通过在卷积核部分之间插入空间让卷积核膨胀。这个增加的参数 l (空洞率)表明了我们想要将卷积核放宽到多大。下图显示了当 l=1,2,4 时的卷积核大小(当 l=1 时,空洞卷积就变成了一个标准的卷积)。
在这里插入图片描述

  • (a) 图 对应3x31-dilated conv,和普通的卷积操作一样;
  • (b)图 对应3x32-dilated conv,实际的卷积 kernel size 还是 3x3,但是空洞为1,也就是对于一个7x7的图像patch,只有9个红色的点和3x3的kernel发生卷积操作,其余的点略过。也可以理解为kernel的size为7x7,但是只有图中的9个点的权重不为0,其余都为0。 可以看到虽然kernel size只有3x3,但是这个卷积的感受野已经增大到了7x7(如果考虑到这个2-dilated conv的前一层是一个1-dilated conv的话,那么每个红点就是1-dilated的卷积输出,所以感受野为3x3,所以1-dilated和2-dilated合起来就能达到7x7的conv);
  • (c )图 对应3x34-dilated conv操作,能达到15x15的感受野。对比传统的conv操作,3层3x3的卷积加起来,stride为1的话,只能达到(kernel-1)*layer+1=7的感受野,也就是和层数layer成线性关系,而dilated conv的感受野是指数级的增长。

由此,我们可以得出:
l=1时,感受野为 3 x 3l=2 时,感受野是 7 x 7l=3时,感受野增至 15x15。有趣的是,这些操作的参数数量本质上是相同的,不需要增加参数运算成本就能观察大的感受野。正因为此,空洞卷积常被用以低成本地增加输出单元上的感受野,同时还不需要增加卷积核大小,当多个空洞卷积一个接一个堆叠在一起时,这种方式是非常有效的。

3.5 可分离卷积

可分离卷积用于某些神经网络体系结构中,例如MobileNet。可以在空间上(空间可分离卷积)或在深度上(深度可分离卷积)进行可分离卷积。

空间可分离卷积


直观理解:
空间可分离卷积在图像的2D-空间维度(即高度和宽度)上运行。从概念上讲,空间可分离卷积将卷积分解为两个单独的运算。

单通道标准卷积单通道可分离卷积

如上图所示 : 空间可分离卷积先用hx1的filter在高度上进行卷积,得到中间输出,然后再在该输出上使用1xw 的filter进行卷积。
空间可分离卷积就是2D卷积kernels的分解(在WH上的分解)。


数值理解:
对于下面显示的示例,将Sobel的kennel(3x3的kennel)分为3x1和1x3的kennel。
[ − 1 0 1 − 2 0 2 − 1 0 1 ] = [ 1 2 1 ] × [ − 1 0 1 ] \left[\begin{array}{lll} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{array}\right]=\left[\begin{array}{l} 1 \\ 2 \\ 1 \end{array}\right] \times\left[\begin{array}{lll} -1 & 0 & 1 \end{array}\right] 121000121 = 121 ×[101]
在原始卷积中,3x3的kennel直接与图像卷积。在空间可分离卷积中,3x1的kennel首先与图像进行卷积,然后应用1x3的kennel。在执行相同操作时,空间可分离卷积只需要6个参数而不是9个参数。

  • 原始卷积:用3 x 3的kennel(步长= 1,填充= 0)在5 x 5图像上进行卷积,需要在水平3个位置(垂直3个位置)上扫描的kennel,总共9个位置(在下图中以点表示)。在每个位置上,将应用9个按元素的乘法,总体来说,这是9 x 9 = 81个乘法运算。
  • 空间可分离卷积:我们首先在5 x 5图像上应用3 x 1的filter。我们在水平5个位置和垂直3个位置扫描这样的kennel,总的位置是5×3 = 15(表示为下面的图像上的点)。在每个位置,应用3个逐元素的乘法,那就是15 x 3 = 45个乘法运算。现在,我们获得了一个3 x 5的矩阵,此矩阵与1 x 3的kennel卷积,该kennel在水平3个位置和垂直3个位置扫描矩阵,总共9个位置。对于这9个位置中的每一个,将应用3个按元素的乘法,此步骤需要9 x 3 = 27个乘法运算。
  • 空间可分离卷积可以节省参数和运算成本

尽管空间可分离卷积节省了成本,但很少在深度学习中使用它。主要原因之一是并非所有kennels都可以分为两个较小的kennels。如果用空间可分离卷积代替所有传统的卷积,在训练过程中,我们将限制卷积核的类型,训练结果可能不是最佳的。

深度可分离卷积


直观理解:

标准的2D卷积深度可分离卷积
使用128个3x3x3的filters先分别使用3 个3x3x1卷积核, 然后再使用128个1

深度可分离卷积就是3D卷积kernels的分解(在深度channel上的分解


深度可分离卷积具体步骤:

  • 第一步:Depthwise Convolution
    这一步的卷积完全是在二维平面内进行,且Filter的数量与上一层的Depth相同。所以一个三通道的图像经过运算后生成了3个Feature map。
    我们在2D-卷积中分别使用 3 个卷积核(每个filter的大小为 3×3×1),而不使用大小为 3×3×3 的单个filter。每个卷积核仅对输入层的 1 个通道做卷积,这样的卷积每次都得出大小为 5×5×1的映射,之后再将这些映射堆叠在一起创建一个 5×5×3的特征图,最终得出一个大小为 5×5×3 的输出图像。这样的话,图像的深度保持与原来的一样。
    在这里插入图片描述

Depthwise Convolution完成后的Feature map数量与输入层的depth相同,但是这种运算对输入层的每个channel独立进行卷积运算后就结束了,没有有效的利用不同map在相同空间位置上的信息。因此需要增加另外一步操作来将这些map进行组合生成新的Feature map,即接下来的Pointwise Convolution。

  • 第二步:Pointwise Convolution
    Pointwise Convolution的运算与常规卷积运算非常相似,不同之处在于卷积核的尺寸为 1×1×M,M为上一层的depth。所以这里的卷积运算会将上一步的map在深度方向上进行加权组合,生成新的Feature map。有几个Filter就有几个Feature map。
    我们用大小为 1×1×3卷积核做 1x1 卷积。每个 1×1×3卷积核对 5×5×3输入图像做卷积后都得出一个大小为 5×5×1的特征图。
    在这里插入图片描述
    这样的话,做 128 次1x1 卷积后,就可以得出一个大小为 5×5×128 的层
    在这里插入图片描述

参数对比
常规卷积的参数个数是:

N = 128 × 3 × 3 × 3 = 3456
# 卷积核的个数*卷积核的长*卷积核的宽*卷积核的通道数

Separable Convolution的参数由两部分相加得到:

N_depthwise = 3 × 3 × 3 = 27
N_pointwise = 1 × 1 × 3 × 128 = 384
N_separable = N_depthwise + N_pointwise = 411

相同的输入,同样是得到4张Feature map,深度可分离卷积的参数个数要比常规卷积小的多。因此,在参数量相同的前提下,采用深度可分离卷积的神经网络层数可以做的更深。


代码实现

class DepthWiseConv(nn.Module):
    def __init__(self,in_channel,out_channel):
 
        #这一行千万不要忘记
        super(DepthWiseConv, self).__init__()
 
        # 逐通道卷积
        self.depth_conv = nn.Conv2d(in_channels=in_channel,
                                    out_channels=in_channel,
                                    kernel_size=3,
                                    stride=1,
                                    padding=1,
                                    groups=in_channel)
        # groups是一个数,当groups=in_channel时,表示做逐通道卷积
 
        #逐点卷积
        self.point_conv = nn.Conv2d(in_channels=in_channel,
                                    out_channels=out_channel,
                                    kernel_size=1,
                                    stride=1,
                                    padding=0,
                                    groups=1)
    
   def forward(self,input):
        out = self.depth_conv(input)
        out = self.point_conv(out)
        return out

3.6 扁平卷积

论文 《Flattened Convolutional Neural Networks for Feedforward Acceleration》 介绍了扁平卷积(Flattened Convolution)。该论文认为通过使用由3D空间中所有方向上的1D-filters的连续序列组成的扁平化网络进行训练,可以提供与标准卷积网络相当的性能,并且由于学习参数的显着减少,计算成本要低得多。


直观理解:

标准的3D卷积深度可分离卷积
应用一个标准filter将输入层映射到输出层将标准filter分为3个1D-filters
扁平卷积与上述空间可分离卷积中的想法相似,其中空间filters是由两个rank-1 filters近似得到的。

3.7 分组卷积

分组卷积(Grouped convolution ),最早在AlexNet中出现,由于当时的硬件资源有限,训练AlexNet时卷积操作不能全部放在同一个GPU处理,因此作者把feature maps分给多个GPU分别进行处理,最后把多个GPU的结果进行融合。

在这里插入图片描述


直观理解:
在分组卷积中,filters被拆分为不同的组,每一个组都负责具有一定深度的传统 2D 卷积的工作。下图的例子表示得更清晰一些:

标准的2D卷积具有两个filters的分组卷积

上图表示的是被拆分为 2 个filters组的分组卷积。在每个filters组中,其深度仅为传统2D-卷积的一 半 ( D i n / 2 ) \left(D_{i n} / 2\right) (Din/2) ,而每个filters组都包含 D out  / 2 D_{\text {out }} / 2 Dout /2 个filters。第一个filters组 (红色) 对输入层的前 半部分做卷积 ( [ : , : , 0 : D i n / 2 ] ) \left.\left[:,:, 0: D_{i n} / 2\right]\right) [:,:,0:Din/2]) ,第二个filters组 (蓝色) 对输入层的后半部分做卷积( [ : , : , D in  / 2 : D in  ] ) \left.\left[:,:, D_{\text {in }} / 2: D_{\text {in }}\right]\right) [:,:,Din /2:Din ]) 。最终,每个filters组都输出了 D out  / 2 D_{\text {out }} / 2 Dout /2 个通道。整体上,两个组输出的通 道数为 2 × D out  / 2 = D out  2 \times D_{\text {out }} / 2=D_{\text {out }} 2×Dout /2=Dout  。之后, 我们再将这些通道堆叠到输出层中,输出层就有了 D out  D_{\text {out }} Dout  个 通道。


分组卷积可以节省参数量

卷积参数量的计算公式是:
(输入通道数 × 输出通道数 × 卷积核大 小 2 ) / 组数 = i n _ c h a n n e l s × o u t _ c h a n n e l s × k 2 / g r o u p s (输入通道数 \times 输出通道数 \times 卷积核大小^2 )/ 组数 =in\_channels\times out\_channels\times k^2/groups (输入通道数×输出通道数×卷积核大2/组数=in_channels×out_channels×k2/groups

不分组
分两组
分四组

k 、 c 1 、 c 2 k、c_1、c_2 kc1c2 分别表示卷积核的宽度、输入通道数、输出通道数。
不分组卷积需要的参数量是: n = k 2 c 1 c 2 n=k^2c_1c_2 n=k2c1c2
分两组卷积需要的参数量是: n = k 2 c 1 c 2 2 n= \frac{k^2c_1c_2}{2} n=2k2c1c2
分四组卷积需要的参数量是: n = k 2 c 1 c 2 4 n= \frac{k^2c_1c_2}{4} n=4k2c1c2
以此类推,如果使用分组卷积,分为 m m m组,则卷积核的权重参数量为 n = k 2 c 1 c 2 m n=\frac {k^2c_1c_2}{m} n=mk2c1c2,但是偏置值是不会被节省的,都是 c2 个。


代码理解

首先定义输入数据data

# 输入数据的维度[N, C, H, W] -> [1, 4, 1, 1]
data = torch.arange(4, dtype=torch.float32).view(1,4, 1, 1)
'''
data : 
tensor([[[[0.]],

         [[1.]],

         [[2.]],

         [[3.]]]])
'''
  • group =1
    然后定义权重大小kernel_weight
kernel_weight = torch.nn.Parameter(torch.arange(16, dtype=torch.float32).view(4, 4, 1, 1))
'''
Parameter containing:
tensor([[[[ 0.]],

         [[ 1.]],

         [[ 2.]],

         [[ 3.]]],


        [[[ 4.]],

         [[ 5.]],

         [[ 6.]],

         [[ 7.]]],


        [[[ 8.]],

         [[ 9.]],

         [[10.]],

         [[11.]]],


        [[[12.]],

         [[13.]],

         [[14.]],

         [[15.]]]], requires_grad=True)
'''

groups=1,out_channels=4, 卷积核的大小是[4,4,1,1],每个卷积核的通道数:in_channels/groups=4/1=4
所有输入通道和每个卷积核进行卷积操作。

conv_groups_1 = nn.Conv2d(in_channels=4, out_channels=4, kernel_size=1, groups=1, bias=False)
conv_groups_1.weight = kernel_weight
conv_groups_1(data)
'''
tensor([[[[14.]],

         [[38.]],

         [[62.]],

         [[86.]]]], grad_fn=<ThnnConv2DBackward>)
'''

四个卷积核对应的权重分别是 k e r n e l 1 = [ 0 , 1 , 2 , 3 ] , k e r n e l 2 = [ 4 , 5 , 6 , 7 ] 、 k e r n e l 3 = [ 8 , 9 , 10 , 11 ] 、 k e r n e l 4 = [ 12 , 13 , 14 , 15 ] kernel_1=[0,1,2,3],kernel_2=[4,5,6,7]、kernel_3=[8,9,10,11]、kernel_4=[12,13,14,15] kernel1=[0,1,2,3]kernel2=[4,5,6,7]kernel3=[8,9,10,11]kernel4=[12,13,14,15]
四个通道的结果分别是:
输入通道1、2、3、4和卷积核1的卷积结果:
o u t _ c h a n n e l _ 1 = 0 × 0 + 1 × 1 + 2 × 2 + 3 × 3 = 14 out\_channel\_1 =0\times 0+1\times1+2\times2+3\times3=14 out_channel_1=0×0+1×1+2×2+3×3=14
输入通道1、2、3、4和卷积核2的卷积结果:
o u t _ c h a n n e l _ 2 = 0 × 4 + 1 × 5 + 2 × 6 + 3 × 7 = 38 out\_channel\_2 =0\times4+1\times5+2\times6+3\times7=38 out_channel_2=0×4+1×5+2×6+3×7=38
输入通道1、2、3、4和卷积核3的卷积结果:
o u t _ c h a n n e l _ 1 = 0 × 8 + 1 × 9 + 2 × 10 + 3 × 11 = 62 out\_channel\_1 =0\times8+1\times9+2\times10+3\times11=62 out_channel_1=0×8+1×9+2×10+3×11=62
输入通道1、2、3、4和卷积核4的卷积结果:
o u t _ c h a n n e l _ 2 = 0 × 12 + 1 × 13 + 2 × 14 + 3 × 15 = 85 out\_channel\_2 =0\times12+1\times13+2\times14+3\times15=85 out_channel_2=0×12+1×13+2×14+3×15=85

  • group=2
    定义权重大小kernel_weight_2, 卷积核的通道数 = i n _ c h a n n e l s / g r o u p s = 2 =in\_channels/groups=2 =in_channels/groups=2
kernel_weight_2 = torch.nn.Parameter(torch.arange(8, dtype=torch.float32).view(4, 2, 1, 1))
'''
Parameter containing:
tensor([[[[0.]],

         [[1.]]],


        [[[2.]],

         [[3.]]],


        [[[4.]],

         [[5.]]],


        [[[6.]],

         [[7.]]]], requires_grad=True)
'''

输入通道和卷积核kernel通道都分成2组,前两个输入通道和前两组的卷积核进行卷积操作,后2个输入通道和后2组卷积核进行卷积操作。

conv_groups_2 = nn.Conv2d(in_channels=4, out_channels=4, kernel_size=1, groups=2, bias=False)
conv_groups_2.weight = kernel_weight_2
conv_groups_2(data)
'''
tensor([[[[ 1.]],

         [[ 3.]],

         [[23.]],

         [[33.]]]], grad_fn=<MkldnnConvolutionBackward>)
'''

前两个输入通道形状 [ 1 , 2 , 1 , 1 ] [1,2,1,1] [1,2,1,1],前两组卷积核形状 [ 2 , 2 , 1 , 1 ] [2,2,1,1] [2,2,1,1],卷积输出结果形状 [ 1 , 2 , 1 , 1 ] [1,2,1,1] [1,2,1,1]
后两个输入通道形状 [ 1 , 2 , 1 , 1 ] [1,2,1,1] [1,2,1,1],前两组卷积核形状 [ 2 , 2 , 1 , 1 ] [2,2,1,1] [2,2,1,1],卷积输出结果形状 [ 1 , 2 , 1 , 1 ] [1,2,1,1] [1,2,1,1]
将两组卷积结果进行concat,最终得到结果形状 [ 1 , 4 , 1 , 1 ] [1,4,1,1] [1,4,1,1]
4个卷积核对应的权重分别是 k e r n e l 1 = [ 0 , 1 ] , k e r n e l 2 = [ 2 , 3 ] , k e r n e l 3 = [ 4 , 5 ] , k e r n e l 4 = [ 6 , 7 ] kernel1=[0,1], kernel_2=[2,3], kernel_3=[4,5], kernel_4=[6,7] kernel1=[0,1],kernel2=[2,3],kernel3=[4,5],kernel4=[6,7]
输入通道1、2和卷积核1的卷积结果: o u t _ c h a n n e l s _ 1 = 0 × 0 + 1 × 1 = 1 out\_channels\_1=0\times 0+1\times 1=1 out_channels_1=0×0+1×1=1
输入通道1、2和卷积核2的卷积结果: o u t _ c h a n n e l s _ 2 = 0 × 2 + 1 × 3 = 3 out\_channels\_2=0\times 2+1\times 3 =3 out_channels_2=0×2+1×3=3
输入通道3、4和卷积核3的卷积结果: o u t _ c h a n n e l s _ 3 = 2 × 4 + 3 × 5 = 23 out\_channels\_3=2\times 4+3\times 5=23 out_channels_3=2×4+3×5=23
输入通道3、4和卷积核4的卷积结果: o u t _ c h a n n e l s _ 4 = 2 × 6 + 3 × 7 = 33 out\_channels\_4=2 \times6+3\times 7=33 out_channels_4=2×6+3×7=33
这是groups=2的场景,groups=2时有2个分组,每个分组中包含2个卷积核,每组卷积核和2个输入通道进行卷积操作得到一组输出结果,将两组输出结果进行叠加得到最终的卷积结果

  • group =4
    g r o u p s = 4 , o u t c h a n n e l s = 4 groups=4,out_channels=4 groups=4,outchannels=4, 卷积核kernel大小为 [ 4 , 1 , 1 , 1 ] [4,1,1,1] [4,1,1,1], 每个卷积核的通道数: i n _ c h a n n e l s / g r o u p s = 1 in\_channels/groups =1 in_channels/groups=1
    4个卷积核对应的权重分别是 k e r n e l 1 = [ 0 ] , k e r n e l 2 = [ 1 ] , k e r n e l 3 = [ 2 ] , k e r n e l 4 = [ 3 ] kernel_1=[0],kernel_2=[1],kernel_3=[2],kernel_4=[3] kernel1=[0],kernel2=[1],kernel3=[2],kernel4=[3]
kernel_weight_4 = torch.nn.Parameter(torch.arange(4, dtype=torch.float32).view(4, 1, 1, 1))
'''
Parameter containing:
tensor([[[[0.]]],


        [[[1.]]],


        [[[2.]]],


        [[[3.]]]], requires_grad=True)
'''

输入通道1和卷积核1的卷积结果: o u t _ c h a n n e l _ 1 = 0 × 0 = 0 out\_channel\_1=0\times 0 =0 out_channel_1=0×0=0
输入通道2和卷积核2的卷积结果: o u t _ c h a n n e l _ 2 = 1 × 1 = 1 out\_channel\_2=1\times 1 =1 out_channel_2=1×1=1
输入通道3和卷积核3的卷积结果: o u t _ c h a n n e l _ 3 = 2 × 2 = 4 out\_channel\_3=2\times 2 =4 out_channel_3=2×2=4
输入通道4和卷积核4的卷积结果: o u t _ c h a n n e l _ 4 = 3 × 3 = 9 out\_channel\_4=3\times 3 =9 out_channel_4=3×3=9

conv_groups_4 = nn.Conv2d(in_channels=4, out_channels=4, kernel_size=1, groups=4, bias=False)
conv_groups_4.weight = kernel_weight_4
conv_groups_4(data)
'''
tensor([[[[0.]],

         [[1.]],

         [[4.]],

         [[9.]]]], grad_fn=<MkldnnConvolutionBackward>)
'''

groups=4时有4个分组,每个分组中包含1个卷积核,每组卷积核和1个输入通道进行卷积操作得到一组输出结果,将4组输出结果进行叠加得到最终的卷积结果。
groups=4的场景,也是groups等于输入通道数的场景和3.5节中的深度分离卷积相同


分组卷积的优点:

  • 第一个优点是有效的训练。由于卷积被划分为多个路径,因此每个路径可以由不同的GPU分别处理,此过程允许以并行方式在多个GPU上进行模型训练。与使用一个GPU进行所有训练相比,通过多GPU进行的模型并行化,可以将更多图像传到网络中。模型并行化被认为比数据并行化更好的方式,最终将数据集分成多个批次,然后我们对每个批次进行训练。但是,当批次大小变得太小时,与batch梯度下降相比,我们实际上是随机的,这将导致收敛变慢,有时甚至变差。
  • 第二个优点是模型更有效,即模型参数随着filters组数的增加而减小。
  • 第三个优点分组卷积可以提供比标准2D卷积更好的模型

参考


在整理的过程中,感谢如下文章对我的帮助和启发~

https://cs231n.github.io/convolutional-networks/
https://towardsdatascience.com/pytorch-basics-how-to-train-your-neural-net-intro-to-cnn-26a14c2ea29
https://arxiv.org/pdf/1603.07285.pdf
https://github.com/vdumoulin/conv_arithmetic
https://stackoverflow.com/questions/42883547/intuitive-understanding-of-1d-2d-and-3d-convolutions-in-convolutional-neural-n
https://www.zhihu.com/question/54149221
https://pytorch.org/docs/stable/nn.html#convolution-layers
https://zh.d2l.ai/chapter_convolutional-neural-networks/channels.html
https://blog.csdn.net/u012348774/article/details/104695411
https://blog.csdn.net/cxx654/article/details/109681004
https://blog.csdn.net/weixin_43135178/article/details/122426414

  • 15
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

zyw2002

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

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

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

打赏作者

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

抵扣说明:

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

余额充值