import torch
from pprint import pprint
from torch import nn
'''
填充和步幅
之前,我们使用高和宽为3的输入与高和宽为2的卷积核得到高和宽为2的输出。一般来说,我们假设
输入形状是n(h,w),卷积核窗口的形状是k(h,w),那么输出的形状将会是
(n(h,)-k(h,)+1)*(n(,w)-k(,w)+1)
所以卷积层的输出形状由输入形状和卷积核窗口形状决定。填充和步幅可以对给定形状的输入和卷积核
改变输出形状。
'''
# 填充
'''
填充是值在输入高和宽两侧填充元素(通常是0)。
一般来说如果在高的两侧一拱填充p(h,),在宽的两侧填充p(,w)行,那么输出的形状将会是
(n(h,)-k(h,)+p(h,)+1)*(n(,w)-k(,w)+p(,w)+1)
也就是说 输出的高和宽分别增加p(h,)和p(,w)
一般情况下会设置p(,w)=k(,w)-1 p(h,)=k(h,)-1,这样就会保持输入输出的形状一样,具有相同的高
和宽。这样以便在构造网络的时候来推测每个层的输出形状。假设这里k(,h)是奇数,我们会在搞的两侧
分别填充p(h,)/2行,如果是偶数,一种可能是再输入的顶端一侧填充[p(h,)/2],而早低端一侧填充[p(h,)/2]
在宽的两侧同理
卷积神经网络经常使用奇数高和宽的卷积核 如1,3,5和7,所以两端上的填充个数相等。对任意二维数组x,设他的第i行
第j列的元素为x[i,j]。当两端上填充个数相等,并使输入和输出具有相等的高和宽时,我们就知道输出Y[i,j]是由
输入以x[i,j]为中心的窗口同卷积核进项互相关计算得到的。
为了表述简洁, 当输入的高和宽两侧的填充书分别是p(h,)和p(,w)时,我们称填充为(p(h,),p(,w))特别的 当
Ph=Pw=P的时候 填充为P。当在高和宽上的步幅分别为Sh和Sw时,我们称步幅为(S(h,),S(,w))。特别的,当
Sh=Sw=S时,步幅为s,在默认情况下,填充为0,步幅为1
总结
填充可以增加输出的高和宽,这常用来使用输出与输入具有相同的高和宽
步幅可以减少输出的高和宽,例如输出的高和宽仅为输入的高和宽的1/n(n大于1的整数)
'''
def comp_conv2d(conv2d, x):
x = x.view((1, 1) + x.shape)
y = conv2d(x)
return y.view(y.shape[2:])
# 卷积核相同时
conv2d = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, padding=1)
x = torch.rand(8, 8)
y = comp_conv2d(conv2d, x).shape
print(y)
# 使用搞为5,宽为3的卷积核。在高和宽两侧的填充数分别是2,1
conv2d = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=(5, 3), padding=(2, 1))
print(comp_conv2d(conv2d, x).shape)
# 步幅
'''
在上一节我们介绍了二维互相关运算。卷积窗口从输入数组的最左上方开,从左到右,从上到下的顺序,依次再输入数组上滑动
每次滑动的行数和列数成为步幅。图5.3展示了在高上步幅为3 宽上步幅为2的二维互相关运算,即卷积核函数向左移动了两个单位
向下移动了3个单位而在输出第一行第二个元素时卷积窗口向右滑动了2列。当卷积窗口在输入上再向右欢动2列时,由于输入元素
无法填满窗口,无法输出结果。
一般来说高上的步幅是Sh,宽上的步幅是Sw时,的输出形状是
eg:
输入形状是n(h,w),卷积核窗口的形状是k(h,w)
高的两侧一拱填充p(h,),在宽的两侧填充p(,w)行
[((n(h,)-k(h,)+p(h,)+Sh))/Sh]*[((n(,w)-k(,w)+p(,w)+Sw))/Sw]
假设 p(,w)=k(,w)-1 p(h,)=k(h,)-1, 那么输出形状可以简化为
[((n(h,)-1+Sh))/Sh]*[((n(,w)-1+Sw))/Sw]
如果 输入的高和宽能分别被搞上的步幅整除那么输出的形状将是(n(h,)/Sh,n(,w)//Sw)
'''
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
print(comp_conv2d(conv2d, x).shape)