D2L

核心基石:CNN 的三大件

  1. 卷积层(Conv):提取空间特征。靠叠加通道(Channel)丰富特征内涵。
  2. 池化层(Pooling):空间降维。提取主要特征,带来平移不变性,减小计算量。
  3. 全连接层(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. 引入 $1 \times 1$ 卷积:在像素位置不变的前提下,做跨通道的特征融合,相当于在通道维度塞入了一个 MLP(多层感知机)。
    2. 发明全局平均池化(GAP):直接把最后一个卷积层的通道数设为类别数,求个平均值直接当得分输出,彻底消灭全连接层

      4. GoogLeNet (Inception v1) —— “小孩子才做选择,我全都要”

  • 痛点:图片里的物体有大有小,固定大小的卷积核很难兼顾宏观和微观。但如果乱用大卷积核,计算量会爆炸。
  • 解决逻辑(Inception 块)

    1. 并行多尺度:把 $1 \times 1$、$3 \times 3$、$5 \times 5$ 和最大池化并列放,提取不同尺度的特征后,在通道维度 Concat(拼接)起来。
    2. 降维打击:利用 NiN 的遗产,在做大卷积之前,先插一个 $1 \times 1$ 卷积压缩通道数,暴风式降低计算量。
Inception块的架构GoogLeNet架构

第三阶段:驯服深度的利器(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)

    1. 稠密块(放):抛弃相加,改用拼接(Concat)。每一层都接受前面所有层的输出作为输入。用 Growth Rate(增长率 $k$) 控制每层吐出的通道数。
    2. 过渡层(收):为了防止通道数滚雪球爆炸,在块与块之间,用 $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 + identity

5. 拼接操作 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

标签: none

评论已关闭