6-4 填充和步幅

在前面的例子 图6.2.1中,输入的高度和宽度都为 3 3 3,卷积核的高度和宽度都为 2 2 2,生成的输出表征的维数为 2 × 2 2\times 2 2×2。 正如我们在 6-2节中所概括的那样,假设输入形状为 n h × n w n_{h}\times n_{w} nh×nw,卷积核形状为 k h × k w k_{h}\times k_{w} kh×kw,那么输出形状将是 ( n h − k h + 1 ) × ( n w − k w + 1 ) (n_{h}-k_{h}+1)\times(n_{w}-k_{w}+1) (nh−kh+1)×(nw−kw+1)。 因此,卷积的输出形状取决于输入形状卷积核的形状

还有什么因素会影响输出的大小呢?本节我们将介绍填充(padding)步幅(stride)。假设以下情景:

  • 有时,在应用了连续的卷积之后,我们最终得到的输出远小于输入大小。这是由于卷积核的宽度和高度通常大于 1 1 1所导致的。比如,一个 240 × 240 240\times 240 240×240像素的图像,经过 10 10 10层 5 × 5 5\times 5 5×5的卷积后,将减少到 200 × 200 200\times 200 200×200像素。如此一来,原始图像的边界丢失了许多有用信息 。而填充是解决此问题最有效的方法
  • 有时,我们可能希望大幅降低图像的宽度和高度。例如,如果我们发现原始的输入分辨率十分冗余。步幅则可以在这类情况下提供帮助。

填充

如上所述,在应用多层卷积时,我们常常丢失边缘像素。 由于我们通常使用小卷积核,因此对于任何单个卷积,我们可能只会丢失几个像素。 但随着我们应用许多连续卷积层,累积丢失的像素数就多了。

解决这个问题的简单方法即为填充(padding)在输入图像的边界填充元素(通常填充元素是 0 0 0)。 例如,在 图6.3.1中,我们将 3 × 3 3\times 3 3×3输入填充到 5 × 5 5\times 5 5×5,那么它的输出就增加为 4 × 4 4\times 4 4×4。阴影部分是第一个输出元素以及用于输出计算的输入和核张量元素: 0 × 0 + 0 × 1 + 0 × 2 + 0 × 3 = 0 0\times 0+0\times 1+0\times 2+ 0\times 3=0 0×0+0×1+0×2+0×3=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 ) (n_{h}-k_{h}+p_{h}+1)\times(n_{w}-k_{w}+p_{w}+1) (nh−kh+ph+1)×(nw−kw+pw+1)

这意味着输出的高度宽度 将分别增加 p h p_{h} ph和 p w p_{w} pw。

在许多情况下,我们需要设置 p h = k h − 1 p_{h}=k_{h}-1 ph=kh−1和 p w = k w − 1 p_{w}=k_{w}-1 pw=kw−1,使输入和输出具有相同的高度和宽度。 这样可以在构建网络时更容易地预测每个图层的输出形状 。假设 k h k_{h} kh是奇数,我们将在高度的两侧填充 p h / 2 p_{h}/2 ph/2行。 如果 k h k_{h} kh是偶数,则一种可能性是在输入顶部填充 ⌈ p h / 2 ⌉ \lceil p_{h}/2 \rceil ⌈ph/2⌉行,在底部填充 ⌊ p h / 2 ⌋ \lfloor p_{h}/2 \rfloor ⌊ph/2⌋行。同理,我们填充宽度的两侧。

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

此外,使用奇数的核大小和填充大小也提供了书写上的便利。

对于任何二维张量 X X X,当满足:

  1. 卷积核的大小是奇数;
  2. 所有边的填充行数和列数相同;
  3. 输出与输入具有相同高度和宽度

则可以得出:输出 Y [ i , j ] Y[i, j] Y[i,j]是通过以输入 X [ i , j ] X[i, j] X[i,j]为中心,与卷积核进行互相关计算得到的。

比如,在下面的例子中,我们创建一个高度和宽度为 3 3 3的二维卷积层,并在所有侧边填充 1 1 1个像素。给定高度和宽度为 8 8 8的输入,则输出的高度和宽度也是 8 8 8。

在卷积神经网络中,如果你创建了一个高度和宽度都为 3 3 3的二维卷积层,并在所有侧边填充 1 1 1个像素,然后用这个卷积层处理一个高度和宽度都为 8 8 8的输入,输出的高度和宽度仍然是 8 8 8。下面是为什么这样设置可以达到这个效果的解释:

卷积核尺寸 :在这个例子中,卷积核(filter)的高度和宽度 k h = k w = 3 k_h = k_w = 3 kh=kw=3。这意味着每个卷积核覆盖了输入数据的 3 × 3 3 \times 3 3×3区域。

填充大小 :为了确保输出尺寸与输入尺寸相同,我们使用了填充。填充是在输入张量的边界添加额外的行和列,这里填充的尺寸 p h = p w = 2 p_h = p_w = 2 ph=pw=2。这意味着在输入的每一边(顶部、底部、左侧和右侧)都加上了一个像素的填充。

