📕 第5章 卷积神经网络
📄 一句话概括

一个卷积层就是若干个核,每个核的形状是「输入通道数 × 3 × 3」。核的深度必须等于输入通道数。 输出通道数等于核的个数。3 通道进、2 个核出,输出就是 2 通道。 实测 3×8×8 进、形状 2×3×3×3 的核:输出 2×6×6,参数 56 个;同样映射用全连接要 13896 个。

📝 本节和前面几节的关系

上一节的核是单通道的,这一节加上通道维。 5.9 节的全连接层要把这里的 (C, H, W) 摊平,届时会用到这一节的形状约定。 参数「与输入尺寸无关」这条性质,是卷积能吃大图的根本原因。

一、多通道:核也要有深度

输入的每个通道各配一个 3×3 的核片,三个核片合起来才是一个完整的核。 计算时三个通道的结果直接相加,所以一个核只产出一个通道。 这就是「3×3×3」里第三个 3 的来历:不是三个核,是一个核有三层。这一点初次接触时最容易看错。

二、多核:输出通道数

想要 2 个输出通道,就放 2 个核,每个核都是 3×3×3。 每个核独立地扫全图,得到一张特征图。所以输出通道数恰好等于核的个数。 这些核互不干扰,训练后会分化:一个学成横线检测器,一个学成竖线检测器, 它们看的是同一张图的不同侧面。

三、参数量的算法

参数量 = 核个数 × 输入通道数 × 核高 × 核宽 + 核个数。 实测是 2 乘 3 乘 3 乘 3 加 2 等于 56。注意偏置是每个核一个,不是每个位置一个。 这个数不含输入尺寸。图从 8×8 变成 800×800,参数还是 56。全连接那边同样变化会让参数涨一万倍。

四、层与层怎么接

上一层输出 C 个通道,这一层的核深度就必须是 C。 通道数是上一层定的,这一层只能定「输出几个通道」。 接错深度是最常见的形状错误, 报错信息通常是矩阵乘的维度不匹配——它不会告诉你「核深度写错了」,只会说 27 和 32 不匹配。

五、计算量在哪

参数少不等于计算少。计算量 = 输出点数 × 核面积 × 输入通道 × 输出通道。 8×8 输入、3 通道、2 个 3×3 核:6 乘 6 乘 2 乘 27 等于 1944 次乘加。 参数量只有 56,计算量却是它的三十多倍。 图变大时计算量按面积增长,参数不变。 所以卷积网络吃的是算力,不是模型体积——这也是移动端要降分辨率、压通道的原因。

六、动手 Demo:三通道进、两通道出

输入 3 通道 8×8 的随机数据,放 2 个 3×3 的核(每个核都是 3 通道),用纯 Python 循环算一遍, 看输出形状、参数量和耗时。

import numpy as np

def conv_multi(x, K, b):
    c_out, _, f, _ = K.shape
    out = np.zeros((c_out, x.shape[1] - f + 1, x.shape[2] - f + 1))
    for o in range(c_out):
        acc = np.zeros((out.shape[1], out.shape[2]))
        for c in range(x.shape[0]):
            for i in range(out.shape[1]):
                for j in range(out.shape[2]):
                    acc[i, j] += float((x[c, i:i + f, j:j + f] * K[o, c]).sum())
        out[o] = acc + b[o]
    return out

rg = np.random.default_rng(5)
x = rg.normal(0, 1, (3, 8, 8))             # 3 通道 8×8
K = rg.normal(0, 0.3, (2, 3, 3, 3))        # 2 个核,每个 3 通道 3×3
y = conv_multi(x, K, np.zeros(2))
print("输出形状", y.shape, "参数", K.size + 2)
print("同样映射用全连接要", 3 * 8 * 8 * 2 * 6 * 6 + 2 * 6 * 6)

实跑输出:

--------------------------------------------------------------------
卷积层:三通道进、两通道出
--------------------------------------------------------------------

  输入 3 通道 8×8,2 个 3×3 的核(每个核都是 3 通道),偏置 2 个:

  量            值
  ----------------------------------
  输入形状      3×8×8
  核形状        2×3×3×3
  输出形状      2×6×6
  参数个数      56
  耗时          1.26 毫秒

  参数只有 56 个。
  **要是用全连接把 3×8×8=192 维映到 2×6×6=72 维,需要 192 乘 72 再加 72,一共 13896 个。**

  输出的通道数等于核的个数:2 个核就给 2 张特征图。
  **每张图记的是「同一个核在不同位置的响应」,所以核又叫滤波器。**

  每个核要同时看所有输入通道,这是「3×3×3」里第三个 3 的来历。
  **层与层之间的通道数就是这么接起来的:上一层的输出通道数,等于这一层核的深度。**

输出 2×6×6:2 是核的个数,6 是 8 − 3 + 1。两个数字来源不同,别混。 参数 56,等于 2 乘 3 乘 3 乘 3 加 2。偏置那一项只有 2 个,是每个核一个。 同样的事用全连接做要 13896 个参数,是 248 倍。 而且图像越大差距越大:全连接的参数随面积平方增长,卷积的完全不变。 耗时不到 1 毫秒——因为图小。 真实框架不会这样写循环,它们用 im2col 加 BLAS,5.12 节会实测那个差距(差两千多倍)。 输出的两张特征图对应两个核。 训练时这两个核会分化成不同的检测器,而且它们的差别不是人指定的,是从数据里长出来的。

30 秒实验

  1. 把核的个数从 2 改成 8,参数量和输出形状各变成多少?
  2. 如果输入改成 5 通道,核的形状该怎么写?参数变成多少?
  3. 输入图从 8×8 换成 32×32,参数量和输出尺寸各变成多少?

常见误区

  1. 以为核的个数等于通道数。核的个数决定输出通道数,核的深度由输入通道数决定, 这是两个不同的维度。
  2. 以为参数少就算得少。参数和计算量是两件事。卷积参数少,但计算量随图像面积线性增长。
  3. 把补零与否搞混。这一节全是 valid 卷积(不补零),所以尺寸一直变小。 要保尺寸得等 5.5 节的补零。
自测三问(点开对答案) 1. 一个 3×3×3 的核处理 3 通道输入,输出几个通道? —— 一个。核的深度和输入通道对齐,三个通道的结果相加,所以一个核只产出一个通道。 2. 参数量怎么算? —— 核个数 × 输入通道 × 核高 × 核宽 + 核个数。最后那一项是偏置,每个核一个。 3. 为什么卷积的参数和图像大小无关? —— 同一套权重在整张图上重复使用,位置再多也只有一套参数。这就是权重共享的直接后果。

一层卷积的感受野只有 3×3,看不出「这是一只猫」。得靠堆层。 下一节看堆层之后感受野怎么长、参数又怎么省。

相关笔记