文章目录
- 为什么需要padding?
- 1.Valid Padding(有效填充)
- 2.Same Padding(相同填充)
- 2.1.如何计算padding?
- 1. 计算总 padding
- 2. 分配 padding:
- 2.2.举例子
- 1. 步幅为 1 的 Same Padding
- 2. 步幅不为 1 的 Same Padding
- 3.F.pad()和卷积中的padding互相转换
- 1.torch.nn.functional.pad()函数
- 2.举例
为什么需要padding?
- 控制输出尺寸: 卷积操作通常会减少输入图像的尺寸。例如,使用3×3
卷积核时,边缘的像素不会像中间部分的像素一样得到处理(因为卷积核无法完全跨越边缘区域),导致输出图像的尺寸小于输入图像。通过适当的
padding,可以保持输入和输出尺寸相同,或者控制输出的尺寸。 - 保留边缘信息: 如果没有padding,卷积操作会丢失输入图像的边缘部分的信息。通过为输入图像添加零填充,卷积核可以处理这些边缘区域,从而保留更多的信息。
1.Valid Padding(有效填充)
- 定义:在卷积操作中没有额外的填充,也就是在卷积时不添加任何零填充。此时,输出图像的尺寸会小于输入图像,具体减少的大小取决于卷积核的尺寸和步幅。
- 输出尺寸:由于没有 padding,输出尺寸通常会小于输入尺寸,且与卷积核大小和步幅有关。
- 优点:计算效率较高,减少了不必要的计算。
- 缺点:会丢失图像的边缘信息。
2.Same Padding(相同填充)
- 定义:通过添加零填充来确保输出的尺寸与输入尺寸相同(在步幅为 1 时)。为了实现这一点,通常需要在输入的边缘添加一定数量的零。
- 输出尺寸:输入和输出尺寸相同(对于步幅为1的情况)。当步幅大于1时,输出尺寸会根据步幅的大小调整,但仍尽量保持输入和输出的比例相同。
- 优点:能够保留输入的边缘信息,适用于需要保持特征图大小不变的任务。
- 缺点:可能会引入一些零值,这对网络学习可能是无用的。
2.1.如何计算padding?
1. 计算总 padding
P_h = (H - 1) * S + K - H
P_w = (W - 1) * S + K - W
2. 分配 padding:
P_top = P_h // 2
P_bottom = P_h - P_top
P_left = P_w // 2
P_right = P_w - P_left
2.2.举例子
1. 步幅为 1 的 Same Padding
当stride = 1,dilation=1,卷积公式的输出为H_out = H_in+2p-k+1,要保证H_out = H_in,所以2p = k-1,p = (k-1)/2。假设输入尺寸是 5×5,卷积核大小是 3×3,步幅为 1。我们希望卷积操作后的输出尺寸与输入相同。计算padding:
import torch
import torch.nn as nn
# 定义输入数据(5x5)
input_data = torch.randn(1, 1, 5, 5) # Batch size = 1, Channels = 1, Height = 5, Width = 5
# 定义卷积层,kernel_size = 3, padding = 1 (为了使用same padding)
conv_layer = nn.Conv2d(1, 1, kernel_size=3, stride=1, padding=1)
# 输出卷积后的数据
output_data = conv_layer(input_data)
print(f'输入形状: {input_data.shape}')
print(f'输出形状: {output_data.shape}')
输入形状: torch.Size([1, 1, 5, 5])
输出形状: torch.Size([1, 1, 5, 5])
2. 步幅不为 1 的 Same Padding
根据公式推导得,P = (S(H-1)-H+K)/2,当S=2,上述例子为,P = floor[(8-4+3)/2]=3
import torch
import torch.nn as nn
# 定义输入数据(5x5)
input_data = torch.randn(1, 1, 5, 5) # Batch size = 1, Channels = 1, Height = 5, Width = 5
# 定义卷积层,kernel_size = 3, padding = 1 (为了使用same padding)
conv_layer = nn.Conv2d(1, 1, kernel_size=3, stride=2, padding=3)
# 输出卷积后的数据
output_data = conv_layer(input_data)
print(f'输入形状: {input_data.shape}')
print(f'输出形状: {output_data.shape}')
输入形状: torch.Size([1, 1, 5, 5])
输出形状: torch.Size([1, 1, 5, 5])
3.F.pad()和卷积中的padding互相转换
1.torch.nn.functional.pad()函数
torch.nn.functional.pad(input, pad, mode='constant', value=0)
参数说明
- input (Tensor): 输入张量。
- pad (tuple or int): 指定每个维度上的填充宽度。可以是一个整数或一个元组。
- 如果是整数,表示所有维度的两侧都填充相同的值。
- 如果是元组,元组的长度必须是输入张量维度的两倍。元组的形式为 (pad_left,pad_right, pad_top, pad_bottom, …),表示每个维度上的填充宽度。
- mode (str, optional): 填充模式,默认为 ‘constant’。可选值有:
- constant: 使用常数值填充,默认值为 0。
- reflect: 使用边界元素的反射值填充。
- replicate:使用边界元素的复制值填充。
- circular: 使用循环方式填充。
- value (float, optional): 当 mode 为 constant’时,指定填充的常数值,默认为 0。
2.举例
input = torch.arange(1, 65,dtype=torch.float).view(2, 2, 4, 4)
padded_input = F.pad(input, pad=[2, 2, 1, 1], mode='constant', value=0)
conv_padding = nn.Conv2d(in_channels=2, out_channels=2, kernel_size=2,stride=1,padding=[1,2],padding_mode='zeros',bias=False)
conv = nn.Conv2d(in_channels=2, out_channels=2, kernel_size=2,stride=1,bias=False)
conv.weight.data = torch.ones(2,2,2,2)
conv_padding.weight.data = torch.ones(2,2,2,2)
out11 = conv_padding(input)
out2 = conv(padded_input)
print(torch.equal(out11,out2)) # True
tensor([[[[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 1., 2., 3., 4., 0., 0.],
[ 0., 0., 5., 6., 7., 8., 0., 0.],
[ 0., 0., 9., 10., 11., 12., 0., 0.],
[ 0., 0., 13., 14., 15., 16., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.]],
[[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 17., 18., 19., 20., 0., 0.],
[ 0., 0., 21., 22., 23., 24., 0., 0.],
[ 0., 0., 25., 26., 27., 28., 0., 0.],
[ 0., 0., 29., 30., 31., 32., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.]]],
[[[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 33., 34., 35., 36., 0., 0.],
[ 0., 0., 37., 38., 39., 40., 0., 0.],
[ 0., 0., 41., 42., 43., 44., 0., 0.],
[ 0., 0., 45., 46., 47., 48., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.]],
[[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 49., 50., 51., 52., 0., 0.],
[ 0., 0., 53., 54., 55., 56., 0., 0.],
[ 0., 0., 57., 58., 59., 60., 0., 0.],
[ 0., 0., 61., 62., 63., 64., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.]]]])
import pdb;pdb.set_trace()
(Pdb) padded_input
tensor([[[[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 1., 2., 3., 4., 0., 0.],
[ 0., 0., 5., 6., 7., 8., 0., 0.],
[ 0., 0., 9., 10., 11., 12., 0., 0.],
[ 0., 0., 13., 14., 15., 16., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.]],
[[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 17., 18., 19., 20., 0., 0.],
[ 0., 0., 21., 22., 23., 24., 0., 0.],
[ 0., 0., 25., 26., 27., 28., 0., 0.],
[ 0., 0., 29., 30., 31., 32., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.]]],
[[[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 33., 34., 35., 36., 0., 0.],
[ 0., 0., 37., 38., 39., 40., 0., 0.],
[ 0., 0., 41., 42., 43., 44., 0., 0.],
[ 0., 0., 45., 46., 47., 48., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.]],
[[ 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 49., 50., 51., 52., 0., 0.],
[ 0., 0., 53., 54., 55., 56., 0., 0.],
[ 0., 0., 57., 58., 59., 60., 0., 0.],
[ 0., 0., 61., 62., 63., 64., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0.]]]])