多个输入输出通道
引用翻译:《动手学深度学习》
虽然我们已经描述了构成每个图像的多个通道(例如,有标准的彩色图像)RGB通道表示红、绿、蓝的数量),但到目前为止,我们只使用一个输入和一个输出通道来简化所有的数字例子。这使我们能够将我们的输入、卷积核和输出视为二维数组。
当我们将通道添加到混合中时,我们的输入和隐藏表示都变成了三维数组。例如,每个RGB输入图像的形状为3×?×??。我们称这个大小为3的轴为通道维。本节将深入研究多输入多输出通道的卷积核。
一、多个输入通道
当输入数据包含多个通道时,我们需要构建一个与输入数据相同的输入的卷积核,以便与输入数据交叉关联。假设输入数据的通道数为 c i c_i ci,,卷积核的输入通道数也需要为 c i c_i ci。如果我们的卷积核的窗户形状是 k h × k w k_h\times k_w kh×kw,,那么当 c i c_i ci=1时,我们可以认为我们的卷积核只是一个形状为 k h × k w k_h\times k_w kh×kw.的二维阵列。
然而,当𝑐𝑖>1时,我们需要一个包含每个输入通道的 k h × k w k_h\times k_w kh×kw.形状阵列的内核。将这些 c i c_i ci数组串联起来,就得到了形状为 c i × k h × k w c_i\times k_h\times k_w ci×kh×kw.的卷积核。由于输入和卷积核各有𝑐𝑖个通道,我们可以对输入的二维数组和卷积核的二维数组的每个通道进行交叉相关操作,将 c i c_i ci个结果加在一起(对通道求和),得到一个二维数组。这就是多通道输入数据和多输入通道卷积核之间的二维交叉关联的结果。
在下图中,我们展示了一个有两个输入通道的二维交叉相关的例子。阴影部分是第一个输出元素,以及用于计算的输入和内核阵列元素。 (1×1+2×2+4×3+5×4)+(0×0+1×1+3×2+4×3)=56 .
from IPython.display import SVG, Image
SVG(filename="../img/conv_multi_in.svg")
为了确保我们真正理解这里发生的事情,我们可以自己实现多个输入通道的交叉相关操作。请注意,我们所做的只是对每个通道进行一次交叉相关操作,然后用add_n函数将结果相加。
import sys
sys.path.insert(0, '..')
import torch
def corr2d(X, K):
h, w = K.shape # 卷积核的大小
print('h,w: ',h,w)
Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i: i + h, j: j + w] * K).sum() # 点乘之后相加
return Y
def corr2d_multi_in(X, K):
# 首先,沿着X和K的第0维(通道维)进行遍历,然后使用Python的sum()函数将它们相加,该函数以一个列表作为参数
# 循环遍历通道数,然后结果相加
return sum([corr2d(x, k) for x, k in zip(X, K)])
我们可以构建与上图中的数值相对应的输入数组X和内核数组K来验证交叉相关操作的输出。
X = torch.tensor([[[0, 1, 2], [3, 4, 5], [6, 7, 8]],
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]])
K = torch.tensor([[[0, 1], [2, 3]],
[[1, 2], [3, 4]]])
corr2d_multi_in(X, K)
h,w: 2 2
h,w: 2 2
tensor([[ 56., 72.],
[104., 120.]])
二、多个输出通道
不管输入通道的数量如何,到目前为止,我们总是以一个输出通道结束。然而,正如我们前面所讨论的,事实证明,在每一层有多个通道是至关重要的。在最流行的神经网络架构中,我们实际上是在神经网络中越往上越增加通道的维度,通常是通过降采样来交换空间分辨率以获得更大的通道深度。直观地讲,你可以认为每个通道都是对一些不同的特征集的反应。现实比对这一直觉的最天真的解释要复杂一些,因为表征不是独立学习的,而是被优化为共同有用。因此,可能不是一个单一的通道学会了一个边缘检测器,而是通道空间的某些方向对应于检测边缘。
用 c i c_i ci 和 c o c_o co分别表示输入和输出通道的数量,让 k h k_h kh 和 k w k_w kw为内核的高度和宽度。为了得到一个多通道的输出,我们可以为每个输出通道创建一个形状为 c i × k h × k w c_i\times k_h\times k_w ci×kh×kw的内核阵列。我们在输出通道维度上将它们串联起来,因此卷积核的形状是 c o × c i × k h × k w c_o\times c_i\times k_h\times k_w co×ci×kh×kw.。在交叉相关操作中,每个输出通道的结果都是由该输出通道对应的卷积核计算出来的,并从输入阵列的所有通道中获取输入。
我们实现一个交叉相关函数来计算多个通道的输出,如下图所示。
def corr2d_multi_in_out(X, K):
# 沿着K遍历,每次都对输入的X进行交叉相关操作,所有的结果都用堆栈函数合并起来
return torch.stack([corr2d_multi_in(X, k) for k in K], dim=0)
我们通过将内核数组K与K+1(K中的每个元素加一个)和K+2连接起来,构建一个具有3个输出通道的卷积内核。
# 构建一个具有3个输出通道的卷积内核。类比如GBK的结构
K = torch.stack([K, K + 1, K + 2], dim=0)
K.shape
print('K: ',K)
输出:
K: tensor([[[[0, 1],
[2, 3]],
[[1, 2],
[3, 4]]],
[[[1, 2],
[3, 4]],
[[2, 3],
[4, 5]]],
[[[2, 3],
[4, 5]],
[[3, 4],
[5, 6]]]])
下面,我们用内核数组K对输入数组X进行交叉相关操作,现在输出包含3个通道。第一个通道的结果与之前输入数组X和多输入通道、单输出通道内核的结果一致。
# 此时输入的K是一个栈,会对每个数组分布计算卷积
corr2d_multi_in_out(X, K)
输出:
tensor([[[ 56., 72.],
[104., 120.]],
[[ 76., 100.],
[148., 172.]],
[[ 96., 128.],
[192., 224.]]])
三、1×1卷积层
起初,1×1卷积,即 k h = k w = 1 k_h = k_w = 1 kh=kw=1,似乎没有什么意义。毕竟,卷积将相邻的像素联系起来。而1×1卷积显然不是这样。尽管如此,它们都是流行的操作,有时被包含在复杂的深度网络的设计中。让我们详细看看它的实际作用。
由于使用了最小窗口,1×1卷积失去了更大的卷积层识别由高度和宽度维度上的相邻元素之间的相互作用组成的模式的能力。1×1卷积的唯一计算发生在通道维度上。
下图显示了使用1×1卷积核的3个输入通道和2个输出通道的交叉相关计算。请注意,输入和输出具有相同的高度和宽度。输出中的每个元素都是由输入图像中相同位置的元素线性组合而来。你可以认为1×1卷积层构成了一个全连接层,应用于每个单一的像素位置,将c_i对应的输入值转化为c_o输出值。因为这仍然是一个卷积层,权重在不同的像素位置上是绑定的,因此1×1卷积层需要 c o × c i c_o\times c_i co×ci权重(加上偏置项)。
互相关计算使用了具有3个输入通道和2个输出通道的 卷积核。其中,输入和输出具有相同的高度和宽度。 偏置bias是针对输出而言的,不是针对输入,偏置项的维度跟输出项的维度是一样的,而不是跟输入项,不是“每个输入都有独立的偏差",而是每个输出维度都有独立的偏差(当然很多时候,bias初始值在各个维度上的值都设为一样)。
让我们检查一下这在实践中是否可行:我们使用全连接层实现1×1卷积。唯一的一点是,我们需要在矩阵乘法前后对数据形状做一些调整。
def corr2d_multi_in_out_1x1(X, K):
c_i, h, w = X.shape
c_o = K.shape[0]
X = X.reshape((c_i, h * w))
K = K.reshape((c_o, c_i))
Y = torch.mm(K, X) # 全连接层的矩阵乘法
return Y.reshape((c_o, h, w))
当进行1×1卷积时,上述函数等同于之前实现的交叉相关函数corr2d_multi_in_out。让我们用一些参考数据来检验一下。
X = torch.randn(size=(3, 3, 3))
K = torch.randn(size=(2, 3, 1, 1))
print('K: ',K)
Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
print('Y1: ',Y1)
print('Y2: ',Y2)
(Y1 - Y2).norm().item() < 1e-6
输出:
K: tensor([[[[-1.8386]],
[[-0.5579]],
[[-0.5867]]],
[[[ 0.2853]],
[[-0.5861]],
[[-1.9174]]]])
h,w: 1 1
h,w: 1 1
h,w: 1 1
h,w: 1 1
h,w: 1 1
h,w: 1 1
Y1: tensor([[[-0.5154, 0.7390, 1.3307],
[-0.7190, 1.5918, 2.0614],
[-0.0331, -2.6029, -0.7010]],
[[ 0.4106, -0.8177, 2.3917],
[ 1.1456, 1.4350, 3.1362],
[-1.4070, 2.3284, -1.1643]]])
Y2: tensor([[[-0.5154, 0.7390, 1.3307],
[-0.7190, 1.5918, 2.0614],
[-0.0331, -2.6029, -0.7010]],
[[ 0.4106, -0.8177, 2.3917],
[ 1.1456, 1.4350, 3.1362],
[-1.4070, 2.3284, -1.1643]]])
输出:
True
四、摘要
- 多通道可以用来扩展卷积层的模型参数。
- 1×1卷积层相当于全连接层,在每个像素的基础上应用。
- 1×1卷积层通常用于调整网络层之间的通道数量,并控制模型的复杂性。
五、练习题
1、假设我们有两个卷积核,大小分别为𝑘1和𝑘2(中间没有非线性)。
- 证明该操作的结果可以用一个卷积来表达。
- 等效的单一卷积的维数是多少?
- 反之亦然吗?
2、假设输入形状为 c i × h × w c_i\times h\times w ci×h×w ,卷积核形状为 c o × c i × k h × k w c_o\times c_i\times k_h\times k_w co×ci×kh×kw,填充为 ( p h , p w ) (p_h, p_w) (ph,pw),步长为 ( s h , s w ) (s_h, s_w) (sh,sw)。
- 前向计算的计算成本(乘法和加法)是多少?
- 其内存占用是多少?
- 后向计算的内存占用是多少?
- 后向计算的计算成本是多少?
3、如果我们把输入通道 c i c_i ci和输出通道 c o c_o co的数量增加一倍,计算量会增加多少倍?如果我们把填充物增加一倍会怎样?
4、 如果卷积核的高度和宽度为 k h = k w = 1 k_h=k_w=1 kh=kw=1 ,前向计算的复杂性是什么?
5、 本节最后一个例子中的变量Y1和Y2是否完全相同?为什么?
6、 当卷积窗口不是1×1时,你如何用矩阵乘法实现卷积?