# 06. CNN 卷积神经网络(MNIST 冲 99%) > 同一份 MNIST 数据,全连接只能 97.7%,CNN 直接 **99.15%**。 > 这一篇讲清卷积为什么更适合图像。 --- ## 一、跑通结果对比 ``` 全连接 MLP : 97.70% CNN : 99.15% ← 错误率 2.3% → 0.85%,砍掉一大半 CNN 参数量 : 421,642 ``` --- ## 二、卷积层在算什么? 全连接把图片**拉平**成 784 维 → **丢掉空间信息**(谁挨着谁不知道了)。 CNN 不拉平,保留 2D 结构,用小窗口(卷积核)在图上滑动: ``` 卷积核(3×3)滑过图像,每处做"对应相乘再求和": Σ(核 × 覆盖像素) = 1个输出值 滑遍全图 → 一张特征图(feature map) ``` 每个核学会检测一种**局部特征**(边缘、笔画、拐角)。 ```python nn.Conv2d(1, 32, kernel_size=3, padding=1) # ↑ ↑ ↑ # 输入1通道 32个核 3×3窗口 ``` --- ## 三、CNN 三大法宝 ### ① 局部连接 + 权重共享 → 高效 - 全连接:每个输出连**所有**784像素 - 卷积:每个输出只连 **3×3=9** 个邻近像素,且**同一个核滑遍全图共享权重** - 一个"检测横线"的核,图片任何位置都能复用 → 参数少、泛化好 ### ② 池化 MaxPool → 降采样 + 抗位移 ```python nn.MaxPool2d(2) # 2×2取最大, 尺寸减半 # [B,32,28,28] -> [B,32,14,14] ``` 降维省算力;数字稍微挪动,最大值还在 → 抗位移。 #### 池化到底怎么运作?(具体例子) 和卷积**不同**:池化窗口**不重叠**地跳着走(stride=窗口大小), 而且**没有可学参数**(不加权,纯粹取最大/平均)。 `MaxPool2d(2)`:把图切成不重叠的 2×2 小块,每块只留**最大值**。 ``` 输入 4×4: 2×2 MaxPool 后 2×2: ┌─────────────┐ │ 1 3 │ 2 0│ 每个2×2块取max: │ 4 2 │ 1 5│ ──► ┌─────────┐ ├───────┼─────┤ │ 4 │ 5 │ 左上块max(1,3,4,2)=4 │ 0 1 │ 6 2│ │ 7 │ 8 │ 右上块max(2,0,1,5)=5 │ 7 3 │ 4 8│ └─────────┘ 左下max(0,1,7,3)=7 └─────────────┘ 右下max(6,2,4,8)=8 ``` **关键特点**: - **不重叠**:4×4 → 2×2(尺寸减半),通道数不变 [B,32,28,28]→[B,32,14,14] - **无参数**:不需要学习,直接取最大 - **逐通道独立**:32个通道各自池化,互不干扰 **为什么取最大?** - 卷积特征图里"值越大"=该位置"越像这个核检测的特征" - 取最大 = **保留最强响应**,丢掉弱的 - 数字笔画稍微平移/抖动,2×2 窗口里的最大值大概率还在 → **抗位移/抗噪** > 另有 `AvgPool`(取平均),但图像分类里 MaxPool 更常用(突出强特征)。 ### ③ 感受野逐层变大 浅层看局部细节(笔画),深层看整体结构(这是个8)。 --- ## 四、整体结构(数据形状变化) ``` 输入 [B,1,28,28] ↓ Conv2d(1→32)+ReLU [B,32,28,28] 提32种特征 ↓ MaxPool(2) [B,32,14,14] ↓ Conv2d(32→64)+ReLU [B,64,14,14] 提高级特征 ↓ MaxPool(2) [B,64,7,7] ↓ Flatten [B,3136] ↓ Linear(3136→128)+ReLU[B,128] ↓ Dropout(0.25) 防过拟合 ↓ Linear(128→10) [B,10] ``` > 结尾还是 **Flatten + 全连接 + CrossEntropyLoss**——CNN 只是前面加了"特征提取器",分类头还是老一套。 --- ## 五、Dropout 防过拟合 ```python nn.Dropout(0.25) # 训练时随机关闭25%神经元 ``` - 强迫网络不依赖单个神经元,更鲁棒 - **只训练时生效**,`model.eval()` 时自动关闭 --- ## 六、五步骨架变了吗?没有! ```python optimizer.zero_grad() # 1 loss = criterion(model(x), y) # 2+3 loss.backward() # 4 optimizer.step() # 5 ``` 和 sin、MLP **一模一样**,只有 `model` 内部结构变了。 --- ## 七、可视化:CNN 到底学到了什么 ### 卷积核(cnn_kernels.png) 第一层 32 个 3×3 核,训练后自发变成**边缘/方向检测器**(红蓝代表权重正负)。没人教,反向传播自己学出来的。 ### 特征图(cnn_featuremaps.png) 同一个数字过 32 个核 → 32 张特征图:有的只亮横笔画、有的只亮竖/斜笔画。 **每个核 = 一个专门的笔画探测器**,把数字拆成基本笔画,深层再组合判断。 ``` 原图(7) → 32核扫描 → 32张特征图(横/竖/斜...) → 第二层组合(转角/闭环) → 全连接 → "这是7" ``` --- ## 八、MLP vs CNN 总结 | | 全连接 MLP | CNN | |---|---|---| | 输入 | 拉平丢空间 | 保留2D结构 | | 连接 | 全连接 | 局部+权重共享 | | 参数 | ~24万 | ~42万(更高效) | | 准确率 | 97.7% | **99.15%** | | 适合 | 表格/向量 | **图像** | 代码:`code/mnist_cnn.py`、`code/cnn_visualize.py` 图:`cnn_kernels.png`、`cnn_featuremaps.png` --- by 小小叶 · OpenClaw