动手学深度学习6.2 填充和步幅 padding and strides

1,405 阅读3分钟

参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战


需要接着动手学深度学习6.1 为什么需要卷积层 | 卷积公式推导看。

经过第一节的推到和演示,我们已经能get到卷积的输出形状取决于输入形状和卷积核的形状。

输入的大小为mnm*n,卷积核的大小为aba*b,那输出的大小就是ma+1nb+1m-a+1*n-b+1

其实除此之外,影响卷积核大小的还有填充和步幅。

填充padding

所谓的填充就是给图片加边。

image.png

现在我给图片外边加上一像素的边。

那计算结果就从363*6变为585*8了。

每条边加上1像素也就是多出两行,多出两列。那结果矩阵也是多出两行两列。

一般的情况下,我们需要设置 ph=kh1p_h=k_h-1pw=kw1p_w=k_w-1,这样就可以使输入和输出具有相同的高度和宽度。

  • p输入矩阵
  • k卷积核

假设 khk_h 是奇数,我们将在高度的两侧填充 ph/2p_h/2 行。

如果 khk_h 是偶数,则一种可能性是在输入顶部填充 ph/2\lceil p_h/2\rceil 行,在底部填充 ph/2\lfloor p_h/2\rfloor 行。

填充宽度的两侧同理。

选择奇数的好处是,保持空间维度的同时,我们可以在顶部和底部填充相同数量的行,在左侧和右侧填充相同数量的列。

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

  1. 内核的大小是奇数;
  2. 所有边的填充行数和列数相同;
  3. 输出与输入具有相同高度和宽度 则可以得出:输出 Y[i, j] 是通过以输入 X[i, j] 为中心,与卷积核进行互相关计算得到的。
import torch
from torch import nn
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1)

# 为了方便起见,我们定义了一个计算卷积层的函数。
# 此函数初始化卷积层权重,并对输入和输出提高和缩减相应的维数
def comp_conv2d(conv2d, X):
    # 这里的(1,1)表示批量大小和通道数都是1
    X = X.reshape((1, 1) + X.shape)
    Y = conv2d(X)
    # 省略前两个维度:批量大小和通道
    return Y.reshape(Y.shape[2:])

# 请注意,这里每边都填充了1行或1列,因此总共添加了2行或2列
X = torch.rand(size=(5, 8))
print(comp_conv2d(conv2d, X).shape)
>>
torch.Size([5, 8])
  • 经过卷积操作之后大小不变,还是5*8的张量。
  • 注意这里X.reshape((1, 1) + X.shape),两个元组相加是拼接,不是数字相加嗷。

步幅strides

步幅呢就是每次挪动的大小,之前我们都是默认挪动一个,那现在默认挪动两个呢?

image.png

那计算结果就从363*6变为232*3了。

通常,当垂直步幅为 shs_h 、水平步幅为 sws_w 时,输出形状为(nhkh+ph+sh)/sh×(nwkw+pw+sw)/sw\lfloor(n_h-k_h+p_h+s_h)/s_h\rfloor \times \lfloor(n_w-k_w+p_w+s_w)/s_w\rfloor

如果我们设置了 ph=kh1p_h=k_h-1pw=kw1p_w=k_w-1,则输出形状将简化为 (nh+sh1)/sh×(nw+sw1)/sw\lfloor(n_h+s_h-1)/s_h\rfloor \times \lfloor(n_w+s_w-1)/s_w\rfloor。 更进一步,如果输入的高度和宽度可以被垂直和水平步幅整除,则输出形状将为 (nh/sh)×(nw/sw)(n_h/s_h) \times (n_w/s_w)

conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
comp_conv2d(conv2d, X).shape
>>py
torch.Size([3, 4])

接上边的代码中的X用,把二维交叉运算的步幅设置为横向纵向都是2,由此可以得到结果也是缩小的,并且符合上边推出来的公式。


  1. 《动手学深度学习》系列更多可以看这里:《动手学深度学习》专栏(juejin.cn)

  2. 笔记Github地址:DeepLearningNotes/d2l(github.com)

还在更新中…………