多输入通道和多输出通道
前面两节里用到的输入和输出都是二维数组,但真实数据的维度经常更高。例如,彩色图像在高和宽2
个维度外还有RGB
(红、绿、蓝)3
个颜色通道。假设彩色图像的高和宽分别是(h)和(w)(像素),那么它可以表示为一个(3 imes h imes w)的多维数组
将大小为3
的这一维称为通道(channel
)维。
多输入通道
当输入数据含多个通道时,需要构造一个输入通道数与输入数据的通道数相同的卷积核,从而能够与含多通道的输入数据做互相关运算。假设输入数据的通道数为(c_i),那么卷积核的输入通道数同样为(c_i)。设卷积核窗口形状为(k_h imes k_w)。当(c_i=1)时,我们知道卷积核只包含一个形状为(k_h imes k_w)的二维数组。当(c_i > 1)时,我们将会为每个输入通道各分配一个形状为(k_h imes k_w)的核数组。把这(c_i)个数组在输入通道维上连结,即得到一个形状为(c_i imes k_h imes k_w)的卷积核。由于输入和卷积核各有(c_i)个通道,我们可以在各个通道上对输入的二维数组和卷积核的二维核数组做互相关运算,再将这(c_i)个互相关运算的二维输出按通道相加,得到一个二维数组。这就是含多个通道的输入数据与多输入通道的卷积核做二维互相关运算的输出。
mxnet.ndarray.add_n(*args, **kwargs)
Adds all input arguments element-wise.
add\_n(a_1, a_2, ..., a_n) = a_1 + a_2 + ... + a_n
Parameters
输入元素(之后需要相加)的位置
args (NDArray[]) – Positional input arguments
out (NDArray, optional) – The output NDArray to hold the result.
zip函数
zip:并行迭代多个序列,内置zip函数允许我们使用for循环来并行迭代多个序列
import d2lzh as d2l
from mxnet import nd
def corr2d_multi_in(X, K):
# 首先沿着X和K的第0维(通道维)遍历。然后使用*将结果列表变成add_n函数的位置参数
# (positional argument)来进行相加
return nd.add_n(*[d2l.corr2d(x, k) for x, k in zip(X, K)])
构造输入数组X
、核数组K
来验证互相关运算的输出。
X = nd.array([[[0, 1, 2], [3, 4, 5], [6, 7, 8]],
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]])
K = nd.array([[[0, 1], [2, 3]], [[1, 2], [3, 4]]])
corr2d_multi_in(X, K)
多输出通道
当输入通道有多个时,因为我们对各个通道的结果做了累加,所以不论输入通道数是多少,输出通道数总是为1
设卷积核输入通道数和输出通道数分别为(c_i)和(c_o),高和宽分别为(k_h)和(k_w)。如果希望得到含多个通道的输出,我们可以为每个输出通道分别创建形状为(c_i imes k_h imes k_w)的核数组。将它们在输出通道维上连结,卷积核的形状即(c_o imes c_i imes k_h imes k_w)。在做互相关运算时,每个输出通道上的结果由卷积核在该输出通道上的核数组与整个输入数组计算而来。
实现一个互相关运算函数来计算多个通道的输出
mxnet.ndarray.stack(*data, **kwargs)
将一些array数组以一个新的维度合并
Join a sequence of arrays along a new axis. The axis parameter specifies the index of the new axis in the dimensions of the result. For example, if axis=0 it will be the first dimension and if axis=-1 it will be the last dimension
def corr2d_multi_in_out(X, K):
# 对K的第0维遍历,每次同输入X做互相关计算。所有结果使用stack函数合并在一起
return nd.stack(*[corr2d_multi_in(X, k) for k in K])
将核数组K
同K+1
(K
中每个元素加一)和K+2
连结在一起来构造一个输出通道数为3
的卷积核。
K = nd.stack(K, K + 1, K + 2)
K.shape
对输入数组X
与核数组K
做互相关运算。此时的输出含有3个通道。其中第一个通道的结果与之前输入数组X
与多输入通道、单输出通道核的计算结果一致。
corr2d_multi_in_out(X, K)
这里的输入层的输入通道数和卷积层的是相同的,只是卷积层增加了输出通道数,由之前的(1)个,变成了(3)个。
1×1 卷积层
讨论卷积窗口形状为(1 imes 1)((k_h=k_w=1))的多通道卷积层。我们通常称之为(1 imes 1)卷积层,并将其中的卷积运算称为(1 imes 1)卷积
因为使用了最小窗口,(1 imes 1)卷积失去了卷积层可以识别高和宽维度上相邻元素构成的模式的功能。实际上,(1 imes 1)卷积的主要计算发生在通道维上。
使用全连接层中的矩阵乘法来实现 (1 imes 1) 卷积。这里需要在矩阵乘法运算前后对数据形状做一些调整。
假设将通道维当作特征维,将高和宽维度上的元素当成数据样本,那么 (1 imes 1) 卷积层的作用与全连接层等价。
def corr2d_multi_in_out_1x1(X, K):
# 输入通道数为3,高是3,宽是3
c_i, h, w = X.shape
# 输出通道数是2
c_o = K.shape[0]
# X.shape = (3,9)
X = X.reshape((c_i, h * w))
# K.shape = (2,3)
K = K.reshape((c_o, c_i))
# 这里使用全连接层的矩阵乘法和之前的X*W一样,Y = (2,9)
Y = nd.dot(K, X) # 全连接层的矩阵乘法
# reshape 答案变成shape=(2,3,3)
return Y.reshape((c_o, h, w))
经验证,做 (1 imes 1) 卷积时,以上函数与之前实现的互相关运算函数corr2d_multi_in_out
等价
X = nd.random.uniform(shape=(3, 3, 3))
K = nd.random.uniform(shape=(2, 3, 1, 1))
Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
(Y1 - Y2).norm().asscalar() < 1e-6
看到(1 imes 1)卷积层被当作保持高和宽维度形状不变的全连接层使用。于是,我们可以通过调整网络层之间的通道数来控制模型复杂度。(这里的原来的输入是(3)通道的,现在变成了(2)通道,可以调整通道数,)