输出尺寸的计算 : 通常输出尺寸的公式是 ( n h − k h + p h + 1 ) × ( n w − k w + p w + 1 ) (n_{h} - k_{h} + p_{h} + 1) \times (n_{w} - k_{w} + p_{w} + 1) (nh−kh+ph+1)×(nw−kw+pw+1)。 在这个例子中,输入尺寸 n h = n w = 8 n_h = n_w = 8 nh=nw=8,卷积核尺寸 k h = k w = 3 k_h = k_w = 3 kh=kw=3,填充 p h = p w = 2 p_h = p_w = 2 ph=pw=2。 代入公式得到输出尺寸为 ( 8 − 3 + 2 + 1 ) × ( 8 − 3 + 2 + 1 ) = 8 × 8 (8 - 3 + 2 + 1) \times (8 - 3 + 2 +1) = 8 \times 8 (8−3+2+1)×(8−3+2+1)=8×8。
保持尺寸相同 :通过添加等量的填充,卷积操作可以在不改变数据的高度和宽度的情况下进行。这样做的好处是简化了网络结构的设计,因为你可以确保无论添加多少这种类型的层,输入与输出的尺寸都保持一致,这使得每层的输出都可以明确计算和预见。

因此,通过在高度和宽度为 3 3 3的卷积层周围加上 1 1 1像素的填充(这里说的填充,是加在输入张量上 的,周围加1,即为共加了 2 2 2行,加了 2 2 2列),并处理高度和宽度为 8 8 8的输入,可以确保输出仍然保持 8 × 8 8 \times 8 8×8的尺寸,这有助于在设计深层网络时维持结构的一致性

python 复制代码
import torch
from torch import nn

# 为了方便起见,我们定义了一个计算卷积层的函数。
# 此函数初始化卷积层权重,并对输入和输出提高和缩减相应的维数
# 接受两个参数:conv2d(一个卷积层对象)和 X(输入数据张量)
def comp_conv2d(conv2d, X):
    # 这里的(1,1)表示批量大小和通道数都是1
    X = X.reshape((1, 1) + X.shape)
    # 将输入张量 X 的形状重塑为 (1, 1, height, width)
    # 这里的 (1, 1) 分别表示批量大小和通道数都是 1
    # PyTorch 的卷积层通常期望输入的形状为 (batch_size, channels, height, width)。
    Y = conv2d(X)
    # conv2d 对象被应用于已调整形状的输入 X
    # conv2d 是一个卷积层,它执行卷积运算并输出结果 Y。
    
    return Y.reshape(Y.shape[2:]) # 省略前两个维度:批量大小和通道
    # 去除 Y 的前两个维度(批量大小和通道数),只保留高度和宽度,这样输出就是卷积结果的空间维度。

# 请注意,这里每边都填充了1行或1列,因此总共添加了2行或2列
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1)
# 创建了一个 Conv2d 卷积层,具有 1 个输入通道和 1 个输出通道,卷积核大小为 3(3x3),并且在所有边界上填充了 1 个像素(这样做可以保持输入和输出的空间尺寸相同)。
# padding=1意思是在上下左右各填充一行!

X = torch.rand(size=(8, 8))
# 生成一个形状为 (8, 8) 的张量,填充了 0 到 1 之间的随机数,代表一个 8x8 的图像数据。
comp_conv2d(conv2d, X).shape
# 使用定义好的 comp_conv2d 函数来计算卷积,并输出处理后的数据的形状。
# 由于卷积层使用了填充,输入和输出的形状应该是相同的,即 (8, 8)。


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

python 复制代码
conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1))
comp_conv2d(conv2d, X).shape

这个例子其实也说明了, p h p_{h} ph和 p w p_{w} pw在实际使用的时候可以分别设置不一样的

巧记

  • p h = k h − 1 p_{h}=k_{h}-1 ph=kh−1和 p w = k w − 1 p_{w}=k_{w}-1 pw=kw−1
    p h p_{h} ph是指在高度上,在原始输入的上部和下部,一共添加了多少行
    p w p_{w} pw是指在宽度上,在原始输入的左边和右边,一共添加了多少行

  • nn.Conv2d中的padding=(x,y),这里的(x,y)指的是,在上部和下部分别添加x行,在左边和右边分别添加y列,padding参数的值是 p h p_{h} ph和 p w p_{w} pw的一半。

  • 输出形状计算公式: ( n h − k h + p h + 1 ) × ( n w − k w + p w + 1 ) (n_{h}-k_{h}+p_{h}+1)\times(n_{w}-k_{w}+p_{w}+1) (nh−kh+ph+1)×(nw−kw+pw+1)

  • 卷积神经网络中卷积核的高度和宽度通常为奇数


步幅

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

