反卷积是计算机视觉领域中的一种上采样的方法,除了反卷积以外,还可以采用unpooling和bilinear interpolation方法做上采样。反卷积并不是卷积操作的逆过程。通过反卷积,只能恢复原矩阵的大小,但并不能完全恢复原矩阵的数值。
在pytorch中,反卷积的函数为ConvTranspose. 因为最近需要用到一维的反卷积,所以下面以一维的反卷积操作为例。计算反卷积output shape的公式如下:
I n p u t : ( N , C i n , L i n ) O u t p u t : ( N , C o u t , L o u t ) L o u t = ( L i n − 1 ) × s t r i d e − 2 × p a d d i n g + d i l a t i o n × ( k e r n e l _ s i z e − 1 ) + o u t p u t _ p a d d i n g + 1 Input: (N, C_{in}, L_{in}) \\ Output:(N, C_{out}, L_{out}) \\ L_{out} =(L_{in}−1)×stride−2×padding+dilation×(kernel\_size−1)+output\_padding+1 Input:(N,Cin,Lin)Output:(N,Cout,Lout)Lout=(Lin−1)×stride−2×padding+dilation×(kernel_size−1)+output_padding+1
下面这段代码的kernel是[1, 2, 3]
input = torch.ones(1, 1, 4) # input [[[1, 1, 1, 1]]]
t_conv = nn.ConvTranspose1d(
in_channels=1,
out_channels=1,
stride=1,
kernel_size=3,
padding=0,
output_padding=0,
dilation=1,
padding_mode="zeros",
bias=False
)
t_conv.weight.data = torch.tensor(data=[[[1, 2, 3]]], dtype=torch.float)
print(t_conv(input)) # output [[[1, 3, 6, 6, 5, 3]]]
in_channels,out_channels,output_padding, kernel_size相对好理解。ConvTranspose1d目前只支持“zeros”这一种padding模式。
首先来看一下stride, 反卷积中的stride与卷积操中的stride是类似的,可以简单理解为kernel滑动的步长,可以先看一下snippet code:
input = torch.ones(1, 1, 4) # input [[[1, 1, 1, 1]]]
t_conv = nn.ConvTranspose1d(
in_channels=1,
out_channels=1,
stride=2,
kernel_size=3,
padding=0,
output_padding=0,
dilation=1,
padding_mode="zeros",
bias=False
)
t_conv.weight.data = torch.tensor(data=[[[1, 2, 3]]], dtype=torch.float)
print(t_conv(input)) # output [[[1., 2., 4., 2., 4., 2., 4., 2., 3.]]]
当stride设置为2时,根据公式 o = s × ( i − 1 ) + d × ( k − 1 ) + 1 o = s \times (i - 1) + d \times (k - 1) + 1 o=s×(i−1)+d×(k−1)+1我们可以计算出此时的output维度是9,那么这个结果是怎么得到的呢。请看下图:
通过图片就可以很清楚的看到,stride变为2时,kernel滑动的步长增加了一位。
反卷积的dilation也和卷积操作中的dilation类似,指出了kernel间隔的距离。我们还是先看一下snippet code:
input = torch.ones(1, 1, 4) # input [[[1, 1, 1, 1]]]
t_conv = nn.ConvTranspose1d(
in_channels=1,
out_channels=1,
stride=1,
kernel_size=3,
padding=0,
output_padding=0,
dilation=2,
padding_mode="zeros",
bias=False
)
t_conv.weight.data = torch.tensor(data=[[[1, 2, 3]]], dtype=torch.float)
print(t_conv(input)) # output[[[1., 1., 3., 3., 5., 5., 3., 3.]]]
padding和卷积操作的padding就有些不同了,反卷积的padding加在input上,但在结果中却要将padding去掉,我们直接看一下python运行的结果:
input = torch.ones(1, 1, 4) # input [[[1, 1, 1, 1]]]
t_conv = nn.ConvTranspose1d(
in_channels=1,
out_channels=1,
stride=1,
kernel_size=3,
padding=1,
output_padding=0,
dilation=1,
padding_mode="zeros",
bias=False
)
t_conv.weight.data = torch.tensor(data=[[[1, 2, 3]]], dtype=torch.float)
print(t_conv(input)) # output[[[3., 6., 6., 5.]]]
与第一段代码的结果相比,我们可以发现加了padding以后,我们的结果的维度反而减少了,这和公式是一致的,其实就是在结果的两端,减去padding所取值的维度。当然padding值如果过大,结果的维度不再大于0时,pytorch会报错。
而output_padding必须要小于stride或dilation其中之一,output_padding只会在output的后端添加padding去掉的值,如果没有padding,则会添加0。所以output_padding,并不会对称的添加数值。
最后做个总结的话,可以发现tranposed convolution实现上采样的方式是比较灵活的,不同参数的组合可能会得到相同大小的matrix,不知道有没有最佳的选择方式。