动手学深度学习 5.3 多输入通道和多输出通道

前言

从零开始学习ai文章系列计划是个人在《动手学深度学习》和《磨菇书》两本书的学习中的个人笔记,文章也会以课本中的章节分开,即每个章节一片笔记。我会尽量的把主要内容以及遇到的难点进行记录与解决,如果哪里有错误的欢迎指正。或者不清晰的可以直接查看原文部分。

《动手学深度学习》原文(课本):https://tangshusen.me/Dive-into-DL-PyTorch/#/

《动手学深度学习》代码:https://github.com/ShusenTang/Dive-into-DL-PyTorch

(由于有时候公式太多,可能会直接贴图片)

前面两节里我们用到的输入和输出都是二维数组,但真实数据的维度经常更高。

例如,彩色图像在高和宽2个维度外还有RGB(红、绿、蓝)3个颜色通道。假设彩色图像的高和宽分别是hw(像素),那么它可以表示为一个3×h×w的多维数组。我们将大小为3的这一维称为通道(channel)维。

1 多输入通道

当输入数据含多个通道时,我们需要构造一个输入通道数与输入数据的通道数相同的卷积核,从而能够与含多通道的输入数据做互相关运算。

接下来我们实现含多个输入通道的互相关运算。我们只需要对每个通道做互相关运算,然后通过add_n函数来进行累加。

1
2
3
4
5
6
def corr2d_multi_in(X, K):
# 沿着X和K的第0维(通道维)分别计算再相加
res = d2l.corr2d(X[0, :, :], K[0, :, :])
for i in range(1, X.shape[0]):
res += d2l.corr2d(X[i, :, :], K[i, :, :])
return res

这里corr2d互相关计算函数的步幅为1。

我们可以构造图5.4中的输入数组X、核数组K来验证互相关运算的输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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]
]
])
res = corr2d_multi_in(X, K)
print(res)

2 多输出通道

我们之前使用一组核K仅获得一个输出,多输出的意思则是多组核获取多个输出

下面我们实现一个互相关运算函数来计算多个通道的输出。

下面的K中有多个一组核的k,多组核计算后的多个结果通过stack堆在在一个tensor中。

1
2
3
def corr2d_multi_in_out(X, K):
# 对K的第0维遍历,每次同输入X做互相关计算。所有结果使用stack函数合并在一起
return torch.stack([corr2d_multi_in(X, k) for k in K])

stack使用举例

我们将核数组KK+1K中每个元素加一)和K+2连结在一起来构造一个输出通道数为3的卷积核

1
2
K = torch.stack([K, K + 1, K + 2])
print(K.shape) # torch.Size([3, 2, 2, 2])

下面我们对输入数组X与核数组K做互相关运算。此时的输出含有3个通道。其中第一个通道的结果与之前输入数组X与多输入通道、单输出通道核的计算结果一致。

1
2
res = corr2d_multi_in_out(X, K)
print(res)

3. 1×1卷积层

最后我们讨论卷积窗口形状为1×1\(k_h=k_w=1\))的多通道卷积层。我们通常称之为1×1卷积层,并将其中的卷积运算称为1×1卷积。 实际上,1×1 卷积的主要计算发生在通道维上。

假设我们将通道维当作特征维,将高和宽维度上的元素当成数据样本,那么1×1卷积层的作用与全连接层等价。

输出中的每个元素来自输入中在高和宽上相同位置的元素在不同通道之间的按权重累加。

下面我们使用全连接层中的矩阵乘法来实现1×1卷积。这里需要在矩阵乘法运算前后对数据形状做一些调整。

1
2
3
4
5
6
7
8
9
def corr2d_multi_in_out_1x1(X, K):
c_i, h, w = X.shape
c_o = K.shape[0]

X = X.view(c_i, h * w)
K = K.view(c_o, c_i)

Y = torch.mm(K, X) # 全连接层的矩阵乘法
return Y.view(c_o, h, w)

注意我们这里的矩阵乘法是 \(K*X\) ,然后我们就清楚 X = X.view(c_i, h * w); K = K.view(c_o, c_i) 变换的意义。

最终我们将计算结果恢复成 (通道,高,宽) 的格式

做1×1 卷积时,以上函数与之前实现的互相关运算函数corr2d_multi_in_out等价。

1
2
3
4
5
6
7
X = torch.rand(3, 3, 3)
K = torch.rand(2, 3, 1, 1)

Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)

print((Y1 - Y2).norm().item() < 1e-6)

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