转置卷积
转置卷积(Transposed Convolution)也是卷积运算,需要强调的是,有些地方写成反卷积,其实不是很妥当,这个转置卷积看起来像是我们以前接触的卷积运算的反运算,其实是不可逆的,由于它的名称来自于矩阵的转置操作,所以正确叫法叫做转置卷积。我们先通过一张图来对比下这个卷积运算与转置卷积运算的区别在哪儿:
第一行是我们很熟悉的卷积运算,4x4的输入,通过3x3的步幅为1的卷积核,我们得到了一个2x2的输出,这个大家都熟悉,滑动窗口做加权运算即可。
观察第二行,我们的输入尺寸是2x2,也就是说输入的特征图尺寸变小了,运算同样是通过3x3的步幅为1的卷积核的卷积运算,最终我们却得到了一个更大尺寸的输出(4x4),原因是输入特征图的周围也就是像素的上下左右填充了0,将输入尺寸变成了6x6的尺寸了,然后同样做卷积运算。
动态图如下:
通过代码我们来验证下:
import d2lzh as d2l
from mxnet import nd,init
from mxnet.gluon import nn
X=nd.arange(1,17).reshape(1,1,4,4)
K=nd.arange(1,10).reshape(1,1,3,3)
conv=nn.Conv2D(channels=1,kernel_size=3)
conv.initialize(init.Constant(K))
print(conv(X))
'''
[[[[348. 393.]
[528. 573.]]]]
<NDArray 1x1x2x2 @cpu(0)>
'''
这个是正向卷积的情况,4x4的输入,经过3x3卷积之后,得到了2x2的输出。
我们从矩阵乘法的角度来了解这个卷积运算:
X=nd.arange(1,17).reshape(1,1,4,4)
K=nd.arange(1,10).reshape(1,1,3,3)
W,k=nd.zeros((4,16)),nd.zeros(11)
k[:3],k[4:7],k[8:]=K[0,0,0,:],K[0,0,1,:],K[0,0,2,:]
#print(k)#[1. 2. 3. 0. 4. 5. 6. 0. 7. 8. 9.]
W[0,0:11],W[1,1:12],W[2,4:15],W[3,5:16]=k,k,k,k
#print(W)
'''
[[1. 2. 3. 0. 4. 5. 6. 0. 7. 8. 9. 0. 0. 0. 0. 0.]
[0. 1. 2. 3. 0. 4. 5. 6. 0. 7. 8. 9. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 2. 3. 0. 4. 5. 6. 0. 7. 8. 9. 0.]
[0. 0. 0. 0. 0. 1. 2. 3. 0. 4. 5. 6. 0. 7. 8. 9.]]
'''
print(W.shape,X.reshape(16).shape)#(4, 16) (16,)
print(nd.dot(W,X.reshape(16)))#[348. 393. 528. 573.]
print(nd.dot(W,X.reshape(16)).reshape(1,1,2,2))
'''
[[[[348. 393.]
[528. 573.]]]]
<NDArray 1x1x2x2 @cpu(0)>
'''
这里的权重矩阵W的形状是4x16,然后对于输入是16(4x4尺寸)的向量,卷积前向计算之后的输出长度是4(2x2尺寸)。
我们知道在反向传播中,做乘法的时候,需要乘以转置后的权重矩阵。
那不难发现,当我们的输入向量长度是4(2x2尺寸),转置权重矩阵的形状是16x4,那么转置卷积层输出的长度将是16(4x4尺寸)
X1=nd.arange(1,5)
W1=nd.arange(1,65).reshape(16,4)
print(W1.shape,X1.shape)#(16, 4) (4,)
print(nd.dot(W1,X1))#[ 30. 70. 110. 150. 190. 230. 270. 310. 350. 390. 430. 470. 510. 550. 590.