Run this notebook online:Binder or Colab: Colab

6.4. 多个输入和输出通道

虽然我们已经描述了多个通道 包括每个图像的(例如,彩色图像具有标准RGB通道) 表示红色、绿色和蓝色的数量), 到目前为止,我们简化了所有的数值例子 只使用一个输入和一个输出通道。 这让我们能够思考我们的输入,卷积核, 并以二维数组的形式输出。

当我们在混合中加入频道时, 我们的输入和隐藏的表示 两者都变成了三维阵列。 例如,每个RGB输入图像的形状为\(3\times h\times w\)。 我们称这个轴为通道尺寸,大小为3。 在本节中,我们将更深入地了解 具有多个输入和多个输出通道的卷积核。

6.4.1. 多输入通道

当输入数据包含多个通道时, 我们需要构造一个卷积核 使用与输入数据相同数量的输入通道, 这样它就可以与输入数据进行互相关。 假设输入数据的通道数为\(c_i\), 卷积内核的输入通道数也需要是\(c_i\)。如果我们的卷积核的窗口形状是\(k_h\times k_w\), 然后当\(c_i=1\),时,我们可以考虑我们的卷积核 就像形状\(k_h\times k_w\)的二维数组。

然而,当\(c_i>1\)时,我们需要一个内核 对于每个输入通道,它包含一个形状为:math:`k_htimes k_w`的数组。 将这些\(c_i\)数组连接在一起 产生一个形状为\(c_i\times k_h\times k_w\)的卷积核。

由于输入和卷积内核都有\(c_i\)通道, 我们可以进行互相关运算 关于输入的二维数组 以及卷积核的二维核数组 对于每个频道,将\(c_i\)结果相加 (通过通道求和) 生成二维数组。 这是二维互相关的结果 在多通道输入数据和 一个多输入通道卷积内核。

Fig. 6.4.1,我们举一个例子 与两个输入通道的二维互相关。 阴影部分是第一个输出元素 以及计算中使用的输入和内核数组元素: \((1\times1+2\times2+4\times3+5\times4)+(0\times0+1\times1+3\times2+4\times3)=56\).

https://raw.githubusercontent.com/d2l-ai/d2l-en/master/img/conv-multi-in.svg

Fig. 6.4.1 使用2个输入通道进行互相关计算。阴影部分是第一个输出元素以及计算中使用的输入和内核数组元素: \((1\times1+2\times2+4\times3+5\times4)+(0\times0+1\times1+3\times2+4\times3)=56\).

为了确保我们真正了解这里发生了什么, 我们可以用多个输入通道实现互相关运算。 请注意,我们所做的只是执行一个互相关操作 然后使用sum()函数将结果相加。

让我们先导入这些库,然后再开始讨论多个输入和输出通道的概念。

%load ../utils/djl-imports
NDManager manager = NDManager.newBaseManager();

public NDArray corr2D(NDArray X, NDArray K) {

    long h = K.getShape().get(0);
    long w = K.getShape().get(1);

    NDArray Y = manager.zeros(new Shape(X.getShape().get(0) - h + 1, X.getShape().get(1) - w + 1));

    for (int i = 0; i < Y.getShape().get(0); i++) {
        for (int j = 0; j < Y.getShape().get(1); j++) {
            NDArray temp = X.get(i + ":" + (i + h) + "," + j + ":" + (j + w)).mul(K);
            Y.set(new NDIndex(i + "," + j), temp.sum());
        }
    }
    return Y;
}

public NDArray corr2dMultiIn(NDArray X, NDArray K) {

    long h = K.getShape().get(0);
    long w = K.getShape().get(1);

    // 首先,沿着'X'的第0维(通道维)进行遍历
    // “K”。然后,把它们加在一起

    NDArray res = manager.zeros(new Shape(X.getShape().get(0) - h + 1, X.getShape().get(1) - w + 1));
    for (int i = 0; i < X.getShape().get(0); i++) {
        for (int j = 0; j < K.getShape().get(0); j++) {
            if (i == j) {
                res = res.add(corr2D(X.get(new NDIndex(i)), K.get(new NDIndex(j))));
            }
        }
    }
    return res;
}

