卷积操作作为卷积神经网络的核心模块,在其计算过程中必须考虑图像“边缘像素”的卷积方式。查阅资料发现,我们可以采用“卷积之前进行边界填充”或“卷积之后进行边界填充两种方式”。同时边界填充的具体手段包含
- 常量填充
- 零填充
- 镜像填充
- 重复填充
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 | x = torch.Tensor([[1, 2, 3], [4, 5, 6]]) |
输出
1 | tensor([[0., 0., 0., 0., 0., 0.], |
常数填充ConstantPad2d
零填充是常数填充的一个特例,常数填充nn.ConstantPad2d()
需要我们指定填充所用的常数值value
核填充数padding
,这里选择四个方向上均填充为1dim
,即padding
为(1,1,1,1
),代码如下:
1 | pad = nn.ConstantPad2d(padding=(1, 2, 3, 4), value=666) |
输出
1 | tensor([[666., 666., 666., 666., 666., 666.], |
镜像填充ReflectionPad2d
ps: 关于这个我也只是一知半解,所以,用的话还是需要查阅资料的。
镜像填充的方式相比于前面使用固定数值进行填充,有可能获得更好的卷积结果。镜像填充封装在nn.ReflectionPad2d
中,其填充方式为新的dim
值使用反方向的最下边元素的值,代码如下:
1 | x = torch.arange(9).reshape(1, 1, 3, 3).float() |
输出
1 | tensor([[[[8., 7., 6., 7., 8., 7., 6.], |
重复填充ReplicationPad2d
ps: 这个我也只是一知半解。
重复填充即重复图像的边缘像素值,将新的边界像素值用边缘像素值扩展,封装于nn.ReplicationPad2d()
中,代码如下
1 | x = torch.arange(9).reshape(1, 1, 3, 3).float() |
输出
1 | tensor([[[[0., 0., 0., 1., 2., 2., 2.], |
实际应用
在许多计算机视觉任务中,例如图像分类,zero padding
已经能够满足要求。但是不结合实际地乱用也是不行的。比方说,在图像增强/图像生成领域,zero padding
可能会导致边缘出现伪影,如下所示:
图上边缘有黑边,也就是伪影。
这时候,可以改用镜像填充来代替零填充操作。我们定义一个新的padding
层,然后把卷积层里的padding
参数置为0
.
具体写法如下:
1 | class DEMO(nn.Module): |
以低光照增强任务为例,最终对比效果如下图。零填充会产生边缘伪影,而镜像填充很好地缓解了这一效应。