CS231n(陆)——CNN训练与架构
本讲涵盖两大主题: - 如何构建 CNN? 层组件、激活函数、经典架构、权重初始化 - 如何训练 CNN? 数据预处理、数据增强、迁移学习、超参数选择
$\large\star$ Part I:如何构建 CNN
一、CNN 组件回顾
1.1 基本组件
| 组件 | 作用 | 关键点 |
|---|---|---|
| 卷积层 | 空间局部特征提取 | 可学习滤波器,权重共享 |
| 池化层 | 下采样 | 无参数,Max/Avg Pool |
| 全连接层 | 全局信息综合 | 展平后线性变换 |
| 激活函数 | 引入非线性 | 置于线性层后 |
| 归一化层 | 稳定训练 | LayerNorm / BatchNorm |
| Dropout | 正则化 | 随机置零,防止过拟合 |
1.2 归一化层:LayerNorm
核心思想:学习可调参数来缩放/平移数据。
$$\hat{x} = \frac{x - \mu}{\sigma}, \quad y = \gamma \hat{x} + \beta$$
- μ, σ:从数据计算(均值、标准差)
- γ, β:可学习的缩放和平移参数
| 归一化类型 | 统计量计算维度 | 常见用途 |
|---|---|---|
| BatchNorm | 沿 Batch 维度 | CNN 常用(Assign 2 中实现) |
| LayerNorm | 沿特征维度(每个样本独立) | Transformer 常用 |
| GroupNorm | BatchNorm 和 LayerNorm 的折中 | 小 batch 场景 |
1.3 Dropout
训练时:每个前向传播随机将一部分神经元置零(概率 p,常用 p = 0.5)
为什么有效? 1. 冗余表示:强制网络学到冗余特征,不能依赖任何单个神经元 2. 集成学习:每次 dropout 产生不同的子网络,相当于训练了一个庞大模型集合的共享参数版本
一个 4096 单元的 FC 层有 24096 ≈ 101233 种可能的 dropout 掩码——远超宇宙原子总数!
测试时:所有神经元保持激活,需要缩放输出以匹配训练时的期望值。
训练:随机丢 p 的神经元 测试:保留所有,乘以 p
一般模式:训练时加随机性 → 测试时”平均”掉随机性
二、激活函数
2.1 激活函数总览
| 函数 | 公式 | 优点 | 缺点 |
|---|---|---|---|
| Sigmoid | $\sigma(x) = \frac{1}{1+e^{-x}}$ | 有界 [0,1],历史意义 | 饱和→梯度消失;非零中心 |
| tanh | tanh (x) | 零中心 | 仍会饱和 |
| ReLU | max (0, x) | 计算快,不饱和(正半轴),实践中收敛比 sigmoid 快 ~6 倍 | 非零中心;x < 0 梯度为零→Dead ReLU |
| Leaky ReLU | max (0.01x, x) | 缓解 Dead ReLU | 负半轴超参数需手动设 |
| ELU | x(x ≥ 0), α(ex − 1)(x < 0) | 更平滑,接近零中心 | 计算代价更高 |
| GELU | x ⋅ Φ(x) | 行为平滑,接近 0 时梯度和缓 | 计算代价高于 ReLU |
2.2 Sigmoid 的问题
σ′(x) = σ(x)(1 − σ(x))
- x 很大或很小时,梯度趋近 0 → 梯度消失
- 多层 sigmoid 叠加 → 梯度越来越小,深层无法训练
2.3 ReLU:默认首选
f(x) = max (0, x)
- 正半轴不饱和 → 梯度可以一直流动
- 计算极简(一个阈值操作)
- Dead ReLU 问题:x < 0 时梯度为 0,若进入该区域且无法恢复,对应神经元”死亡”
- 实践中通过合适的权重初始化缓解
2.4 GELU:现代替代方案
f(x) = x ⋅ Φ(x) (Φ 是标准正态 CDF)
- 在 0 附近平滑过渡(而非 ReLU 的硬截断)
- 负半轴也保留微小梯度
- 现代 Transformer 模型(如 ViT、GPT)广泛使用
放置位置:激活函数通常置于线性算子之后(FC 后、Conv 后)。
三、CNN 经典架构
3.1 CNN经典模型
| 年份 | 模型 | 层数 | 核心创新 |
|---|---|---|---|
| 2012 | AlexNet | 8 | 首个在大规模数据上的深度 CNN |
| 2014 | VGG | 16-19 | 小滤波器 (3×3) + 更深网络 |
| 2014 | GoogLeNet | 22 | Inception 模块 |
| 2015 | ResNet | 152 | 残差连接,深度革命 |
3.2 VGGNet:小而深的滤波器
核心哲学:全部使用 3×3 conv,stride=1, pad=1 + 2×2 max pool, stride=2
为什么使用小滤波器(3×3)?
三层 3×3 conv (stride 1) 的有效感受野 = 一个 7×7 conv:
1 | 输入 → Conv1(3×3) → Conv2(3×3) → Conv3(3×3) |
小滤波器的优势: - 更深 + 更多非线性:每个 conv 后跟 ReLU → 3 个 3×3 比 1 个 7×7 多 2 次 ReLU - 更少参数:3 × (32C2) = 27C2 vs 72C2 = 49C2(近一半的参数) - 相同感受野 + 更强表达力 + 更省内存
| 版本 | 层数 | 结构 |
|---|---|---|
| VGG16 | 16 | 13 Conv + 3 FC |
| VGG19 | 19 | 16 Conv + 3 FC |
VGG 将 ILSVRC top-5 错误率从 ZFNet 的 11.7% 降至 7.3%
3.3 ResNet:残差连接与深度革命
问题:更深的”plain”网络表现反而更差?
- 56 层网络的训练误差和测试误差均高于 20 层
- 不是过拟合——是优化问题!深层模型更难优化
关键洞察: - 更深的模型理应至少和更浅的一样好(可以将额外层设为恒等映射) - 但 SGD 学不出这样的恒等映射
解决方案:残差块(Residual Block)
H(x) = F(x) + x
- 让网络层拟合残差 F(x) = H(x) − x,而不是直接拟合目标映射 H(x)
- 若最优映射接近恒等,只需让 F(x) → 0,这比让权重学到恒等矩阵容易得多
1 | ┌──────────────────────┐ |
ResNet 架构设计: - 残差块内两个 3×3 conv - 周期性加倍通道数 + 空间下采样(stride 2) - 开头的 stem:7×7 conv + 池化 - 结尾:全局平均池化 + FC
成果: - ImageNet 上训练 152 层 - ILSVRC’15 分类冠军(3.57% top-5 错误率) - 横扫 ILSVRC’15 和 COCO’15 所有分类和检测任务
💡 ResNet 的核心贡献:残差连接使优化极深网络成为可能——它为梯度提供了一条”高速公路”(恒等映射),缓解了深层网络的梯度消失问题。
四、权重初始化
问题:权重的初始值直接影响训练稳定性。
| 情况 | 后果 |
|---|---|
| 太小(如 std=0.01) | 激活值逐层趋近 0,梯度消失 |
| 太大(如 std=0.05) | 激活值爆炸,梯度爆炸 |
解决方案:Kaiming / MSRA 初始化(He et al., ICCV 2015)
$$W \sim \mathcal{N}\left(0, \frac{2}{D_{in}}\right)$$
- Din:该层输入维度
- $\sqrt{2}$ 因子:补偿 ReLU 将一半输入置零导致的方差衰减
- 效果:各层激活值保持均匀分布,即”刚刚好”
不同激活函数对应不同初始化策略——Kaiming 初始化专为 ReLU 设计;对于其他激活函数(如 GELU),现代框架通常有对应的自适应版本。
$\large\star$ Part II:如何训练 CNN
五、数据预处理
TLDR:对每个通道做中心化 + 缩放:
1 | # 对每个通道分别计算(如 RGB: 3 个均值, 3 个标准差) |
- 几乎所有现代模型都采用此方式
- 统计量在整个训练集上预计算
六、数据增强
核心模式:训练时引入随机变换 → 测试时平均(有时近似)
6.1 水平翻转
最简单有效的方式——大多数自然图像翻转后仍是合理图像。
6.2 随机裁剪和缩放
训练时(ResNet 风格): 1. 在 [256, 480] 中随机选短边长度 L 2. 将训练图像缩放到短边 = L 3. 随机裁剪 224 × 224 patch
测试时:对固定的一组裁剪求平均 1. 以 5 种尺度缩放:{224, 256, 384, 480, 640} 2. 每种尺度取 10 个 224 × 224 裁剪:4 角 + 中心,及各自的水平翻转
6.3 颜色抖动(Color Jitter)
随机改变对比度和亮度——增强模型对光照变化的鲁棒性。
6.4 Cutout
训练时将随机图像区域置零,测试时使用完整图像。
在小数据集(如 CIFAR)上效果显著,大数据集(如 ImageNet)较少使用。
七、迁移学习
问题:没有大数据集怎么办?
解决方案:先在大型数据集上预训练,再迁移到目标任务。
7.1 三种策略
| 场景 | 数据量 | 与源任务相似度 | 策略 |
|---|---|---|---|
| 小数据 + 相似任务 | 很少 | 高 | 冻结卷积层,只训练最后的线性分类器 |
| 较多数据 + 相似任务 | 较多 | 高 | 从预训练模型初始化,微调所有层 |
| 小数据 + 不同任务 | 很少 | 低 | 尝试其他预训练模型,或收集更多数据 |
| 较多数据 + 不同任务 | 较多 | 低 | 微调全部或从头训练 |
7.2 实际建议(咕咕嘎嘎)
🧠 如果你有 < ~100 万张图像的数据集: 1. 找一个有相似数据的大数据集,在上面训练一个大模型 2. 迁移学习到你的数据集
深度学习框架提供”Model Zoo”,无需自己训练: - PyTorch:
torchvision.models- HuggingFace:timm(pytorch-image-models)
八、超参数选择
8.1 调参流程
| 步骤 | 操作 | 说明 |
|---|---|---|
| Step 1 | 检查初始损失 | 关闭正则化 → 损失应与随机猜测一致 |
| Step 2 | 在小样本上过拟合 | 取少量数据 → 损失应能到 0,验证架构正确 |
| Step 3 | 找能使 loss 下降的 LR | 用全部数据,弱正则化,尝试 LR ∈ {1e-1, 1e-2, 1e-3, 1e-4, 1e-5} |
| Step 4 | 粗网格搜索,训练 1-5 epoch | 缩小 LR 范围 |
| Step 5 | 细化网格,训练更久 | |
| Step 6 | 观察 loss/accuracy 曲线 | 诊断过拟合/欠拟合 |
| Step 7 | 回到 Step 5 迭代 |
8.2 诊断曲线
| 现象 | 诊断 | 对策 |
|---|---|---|
| 验证准确率仍在上升 | 训练还不够久 | 延长训练 |
| 训练和验证差距巨大 | 过拟合 | 增加正则化 / 收集更多数据 |
| 训练和验证几乎无差距 | 欠拟合 | 训练更长 / 使用更大模型 |
8.3 随机搜索 vs 网格搜索
Random Search ≫ Grid Search
当某些超参数对性能几乎无影响时,网格搜索会在不重要维度上浪费大量实验。随机搜索在每个重要维度上都能探索更多不同值。
$\large\star$ 九、总结
| 类别 | 总结 |
|---|---|
| CNN 组件 | Conv → Norm → ReLU 是标准配置;Dropout 偶尔使用 |
| 激活函数 | ReLU 是简单有效的默认选择;GELU 在现代 Transformer 中更常见 |
| 经典架构 | VGG:小滤波器加深网络;ResNet:残差连接使 152 层训练成为可能 |
| 权重初始化 | Kaiming 初始化保证各层激活值分布稳定:$std = \sqrt{2/D_{in}}$ |
| 数据预处理 | 逐通道减均值除标准差 |
| 数据增强 | 翻转、裁剪、颜色抖动、Cutout → 训练加噪,测试平均 |
| 迁移学习 | 没有大数据 → 用 ImageNet 预训练模型微调 |
| 超参数 | 先过拟合小样本 → 粗搜索LR → 观曲线诊断 → 随机搜索更高效 |
