DL_Notebook 5.1_CNN基础/padding/channel

文章目录

  • 卷积神经网络基础
    • 二维卷积层
      • 二维互相关代码
      • 二维卷积层代码
    • 填充和步幅
      • 填充
      • 步幅
    • 多输入通道和多输出通道
      • 多输入通道
      • 多输出通道
      • 1x1卷积层
    • 卷积层 V.S 全连接层
    • 卷积层的简洁实现
    • 池化
      • 池化层的简洁实现

卷积神经网络基础

二维卷积层

二维互相关代码

corr2d函数实现二维互相关运算

import torch 
import torch.nn as nn

def corr2d(X, K):
    H, W = X.shape
    h, w = K.shape
    Y = torch.zeros(H - h + 1, W - w + 1)
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            Y[i, j] = (X[i: i + h, j: j + w] * K).sum()
    return Y

函数测试

X = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
K = torch.tensor([[0, 1], [2, 3]])
Y = corr2d(X, K)
print(Y)

tensor([[19., 25.],
[37., 43.]])

二维卷积层代码

代码:

class Conv2D(nn.Module):
    def __init__(self, kernel_size):
        super(Conv2D, self).__init__()
        self.weight = nn.Parameter(torch.randn(kernel_size))
        self.bias = nn.Parameter(torch.randn(1))

    def forward(self, x):
        return corr2d(x, self.weight) + self.bias
        

代码讲解:

  • 卷积核weight,标量偏置bias定义为nn.Parameter( ),用于在学习中定义一些可学习的参数。因为 1) Parameter是tensor的一个子类,会自动为参数附梯度,参数可学习; 2) 在nn.Module这个类当中定义parameter,会自动注册到一个参数集合中。(只看了网课,笔记可能比较不易懂,等实际运用后再补充)
  • last line :这里的bias是长度为1的tensor,这里的加法是基于广播机制的加法,即在每一个位置上都加了这个bias

例子: 6x8图像,中间四列为黑(0),其余为白(1),希望实现边缘检测。标签是6x7的二维数组,第2列为1(标志图片中从白色–>黑色的边缘),第6列为-1(从黑到白的边缘)

X = torch.ones(6, 8)
Y = torch.zeros(6, 7)
X[:, 2: 6] = 0
Y[:, 1] = 1
Y[:, 5] = -1
print(X)
print(Y)

tensor([[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.]])
tensor([[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.]])

学习一个1x2的卷积层,通过卷积层来检测颜色边缘:

conv2d = Conv2D(kernel_size=(1, 2))
step = 30
lr = 0.01
for i in range(step):
    Y_hat = conv2d(X)
    l = ((Y_hat - Y) ** 2).sum()
    l.backward()
    # 梯度下降
    conv2d.weight.data -= lr * conv2d.weight.grad
    conv2d.bias.data -= lr * conv2d.bias.grad
    
    # 梯度清零
    conv2d.weight.grad.zero_()
    conv2d.bias.grad.zero_()
    if (i + 1) % 5 == 0:
        print('Step %d, loss %.3f' % (i + 1, l.item()))
        
print(conv2d.weight.data)
print(conv2d.bias.data)

Step 5, loss 4.569
Step 10, loss 0.949
Step 15, loss 0.228
Step 20, loss 0.060
Step 25, loss 0.016
Step 30, loss 0.004
tensor([[ 1.0161, -1.0177]])
tensor([0.0009])

填充和步幅

填充

如果原输入的高和宽是 n h n_h nh n w n_w nw,卷积核的高和宽是 k h k_h kh k w k_w kw,在高的两侧一共填充 p h p_h ph行,在宽的两侧一共填充 p w p_w pw列,则输出形状为:
( n h + p h − k h + 1 ) × ( n w + p w − k w + 1 ) (n_h+p_h-k_h+1)\times(n_w+p_w-k_w+1) (nh+phkh+1)×(nw+pwkw+1)
在卷积神经网络中使用奇数高宽的核,比如 3 × 3 3 \times 3 3×3 5 × 5 5 \times 5 5×5的卷积核,对于高度(或宽度)为大小为 2 k + 1 2 k + 1 2k+1的核,令步幅为1,在高(或宽)两侧选择大小为 k k k的填充,便可保持输入与输出尺寸相同。(例如:对于3x3的卷积核,给输入在上下左右分别填充1,即高+2、宽+2)
DL_Notebook 5.1_CNN基础/padding/channel_第1张图片