我们可以构造输入数组X和内核数组K 对应于上图中的值 验证互相关操作的输出。

NDArray X = manager.create(new Shape(2, 3, 3), DataType.INT32);
X.set(new NDIndex(0), manager.arange(9));
X.set(new NDIndex(1), manager.arange(1, 10));
X = X.toType(DataType.FLOAT32, true);

NDArray K = manager.create(new Shape(2, 2, 2), DataType.INT32);
K.set(new NDIndex(0), manager.arange(4));
K.set(new NDIndex(1), manager.arange(1, 5));
K = K.toType(DataType.FLOAT32, true);

corr2dMultiIn(X, K);
ND: (2, 2) gpu(0) float32
[[ 56.,  72.],
 [104., 120.],
]

6.4.2. 多输出通道

无论输入通道的数量如何, 到目前为止,我们总是以一个输出通道结束。 但是,正如我们之前讨论的, 事实证明,在每一层有多个通道是必要的。 在最流行的神经网络架构中, 我们实际上增加了通道维度 当我们在神经网络中往上走的时候, 通常通过降低采样来权衡空间分辨率 更大的通道深度。 凭直觉,你可以想到每个频道 作为对不同特征的回应。 现实比对这种直觉的最天真的解释要复杂一些,因为表征并不是独立学习的,而是经过优化以共同有用的。 因此,可能不是单个通道学习边缘检测器,而是通道空间中的某个方向对应于检测边缘。

\(c_i\)\(c_o\)表示数字 输入和输出通道, 让\(k_h\)\(k_w\)作为内核的高度和宽度。 要获得具有多个通道的输出, 我们可以创建一个内核数组 形状为\(c_i\times k_h\times k_w\) 对于每个输出通道。 我们在输出通道维度上连接它们, 所以卷积核的形状 是\(c_o\times c_i\times k_h\times k_w\)。 在互相关运算中, 计算每个输出通道上的结果 来自对应于该输出通道的卷积内核 并从输入阵列中的所有通道获取输入。

我们实现了一个互相关函数 计算多个通道的输出,如下所示。

public NDArray corrMultiInOut(NDArray X, NDArray K) {

    long cin = K.getShape().get(0);
    long h = K.getShape().get(2);
    long w = K.getShape().get(3);

    // 沿“K”的第0维遍历,每次执行
    // 输入'X'的互相关运算。所有结果都是正确的
    // 使用stack函数合并在一起

    NDArray res = manager.create(new Shape(cin, X.getShape().get(1) - h + 1, X.getShape().get(2) - w + 1));

    for (int j = 0; j < K.getShape().get(0); j++) {
        res.set(new NDIndex(j), corr2dMultiIn(X, K.get(new NDIndex(j))));
    }

    return res;
}

我们构造了一个具有3个输出通道的卷积核 通过将内核数组KK+1连接起来 (K中每个元素加一个)和K+2

K = NDArrays.stack(new NDList(K, K.add(1), K.add(2)));
K.getShape()
(3, 2, 2, 2)

下面,我们执行互相关操作 在输入数组X和内核数组K上。 现在输出包含3个通道。 第一个通道的结果是一致的 使用上一个输入数组的结果X 以及多输入通道, 单输出通道内核。

corrMultiInOut(X, K);
ND: (3, 2, 2) gpu(0) float32
[[[ 56.,  72.],
  [104., 120.],
 ],
 [[ 76., 100.],
  [148., 172.],
 ],
 [[ 96., 128.],
  [192., 224.],
 ],
]

6.4.3. \(1\times 1\) 卷积层

