多输入通道

对于RGB图像,它实际上就是一个三维的矩阵,那么我们也可以定义一个三维的卷积核,用于在每个通道上与矩阵做二维互相关运算。
没什么好说的,直接上代码:

  1. import torch
  2. from torch import nn
  3. from Convolution import *
  4. def corr2d_multi_in(x, k):
  5. res = 0
  6. for i in range(0, x.shape[0]):
  7. res += corr2d(x[i], k[i])
  8. # 也可写成 res = corr2d(X[0, :, :], K[0, :, :])
  9. return res
  10. if __name__ == '__main__':
  11. X = torch.tensor([[[0, 1, 2], [3, 4, 5], [6, 7, 8]],
  12. [[1, 2, 3], [4, 5, 6], [7, 8, 9]]])
  13. K = torch.tensor([[[0, 1], [2, 3]], [[1, 2], [3, 4]]])
  14. print(corr2d_multi_in(X, K))
  15. 结果:
  16. tensor([[ 56., 72.],
  17. [104., 120.]])

多输出通道

既然有多输入通道,那么显然也可以定义多输出通道。刚才我们定义的卷积核是多输入通道和多输出通道 - 图1形状的,然后把三个输入通道的矩阵做互相关运算后累加起来,这样我们就得到了单个输出通道的结果。若要想得到多个,就要进一步对卷积核进行增维,将形状变为多输入通道和多输出通道 - 图2
再介绍一个很有意思的pytorch函数:torch.stack([tensor1, tensor2, …])
很明显,stack函数接受的是一个装有若干个tensor的数组,这些tensor应当是相同形状的,然后这个函数就会自动对它们进行concatenate操作,也就是堆叠增维。代码如下:

  1. def corr2d_multi_in_out(x, k):
  2. return torch.stack([corr2d_multi_in(x, j) for j in k])
  3. if __name__ == '__main__':
  4. X = torch.tensor([[[0, 1, 2], [3, 4, 5], [6, 7, 8]],
  5. [[1, 2, 3], [4, 5, 6], [7, 8, 9]]])
  6. K = torch.tensor([[[0, 1], [2, 3]], [[1, 2], [3, 4]]])
  7. K = torch.stack([K, K+1, K+2])
  8. print(K.shape)
  9. print(corr2d_multi_in_out(X, K))
  10. 结果:
  11. torch.Size([3, 2, 2, 2])
  12. tensor([[[ 56., 72.],
  13. [104., 120.]],
  14. [[ 76., 100.],
  15. [148., 172.]],
  16. [[ 96., 128.],
  17. [192., 224.]]])

1X1卷积层

最后我们讨论卷积窗口形状为1×1的多通道卷积层。我们通常称之为1×1卷积层,并将其中的卷积运算称为1×1卷积。因为使用了最小窗口,1×1卷积失去了卷积层可以识别高和宽维度上相邻元素构成的模式的功能。实际上,1×1卷积的主要计算发生在通道维上。图5.5展示了使用输入通道数为3、输出通道数为2的1×1卷积核的互相关计算。值得注意的是,输入和输出具有相同的高和宽。输出中的每个元素来自输入中在高和宽上相同位置的元素在不同通道之间的按权重累加。假设我们将通道维当作特征维,将高和宽维度上的元素当成数据样本,那么1×1卷积层的作用与全连接层等价

至于为什么与全连接层等价呢,看一下原书上的图,对输入与卷积核做一个形状的变换,其实是很明显的。将卷积核的输出通道数作为矩阵1的行,输入通道数作为矩阵1的列,输入张量的输入通道数作为矩阵2的行,输入张量中小矩阵的多输入通道和多输出通道 - 图3作为矩阵2的列。看看矩阵1乘以矩阵2的结果,不就是一个全连接层嘛。其实就是把输入张量的每一个小矩阵展开成一个向量的操作。
多输入通道和多输出通道 - 图4
代码:

  1. def corr2d_multi_in_out_1x1(x, k):
  2. c_i, h, w = x.shape
  3. c_o = k.shape[0]
  4. x = x.view(c_i, h*w)
  5. k = k.view(c_o, c_i)
  6. res = torch.mm(k, x)
  7. return res.view(c_o, h, w)
  8. if __name__ == '__main__':
  9. X = torch.rand(3, 3, 3)
  10. K = torch.rand(2, 3, 1, 1)
  11. Y1 = corr2d_multi_in_out_1x1(X, K)
  12. Y2 = corr2d_multi_in_out(X, K)
  13. print(Y1-Y2)
  14. 结果:
  15. tensor([[[0., 0., 0.],
  16. [0., 0., 0.],
  17. [0., 0., 0.]],
  18. [[0., 0., 0.],
  19. [0., 0., 0.],
  20. [0., 0., 0.]]])

所以,两者是等价的。

在之后的模型里我们将会看到1×1卷积层被当作保持高和宽维度形状不变的全连接层使用。于是,我们可以通过调整网络层之间的通道数来控制模型复杂度。