我们将每次滑动元素的数量称为步幅(stride)。到目前为止,我们只使用过高度或宽度为 1 1 1的步幅,那么如何使用较大的步幅呢? 图6.3.2是垂直步幅为 3 3 3,水平步幅为 2 2 2的二维互相关运算。 着色部分是输出元素以及用于输出计算的输入和内核张量元素: 0 × 0 + 0 × 1 + 1 × 2 + 2 × 3 = 8 0\times 0+0\times 1+1\times2+2\times3=8 0×0+0×1+1×2+2×3=8、 0 × 0 + 6 × 1 + 0 × 2 + 0 × 3 = 6 0\times0+6\times1+0\times2+0\times3=6 0×0+6×1+0×2+0×3=6。

可以看到,为了计算输出中第一列的第二个元素和第一行的第二个元素,卷积窗口分别向下滑动三行和向右滑动两列。但是,当卷积窗口继续向右滑动两列时,没有输出,因为输入元素无法填充窗口(除非我们添加另一列填充)。

通常,当垂直步幅为 s h s_{h} sh、水平步幅为 s w s_{w} sw时,输出形状为:

下面,我们将高度和宽度的步幅设置为 2 2 2,从而将输出的高度和宽度减半。

python 复制代码
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
comp_conv2d(conv2d, X).shape

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

python 复制代码
conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
comp_conv2d(conv2d, X).shape

为了简洁起见,当输入高度和宽度两侧的填充数量分别为 p h p_{h} ph和 p w p_{w} pw时,我们称之为填充 ( p h , p w ) (p_{h},p_{w}) (ph,pw)。当 p h = p w = p p_{h}=p_{w}=p ph=pw=p时,填充是 p p p。

同理,当高度和宽度上的步幅分别为 s h s_{h} sh和 s w s_{w} sw时,我们称之为步幅 ( s h , s w ) (s_{h},s_{w}) (sh,sw)。特别地,当 s h = s w = s s_{h}=s_{w}=s sh=sw=s时,我们称步幅为 s s s。

默认情况下,填充为 0 0 0,步幅为 1 1 1。
在实践中,我们很少使用不一致的步幅或填充 ,也就是说,我们通常有 p h = p w p_{h}=p_{w} ph=pw和 s h = s w s_{h}=s_{w} sh=sw。

小结

  • 填充可以增加输出的高度和宽度。这常用来使输出与输入具有相同的高和宽。

  • 步幅可以减小输出的高和宽 ,例如输出的高和宽仅为输入的高和宽的 1 / n 1/n 1/n

    ( n n n是一个大于 1 1 1的整数)。

  • 填充和步幅可用于有效地调整数据的维度


可以通过统筹选择设置填充和步幅 ,来将矩形的输入张量,转换为指定高宽比的输出张量


小trick

  • 一般来说,填充的目的是让输入张量和输出张量的形状一致
  • 通常来讲,步幅等于 1 1 1是最好的,如果不选步幅为 1 1 1,一般是我们觉得计算量太大了,需要很多很多层才会使得我这个图片变得越来越小,我不想真的用很多层去做这个事情。通常来说步幅是取 2 2 2,每一次减半。步幅取多少,一般看我想把模型复杂度控制在什么程度。
  • 步幅、填充、核大小和通道数,都是一个神经网络架构的一部分,是我整个网络怎么设计的一部分。
  • 在实际情况中,我们一定是参照经典的网络结构做设计
相关推荐
zd20057210 小时前
两个人群填充参考(CHN100K和NARD)
填充·基因组
chuanzhi_tech2 天前
传知代码-融合经典与创新的图像分类新途径
图像处理·人工智能·深度学习·神经网络·卷积神经网络
高-老师2 天前
【视频教程】基于PyTorch深度学习无人机遥感影像目标检测、地物分类及语义分割实践技术应用
pytorch·深度学习·卷积神经网络·无人机·地物分类·遥感影像目标检测·无人机航拍
高-老师2 天前
【视频教程】基于python深度学习遥感影像地物分类与目标识别、分割实践技术应用
python·深度学习·tensorflow·卷积神经网络·遥感影像
逼子格6 天前
深度学习驱动的车牌识别:技术演进与未来挑战
深度学习·神经网络·yolo·卷积神经网络·车牌识别·字符识别
阿_旭13 天前
【保姆级教程】使用 PyTorch 自定义卷积神经网络(CNN) 实现图像分类、训练验证、预测全流程【附数据集与源码】
pytorch·深度学习·cnn·卷积神经网络·图像分类
Francek Chen18 天前
【机器学习-神经网络】卷积神经网络
神经网络·机器学习·cnn·卷积神经网络·图像分类·vgg网络
简简单单做算法24 天前
基于贝叶斯优化卷积神经网络(Bayes-CNN)的多因子数据分类识别算法matlab仿真
人工智能·分类·卷积神经网络·贝叶斯优化·bayes-cnn·多因子数据分类·bo-cnn
Fuliy961 个月前
深度学习基础之卷积神经网络
人工智能·python·深度学习·神经网络·cnn·卷积神经网络
胖哥真不错1 个月前
Python基于TensorFlow实现卷积神经网络-双向长短时记忆循环神经网络分类模型(CNN-BiLSTM分类算法)项目实战
python·tensorflow·卷积神经网络·项目实战·cnn-bilstm·分类模型·双向长短时记忆循环神经网络