首先,一个\(1 \times 1\)的卷积,即 \(k_h = k_w = 1\), 这似乎没有多大意义。 毕竟,卷积将相邻像素关联起来。 一个\(1 \times 1\)的卷积显然不是。 尽管如此,它们还是很受欢迎的业务,有时也包括在内 在复杂深层网络的设计中。 让我们详细了解一下它的实际功能。 因为使用了最小窗口, \(1\times 1\) 卷积将失去该能力 更大的卷积层 识别由相互作用组成的模式 在高度和宽度尺寸中的相邻元素之间。 只会进行\(1\times 1\)的卷积运算 在通道维度上。

Fig. 6.4.2 显示了互相关计算 使用\(1\times 1\)卷积内核 有3个输入通道和2个输出通道。 请注意,输入和输出具有相同的高度和宽度。 输出中的每个元素都是派生的 来自同一位置的元素的线性组合 在输入图像中。 你可以想象\(1\times 1\)的卷积层 构成一个应用于每个像素位置的完全连接层 将\(c_i\)对应的输入值转换为\(c_o\)输出值。 因为这仍然是一个卷积层, 权重在像素位置上绑定。 因此\(1\times 1\)卷积层需要\(c_o\times c_i\)权重 (加上偏差项)。

https://raw.githubusercontent.com/d2l-ai/d2l-en/master/img/conv-1x1.svg

Fig. 6.4.2 The cross-correlation computation uses the \(1\times 1\) convolution kernel with 3 input channels and 2 output channels. The inputs and outputs have the same height and width.

让我们看看这在实践中是否有效: 我们实现了\(1 \times 1\)的卷积运算 使用完全连接的层。 唯一的问题是我们需要做一些调整 矩阵乘法前后的数据形状。

public NDArray corr2dMultiInOut1x1(NDArray X, NDArray K) {

    long channelIn = X.getShape().get(0);
    long height = X.getShape().get(1);
    long width = X.getShape().get(2);

    long channelOut = K.getShape().get(0);
    X = X.reshape(channelIn, height * width);
    K = K.reshape(channelOut, channelIn);
    NDArray Y = K.dot(X); // 全连通层中的矩阵乘法

    return Y.reshape(channelOut, height, width);
}

执行\(1\times 1\)卷积时, 上述函数相当于之前实现的互相关函数 corrMultiInOut(). 让我们用一些参考数据来验证这一点。

X = manager.randomUniform(0f, 1.0f, new Shape(3, 3, 3));
K = manager.randomUniform(0f, 1.0f, new Shape(2, 3, 1, 1));

NDArray Y1 = corr2dMultiInOut1x1(X, K);
NDArray Y2 = corrMultiInOut(X, K);

System.out.println(Math.abs(Y1.sum().getFloat() - Y2.sum().getFloat()) < 1e-6);
true

6.4.4. 总结

  • 可以使用多个通道来扩展卷积层的模型参数。

  • 当以每像素为基础应用时,\(1\times 1\)卷积层相当于完全连接层。

  • \(1\times 1\)卷积层通常用于调整网络层之间的通道数并控制模型复杂性。

6.4.5. 练习

  1. 假设我们有两个卷积核,大小分别为\(k_1\)\(k_2\)(中间没有非线性)。

    • 证明运算结果可以用一次卷积来表示。

    • 等效单卷积的维数是多少?

    • 反过来是真的吗?

  2. 假设输入形状为\(c_i\times h\times w\),卷积核形状为\(c_o\times c_i\times k_h\times k_w\),填充为 \((p_h, p_w)\),步长为\((s_h, s_w)\)

    • 正向计算的计算成本(乘法和加法)是多少?

    • 内存占用是多少?

    • 反向计算的内存占用是多少?

    • 反向计算的计算成本是多少?

  3. 如果我们将输入通道数\(c_i\)和输出通道数\(c_o\)翻一番,计算的数量会增加多少?如果我们把填充物翻一番会怎么样?

  4. 如果卷积核的高度和宽度是\(k_h=k_w=1\),那么正向计算的复杂度是多少?

  5. 本节最后一个示例中的变量Y1Y2是否完全相同?为什么?

  6. 当卷积窗口不是\(1\times 1\)时,如何使用矩阵乘法实现卷积?