注意:这里展示的是本篇博文写时的版本最新的实现,但是后续会代码可能会迭代更新,建议对照 官方文档 进行学习。
首先看一下这个类的定义:
class Conv2d(_ConvNd):# 初始化函数,这里主要了解有哪些参数传进来就可以了def __init__(self,in_channels: int,out_channels: int,kernel_size: _size_2_t,stride: _size_2_t = 1,padding: _size_2_t = 0,dilation: _size_2_t = 1,groups: int = 1,bias: bool = True,padding_mode: str = 'zeros' # TODO: refine this type):#————————————————看到这就可以了————————————————————kernel_size = _pair(kernel_size)stride = _pair(stride)padding = _pair(padding)dilation = _pair(dilation)super(Conv2d, self).__init__(in_channels, out_channels, kernel_size, stride, padding, dilation,False, _pair(0), groups, bias, padding_mode)def _conv_forward(self, input, weight):if self.padding_mode != 'zeros':return F.conv2d(F.pad(input, self._reversed_padding_repeated_twice, mode=self.padding_mode),weight, self.bias, self.stride,_pair(0), self.dilation, self.groups)return F.conv2d(input, weight, self.bias, self.stride,self.padding, self.dilation, self.groups)# 前向传播计算def forward(self, input: Tensor) -> Tensor:return self._conv_forward(input, self.weight)
上面就是Cov2d这个类的源码实现,是不是感觉代码特别少?是不是突然对掌握它信心倍增!
从整体上来看:
Conv2d是一个类,它包含了做卷积运算所需要的参数(__init__函数),以及卷积操作(forward函数)。
再来看一下它的详细参数:
一共九个参数,一般用前三个就可以处理一般的任务:
in_channels
:输入通道数目out_channels
:输出通道数目kernel_size
:卷积核大小,如果输入是一个值,比如 3 3 3,那么卷积核大小就是 3 × 3 3 \times 3 3×3 ,如果不想卷积核宽和高相等,还可以输入tuple类型数据,比如: ( 3 , 5 ) (3, 5) (3,5)stride
:步长大小,跟上面卷积核参数一样,如果输入是一个值,比如 2 2 2 ,步长就是 2 × 2 2 \times 2 2×2 ,还可以输入元组 ( 2 , 1 ) (2, 1) (2,1) ,表示卷积核每次向右移动 1 1 1 个步长,向下移动 2 2 2 个步长。padding
:填充,参数表示在周围补0的情况。补0的方向为上、下、左、右四个方向。如果是输入是单个值,比如1,就是在上下左右四个方向补一圈0。如果输入是元组比如(2,1)
,表示在上下两个方向各补两行0,在左右两个方向各补一列0。dilation
:进行扩展卷积需要的参数。groups
:进行分组卷积需要的参数。(有需要自行深入了解)bias
:偏置,布尔类型,默认为True
,即增加一个学习的偏置项。padding_mode
:填充的模式,默认是zero
,还可以选择reflect
、replicate
、circular
。(有需要自行深入了解)
首先是关于输入通道数 in_channels
和输出通道数 out_channels
的解释:
如果对通道是什么还不太理解可以去看我的这篇博客:如何理解卷积神经网络中的通道(channel)
如果卷积核输入的是原始的图片,那么我们可以根据图片类型决定图片的输入通道数,如果是RGB图片, in_channels=3
,如果是灰度图像,in_channels=1
。输出通道数需要我们自己指定,具体指定多少需要根据经验。
如果是第二层或者更多层卷积,当前层的输入通道数就是上一层卷积的输出通道数,当前层的输出通道数需要自己指定。
关于卷积核的解释:
这里的卷积核参数只是指定卷积核的宽度和高度,对于多通道的卷积,代码内部会根据输入通道数初始化对应的卷积核数目,即:如果输入是3通道,那么就会有三个我们指定宽高的卷积核做相应的卷积操作。
关于步长的解释:
步长指的是卷积核在输入矩阵的上每次移动几步。移动方向由两个,向右或者向下。分别由两个值指定。
关于填充的解释:
填充的主要目的是为了充分利用输入图片的边缘信息。
关于为什么需要填充,可以参考这篇博客:CNN基础知识——卷积(Convolution)、填充(Padding)、步长(Stride)
关于扩张(dilation)参数的解释:
本质上就是扩大卷积核,比如将 3 × 3 3 \times 3 3×3 的扩大为 7 × 7 7 \times 7 7×7 的,然后对扩大的部分用0进行填充,
目的是扩大感受野,捕获多尺度上下文信息。
具体怎么扩张,详细请参考:
【1】如何理解扩张卷积(dilated convolution)
【2】总结-空洞卷积(Dilated/Atrous Convolution)
关于分组(group)的解释:
对于一般的卷积神经网络,假设通道数有100个,那么我们需要100个卷积核对这100个通道分别做卷积操作,然后求和,最后得到1个特征图。如果我们需要得到30个特征图,就需要30*100=3000个卷积核。参数量非常大。
而对于分组卷积,依然假设通道数有100个,我们分成两组,每组50个通道数,分别对每组单独做卷积操作,每组需要50个卷积核,总的卷积核数目没变,但是我们每组可以得到2个特征图。如果我们分成4组,每组25个通道数,最终可以得到4个特征图。
也就是说,我们在不增加参数量的情况下,得到了更多的特征图。这是分组的优势之一,对于分组的通道,我们可以在不同的GPU上并行训练,加速训练过程。
更详细的解释参考:
卷积网络基础知识—Group Convolution分组卷积
关于偏置项的解释:
这个项是个布尔类型值,如果为True,就会在训练的时候加上偏置项,反之,不会。默认是True。
关于填充模式的解释:
我们一般情况下对于填充的数据,默认是0。还可以选择常数填充、镜像填充、重复填充.。
常数填充就是指定一个常数值进行填充,如果为0,等价于零填充;
镜像填充是指对图像边缘进行镜像对称的填充;
重复填充指的是用图像边缘的像素值进行填充。
详细参考:PyTorch中的padding(边缘填充)操作
OK,到这里就对所有的参数解释完了。下面我们解释一下,经过一次卷积操作之后,图像形状如何进行变化。
图像形状的变化规则
卷积神经网络的输入: N , C i n , H i n , W i n N,C_{in},H_{in},W_{in} N,Cin,Hin,Win
卷积神经网络的输出: N , C o u t , H o u t , W o u t N,C_{out},H_{out},W_{out} N,Cout,Hout,Wout
其中, N N N 表示批处理的大小,即batch_size。 C i n C_{in} Cin 和 C o u t C_{out} Cout 分别表示输入和输出通道数。 H i n H_{in} Hin 和 W i n W_{in} Win 表示输入图片的高和宽,以像素为单位。同理, H o u t H_{out} Hout 和 W o u t W_{out} Wout 表示输出图片的高和宽。
OK,我们来看一下输入输出是怎么变化的。
首先是batch_size,卷积神经网络并不会改变这个参数;然后是 C i n C_{in} Cin 和 C o u t C_{out} Cout 这两个参数是我们初始化的时候就指定的;实际上卷积神经网络改变的是图片的尺寸,图片尺寸变化公式为:
H o u t = ⌊ H i n + 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 ) − 1 s t r i d e ⌊ 0 ⌋ + 1 ⌋ H_{out}=\lfloor \frac{H_{in} + 2 \times padding\lfloor 0 \rfloor - dilation \lfloor 0 \rfloor \times (kernel\_size\lfloor 0 \rfloor - 1)-1}{stride\lfloor 0 \rfloor} + 1 \rfloor Hout=⌊stride⌊0⌋Hin+2×padding⌊0⌋−dilation⌊0⌋×(kernel_size⌊0⌋−1)−1+1⌋
W o u t = ⌊ W i n + 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 ) − 1 s t r i d e ⌊ 1 ⌋ + 1 ⌋ W_{out}=\lfloor \frac{W_{in} + 2 \times padding\lfloor 1 \rfloor - dilation \lfloor 1 \rfloor \times (kernel\_size\lfloor 1 \rfloor - 1)-1}{stride\lfloor 1 \rfloor} + 1 \rfloor Wout=⌊stride⌊1⌋Win+2×padding⌊1⌋−dilation⌊1⌋×(kernel_size⌊1⌋−1)−1+1⌋
然后我们用一段代码测试一下:
import torch
import torch.nn as nn
# in_channels=16, out_channels=33, kernel_size=(3, 5)
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1))
print(m)
# 我们随机构造输入数据
# N=20, C_in=16, H_in=50, W_in=100
# 对应上面说的 (N, C, H, W)
input = torch.randn(20, 16, 50, 100)
output = m(input)
print(output.shape)
结果:
Conv2d(16, 33, kernel_size=(3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1))
torch.Size([20, 33, 26, 100])
上面的torch.size表示输出的形状:
b a t c h _ s i z e = 20 batch\_size=20 batch_size=20
o u t _ c h a n n e l = 33 out\_channel=33 out_channel=33
H o u t = 26 H_{out}=26 Hout=26
W o u t = 100 W_{out}=100 Wout=100
我们带入数据实际计算一下:
H o u t = ⌊ 50 + 2 × 4 − 3 × ( 3 − 1 ) − 1 2 + 1 ⌋ = 26 H_{out}= \lfloor \frac{50 + 2 \times 4 - 3 \times (3 - 1) - 1}{2} + 1\rfloor=26 Hout=⌊250+2×4−3×(3−1)−1+1⌋=26
W o u t = ⌊ 100 + 2 × 2 − 1 × ( 5 − 1 ) − 1 1 + 1 ⌋ = 100 W_{out}=\lfloor \frac{100 + 2 \times 2 - 1 \times (5 - 1) - 1}{1} + 1 \rfloor = 100 Wout=⌊1100+2×2−1×(5−1)−1+1⌋=100
Conv2d中的权重和偏置项
最后我们有必要了解一下Conv2d的两个变量:
~Conv2d.weight
其形状为: ( o u t _ c h a n n e l s , i n _ c h a n n e l s g r o u p , 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{in\_channels}{group},kernel\_size[0], kernel\_size[1]) (out_channels,groupin_channels,kernel_size[0],kernel_size[1])
数据从 U ( − k , k ) \mathcal{U}(-\sqrt{k}, \sqrt{k}) U(−k ,k ) 中取样,其中 k = g r o u p s C i n ∗ ∏ i = 0 1 k e r n e l _ s i z e [ i ] k=\frac{groups}{C_{in}*\prod_{i=0}^1kernel\_size[i]} k=Cin∗∏i=01kernel_size[i]groups
~Conv2d.bias
其形状为: ( o u t _ c h a n n e l s ) (out\_channels) (out_channels)
数据从 U ( − k , k ) \mathcal{U}(-\sqrt{k}, \sqrt{k}) U(−k ,k ) 中取样,其中 k = g r o u p s C i n ∗ ∏ i = 0 1 k e r n e l _ s i z e [ i ] k=\frac{groups}{C_{in}*\prod_{i=0}^1kernel\_size[i]} k=Cin∗∏i=01kernel_size[i]groups
还是上面的代码,我们可以查看这个卷积神经网络的权重和偏置的形状:
import torch
import torch.nn as nn
# in_channels=16, out_channels=33, kernel_size=(3, 5)
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1))
print(m)
# 我们随机构造输入数据
# N=20, C_in=16, H_in=50, W_in=100
# 对应上面说的 (N, C, H, W)
input = torch.randn(20, 16, 50, 100)
output = m(input)
print(m.weight.shape)
print(m.bias.shape)
输出:
torch.Size([33, 16, 3, 5])
torch.Size([33])
本文链接:https://my.lmcjl.com/post/2220.html
4 评论