步幅

步幅(stride)。此前使用的步幅都是1,图1展示了在高上步幅为3、在宽上步幅为2的二维互相关运算。

Image Name

图1 高和宽上步幅分别为3和2的二维互相关运算

一般来说,当高上步幅为 s h s_h sh,宽上步幅为 s w s_w sw时,输出形状为:

⌊ ( n h + p h − k h + s h ) / s h ⌋ × ⌊ ( n w + p w − k w + s w ) / s w ⌋ \lfloor(n_h+p_h-k_h+s_h)/s_h\rfloor \times \lfloor(n_w+p_w-k_w+s_w)/s_w\rfloor (nh+phkh+sh)/sh×(nw+pwkw+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 ⌋ \lfloor(n_h+s_h-1)/s_h\rfloor \times \lfloor(n_w+s_w-1)/s_w\rfloor (nh+sh1)/sh×(nw+sw1)/sw。更进一步,如果输入的高和宽能分别被高和宽上的步幅整除,那么输出形状将是 ( n h / s h ) × ( n w / s w ) (n_h / s_h) \times (n_w/s_w) (nh/sh)×(nw/sw)

一般都是 p h = p w = p p_h = p_w = p ph=pw=p s h = s w = s s_h = s_w = s sh=sw=s,称填充为 p p p,步幅为 s s s

多输入通道和多输出通道

多输入通道

例子:RGB图像是3维的,图片高宽为 h × w h\times w h×w,输入为 3 × h × w 3\times h\times w 3×h×w的多维数组,3为通道维。
DL_Notebook 5.1_CNN基础/padding/channel_第2张图片

图2 含2个输入通道的互相关计算

输入通道数 c i c_i ci,卷积核 k h × k w k_h\times k_w kh×kw,为每个输入通道各分配一个形状为 k h × k w k_h\times k_w kh×kw的核数组,将 c i c_i ci个互相关运算的二维输出按通道相加,得到一个二维数组作为输出。把 c i c_i ci个核数组在通道维上连结,即得到一个形状为 c i × k h × k w c_i\times k_h\times k_w ci×kh×kw的卷积核。

多输出通道

输出通道数 c o c_o co,如果希望得到含多个通道的输出,我们可以为每个输出通道分别创建形状为 c i × k h × k w c_i\times k_h\times k_w ci×kh×kw的核数组,将它们在输出通道维上连结,卷积核的形状即 c o × c i × k h × k w c_o\times c_i\times k_h\times k_w co×ci×kh×kw。(见图3)

对于输出通道的卷积核,我们提供这样一种理解,一个 c i × k h × k w c_i \times k_h \times k_w ci×kh×kw的核数组可以提取某种局部特征,但是输入可能具有相当丰富的特征,我们需要有多个这样的 c i × k h × k w c_i \times k_h \times k_w ci×kh×kw的核数组,不同的核数组提取的是不同的特征。

1x1卷积层

图3展示了 c i = 3 , c o = 2 c_i=3,c_o=2 ci=3co=2 1 × 1 1\times 1 1×1卷积核的互相关计算。

Image Name

图3 1x1卷积核的互相关计算。输入和输出具有相同的高和宽

1 × 1 1 \times 1 1×1卷积核可在不改变高宽的情况下,调整通道数。 1 × 1 1 \times 1 1×1卷积核不识别高和宽维度上相邻元素构成的模式,其主要计算发生在通道维上。假设我们将通道维当作特征维,将高和宽维度上的元素当成数据样本,那么 1 × 1 1\times 1 1×1卷积层的作用与全连接层等价。

卷积层 V.S 全连接层

二维卷积层经常用于处理图像,与此前的全连接层相比,它主要有两个优势:

一是全连接层把图像展平成一个向量,在输入图像上相邻的元素可能因为展平操作不再相邻,网络难以捕捉局部信息。而卷积层的设计,天然地具有提取局部信息的能力。

二是卷积层的参数量更少。不考虑偏置的情况下,一个形状为 ( c i , c o , h , w ) (c_i, c_o, h, w) (ci,co,h,w)的卷积核的参数量是 c i × c o × h × w c_i \times c_o \times h \times w ci×co×h×w,与输入图像的宽高无关。假如一个卷积层的输入和输出形状分别是 ( c 1 , h 1 , w 1 ) (c_1, h_1, w_1) (c1,h1,w1) ( c 2 , h 2 , w 2 ) (c_2, h_2, w_2) (c2,h2,w2),如果要用全连接层进行连接,参数数量就是 c 1 × c 2 × h 1 × w 1 × h 2 × w 2 c_1 \times c_2 \times h_1 \times w_1 \times h_2 \times w_2 c1×c2×h1×w1×h2×w2。使用卷积层可以以较少的参数数量来处理更大的图像。

卷积层的简洁实现

我们使用Pytorch中的nn.Conv2d类来实现二维卷积层,主要关注以下几个构造函数参数:

  • in_channels (python:int) – Number of channels in the input imag
  • out_channels (python:int) – Number of channels produced by the convolution
  • kernel_size (python:int or tuple) – Size of the convolving kernel
  • stride (python:int or tuple, optional) – Stride of the convolution. Default: 1
  • padding (python:int or tuple, optional) – Zero-padding added to both sides of the input. Default: 0
  • bias (bool, optional) – If True, adds a learnable bias to the output. Default: True

forward函数的参数为一个四维张量,形状为 ( 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是批量大小, C , H , W C, H, W C,H,W分别表示通道数、高度、宽度。

X = torch.rand(4, 2, 3, 5)
print(X.shape)

conv2d = nn.Conv2d(in_channels=2, out_channels=3, kernel_size=(3, 5), stride=1, padding=(1, 2))
Y = conv2d(X)
print('Y.shape: ', Y.shape)
print('weight.shape: ', conv2d.weight.shape)
print('bias.shape: ', conv2d.bias.shape)

torch.Size([4, 2, 3, 5])
Y.shape: torch.Size([4, 3, 3, 5])
weight.shape: torch.Size([3, 2, 3, 5])
bias.shape: torch.Size([3])

池化

池化层的简洁实现

我们使用Pytorch中的nn.MaxPool2d实现最大池化层,关注以下构造函数参数:

  • kernel_size – the size of the window to take a max over
  • stride – the stride of the window. Default value is kernel_size
  • padding – implicit zero padding to be added on both sides

forward函数的参数为一个四维张量,形状为 ( N , C , H i n , W i n ) (N, C, H_{in}, W_{in}) (N,C,Hin,Win),返回值也是一个四维张量,形状为 ( N , C , H o u t , W o u t ) (N, C, H_{out}, W_{out}) (N,C,Hout,Wout),其中 N N N是批量大小, C , H , W C, H, W C,H,W分别表示通道数、高度、宽度。
平均池化层使用的是nn.AvgPool2d,使用方法与nn.MaxPool2d相同。

代码讲解

X = torch.arange(32, dtype=torch.float32).view(1, 2, 4, 4)
pool2d = nn.MaxPool2d(kernel_size=3, padding=1, stride=(2, 1))
Y = pool2d(X)
print(X)
print(Y)

tensor([[[[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.]],

[[16., 17., 18., 19.],
[20., 21., 22., 23.],
[24., 25., 26., 27.],
[28., 29., 30., 31.]]]])

tensor([[[[ 5., 6., 7., 7.],
[13., 14., 15., 15.]],

[[21., 22., 23., 23.],
[29., 30., 31., 31.]]]])

你可能感兴趣的:(Pytorch深度学习)