卷积神经网络-填充和步幅

正如前面所说的:
假设输入形状为\(n_h × n_w\),卷积核形状为\(k_h × k_w\),那么输出形状将是\((n_h − k_h + 1) × (n_w − k_w + 1)\)。因此,卷积的输出形状取决于输入形状和卷积核的形状。

假如有一个\(240 × 240\)像素的图像,经过10层\(5 × 5\)的卷积后,将减少到\(200 × 200\)像素。如此一来,原始图像的边界丢失了许多有用信息。而填充是解决此问题最有效的方法;有时,我们可能希望大幅降低图像的宽度和高度。例如,如果我们发现原始的输入分辨率十分冗余。步幅则可以在这类情况下提供帮助。

填充

在应用多层卷积时,我们常常丢失边缘像素。由于我们通常使用小卷积核,因此对于任何单个卷积,我们可能只会丢失几个像素。但随着我们应用许多连续卷积层,累积丢失的像素数就多了。解决这个问题的简单方法即为填充(padding):在输⼊图像的边界填充元素(通常填充元素是0)。
如图所示,我们将\(3 × 3\)输入填充到\(5 × 5\),那么它的输出就增加为4 × 4。阴影部分是第⼀个输出元素以及用于输出计算的输入和核张量元素\(0×0+0×1+0×2+0×3=0\)
image
通常,如果我们添加\(p_h\)行填充(大约一半在顶部,一半在底部)和\(p_w\)列填充(左侧大约⼀半,右侧一半),则
输出形状将为

\[(n_h-k_h+p_h+1)× (n_w-k_w+p_w+1) \]

这意味着输出的高度和宽度将分别增加\(p_h\)\(p_w\)

在许多情况下,我们需要设置\(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。选择奇数的好处是,保持空间维度的同时,我们可以在顶部和底部填充相同数量的行,在左侧和右侧填充相同数量的。
我们一般填充的值是你的卷积核-1。注意这个是上下一共填充的,比如说上面填充的得/2。
比如说kernel_size=3,我们的padding=(3-1)/2。

注意,使用奇数的核大小和填充大小也提供了书写上的便利。对于任何⼆维张量X,当满足:
1.卷积核的大小是奇数
2.所有边的填充行数和列数相同
3.输入和输出具有形同宽度和高度则可以看出:输出的\(Y[i,j]是通过以输入\)X[i,j]为中心,与卷积核进行互相关计算得到的。$$
我们创建一个高度和宽度为3的二维卷积层,并在所有侧边填充1个像素。给定高度和宽度为8的输入,则输出的高度和宽度也是8。

import torch
from torch import nn
def comp_conv2d(conv2d, X):
    # 这⾥的(1,1)表⽰批量⼤⼩和通道数都是
    X = X.reshape((1, 1) + X.shape)
    Y = conv2d(X)
    # 省略前两个维度:批量⼤⼩和通
    return Y.reshape(Y.shape[2:])

# 请注意,这⾥每边都填充了1⾏或1列,因此总共添加了2⾏或2列
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1) #输入通道,输出通道,卷积核大小,填充
X = torch.rand(size=(8, 8))
comp_conv2d(conv2d, X).shape


# torch.Size([8, 8])

当卷积核的高度和宽度不同时,我们可以填充不同的高度和宽度,使输出和输⼊具有相同的高度和宽度。在如下示例中,我们使用高度为5,宽度为3的卷积核,高度和宽度两边的填充分别为2和1。

conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1)) #这个kernel_size正好都是奇数,所以只需要减一再除2就行,这里是先上下,后面一个是左右
comp_conv2d(conv2d, X).shape

# torch.Size([8, 8])

步幅

在计算互相关时,卷积窗口从输入张量的左上角开始,向下、向右滑动。在前面的例子中,我们默认每次滑动一个元素。但是,有时候为了高效计算或是缩减采样次数,卷积窗口可以跳过中间位置,每次滑动多个元素。我们将每次滑动元素的数量称为步幅(stride)。到目前为止,我们只使⽤过高度或宽度为1的步幅,那么如何使用较大的步幅呢?
如图是垂直步幅为3,水平步幅为2的二维互相关运算。着色部分是输出元素以及用于输出计算的输入和内核张量元素:0 × 0 + 0 × 1 + 1 × 2 + 2 × 3 = 8、0 × 0 + 6 × 1 + 0 × 2 + 0 × 3 = 6。
可以看到,为了计算输入中第一列的第二个元素和第一行的第二个元素,卷积窗口分别向下滑动三行和向右滑动两列。但是,当卷积窗口继续向右滑动两列时,没有输出,因为输入元素无法填充窗口(除非我们添加另一列填充)。
image
通常,当垂直步幅为\(s_h\)、水平步幅为\(s_w\)时,输出形状为:

\[⌊\frac{(n_h − k_h + p_h + s_h)}{s_h}⌋×⌊\frac{(n_w − k_w + p_w + s_w)}{sw}⌋. \]

如果我们设置了\(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)\)
下面,我们将高度和宽度的步幅设置为2,从而将输入的高度和宽度减半。

conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
comp_conv2d(conv2d, X).shape

# torch.Size([4, 4])

接下来,看⼀个稍微复杂的例子。

conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
comp_conv2d(conv2d, X).shape

# torch.Size([2, 2])
总结
·填充和步幅是卷积层的超参数
·填充在输入周围添加额外的行/列,来控制输出形状的减少量
·步幅是每次滑动核窗口时的行/列的步长,可以成倍的减少输出形状
posted @ 2023-08-14 16:09  lipu123  阅读(325)  评论(0)    收藏  举报