课程版本:李宏毅《機器學習 2021》spring,CNN 讲(2021-03-12) 官方讲义:
cnn_v4.pdf,35 页 — https://speech.ee.ntu.edu.tw/~hylee/ml/ml2021-course-data/cnn_v4.pdf 分P:BV1ou411N7X3?p=29,55:40 来源边界:B 站匿名 API 无字幕(subtitle.list=[]、need_login_subtitle=true),YouTube 官方源也没有 captionTracks。因此本文不引用逐字台词、不标注分钟时间戳;范围是cnn_v4.pdf全部 35 个物理页的页内标题与图示,加上明确标注的【补充】。本讲是单支完整 CNN 课,不涉及上下讲切分。
1. 这一讲解决什么问题
把一张 100×100 的彩色图拉平,是 30000 维向量;接一个 1000 类输出层,光这一层就是 \(30000\times1000+1000=3.0\times10^7\) 个参数(讲义 p4 写 \(3\times10^7\))。参数多本身不是致命问题,致命的是图像的两个先验完全没被用上:
- pattern 比整张图小。识别鸟不必先看全图,可以先找鸟嘴、眼睛、翅膀(p5–p7)。所以一个神经元不需要连到全部 30000 个像素,只看一个小窗口就够。
- 同一个 pattern 会出现在不同位置。鸟嘴可能在左上也可能在右下(p11)。如果每个位置都训一个"鸟嘴检测器",既浪费参数,也学不到"位置无关"这件事。
- 下采样不改变物体是什么。把图缩小一半,还是鸟(p27)。所以局部统计可以聚合,不必保留每个像素。
CNN 就是把这三点分别写成:感受野(receptive field)、参数共享(parameter sharing)、池化(pooling)。讲义 p16 用一句话概括代价:卷积层相对于全连接层是 "Larger model bias (for image)",也就是人为收窄了假设空间——在图像上这是好事,换成别的任务就未必。
2. 机制一:感受野——每个神经元只看一小块
对一个 3 通道输入,一个神经元的感受野取 \(3\times3\times3\):3×3 的空间窗口,深度等于输入通道数。它连到 27 个输入值,加 1 个 bias,共 28 个参数(p8;本文用 Python 复核:\(3\cdot3\cdot3+1=28\))。
典型设置(p10):
- kernel size \(K=3\)(每个窗口 3×3)
- stride \(S=2\)(窗口每次移动 2 格,允许重叠)
- padding \(P\)(边界补零,控制输出大小)
- 每个空间位置放 64 个神经元,即 64 个 filter
输出边长由同一个式子决定:
代入本讲几个具体数(Python 计算):\(H=100,K=3,P=1,S=1\Rightarrow100\);\(H=100,K=3,P=0,S=2\Rightarrow49\)(不是 50,因为最后剩 1 行不够一个窗口);\(H=6,K=3,P=0,S=1\Rightarrow4\)——正好是 p19 特征图的 4×4。
注意"感受野变大"不等于"核必须变大":两层 3×3、stride 1 的堆叠,第二层一个神经元的有效感受野是 5×5,而参数是 \(2\times9C^2=18C^2\),比一层 5×5 的 \(25C^2\) 少。这是"用小核堆深"的标准理由,属于【补充】,讲义没有展开。
3. 机制二:参数共享——同一组权重扫过整张图
p12–p15 的推理很干净:如果两个神经元在不同位置、但做的是同一件事(找鸟嘴),那就让它们共用同一组权重和 bias。这样"检测器"只有一个,位置变了参数不变。
于是可以换一个叙事(p17–p20):不再讲"每个位置一组神经元",而讲"一个 filter 在整张图上滑动"。两个故事是同一件事(p24–p26 明确写 "They are the same story"):
| 神经元版本 | filter 版本 |
|---|---|
| 每个神经元只看一个感受野 | 每个 filter 检测一个小 pattern |
| 不同感受野的神经元共享参数 | 每个 filter 卷积扫过整张图 |
输出通道数 = filter 个数;每个 filter 的深度 = 输入通道数。所以 64 个 3×3×3 的 filter,参数量是 \(64\times(3\cdot3\cdot3)+64=1792\)(Python 复核),而不是三千万。
玩具数值例(与讲义 p19/p20 逐格对齐)
讲义 p18 给了一张 6×6 二值图和两个 3×3 filter:
图像 X = Filter 1 = Filter 2 =
1 0 0 0 0 1 1 -1 -1 -1 1 -1
0 1 0 0 1 0 -1 1 -1 -1 1 -1
0 0 1 1 0 0 -1 -1 1 -1 1 -1
1 0 0 0 1 0
0 1 0 0 1 0
0 0 1 0 1 0
stride=1、无 padding,用 Python 做 4×4 次乘加,得到
两式与讲义 p19、p20 的数字逐项相同(脚本里 matches_slide_p19=True、matches_slide_p20=True)。这说明卷积不是"差不多"的比喻:左上角 \(3\) 就是 \(1\cdot1+0\cdot(-1)+\cdots+0\cdot1\) 算出来的。
平移等变【术语补充】
讲义 p11 说的是"同一 pattern 出现在不同区域",没有用"等变"这个词。用术语讲:卷积对平移是**等变(equivariant)**的——输入平移 \(k\) 格,特征图也平移 \(k\) 格,数值不变。原因是同一个 filter 在所有位置用同一组权重。等变不是不变:特征图仍然记得位置,只是跟着一起移。真正把位置信息抹掉的是后面的 pooling 或全局池化。
4. 机制三:pooling——把局部统计聚合
p27 的观察是"subsampling the pixels will not change the object"。p28–p29 用 max pooling 实现:每个 2×2 窗口取最大值,窗口不重叠、无参数。
拿上面 \(X*F_1\) 的 4×4 特征图做 2×2 max pooling(Python 计算):
结果 \(\begin{bmatrix}3&0\\3&1\end{bmatrix}\),与 p29 的 pooling 输出一致。
这里必须精确一点:pooling 给的是局部近似不变,不是严格不变。窗口内平移一两格,max 可能不变;平移超过窗口,输出就变了。讲义 p27 的 "will not change the object" 是任务层面的说法,不是数学定理。
整网结构(p30):Convolution → Pooling → Convolution → Pooling → Flatten → Fully Connected → Softmax。
5. 为什么可以这样砍:归纳偏置与适用边界
卷积层用两个假设换来了参数量的骤降:
- 局部性:有用的 pattern 只占一小块;
- 平移共享:同一 pattern 在任何位置都该被同一个检测器抓住。
这两个假设在图像上成立,所以 p16 说这是"for image"的 bias。换到 Go(p31–p33):19×19 棋盘看成 19×19×48 的输入,AlphaGo 第一层用 5×5,没有用 pooling——因为落子位置的全局关系重要,pooling 的"下采样不变"假设不成立(p33 直接写 "Alpha Go does not use Pooling …… How to explain this???")。这是讲义自己留下的开放问题,也提醒我们:CNN 的三件套不是必须捆绑。
边界还有一条(p35):CNN 对缩放和旋转不等变。把鸟放大或转 30°,同样的 filter 未必响应。讲义给的解法是 data augmentation,或引入 Spatial Transformer Layer。这句话很重要,它防止我们把"卷积=视觉万能"当成结论。
6. 伪代码与形状
# x: [C_in, H, W] W: [C_out, C_in, K, K] b: [C_out]
for f in range(C_out):
for i in range(H_out):
for j in range(W_out):
s = b[f]
for c in range(C_in):
for ki in range(K):
for kj in range(K):
s += W[f, c, ki, kj] * x[c, i*S + ki - P, j*S + kj - P]
y[f, i, j] = s
- 权重 shape:
[C_out, C_in, K, K],bias[C_out] - 输出 shape:
[C_out, H_out, W_out] - 乘加量:\(C_{out}\cdot C_{in}\cdot K^2\cdot H_{out}\cdot W_{out}\)(这里能看出卷积省的是参数,不是计算;计算量仍然随空间尺寸走)
7. 常见误区
- "卷积是平移不变"——是平移等变。不变性要靠 pooling/global average pooling 才近似得到。
- "参数共享=所有神经元权重一样"——只有同一个 filter 在空间上共享;不同 filter 的参数不同,否则 64 个 filter 就等于 1 个。
- "pooling 是 CNN 必需"——AlphaGo 不用(p33);现代网络也常用 stride>1 的卷积代替 pooling。它是归纳偏置,不是定义。
- "输出尺寸直接除 stride"——\(100/2=50\),但 \(K=3,S=2,P=0\) 的实际输出是 49。边界不整除时要取下整。
- "核越大越好"——大核参数平方增长;两层 3×3 能得到 5×5 感受野且参数更少。
8. 自测(先自己想,再展开)
Q1. 输入 32×32×3,用 16 个 5×5 的 filter,stride=1,padding=2,输出 shape 和参数量各是多少?
答案
输出边长 \((32+4-5)/1+1=32\),shape 为 16×32×32。每个 filter 有 \(5\cdot5\cdot3=75\) 个权重加 1 个 bias,16 个共 \(16\times76=1216\) 个参数。注意输出通道数由 filter 个数决定,与输入通道数无关。
Q2. 为什么 p19 的特征图是 4×4,而不是 6×6?
答案
因为 3×3 的窗口在 6×6 图上以 stride=1 滑动,横向可放的位置是 \(6-3+1=4\) 个,纵向同理,所以是 4×4。每个输出值对应一个完整的 3×3 窗口,边界像素无法作为窗口中心,因而没有被单独覆盖。
Q3. 如果把 p18 的 filter 换成它的转置(或旋转 180°),特征图会怎样?这说明卷积的什么性质?
答案
数值会变(相当于检测相反方向的pattern),但仍然是同一组权重扫过全图,平移等变性不变。深度学习里的"卷积"实际是互相关(不翻转核),翻转与否只影响学到的核长什么样,不影响参数共享与等变性质。
Q4. 一个任务里"同一个物体可能出现在任意位置,且尺寸固定、方向固定",CNN 的哪些假设匹配、哪些不匹配?
答案
匹配:局部性、平移共享(位置任意);不匹配:缩放/旋转不变性——讲义 p35 明确说 CNN 对 scaling/rotation 不等变,需要数据增强或 Spatial Transformer。若尺寸变化很大,固定 kernel 的感受野也不够,需要多尺度或金字塔结构。
9. 30 秒速记
图像太大 → 全连接参数 \(3\times10^7\)。两个观察:pattern 比图小 → 感受野;同一 pattern 到处出现 → 参数共享(= filter 滑窗)。第三个观察:下采样不改变物体 → pooling。三件套把参数压到 1792,代价是引入图像专用的强 bias;因此 CNN 对缩放/旋转不等变,也不是所有任务都该用 pooling(AlphaGo 就不用)。
跨 P 连接:P36/P37 会把"CNN 是在一个感受野内做 attention"讲成 self-attention 的特例——那时回看 p16 的 "model bias" 会更清楚:CNN 是把手写的邻域先验塞进 attention,Transformer 则把这个先验交还给数据和位置编码。P38 的 RNN 提供另一种"看上下文"的方式(串行、带记忆),P47/P48 的 Transformer 则用 self-attention 同时解决并行与长距离。
引用页(PDF 物理页,1 起)
p4(参数量)、p7–p8(观察一与感受野 28 参数)、p10(kernel/stride/padding/64 neurons)、p11–p15(观察二与参数共享)、p16(model bias)、p17–p20(filter 叙事与 6×6 数值例)、p24–p26(两个故事等价)、p27–p30(pooling 与整网)、p31–p33(Go,无 pooling)、p35(缩放/旋转边界)。