【动手学习深度学习】—0x06:卷积神经网络基础知识
1)二维卷积层
二维互相关运算
- 在二维卷积层中,一个二维输入数组和一个二维核(kernel)数组通过互相关运算输出一个二维数组
- 在二维互相关运算中,卷积窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动
- 当卷积窗口滑动到某一位置时,窗口中的输入子数组与核数组按元素相乘并求和,得到输出数组中相应位置的元素
其中的4个元素由二维互相关运算得出:
0×0+1×1+3×2+4×3=19,
1×0+2×1+4×2+5×3=25,
3×0+4×1+6×2+7×3=37,
4×0+5×1+7×2+8×3=43.
def corr2d(X, K): # 本函数已保存在d2lzh_pytorch包中方便以后使用,实现互相关运算
h, w = K.shape
Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - 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
二位卷积层
- 二维卷积层将输入和卷积核做互相关运算,并加上一个标量偏差来得到输出。
- 卷积层的模型参数包括了卷积核和标量偏差。
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
互相关运算和卷积运算
为了得到卷积运算的输出,我们只需将核数组左右翻转并上下翻转,再与输入数组做互相关运算
特征图和感受野
二维卷积层输出的二维数组可以看作是输入在空间维度(宽和高)上某一级的表征,也叫特征图(feature map)
影响元素x的前向计算的所有可能输入区域(可能大于输入的实际尺寸)叫做x的感受野(receptive field)
2)填充和步幅
填充(padding)
我们在原输入高和宽的两侧分别添加了值为0的元素,使得输入高和宽从3变成了5,并导致输出高和宽由2增加到4
输出和输入具有相同的高和宽:p_h = k_h - 1, p_w = k_w - 1
假设这里k_h是奇数,我们会在高的两侧分别填充p_h / 2行。如果是k_h偶数,一种可能是在输入的顶端一侧填充⌈p_h/2⌉行,而在底端一侧填充⌊p_h/2⌋行。在宽的两侧填充同理。
卷积神经网络经常使用奇数高宽的卷积核,如1、3、5和7,所以两端上的填充个数相等。
注:padding是在两侧同时添加
# 定义一个函数来计算卷积层。它对输入和输出做相应的升维和降维
def comp_conv2d(conv2d, X):
# (1, 1)代表批量大小和通道数(“多输入通道和多输出通道”一节将介绍)均为1
X = X.view((1, 1) + X.shape)
Y = conv2d(X)
return Y.view(Y.shape[2:]) # 排除不关心的前两维:批量和通道
# 注意这里是两侧分别填充1行或列,所以在两侧一共填充2行或列
conv2d = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, padding=1)
# 使用高为5、宽为3的卷积核。在高和宽两侧的填充数分别为2和1
conv2d = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=(5, 3), padding=(2, 1))
步幅(stride)
我们将每次滑动的行数和列数称为步幅(stride)。
展示了在高上步幅为3、在宽上步幅为2的二维互相关运算
先设置p_h = k_h - 1, p_w = k_w - 1,那么输出形状简化为⌊(n_h+s_h−1)/s_h⌋×⌊(n_w+s_w−1)/s_w⌋。更进一步,如果输入的高和宽能分别被高和宽熵的步幅整除,那么输出形状⌊(n_h)/s_h⌋×⌊(n_w)/s_w⌋。
conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
3)多输入通道和多输出通道
- 彩色图像在高和宽2个维度外还有RGB(红、绿、蓝)3个颜色通道
- 假设彩色图像的高和宽分别是h和w(像素),那么它可以表示为一个3×h×w3×h×w的多维数组。我们将大小为3的这一维称为通道(channel)维
多输入通道
当输入数据含多个通道时,我们需要构造一个输入通道数与输入数据的通道数相同的卷积核,从而能够与含多通道的输入数据做互相关运算。
由于输入和卷积核各有c_i个通道,我们可以在各个通道上对输入的二维数组和卷积核的二维核数组做互相关运算,再将这c_i个互相关运算的二维输出按通道相加,得到一个二维数组
def corr2d_multi_in(X, K):
# 沿着X和K的第0维(通道维)分别计算再相加
res = d2l.corr2d(X[0, :, :], K[0, :, :])
for i in range(1, X.shape[0]):
res += d2l.corr2d(X[i, :, :], K[i, :, :])
return res
多输出通道
如果希望得到含多个通道的输出,我们可以为每个输出通道分别创建形状为c_i×k_h×k_w的核数组,将它们在输出通道维上连结,卷积核的形状即c_o ×c_i×k_h×k_w
def corr2d_multi_in_out(X, K):
# 对K的第0维遍历,每次同输入X做互相关计算。所有结果使用stack函数合并在一起
return torch.stack([corr2d_multi_in(X, k) for k in K])
1×1卷积层
- 卷积窗口形状为1×1(k_w = k_h = 1)的多通道卷积层,称为1×1卷积层
- 因为使用了最小窗口,1×11×1卷积失去了卷积层可以识别高和宽维度上相邻元素构成的模式的功能
- 1×11×1卷积的主要计算发生在通道维上
- 输入和输出具有相同的高和宽。输出中的每个元素来自输入中在高和宽上相同位置的元素在不同通道之间的按权重累加。===>1×1卷积层的作用与全连接层等价。
4)池化层
作用:为了缓解卷积层对位置的过度敏感性。
二维最大池化层和平均池化层
池化层每次对输入数据的一个固定形状窗口(又称池化窗口)
在二维最大池化中,池化窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动
当池化窗口滑动到某一位置时,窗口中的输入子数组的最大值即输出数组中相应位置的元素。
def pool2d(X, pool_size, mode='max'):
X = X.float()
p_h, p_w = pool_size
Y = torch.zeros(X.shape[0] - p_h + 1, X.shape[1] - p_w + 1)
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
if mode == 'max':
Y[i, j] = X[i: i + p_h, j: j + p_w].max()
elif mode == 'avg':
Y[i, j] = X[i: i + p_h, j: j + p_w].mean()
return Y
填充和步幅
- 池化层填充和步幅与卷积层填充和步幅的工作机制一样
X = torch.arange(16, dtype=torch.float).view((1, 1, 4, 4))
# 默认情况下,MaxPool2d实例里步幅和池化窗口形状相同。下面使用形状为(3, 3)的池化窗口,默认获得形状为(3, 3)的步幅。
pool2d = nn.MaxPool2d(3)
# 可以手动指定
pool2d = nn.MaxPool2d(3, padding=1, stride=2)
pool2d = nn.MaxPool2d((2, 4), padding=(1, 2), stride=(2, 3))
多通道
池化层对每个输入通道分别池化,而不是像卷积层那样将各通道的输入按通道相加
这意味着池化层的输出通道数与输入通道数相等
参考资料
https://tangshusen.me/Dive-into-DL-PyTorch/#/chapter05_CNN/5.4_pooling