全连接层有两处硬伤:参数爆炸,还把图像当成一串没有邻接关系的数。 卷积用「局部连接 + 权重共享」两个假设,把两个问题一起解决。 同样把 28×28 接到 128 个单元,全连接要 100480 个参数,3×3 卷积只要 1280 个,差 78.5 倍。 更关键的是平移:图案右移一格,全连接的 8 个输出全变(最大差 0.7505), 卷积的输出只是整体挪一格(差 0.0000)。
第 4 章最后停在 MNIST:784 维摊平,接全连接。这一章讲为什么那样做不够。 5.7 到 5.9 会把卷积层、池化层、全连接层拆开,一节看一层。 第 6 章的 RNN 换了一个维度做共享——在时间上共享。思路同源。
一、全连接层的两处硬伤
第一处是参数。784 乘 128 加 128 等于 100480。 要是第一层就 512 个单元、输入是 3×224×224 的彩色图(150528 维),一层就是七千七百万个参数。 第二处更隐蔽:摊平之后,「第 1 个像素和第 2 个像素是邻居」这件事消失了。 模型必须从数据里重新学「相邻像素相关」这个本该免费的先验。 这两处硬伤是同一个原因:把图像当成了一个普通的向量。卷积做的事,就是把这个结构还回去。
二、局部连接:每个单元只看一小块
一个 3×3 的核只看 9 个像素。它在假设「一个像素只和附近几个像素有关系」。 这个假设对图像几乎总成立——边缘、角点、纹理,都是局部现象。 远处的像素当然也相关,但那交给层数去管:第一层看 3×3,第二层看第一层的 3×3,三层之后就看到 7×7。 感受野是靠堆叠长出来的,不是靠一个大核一次看全。
三、权重共享:同一个核扫全图
同一个核在整张图上滑,所有位置共用这 9 个权重。 它等于在假设「一条边缘不管出现在左上角还是右下角,都是同一条边缘」。 参数从「每个位置一套」变成「整张图一套」,这才是 1280 对 100480 的来历。 而且这个数与图像大小无关:图从 28×28 变成 800×800,参数还是 1280。
四、平移等变:结构直接给的性质
实测:竖线右移一格,全连接的 8 个输出最大变化 0.7505;卷积的输出最大差 0.0000。 这个性质叫平移等变,注意是「等变」不是「不变」:输出跟着平移,但数值不变。 真正的不变要靠池化,那是 5.8 节的事。 关键在于它不来自训练。 所以卷积网络在小数据上也能有像样的表现, 而全连接网络得先把「平移后还是同一样东西」这件事从数据里学一遍。
五、卷积网络的基本零件
| 层 | 干什么 | 有没有参数 |
|---|---|---|
| 卷积层 | 用核提取局部特征 | 有,核权重加偏置 |
| ReLU 层 | 负的归零 | 无 |
| 池化层 | 窗口里取代表,缩尺寸 | 无 |
| 全连接层 | 最后做分类 | 有,而且最多 |
| softmax | 把输出变成概率 | 无 |
这一章剩下的十一节,基本就是把这五种层挨个讲一遍。 顺序照教材:先几何(5.2 到 5.6),再层(5.7 到 5.9),最后组装起来训练(5.10 以后)。
六、动手 Demo:参数少了 78 倍,还认得平移
两件事:一是把 28×28 接到 128 个单元,算两种接法各要多少参数; 二是拿一条竖线右移一格,看两种接法的输出怎么变。
import numpy as np
print("全连接 784 到 128,参数 %d" % (784 * 128 + 128))
print("卷积 1 个通道 128 个 3×3 核,参数 %d" % (3 * 3 * 1 * 128 + 128))
img = np.zeros((5, 5))
img[:, 1] = 1.0 # 竖线在第 2 列
img2 = np.zeros((5, 5))
img2[:, 2] = 1.0 # 同一条线右移一格
rg = np.random.default_rng(1)
Wf = rg.normal(0, 0.3, (8, 25))
print("全连接 8 个输出的最大变化 %.4f"
% np.abs(Wf @ img.ravel() - Wf @ img2.ravel()).max())
def conv2d(x, k):
f = k.shape[0]
out = np.zeros((x.shape[0] - f + 1, x.shape[1] - f + 1))
for i in range(out.shape[0]):
for j in range(out.shape[1]):
out[i, j] = float((x[i:i + f, j:j + f] * k).sum())
return out
k = rg.normal(0, 0.5, (3, 3))
c1 = conv2d(img, k)
c2 = conv2d(img2, k)
print("卷积 错开一格比较,最大差 %.4f" % np.abs(c1[:, :-1] - c2[:, 1:]).max())
实跑输出:
--------------------------------------------------------------------
卷积神经网络的基础:参数少了 78 倍,还认得平移
--------------------------------------------------------------------
同样是把 28×28 的图接到 128 个单元上,两种接法要的参数:
接法 参数量 每个单元看几个像素
----------------------------------------------
全连接 100480 784
3×3 卷积 1280 9
相差 78.5 倍。少的不只是数字,是「每个单元只管自己那一小块」这个假设。
把一条竖线从第 2 列挪到第 3 列(只动了 5 个像素):
全连接的 8 个输出,最大变化 0.7505
卷积的 3×3 输出,把右边的和左边的对一格:最大差 0.0000
**全连接那边,输出整个换了一套数;卷积这边,输出只是整体挪了一格,数值一个没变。**
这个性质叫平移等变:输入平移,输出跟着平移。它不靠训练得到,
**是「权重共享」这个结构直接给的。**
书里 5-1 节的说法是:卷积神经网络把「图像」当成图像看,而不是当成一串数。这句话的技术含义,
就是上面这两行。
100480 对 1280,差 78.5 倍。这两个数的算法很朴素:全连接是 784 乘 128 加 128; 卷积是 3 乘 3 乘 1 乘 128 加 128。 平移测试里,全连接那 8 个输出最大变化 0.7505。 「最大变化」取的是 8 个输出里变的最狠的那个,实际上 8 个全变了。 卷积那一行是 0.0000。 注意比较方式:把右移后的输出和原输出错开一格再比,差为 0 说明输出整体跟着平移、 数值一个没变。 这两行合起来才说明问题:卷积省参数,不是「参数少所以弱」, 是它把「局部性」和「平移不变」这两个先验直接写进了结构。 书里 5-1 节的标题就叫「卷积神经网络的基础」,讲的正是这两个假设。 后面的补零、步幅、池化,都是在给这个结构打补丁。
30 秒实验
- 把核从 3×3 换成 5×5,参数变成多少?还比全连接少吗?
- 如果图案在垂直方向移动而不是水平方向,卷积的输出会怎么变?
- 如果把权重共享去掉(每个位置一套 3×3 权重),参数量会变成多少?
常见误区
- 以为卷积只是「参数少」。参数少是结果,原因是两个假设。 去掉假设、光把参数减下来,模型只会变弱。
- 把「平移等变」说成「平移不变」。等变是输出跟着动,不变是输出不动。 卷积给的是等变,池化才给一点不变。
- 以为卷积只能处理图像。一维卷积用于语音和文本,三维用于视频和医学影像, 道理完全一样:在某个方向上共享权重。
自测三问(点开对答案)
1. 卷积比全连接省参数,靠哪两个假设? —— 局部连接(每个单元只看一小块)和权重共享(同一套权重扫全图)。 前者减掉大部分连接,后者让参数与图像大小无关。 2. 平移等变和平移不变差在哪? —— 等变是输入平移、输出跟着平移,数值不变;不变是输入平移、输出完全不变。 卷积是等变,池化给了有限的不变性。 3. 为什么说全连接层「把图像当一串数」? —— 摊平丢掉了像素的邻接关系。 模型看到的是 784 个互不相干的数,「相邻像素相关」这条先验得从数据里重新学。5.1 只说了卷积好在哪,没说卷积到底怎么算。 下一节把它摊开:一个核在图上滑一遍,每一步究竟在做什么。
- ☐ (本节完,下一节 5.2 卷积运算)