CNN 进化史
核心基石:CNN 的三大件
- 卷积层(Conv):提取空间特征。靠叠加通道(Channel)丰富特征内涵。
- 池化层(Pooling):空间降维。提取主要特征,带来平移不变性,减小计算量。
- 全连接层(FC)/ 全局平均池化(GAP):汇总特征,输出最终的分类打分。
第一阶段:蛮荒与探索(LeNet、AlexNet、VGG)
1. LeNet (1998) & AlexNet (2012)
- 痛点:早期的全连接网络看图片,会把二维结构彻底破坏,且参数量大得惊人。
- 解决逻辑:引入局部感受野和权重共享(也就是卷积核),正式确立了
Conv -> Pool -> FC的经典架构。AlexNet 则证明了“网络越深、数据越多、效果越好”,并首次引入了 ReLU 和 Dropout。
2. VGG (2014) —— “乐高式”模块化鼻祖
- 痛点:AlexNet 里的卷积核忽大忽小($11 \times 11, 5 \times 5, 3 \times 3$),没有规律,设计网络像门玄学。
- 解决逻辑:模块化设计。VGG 证明了:用多个串联的 $3 \times 3$ 小卷积核,能完美替代一个大卷积核(感受野相同,但非线性更强,参数更少)。自此,深层网络开始走向“标准化积木(Block)”时代。
第二阶段:结构上的奇思妙想(NiN、GoogLeNet)
3. NiN (Network in Network) —— $1 \times 1$ 卷积的封神之战
- 痛点:传统网络最后的巨大全连接层,占了总参数的 80% 以上,极易过拟合。且普通卷积的非线性表达能力不足。
解决逻辑:
- 引入 $1 \times 1$ 卷积:在像素位置不变的前提下,做跨通道的特征融合,相当于在通道维度塞入了一个 MLP(多层感知机)。
发明全局平均池化(GAP):直接把最后一个卷积层的通道数设为类别数,求个平均值直接当得分输出,彻底消灭全连接层。
4. GoogLeNet (Inception v1) —— “小孩子才做选择,我全都要”
- 痛点:图片里的物体有大有小,固定大小的卷积核很难兼顾宏观和微观。但如果乱用大卷积核,计算量会爆炸。
解决逻辑(Inception 块):
- 并行多尺度:把 $1 \times 1$、$3 \times 3$、$5 \times 5$ 和最大池化并列放,提取不同尺度的特征后,在通道维度
Concat(拼接)起来。 - 降维打击:利用 NiN 的遗产,在做大卷积之前,先插一个 $1 \times 1$ 卷积压缩通道数,暴风式降低计算量。
- 并行多尺度:把 $1 \times 1$、$3 \times 3$、$5 \times 5$ 和最大池化并列放,提取不同尺度的特征后,在通道维度
第三阶段:驯服深度的利器(BN 与 ResNet)
5. Batch Normalization (批量规范化) —— 拯救梯度的神
- 痛点:网络一旦变深,中间层的数据分布就会跑偏(极大或极小),导致激活函数丧失区分度,梯度消失,完全训练不动。
- 解决逻辑:在每次卷积之后、激活之前,强制把这一批次的数据减均值除以标准差,拉回到 $0$ 附近的黄金区分度区域。然后再用可学习参数 $\gamma$ 和 $\beta$ 保证网络的非线性表达能力。
$$ \mathrm{BN}(\mathbf{x}) = \boldsymbol{\gamma} \odot \frac{\mathbf{x} - \hat{\boldsymbol{\mu}}_\mathcal{B}}{\hat{\boldsymbol{\sigma}}_\mathcal{B}} + \boldsymbol{\beta}. $$
6. ResNet (残差网络) —— 深不可测的高速公路
- 痛点:网络叠到 50 层以上时,效果反而不如 20 层(退化问题)。因为后面的层学歪了,把前面好不容易提取的特征给弄丢了。
解决逻辑(Residual Block):增加一条跳跃连接(Shortcut / 捷径)。
- 让输入 $x$ 直接跨过卷积层加到输出上。中间的卷积层只需要学习残差(差额)。
- 预激活(V2改进):把 BN 和 ReLU 挪到卷积前面,保证 Shortcut 这条“大动脉”上没有任何非线性阻碍,让梯度 100% 顺滑回传。造就了 1000 层以上的超深网络。
第四阶段:特征复用的极致(DenseNet)
7. DenseNet (稠密连接网络) —— 滚雪球魔法
- 痛点:ResNet 的“相加(Add)”操作虽然好,但会把特征混在一起。能不能最大化地复用之前所有层提取过的原汁原味特征?
解决逻辑(Dense Block & Transition Layer):
- 稠密块(放):抛弃相加,改用拼接(Concat)。每一层都接受前面所有层的输出作为输入。用 Growth Rate(增长率 $k$) 控制每层吐出的通道数。
- 过渡层(收):为了防止通道数滚雪球爆炸,在块与块之间,用 $1 \times 1$ 卷积砍掉一半通道,用
AvgPool(stride=2)砍掉一半高宽,强制瘦身。
$$ \mathbf{x} \to \left[ \mathbf{x}, f_1(\mathbf{x}), f_2([\mathbf{x}, f_1(\mathbf{x})]), f_3([\mathbf{x}, f_1(\mathbf{x}), f_2([\mathbf{x}, f_1(\mathbf{x})])]), \ldots\right]. $$
PyTorch
1. 卷积层 nn.Conv2d
Python
nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, bias=True)- 高频雷区:如果紧跟着 BN 层,必须设置
bias=False!(因为 BN 第一步减均值会把偏置直接抵消掉,留着浪费显存)。 - 如何保持高宽不变:当
stride=1时,设padding = kernel_size // 2(比如核是 3,padding 就是 1)。 - 如何缩小高宽:通常用
stride=2来下采样(高宽减半)。
2. 批量规范化 nn.BatchNorm2d
nn.BatchNorm2d(num_features)- 参数:
num_features必须等于上一层卷积输出的out_channels。 - 内部机制:自带的
eps=1e-5防止除以 0;自带可学习参数 $\gamma$ 和 $\beta$。你直接实例化就行,参数不用调。
3. 全局平均池化 (GAP) 的现代写法
在 NiN 和现代网络末尾,为了消灭全连接层,我们会用 GAP 把特征图压扁。在 PyTorch 中,最优雅的写法是使用自适应池化:
# 无论前面输入的特征图高宽是多少(比如 7x7 或 14x14)
# 它都会自动帮你算平均,强行输出高宽为 1x1 的特征图
nn.AdaptiveAvgPool2d((1, 1)) 4. 经典的 ResNet 块 (预激活结构缩写)
# 假设是 ResNet 的一个简单块
def forward(self, x):
identity = x # 保留小路保底
# 主干道 (预激活结构)
out = self.bn1(x)
out = self.relu(out)
out = self.conv1(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv2(out)
# 终极相加 (维度必须一样)
return out + identity5. 拼接操作 torch.cat (DenseNet 必备)
# 假设 x1 形状是 [Batch, 32, 28, 28], x2 形状是 [Batch, 64, 28, 28]
out = torch.cat([x1, x2], dim=1)
# dim=1 表示在通道(Channel)维度拼接,结果变成 [Batch, 96, 28, 28]好累,今天学了一章多一节,睡了zzz
评论已关闭