深度学习实战(基于pytroch)系列(二十二)多通道输入输出
多通道
- 多输入通道
- 多输出通道
- 知识扩展(`torch.stack` )
- 情况1:dim=0(默认) - 向上堆叠
- 情况2:dim=1 - 横向并排
- 核心规则
- 1×11×11×1 卷积层
前面两节里我们用到的输入和输出都是二维数组,但真实数据的维度经常更高。例如,彩色图像在高和宽2个维度外还有RGB(红、绿、蓝)3个颜色通道。假设彩色图像的高和宽分别是 hhh 和 www(像素),那么它可以表示为一个 3×h×w3×h×w3×h×w 的多维数组。我们将大小为3的这一维称为通道(channel)维。本节我们将介绍含多个输入通道或多个输出通道的卷积核。
多输入通道
当输入数据含多个通道时,我们需要构造一个输入通道数与输入数据的通道数相同的卷积核,从而能够与含多通道的输入数据做互相关运算。假设输入数据的通道数为 cic_ici,那么卷积核的输入通道数同样为 cic_ici。设卷积核窗口形状为 kh×kwk_h×k_wkh×kw。当 ci=1c_i=1ci=1 时,我们知道卷积核只包含一个形状为 kh×kwk_h×k_wkh×kw 的二维数组。当 ci>1c_i>1ci>1 时,我们将会为每个输入通道各分配一个形状为 kh×kwk_h×k_wkh×kw 的核数组。把这 cic_ici 个数组在输入通道维上连结,即得到一个形状为 ci×kh×kwc_i×k_h×k_wci×kh×kw 的卷积核。由于输入和卷积核各有 cic_ici 个通道,我们可以在各个通道上对输入的二维数组和卷积核的二维核数组做互相关运算,再将这 cic_ici 个互相关运算的二维输出按通道相加,得到一个二维数组。这就是含多个通道的输入数据与多输入通道的卷积核做二维互相关运算的输出。
含2个输入通道的互相关计算展示了在每个通道上,二维输入数组与二维核数组做互相关运算,再按通道相加即得到输出。图中阴影部分为第一个输出元素及其计算所使用的输入和核数组元素:(1×1+2×2+4×3+5×4)+(0×0+1×1+3×2+4×3)=56(1×1+2×2+4×3+5×4)+(0×0+1×1+3×2+4×3)=56(1×1+2×2+4×3+5×4)+(0×0+1×1+3×2+4×3)=56。
接下来我们实现含多个输入通道的互相关运算。我们只需要对每个通道做互相关运算,然后进行累加。
import torch
import torch.nn.functional as Fdef corr2d(X, K):"""计算二维互相关运算"""h, w = K.shapeY = 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 Ydef corr2d_multi_in(X, K):# 首先沿着X和K的第0维(通道维)遍历,然后对每个通道的结果进行相加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]]], dtype=torch.float32)
K = torch.tensor([[[0, 1], [2, 3]], [[1, 2], [3, 4]]], dtype=torch.float32)print(corr2d_multi_in(X, K))
输出:
tensor([[ 56., 72.],
[104., 120.]])
多输出通道
当输入通道有多个时,因为我们对各个通道的结果做了累加,所以不论输入通道数是多少,输出通道数总是为1。设卷积核输入通道数和输出通道数分别为 cic_ici 和 coc_oco,高和宽分别为 khk_hkh 和 kwk_wkw。如果希望得到含多个通道的输出,我们可以为每个输出通道分别创建形状为 ci×kh×kwc_i×k_h×k_wci×kh×kw 的核数组。将它们在输出通道维上连结,卷积核的形状即 co×ci×kh×kwc_o×c_i×k_h×k_wco×ci×kh×kw。在做互相关运算时,每个输出通道上的结果由卷积核在该输出通道上的核数组与整个输入数组计算而来。
下面我们实现一个互相关运算函数来计算多个通道的输出。
def corr2d_multi_in_out(X, K):# 对K的第0维遍历,每次同输入X做互相关计算。所有结果使用stack函数合并在一起return torch.stack([corr2d_multi_in(X, k) for k in K], 0)
我们将核张量K同K+1(K中每个元素加一)和K+2连结在一起来构造一个输出通道数为3的卷积核。
K = torch.stack([K, K + 1, K + 2], 0)
print(K)
print(K.shape) # 输出: torch.Size([3, 2, 2, 2])
输出:
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.]]]])
torch.Size([3, 2, 2, 2])
下面我们对输入张量X与核张量K做互相关运算。此时的输出含有3个通道。其中第一个通道的结果与之前输入张量X与多输入通道、单输出通道核的计算结果一致。
print(corr2d_multi_in_out(X, K))
输出:
tensor([[[ 56., 72.],
[104., 120.]],
[[ 76., 100.],
[148., 172.]],
[[ 96., 128.],
[192., 224.]]])
知识扩展(torch.stack )
torch.stack 的用法可以用一个很形象的比喻来理解:
煎饼堆叠法
想象一下,torch.stack 就像是在堆煎饼:
- 每个张量 = 一个煎饼
- stack操作 = 把多个煎饼叠在一起
- dim参数 = 决定怎么叠(横着叠还是竖着叠)
情况1:dim=0(默认) - 向上堆叠
A = torch.tensor([1, 2, 3]) # 煎饼A
B = torch.tensor([4, 5, 6]) # 煎饼Bresult = torch.stack([A, B], dim=0)
# 相当于:
# [[1, 2, 3],
# [4, 5, 6]]
# 形状从 [3] → [2, 3]
情况2:dim=1 - 横向并排
result = torch.stack([A, B], dim=1)
# 相当于:
# [[1, 4],
# [2, 5],
# [3, 6]]
# 形状从 [3] → [3, 2]
核心规则
torch.stack 会创建一个新的维度,这是它与 torch.cat 的最大区别:
# 假设有两个形状为 [2, 3] 的张量
t1 = torch.tensor([[1, 2, 3], [4, 5, 6]])
t2 = torch.tensor([[7, 8, 9], [10, 11, 12]])# torch.cat - 在现有维度上拼接
cat_result = torch.cat([t1, t2], dim=0) # 形状: [4, 3]# torch.stack - 创建新维度
stack_result = torch.stack([t1, t2], dim=0) # 形状: [2, 2, 3]
cat是拼接,stack是堆叠
cat不增维,stack增一维
简单总结:把多个相同形状的张量,按照指定的方式"堆"起来,创建一个新的维度!
1×11×11×1 卷积层
最后我们讨论卷积窗口形状为 1×11×11×1(kh=kw=1k_h=k_w=1kh=kw=1)的多通道卷积层。我们通常称之为 1×11×11×1 卷积层,并将其中的卷积运算称为 1×11×11×1 卷积。因为使用了最小窗口,1×11×11×1 卷积失去了卷积层可以识别高和宽维度上相邻元素构成的模式的功能。实际上,1×11×11×1 卷积的主要计算发生在通道维上。
使用输入通道数为3、输出通道数为2的1×11\times 11×1卷积核的互相关计算展示了输入和输出具有相同的高和宽。输出中的每个元素来自输入中在高和宽上相同位置的元素在不同通道之间的按权重累加。假设我们将通道维当作特征维,将高和宽维度上的元素当成数据样本,那么 1×11×11×1 卷积层的作用与全连接层等价。
下面我们使用全连接层中的矩阵乘法来实现 1×11×11×1 卷积。这里需要在矩阵乘法运算前后对数据形状做一些调整。
def corr2d_multi_in_out_1x1(X, K):c_i, h, w = X.shapec_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×11×11×1 卷积时,以上函数与之前实现的互相关运算函数corr2d_multi_in_out等价。
X = torch.randn(3, 3, 3)
K = torch.randn(2, 3, 1, 1)Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)print(f"两种实现方式的差异: {(Y1 - Y2).norm().item():.6f}")
输出: 两种实现方式的差异: 0.000000
在之后的模型里我们将会看到 1×11×11×1 卷积层被当作保持高和宽维度形状不变的全连接层使用。于是,我们可以通过调整网络层之间的通道数来控制模型复杂度。
