0%

torch | 填充方式

卷积操作作为卷积神经网络的核心模块,在其计算过程中必须考虑图像“边缘像素”的卷积方式。查阅资料发现,我们可以采用“卷积之前进行边界填充”或“卷积之后进行边界填充两种方式”。同时边界填充的具体手段包含

  • 常量填充
  • 零填充
  • 镜像填充
  • 重复填充
1
x = torch.Tensor([[1, 2, 3], [4, 5, 6]])

零填充ZeroPad2d

我们最常用的是nn.ZeroPad2d,也就是对Tensor使用0进行边界填充,我们可以指定tensor的四个方向上的填充数,比如左边添加1dim、右边添加2dim、上边添加3dim、下边添加4dim,即指定padding参数为(1,2,3,4),如下:

1
2
3
4
x = torch.Tensor([[1, 2, 3], [4, 5, 6]])

pad = nn.ZeroPad2d(padding=(1, 2, 3, 4))
y = pad(x)

输出

1
2
3
4
5
6
7
8
9
tensor([[0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0.],
[0., 1., 2., 3., 0., 0.],
[0., 4., 5., 6., 0., 0.],
[0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0.]])

常数填充ConstantPad2d

零填充是常数填充的一个特例,常数填充nn.ConstantPad2d()需要我们指定填充所用的常数值value核填充数padding,这里选择四个方向上均填充为1dim,即padding为(1,1,1,1),代码如下:

1
2
pad = nn.ConstantPad2d(padding=(1, 2, 3, 4), value=666)
y = pad(x)

输出

1
2
3
4
5
6
7
8
9
tensor([[666., 666., 666., 666., 666., 666.],
[666., 666., 666., 666., 666., 666.],
[666., 666., 666., 666., 666., 666.],
[666., 1., 2., 3., 666., 666.],
[666., 4., 5., 6., 666., 666.],
[666., 666., 666., 666., 666., 666.],
[666., 666., 666., 666., 666., 666.],
[666., 666., 666., 666., 666., 666.],
[666., 666., 666., 666., 666., 666.]])

镜像填充ReflectionPad2d

ps: 关于这个我也只是一知半解,所以,用的话还是需要查阅资料的。

镜像填充的方式相比于前面使用固定数值进行填充,有可能获得更好的卷积结果。镜像填充封装在nn.ReflectionPad2d中,其填充方式为新的dim值使用反方向的最下边元素的值,代码如下:

1
2
3
4
x = torch.arange(9).reshape(1, 1, 3, 3).float()

pad = nn.ReflectionPad2d(2)
y = pad(x)

输出

1
2
3
4
5
6
7
tensor([[[[8., 7., 6., 7., 8., 7., 6.],
[5., 4., 3., 4., 5., 4., 3.],
[2., 1., 0., 1., 2., 1., 0.],
[5., 4., 3., 4., 5., 4., 3.],
[8., 7., 6., 7., 8., 7., 6.],
[5., 4., 3., 4., 5., 4., 3.],
[2., 1., 0., 1., 2., 1., 0.]]]])

重复填充ReplicationPad2d

ps: 这个我也只是一知半解。

重复填充即重复图像的边缘像素值,将新的边界像素值用边缘像素值扩展,封装于nn.ReplicationPad2d()中,代码如下

1
2
3
4
x = torch.arange(9).reshape(1, 1, 3, 3).float()

pad = nn.ReplicationPad2d(2)
y = pad(x)

输出

1
2
3
4
5
6
7
tensor([[[[0., 0., 0., 1., 2., 2., 2.],
[0., 0., 0., 1., 2., 2., 2.],
[0., 0., 0., 1., 2., 2., 2.],
[3., 3., 3., 4., 5., 5., 5.],
[6., 6., 6., 7., 8., 8., 8.],
[6., 6., 6., 7., 8., 8., 8.],
[6., 6., 6., 7., 8., 8., 8.]]]])

实际应用

在许多计算机视觉任务中,例如图像分类,zero padding已经能够满足要求。但是不结合实际地乱用也是不行的。比方说,在图像增强/图像生成领域,zero padding可能会导致边缘出现伪影,如下所示:

图上边缘有黑边,也就是伪影。

这时候,可以改用镜像填充来代替零填充操作。我们定义一个新的padding层,然后把卷积层里的padding参数置为0.

具体写法如下:

1
2
3
4
5
6
7
8
9
10
11
class DEMO(nn.Module):

def __init__(self):
super(DEMO, self).__init__()
self.pad = nn.ReflectionPad2d(1)
self.conv = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=3, padding=0)

def forward(self, x):
x = self.pad(x)
x = self.conv(x)
return F.relu(x)

以低光照增强任务为例,最终对比效果如下图。零填充会产生边缘伪影,而镜像填充很好地缓解了这一效应。

请我喝杯咖啡吧~