需要接着为什么需要卷积层 | 卷积公式推导看。
经过第一节的推到和演示,我们已经能get到卷积的输出形状取决于输入形状和卷积核的形状。
输入的大小为$mn$,卷积核的大小为$ab$,那输出的大小就是$m-a+1*n-b+1$。
其实除此之外,影响卷积核大小的还有填充和步幅。
填充padding
所谓的填充就是给图片加边。
现在我给图片外边加上一像素的边。
那计算结果就从$36$变为$58$了。
每条边加上1像素也就是多出两行,多出两列。那结果矩阵也是多出两行两列。
一般的情况下,我们需要设置 $p_h=k_h-1$ 和 $p_w=k_w-1$,这样就可以使输入和输出具有相同的高度和宽度。
- p输入矩阵
- k卷积核
假设 $k_h$ 是奇数,我们将在高度的两侧填充 $p_h/2$ 行。
如果 $k_h$ 是偶数,则一种可能性是在输入顶部填充 $\lceil p_h/2\rceil$ 行,在底部填充 $\lfloor p_h/2\rfloor$ 行。
填充宽度的两侧同理。
选择奇数的好处是,保持空间维度的同时,我们可以在顶部和底部填充相同数量的行,在左侧和右侧填充相同数量的列。
对于任何二维张量 X
,当满足:
- 内核的大小是奇数;
- 所有边的填充行数和列数相同;
- 输出与输入具有相同高度和宽度
则可以得出:输出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
步幅呢就是每次挪动的大小,之前我们都是默认挪动一个,那现在默认挪动两个呢?
那计算结果就从$36$变为$23$了。
通常,当垂直步幅为 $s_h$ 、水平步幅为 $s_w$ 时,输出形状为$\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$
如果我们设置了 $p_h=k_h-1$ 和 $p_w=k_w-1$,则输出形状将简化为 $\lfloor(n_h+s_h-1)/s_h\rfloor \times \lfloor(n_w+s_w-1)/s_w\rfloor$。
更进一步,如果输入的高度和宽度可以被垂直和水平步幅整除,则输出形状将为 $(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,由此可以得到结果也是缩小的,并且符合上边推出来的公式。