目录
- 卷积神经网络基础
- 经典架构演进
- [ResNet 与残差学习](#ResNet 与残差学习)
- [DenseNet 与密集连接](#DenseNet 与密集连接)
- 高效网络设计
- 模型缩放理论
- [Vision Transformer](#Vision Transformer)
- [Swin Transformer](#Swin Transformer)
- [ConvNeXt 与现代 ConvNet](#ConvNeXt 与现代 ConvNet)
- 架构设计原则与前沿
1. 卷积神经网络基础
1.1 卷积操作
卷积操作是 CNN 的核心:
输入: 特征图 X ∈ ℝ^{C_in × H × W}
卷积核: K ∈ ℝ^{C_out × C_in × k × k}
输出: 特征图 Y ∈ ℝ^{C_out × H' × W'}
数学定义 (单个输出位置):
Y[c_out, i, j] = Σ_{c_in} Σ_{m,n} K[c_out, c_in, m, n] · X[c_in, i+m, j+n] + b[c_out]
直觉理解:
卷积核在输入上滑动
每个位置计算局部区域的加权和
捕获局部模式 (边缘、纹理等)
关键参数:
1. 卷积核大小 (Kernel Size):
3×3: 最常用,感受野 3×3
1×1: 通道变换,无空间信息
5×5, 7×7: 较大感受野
2. 步长 (Stride):
输出尺寸 = (输入尺寸 - 核大小 + 2×填充) / 步长 + 1
步长 > 1: 下采样
3. 填充 (Padding):
保持输出尺寸 (same padding)
或减小输出尺寸 (valid padding)
4. 膨胀率 (Dilation):
膨胀卷积: 在卷积核元素间插入空隙
有效感受野 = 核大小 + (核大小-1)×(膨胀率-1)
import torch
import torch.nn as nn
class ConvolutionBasics:
"""卷积操作基础"""
@staticmethod
def standard_conv(in_channels, out_channels, kernel_size=3, stride=1, padding=1):
"""
标准卷积
参数量: C_out × C_in × k × k
计算量: C_out × C_in × k × k × H × W
"""
return nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding)
@staticmethod
def depthwise_separable_conv(in_channels, out_channels, kernel_size=3):
"""
深度可分离卷积 (Depthwise Separable Convolution)
分为两步:
1. Depthwise: 每个通道独立卷积 (C_in × k × k 参数)
2. Pointwise: 1×1 卷积融合通道 (C_in × C_out 参数)
参数量: C_in × k² + C_in × C_out
计算量: 约为标准卷积的 1/k² + 1/C_out
"""
return nn.Sequential(
# Depthwise
nn.Conv2d(in_channels, in_channels, kernel_size,
padding=kernel_size//2, groups=in_channels),
nn.BatchNorm2d(in_channels),
nn.ReLU6(inplace=True),
# Pointwise
nn.Conv2d(in_channels, out_channels, 1),
nn.BatchNorm2d(out_channels),
nn.ReLU6(inplace=True)
)
@staticmethod
def dilated_conv(in_channels, out_channels, kernel_size=3, dilation=2):
"""
膨胀卷积 (Dilated/Atrous Convolution)
理论:
在不增加参数的情况下扩大感受野
有效感受野 = k + (k-1)×(d-1)
应用:
语义分割 (需要大感受野)
语音处理 (长距离依赖)
"""
return nn.Conv2d(in_channels, out_channels, kernel_size,
padding=dilation, dilation=dilation)
"""
卷积的计算量分析:
标准卷积:
FLOPs = C_out × C_in × k² × H × W × 2 (乘加)
深度可分离卷积:
FLOPs = (C_in × k² × H × W + C_in × C_out × H × W) × 2
压缩比:
标准/可分离 = k² × C_out / (k² + C_out)
当 k=3, C_out=256 时:
压缩比 ≈ 9 × 256 / (9 + 256) ≈ 8.7x
"""
1.2 池化操作
池化 (Pooling) 的作用:
1. 降低空间分辨率 (下采样)
2. 扩大感受野
3. 提供平移不变性
4. 减少计算量
常见池化:
1. 最大池化 (Max Pooling):
取局部区域的最大值
保留最显著的特征
2. 平均池化 (Average Pooling):
取局部区域的平均值
保留整体信息
3. 全局平均池化 (Global Average Pooling, GAP):
对整个特征图取平均
将空间维度压缩为 1
常用于分类任务的最后一层
4. 自适应池化 (Adaptive Pooling):
输出固定大小,无论输入尺寸
用于处理不同尺寸的输入
1.3 归一化技术
归一化 (Normalization) 的作用:
1. 加速训练收敛
2. 稳定训练过程
3. 允许使用更大的学习率
4. 有一定的正则化效果
常见归一化:
1. 批归一化 (Batch Normalization, BN):
对每个通道,沿 batch 维度归一化
μ_c = (1/B) Σ_b x_{b,c}
σ²_c = (1/B) Σ_b (x_{b,c} - μ_c)²
x̂_{b,c} = (x_{b,c} - μ_c) / √(σ²_c + ε)
y_{b,c} = γ_c · x̂_{b,c} + β_c
优点: 效果好,广泛使用
缺点: 依赖 batch size,不适合小 batch
2. 层归一化 (Layer Normalization, LN):
对每个样本,沿特征维度归一化
优点: 不依赖 batch size
缺点: 在 CNN 中效果不如 BN
应用: Transformer, RNN
3. 实例归一化 (Instance Normalization, IN):
对每个样本的每个通道独立归一化
应用: 风格迁移
4. 组归一化 (Group Normalization, GN):
将通道分组,组内归一化
优点: 不依赖 batch size,效果接近 BN
应用: 目标检测、分割
class NormalizationLayers:
"""归一化层"""
@staticmethod
def batch_norm(channels):
"""批归一化"""
return nn.BatchNorm2d(channels)
@staticmethod
def layer_norm(normalized_shape):
"""层归一化"""
return nn.LayerNorm(normalized_shape)
@staticmethod
def group_norm(channels, num_groups=32):
"""
组归一化
理论:
将通道分为 num_groups 组
每组内独立归一化
优势:
- 不依赖 batch size
- 效果接近 BN
- 适合检测/分割任务
"""
return nn.GroupNorm(num_groups, channels)
@staticmethod
def rms_norm(d_model):
"""
RMS 归一化
RMSNorm(x) = γ · x / √(mean(x²) + ε)
优势:
- 比 LayerNorm 更简单
- 不需要计算均值
- 现代 LLM 的默认选择
"""
class RMSNorm(nn.Module):
def __init__(self, d_model, eps=1e-6):
super().__init__()
self.weight = nn.Parameter(torch.ones(d_model))
self.eps = eps
def forward(self, x):
rms = torch.sqrt(torch.mean(x ** 2, dim=-1, keepdim=True) + self.eps)
return self.weight * (x / rms)
return RMSNorm(d_model)
"""
归一化位置:
Post-Norm (原始 Transformer):
x = Norm(x + Sublayer(x))
Pre-Norm (现代架构):
x = x + Sublayer(Norm(x))
Pre-Norm 优势:
- 训练更稳定
- 梯度流更顺畅
- 不需要 warmup
"""
2. 经典架构演进
2.1 架构发展时间线
┌─────────────────────────────────────────────────────────────────────────┐
│ CNN 架构发展时间线 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1998 ──┬── LeNet-5 (卷积网络先驱) │
│ │ 首次成功应用 CNN 于手写数字识别 │
│ │ │
│ 2012 ──┼── AlexNet (深度学习复兴) │
│ │ ImageNet 冠军,ReLU,Dropout,GPU 训练 │
│ │ │
│ 2014 ──┼── VGGNet (深度的重要性) │
│ │ 证明更深的网络效果更好,3×3 卷积核 │
│ │ │
│ 2014 ──┼── GoogLeNet/Inception (网络中的网络) │
│ │ 多尺度特征,1×1 卷积降维 │
│ │ │
│ 2015 ──┼── ResNet (残差连接) │
│ │ 解决深层网络退化问题,152 层 │
│ │ │
│ 2017 ──┼── DenseNet (密集连接) │
│ │ 特征复用,减轻梯度消失 │
│ │ │
│ 2017 ──┼── MobileNet (轻量级网络) │
│ │ 深度可分离卷积,移动端部署 │
│ │ │
│ 2019 ──┼── EfficientNet (复合缩放) │
│ │ 系统化的模型缩放策略 │
│ │ │
│ 2020 ──┼── Vision Transformer (ViT) │
│ │ Transformer 应用于视觉 │
│ │ │
│ 2021 ──┼── Swin Transformer (层次化视觉 Transformer) │
│ │ 移位窗口注意力,多尺度特征 │
│ │ │
│ 2022 ──┼── ConvNeXt (现代化 ConvNet) │
│ │ 用 Transformer 的设计哲学改进 CNN │
│ │ │
│ 2023+ ──┴── 持续发展: 混合架构、更高效的注意力 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
2.2 AlexNet
论文: "ImageNet Classification with Deep Convolutional Neural Networks" (Krizhevsky et al., 2012)
历史意义:
ImageNet 2012 冠军,错误率大幅下降 (25.8% → 16.4%)
开启了深度学习在计算机视觉的革命
关键创新:
1. ReLU 激活函数: 解决 sigmoid 梯度消失
2. Dropout: 防止过拟合
3. 数据增强: 翻转、裁剪、颜色抖动
4. GPU 训练: 两块 GTX 580 并行
5. 局部响应归一化 (LRN): 后来被淘汰
架构:
输入 (224×224×3)
→ Conv 11×11, stride 4, 96 filters
→ MaxPool 3×3, stride 2
→ Conv 5×5, 256 filters
→ MaxPool 3×3, stride 2
→ Conv 3×3, 384 filters
→ Conv 3×3, 384 filters
→ Conv 3×3, 256 filters
→ MaxPool 3×3, stride 2
→ FC 4096
→ FC 4096
→ FC 1000 (输出)
2.3 VGGNet
论文: "Very Deep Convolutional Networks for Large-Scale Image Recognition" (Simonyan & Zisserman, 2014)
核心思想:
使用小卷积核 (3×3) 堆叠,代替大卷积核
两个 3×3 卷积的感受野 = 一个 5×5 卷积
三个 3×3 卷积的感受野 = 一个 7×7 卷积
理论优势:
1. 参数更少:
3 × (3² × C²) = 27C² < 7² × C² = 49C²
2. 非线性更强:
更多的激活函数
3. 特征更丰富:
多层抽象
VGG-16 架构:
[Conv3-64] × 2 → MaxPool
[Conv3-128] × 2 → MaxPool
[Conv3-256] × 3 → MaxPool
[Conv3-512] × 3 → MaxPool
[Conv3-512] × 3 → MaxPool
→ FC-4096 → FC-4096 → FC-1000
2.4 GoogLeNet / Inception
论文: "Going Deeper with Convolutions" (Szegedy et al., 2014)
核心创新:
1. Inception 模块: 多尺度特征融合
2. 1×1 卷积: 降维,减少计算量
3. 辅助分类器: 缓解梯度消失
Inception 模块:
┌─────────────────────────────────────────────────────────────┐
│ │
│ 输入 → ┌─ 1×1 Conv ─────────────────────────┐ │
│ ├─ 1×1 Conv → 3×3 Conv ──────────────┤ │
│ ├─ 1×1 Conv → 5×5 Conv ──────────────┤ → Concat │
│ └─ 3×3 MaxPool → 1×1 Conv ───────────┘ │
│ │
│ 四个分支并行处理,输出拼接 │
│ 1×1 卷积用于降维 (减少通道数) │
│ │
└─────────────────────────────────────────────────────────────┘
理论:
不同尺度的卷积核捕获不同尺度的特征
1×1 卷积降维减少计算量
并行处理后拼接,保留多尺度信息
3. ResNet 与残差学习
3.1 退化问题
退化问题 (Degradation Problem):
理论上: 更深的网络应该 ≥ 更浅的网络
(额外层可以学习恒等映射)
实际上: 更深的网络反而更难训练
56 层网络的训练/测试误差 > 20 层
这不是过拟合 (训练误差也更高)
而是优化困难
原因分析:
1. 梯度消失/爆炸: 深层梯度衰减
2. 优化困难: 深层网络的损失曲面复杂
3. 表示瓶颈: 信息在深层丢失
ResNet 的解决:
引入残差连接,让网络学习残差而非完整映射
3.2 残差连接
论文: "Deep Residual Learning for Image Recognition" (He et al., 2015)
残差块 (Residual Block):
标准块:
y = F(x, {W_i})
残差块:
y = F(x, {W_i}) + x
其中:
F(x): 残差函数 (需要学习的部分)
x: 恒等映射 (跳跃连接)
理论:
如果最优映射是 H(x) = x
残差 F(x) = H(x) - x = 0
学习 0 比学习恒等更容易
数学分析:
设第 l 层的输出为 x_l
标准网络: x_l = F_l(x_{l-1})
残差网络: x_l = F_l(x_{l-1}) + x_{l-1}
从第 l 层到第 L 层 (L > l):
标准网络: x_L = Π_{k=l+1}^L F_k(x_{k-1})
→ 梯度: ∂x_L/∂x_l = Π ∂F_k/∂x_{k-1}
→ 连乘导致梯度消失/爆炸
残差网络: x_L = x_l + Σ_{k=l+1}^L F_k(x_{k-1})
→ 梯度: ∂x_L/∂x_l = 1 + Σ ∂(F_k)/∂x_l
→ 加法保证梯度至少为 1
→ 解决梯度消失
class ResidualBlock(nn.Module):
"""
残差块
y = F(x) + x
理论:
学习残差比学习完整映射更容易
跳跃连接保证梯度流动
变体:
1. Basic Block: 两个 3×3 卷积 (ResNet-18/34)
2. Bottleneck: 1×1 → 3×3 → 1×1 (ResNet-50/101/152)
"""
def __init__(self, in_channels, out_channels, stride=1, downsample=None):
super().__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, 3, stride=stride, padding=1)
self.bn1 = nn.BatchNorm2d(out_channels)
self.conv2 = nn.Conv2d(out_channels, out_channels, 3, padding=1)
self.bn2 = nn.BatchNorm2d(out_channels)
self.downsample = downsample # 当维度不匹配时
def forward(self, x):
identity = x
out = torch.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
if self.downsample is not None:
identity = self.downsample(x)
out += identity # 残差连接
out = torch.relu(out)
return out
class BottleneckBlock(nn.Module):
"""
瓶颈残差块
1×1 降维 → 3×3 卷积 → 1×1 升维
理论:
先降维减少计算量
再升维恢复维度
中间进行 3×3 卷积
参数量: 少于 Basic Block (当通道数大时)
"""
expansion = 4 # 输出通道是中间通道的 4 倍
def __init__(self, in_channels, mid_channels, stride=1, downsample=None):
super().__init__()
out_channels = mid_channels * self.expansion
self.conv1 = nn.Conv2d(in_channels, mid_channels, 1)
self.bn1 = nn.BatchNorm2d(mid_channels)
self.conv2 = nn.Conv2d(mid_channels, mid_channels, 3, stride=stride, padding=1)
self.bn2 = nn.BatchNorm2d(mid_channels)
self.conv3 = nn.Conv2d(mid_channels, out_channels, 1)
self.bn3 = nn.BatchNorm2d(out_channels)
self.downsample = downsample
def forward(self, x):
identity = x
out = torch.relu(self.bn1(self.conv1(x)))
out = torch.relu(self.bn2(self.conv2(out)))
out = self.bn3(self.conv3(out))
if self.downsample is not None:
identity = self.downsample(x)
out += identity
out = torch.relu(out)
return out
"""
ResNet 架构对比:
ResNet-18: [2, 2, 2, 2] Basic Block, 11M 参数
ResNet-34: [3, 4, 6, 3] Basic Block, 21M 参数
ResNet-50: [3, 4, 6, 3] Bottleneck, 25M 参数
ResNet-101: [3, 4, 23, 3] Bottleneck, 44M 参数
ResNet-152: [3, 8, 36, 3] Bottleneck, 60M 参数
[x, y, z, w] 表示 4 个阶段的块数量
"""
3.3 Pre-Activation ResNet
论文: "Identity Mappings in Deep Residual Networks" (He et al., 2016)
改进:
标准 ResNet: Conv → BN → ReLU → Conv → BN → Add → ReLU
Pre-Act: BN → ReLU → Conv → BN → ReLU → Conv → Add
理论:
在残差连接前使用 BN + ReLU
保证输入到加法的路径是恒等映射
梯度可以无阻碍地流动
效果:
训练更稳定
可以训练更深的网络 (1000+ 层)
class PreActBlock(nn.Module):
"""
Pre-Activation 残差块
BN → ReLU → Conv → BN → ReLU → Conv → Add
理论:
保证残差路径是纯恒等映射
梯度可以无阻碍流动
"""
def __init__(self, in_channels, out_channels, stride=1):
super().__init__()
self.bn1 = nn.BatchNorm2d(in_channels)
self.conv1 = nn.Conv2d(in_channels, out_channels, 3, stride=stride, padding=1)
self.bn2 = nn.BatchNorm2d(out_channels)
self.conv2 = nn.Conv2d(out_channels, out_channels, 3, padding=1)
self.shortcut = nn.Sequential()
if stride != 1 or in_channels != out_channels:
self.shortcut = nn.Conv2d(in_channels, out_channels, 1, stride=stride)
def forward(self, x):
out = torch.relu(self.bn1(x))
shortcut = self.shortcut(out)
out = self.conv1(out)
out = self.conv2(torch.relu(self.bn2(out)))
return out + shortcut
"""
Pre-Activation 的理论优势:
标准 ResNet:
y = ReLU(F(x) + x)
梯度: ∂y/∂x = ∂ReLU/∂(F+x) · (∂F/∂x + 1)
问题: ReLU 可能阻断梯度
Pre-Activation:
y = F(ReLU(x)) + x
梯度: ∂y/∂x = ∂F/∂ReLU(x) · ∂ReLU/∂x + 1
优势: 跳跃连接直接传递梯度,不受 ReLU 影响
"""
3.4 ResNet 的理论分析
残差网络的理论分析:
1. 集成学习视角:
残差网络可以看作许多不同深度路径的集成
ResNet-50 有约 2^50 条路径
每条路径的贡献由梯度决定
2. 常微分方程 (ODE) 视角:
残差网络可以看作离散化的 ODE
dx/dt = f(x, t)
x_{t+1} = x_t + f(x_t, t)
这是 Euler 方法的离散化
3. 信号传播分析:
残差连接保证信号可以无衰减地传播
前向: x_L = x_0 + Σ F(x_l)
反向: ∂L/∂x_0 = ∂L/∂x_L · (1 + Σ ∂F/∂x_l)
梯度至少为 1,不会消失
4. DenseNet 与密集连接
4.1 密集连接
论文: "Densely Connected Convolutional Networks" (Huang et al., 2017)
核心思想:
每层都与前面所有层连接
x_l = H_l([x_0, x_1, ..., x_{l-1}])
其中 [·] 表示拼接 (concatenation)
与 ResNet 的区别:
ResNet: 加法 (x + F(x))
DenseNet: 拼接 ([x, F(x)])
优势:
1. 特征复用: 每层都可以访问前面所有层的特征
2. 梯度流: 每层都有直接的梯度路径
3. 参数效率: 每层只需学习少量新特征
4. 隐式深度监督: 每层都受到损失函数的监督
class DenseBlock(nn.Module):
"""
密集块
每层与前面所有层连接
x_l = H_l([x_0, x_1, ..., x_{l-1}])
理论:
特征复用: 前面层的特征直接传递
梯度流: 每层都有直接的梯度路径
参数效率: 每层只需学习少量新特征
"""
def __init__(self, num_layers, growth_rate, in_channels):
super().__init__()
self.layers = nn.ModuleList()
for i in range(num_layers):
self.layers.append(
DenseLayer(in_channels + i * growth_rate, growth_rate)
)
def forward(self, x):
features = [x]
for layer in self.layers:
new_feature = layer(torch.cat(features, dim=1))
features.append(new_feature)
return torch.cat(features, dim=1)
class DenseLayer(nn.Module):
"""
密集层
BN → ReLU → Conv1×1 → BN → ReLU → Conv3×3
"""
def __init__(self, in_channels, growth_rate):
super().__init__()
self.bn1 = nn.BatchNorm2d(in_channels)
self.conv1 = nn.Conv2d(in_channels, 4 * growth_rate, 1)
self.bn2 = nn.BatchNorm2d(4 * growth_rate)
self.conv2 = nn.Conv2d(4 * growth_rate, growth_rate, 3, padding=1)
def forward(self, x):
out = self.conv1(torch.relu(self.bn1(x)))
out = self.conv2(torch.relu(self.bn2(out)))
return out
"""
DenseNet 的增长率 (Growth Rate):
每层新增 k 个特征图 (k 称为增长率)
第 l 层的输入通道: k_0 + k × (l-1)
其中 k_0 是初始通道数
典型值: k = 12, 24, 32, 40
理论:
k 较小,但通过密集连接,特征被充分复用
总参数量比 ResNet 更少
"""
4.2 Transition Layer
Transition Layer (过渡层):
作用:
1. 降低特征图尺寸 (下采样)
2. 压缩通道数 (减少参数)
结构:
BN → Conv 1×1 (压缩) → Average Pooling 2×2
压缩因子 θ:
如果 Dense Block 输出 m 个通道
Transition Layer 输出 θm 个通道
典型值: θ = 0.5
class TransitionLayer(nn.Module):
"""
过渡层
1×1 卷积压缩通道 + 2×2 平均池化下采样
理论:
压缩通道减少参数
下采样降低分辨率
"""
def __init__(self, in_channels, out_channels):
super().__init__()
self.bn = nn.BatchNorm2d(in_channels)
self.conv = nn.Conv2d(in_channels, out_channels, 1)
self.pool = nn.AvgPool2d(2, stride=2)
def forward(self, x):
x = self.conv(torch.relu(self.bn(x)))
x = self.pool(x)
return x
"""
DenseNet 架构:
DenseNet-121: [6, 12, 24, 16], k=32
DenseNet-169: [6, 12, 32, 32], k=32
DenseNet-201: [6, 12, 48, 32], k=32
DenseNet-264: [6, 12, 64, 32], k=32
[x, y, z, w] 表示 4 个 Dense Block 的层数
"""
4.3 DenseNet 的理论优势
DenseNet vs ResNet:
1. 特征复用:
ResNet: 只复用相邻层的特征
DenseNet: 复用所有前面层的特征
2. 参数效率:
DenseNet 每层只需学习 k 个新特征
总参数量通常更少
3. 梯度流:
DenseNet 每层都有直接的梯度路径
梯度消失问题更轻
4. 隐式深度监督:
每层都受到损失函数的监督
类似于 GoogLeNet 的辅助分类器
DenseNet 的劣势:
1. 内存占用: 拼接所有特征需要大量内存
2. 计算效率: 拼接操作增加计算
3. 不如 ResNet 流行
5. 高效网络设计
5.1 MobileNet
论文: "MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications"
(Howard et al., 2017)
核心创新:
深度可分离卷积 (Depthwise Separable Convolution)
深度可分离卷积:
标准卷积: C_in × k × k × C_out 参数
深度可分离:
Depthwise: C_in × k × k 参数 (每个通道独立)
Pointwise: C_in × C_out 参数 (1×1 卷积融合)
压缩比:
(C_in × k² + C_in × C_out) / (C_in × k² × C_out)
= 1/C_out + 1/k²
当 k=3, C_out=256 时: ≈ 1/256 + 1/9 ≈ 0.115
即: 参数量约为标准卷积的 1/9
class DepthwiseSeparableConv(nn.Module):
"""
深度可分离卷积
Depthwise: 每个通道独立卷积
Pointwise: 1×1 卷积融合通道
理论:
标准卷积同时进行空间和通道滤波
深度可分离将两者分离
大幅减少参数和计算量
"""
def __init__(self, in_channels, out_channels, kernel_size=3, stride=1):
super().__init__()
padding = (kernel_size - 1) // 2
# Depthwise
self.depthwise = nn.Conv2d(
in_channels, in_channels, kernel_size,
stride=stride, padding=padding, groups=in_channels
)
self.bn1 = nn.BatchNorm2d(in_channels)
# Pointwise
self.pointwise = nn.Conv2d(in_channels, out_channels, 1)
self.bn2 = nn.BatchNorm2d(out_channels)
def forward(self, x):
x = torch.relu(self.bn1(self.depthwise(x)))
x = torch.relu(self.bn2(self.pointwise(x)))
return x
"""
MobileNet 的宽度乘子 (Width Multiplier):
α ∈ (0, 1]: 控制网络宽度
每层的通道数: α × C
α=1.0: 标准 MobileNet
α=0.75: 更窄,更快
α=0.5: 更窄,更小
参数量 ≈ α² × 原始参数量
"""
5.2 MobileNetV2
论文: "MobileNetV2: Inverted Residuals and Linear Bottlenecks" (Sandler et al., 2018)
核心创新:
1. 倒残差 (Inverted Residual):
传统: 大通道 → 小通道 → 大通道
倒残差: 小通道 → 大通道 → 小通道
2. 线性瓶颈 (Linear Bottleneck):
最后一层不使用 ReLU
3. 残差连接:
在倒残差之间添加残差连接
倒残差块:
1×1 卷积 (扩展) → 3×3 DWConv → 1×1 卷积 (压缩)
扩展比 t (通常 t=6):
中间通道 = t × 输入通道
class InvertedResidual(nn.Module):
"""
倒残差块
1×1 扩展 → 3×3 DWConv → 1×1 压缩
理论:
在高维空间进行卷积
然后压缩回低维
最后一层不使用 ReLU (线性瓶颈)
"""
def __init__(self, in_channels, out_channels, stride=1, expand_ratio=6):
super().__init__()
mid_channels = in_channels * expand_ratio
self.use_residual = (stride == 1 and in_channels == out_channels)
layers = []
# 扩展 (1×1 Conv)
if expand_ratio != 1:
layers.extend([
nn.Conv2d(in_channels, mid_channels, 1),
nn.BatchNorm2d(mid_channels),
nn.ReLU6(inplace=True)
])
# 深度卷积 (3×3 DWConv)
layers.extend([
nn.Conv2d(mid_channels, mid_channels, 3, stride=stride,
padding=1, groups=mid_channels),
nn.BatchNorm2d(mid_channels),
nn.ReLU6(inplace=True)
])
# 压缩 (1×1 Conv, 线性)
layers.extend([
nn.Conv2d(mid_channels, out_channels, 1),
nn.BatchNorm2d(out_channels)
])
self.conv = nn.Sequential(*layers)
def forward(self, x):
if self.use_residual:
return x + self.conv(x)
else:
return self.conv(x)
"""
倒残差的理论动机:
传统残差: 大通道 → 小通道 → 大通道
问题: 信息在低维空间丢失
倒残差: 小通道 → 大通道 → 小通道
优势:
1. 在高维空间进行空间卷积
2. 信息保留更好
3. 计算量更小 (DWConv 在高维)
"""
5.3 MobileNetV3
论文: "Searching for MobileNetV3" (Howard et al., 2019)
改进:
1. NAS 搜索架构
2. SE 注意力模块
3. h-swish 激活函数
4. 高效的最后几层
h-swish 激活函数:
h-swish(x) = x · ReLU6(x + 3) / 6
优势: 比 swish 更快 (用 ReLU6 近似 sigmoid)
SE 模块 (Squeeze-and-Excitation):
全局平均池化 → FC → ReLU → FC → Sigmoid → 通道加权
学习通道间的依赖关系
6. 模型缩放理论
6.1 EfficientNet
论文: "EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks"
(Tan & Le, 2019)
核心思想:
系统化地缩放网络的三个维度:
1. 深度 (Depth): 层数
2. 宽度 (Width): 通道数
3. 分辨率 (Resolution): 输入尺寸
复合缩放 (Compound Scaling):
d = α^φ
w = β^φ
r = γ^φ
约束: α · β² · γ² ≈ 2
其中:
φ: 缩放系数 (用户指定)
α, β, γ: 通过网格搜索确定
理论:
增加深度、宽度、分辨率都有收益
但收益递减
需要平衡三者的增长
class EfficientNet:
"""
EfficientNet 复合缩放
理论:
深度、宽度、分辨率应该同步缩放
而非只缩放其中一个
缩放公式:
深度: d = α^φ
宽度: w = β^φ
分辨率: r = γ^φ
α · β² · γ² ≈ 2
"""
# EfficientNet 系列的缩放参数
scaling_params = {
'B0': {'phi': 0, 'alpha': 1.0, 'beta': 1.0, 'gamma': 1.0},
'B1': {'phi': 1, 'alpha': 1.1, 'beta': 1.2, 'gamma': 1.15},
'B2': {'phi': 2, 'alpha': 1.2, 'beta': 1.4, 'gamma': 1.3},
'B3': {'phi': 3, 'alpha': 1.4, 'beta': 1.8, 'gamma': 1.4},
'B4': {'phi': 4, 'alpha': 1.8, 'beta': 2.2, 'gamma': 1.6},
'B5': {'phi': 5, 'alpha': 2.2, 'beta': 2.6, 'gamma': 1.8},
'B6': {'phi': 6, 'alpha': 2.6, 'beta': 3.1, 'gamma': 2.0},
'B7': {'phi': 7, 'alpha': 3.1, 'beta': 3.6, 'gamma': 2.2},
}
@staticmethod
def scale_model(base_channels, base_layers, base_resolution, model_name):
"""缩放模型"""
params = EfficientNet.scaling_params[model_name]
phi = params['phi']
depth = int(base_layers * params['alpha'] ** phi)
width = int(base_channels * params['beta'] ** phi)
resolution = int(base_resolution * params['gamma'] ** phi)
return depth, width, resolution
"""
EfficientNet 的性能:
EfficientNet-B0: 77.1% ImageNet top-1, 5.3M 参数, 0.39B FLOPs
EfficientNet-B7: 84.3% ImageNet top-1, 66M 参数, 37B FLOPs
对比:
ResNet-50: 76.0%, 25M 参数, 4.1B FLOPs
EfficientNet-B0 用更少的参数达到了更高的精度
"""
6.2 MBConv 块
MBConv (Mobile Inverted Bottleneck Convolution):
结构:
1×1 扩展 → 3×3/5×5 DWConv → SE → 1×1 压缩 → 残差连接
EfficientNet 的基础块:
- 使用 MBConv 作为基本单元
- 不同阶段使用不同的扩展比和核大小
- 添加 SE 注意力模块
class MBConv(nn.Module):
"""
MBConv (Mobile Inverted Bottleneck Convolution)
1×1 扩展 → DWConv → SE → 1×1 压缩 → 残差
理论:
EfficientNet 的基础块
结合了倒残差、深度可分离卷积和 SE 注意力
"""
def __init__(self, in_channels, out_channels, kernel_size=3,
stride=1, expand_ratio=6, se_ratio=0.25):
super().__init__()
mid_channels = in_channels * expand_ratio
self.use_residual = (stride == 1 and in_channels == out_channels)
layers = []
# 扩展
if expand_ratio != 1:
layers.extend([
nn.Conv2d(in_channels, mid_channels, 1, bias=False),
nn.BatchNorm2d(mid_channels),
nn.SiLU(inplace=True) # Swish
])
# 深度卷积
layers.extend([
nn.Conv2d(mid_channels, mid_channels, kernel_size,
stride=stride, padding=kernel_size//2,
groups=mid_channels, bias=False),
nn.BatchNorm2d(mid_channels),
nn.SiLU(inplace=True)
])
# SE 注意力
se_channels = max(1, int(in_channels * se_ratio))
self.se = SEBlock(mid_channels, se_channels)
# 压缩
layers.extend([
nn.Conv2d(mid_channels, out_channels, 1, bias=False),
nn.BatchNorm2d(out_channels)
])
self.conv = nn.Sequential(*layers)
def forward(self, x):
out = self.conv(x)
out = self.se(out)
if self.use_residual:
return x + out
return out
class SEBlock(nn.Module):
"""
Squeeze-and-Excitation 注意力
全局池化 → FC → ReLU → FC → Sigmoid → 通道加权
理论:
学习通道间的依赖关系
自适应地调整通道的重要性
"""
def __init__(self, channels, se_channels):
super().__init__()
self.se = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Flatten(),
nn.Linear(channels, se_channels),
nn.SiLU(inplace=True),
nn.Linear(se_channels, channels),
nn.Sigmoid()
)
def forward(self, x):
scale = self.se(x).unsqueeze(-1).unsqueeze(-1)
return x * scale
7.1 ViT 架构
论文: "An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale"
(Dosovitskiy et al., 2020)
核心思想:
将图像分割为 patch,每个 patch 作为一个 token
使用标准 Transformer 处理
架构:
图像 (224×224×3)
→ 分割为 16×16 patches (14×14=196 个 patches)
→ 线性投影为 embedding (196×768)
→ 添加 [CLS] token (197×768)
→ 添加位置编码
→ Transformer Encoder × 12 层
→ [CLS] token → 分类头
class VisionTransformer(nn.Module):
"""
Vision Transformer (ViT)
核心思想:
将图像分割为 patches
每个 patch 作为一个 token
使用标准 Transformer 处理
"""
def __init__(self, img_size=224, patch_size=16, in_channels=3,
num_classes=1000, d_model=768, n_layers=12, n_heads=12):
super().__init__()
self.patch_size = patch_size
num_patches = (img_size // patch_size) ** 2
# Patch Embedding
self.patch_embedding = nn.Conv2d(
in_channels, d_model,
kernel_size=patch_size, stride=patch_size
)
# CLS Token
self.cls_token = nn.Parameter(torch.randn(1, 1, d_model))
# 位置编码
self.position_embedding = nn.Parameter(
torch.randn(1, num_patches + 1, d_model)
)
# Transformer Encoder
encoder_layer = nn.TransformerEncoderLayer(
d_model=d_model,
nhead=n_heads,
dim_feedforward=d_model * 4,
dropout=0.1,
activation='gelu'
)
self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=n_layers)
# 分类头
self.norm = nn.LayerNorm(d_model)
self.head = nn.Linear(d_model, num_classes)
def forward(self, x):
B = x.shape[0]
# Patch Embedding
x = self.patch_embedding(x) # [B, d_model, H/P, W/P]
x = x.flatten(2).transpose(1, 2) # [B, num_patches, d_model]
# 添加 CLS Token
cls_tokens = self.cls_token.expand(B, -1, -1)
x = torch.cat([cls_tokens, x], dim=1) # [B, num_patches+1, d_model]
# 添加位置编码
x = x + self.position_embedding
# Transformer Encoder
x = self.encoder(x)
# 分类
x = self.norm(x[:, 0]) # 使用 CLS Token
x = self.head(x)
return x
"""
ViT 的理论分析:
优势:
1. 全局注意力: 每个 patch 可以关注所有其他 patch
2. 灵活的位置编码: 可以外推到不同分辨率
3. 统一架构: 与 NLP 的 Transformer 一致
劣势:
1. 计算复杂度: O(n²) 注意力,n 是 patch 数量
2. 需要大数据: 没有归纳偏置,需要大量数据预训练
3. 位置信息弱: 需要学习位置编码
数据需求:
在 ImageNet (1M 图像) 上训练: 效果不如 CNN
在 JFT (300M 图像) 上预训练: 超过 CNN
"""
7.2 Patch Embedding
Patch Embedding 的作用:
将 2D 图像转换为 1D token 序列
方法:
1. 线性投影 (ViT):
将 patch 展平后线性投影
patch: [P, P, C] → 展平 → [P²×C] → 线性投影 → [d_model]
2. 卷积投影:
使用 kernel_size=patch_size 的卷积
Conv2d(C, d_model, kernel_size=P, stride=P)
等价于线性投影,但更高效
位置编码:
1. 可学习位置编码:
每个位置一个可学习的向量
2. 正弦位置编码:
使用正弦/余弦函数
3. 相对位置编码:
编码相对距离而非绝对位置
7.3 ViT 变体
ViT 变体:
1. DeiT (Data-efficient Image Transformers):
使用知识蒸馏,在 ImageNet 上训练
不需要大规模数据
2. BEiT (BERT Pre-Training of Image Transformers):
使用掩码图像建模预训练
类似于 BERT 的 MLM
3. MAE (Masked Autoencoders):
掩码自编码器
高掩码率 (75%)
只编码可见 patches
4. EVA (Exploring the Limits of Masked Visual Representation Learning):
更大规模的预训练
CLIP 特征作为监督信号
8.1 窗口注意力
论文: "Swin Transformer: Hierarchical Vision Transformer using Shifted Windows"
(Liu et al., 2021)
核心创新:
1. 窗口注意力 (Window Attention):
在局部窗口内计算注意力
而非全局注意力
2. 移位窗口 (Shifted Window):
交替使用常规和移位窗口
实现跨窗口信息交互
3. 层次化结构:
类似 CNN 的多尺度特征
窗口注意力:
将特征图划分为不重叠的窗口 (如 7×7)
每个窗口内独立计算注意力
计算复杂度: O(n × w²) 而非 O(n²)
其中 n 是 patch 数量,w 是窗口大小
class WindowAttention(nn.Module):
"""
窗口注意力
在局部窗口内计算注意力
理论:
将特征图划分为不重叠的窗口
窗口内独立计算注意力
大幅降低计算复杂度
"""
def __init__(self, d_model, window_size=7, num_heads=8):
super().__init__()
self.d_model = d_model
self.window_size = window_size
self.num_heads = num_heads
self.head_dim = d_model // num_heads
self.scale = self.head_dim ** -0.5
self.qkv = nn.Linear(d_model, d_model * 3)
self.proj = nn.Linear(d_model, d_model)
# 相对位置编码
self.relative_position_bias_table = nn.Parameter(
torch.zeros((2 * window_size - 1) * (2 * window_size - 1), num_heads)
)
nn.init.trunc_normal_(self.relative_position_bias_table, std=0.02)
def forward(self, x):
B, N, C = x.shape
H = W = int(N ** 0.5)
# 重塑为窗口
x = x.view(B, H, W, C)
x = x.view(B, H // self.window_size, self.window_size,
W // self.window_size, self.window_size, C)
x = x.permute(0, 1, 3, 2, 4, 5).contiguous()
x = x.view(-1, self.window_size * self.window_size, C)
# QKV
qkv = self.qkv(x).reshape(-1, self.window_size * self.window_size,
3, self.num_heads, self.head_dim)
qkv = qkv.permute(2, 0, 3, 1, 4)
q, k, v = qkv.unbind(0)
# 注意力
attn = (q @ k.transpose(-2, -1)) * self.scale
attn = attn.softmax(dim=-1)
out = (attn @ v).transpose(1, 2).reshape(-1, self.window_size * self.window_size, C)
out = self.proj(out)
# 恢复原始形状
out = out.view(B, H // self.window_size, W // self.window_size,
self.window_size, self.window_size, C)
out = out.permute(0, 1, 3, 2, 4, 5).contiguous()
out = out.view(B, N, C)
return out
"""
窗口注意力的复杂度分析:
全局注意力: O(N²) = O((HW)²)
窗口注意力: O(N × w²) = O(HW × w²)
当 w=7, H=W=56 时:
全局: 56⁴ ≈ 10M
窗口: 56² × 49 ≈ 154K
加速比: ~65x
"""
8.2 移位窗口
移位窗口 (Shifted Window):
问题: 窗口注意力无法跨窗口交互
解决: 交替使用常规和移位窗口
常规窗口:
┌───┬───┬───┐
│ 1 │ 2 │ 3 │
├───┼───┼───┤
│ 4 │ 5 │ 6 │
├───┼───┼───┤
│ 7 │ 8 │ 9 │
└───┴───┴───┘
移位窗口 (偏移 w/2):
┌─┬───┬───┬─┐
│ │ 2 │ 3 │ │
├─┼───┼───┼─┤
│4│ 5 │ 6 │ │
├─┼───┼───┼─┤
│7│ 8 │ 9 │ │
└─┴───┴───┴─┘
通过移位,相邻层的窗口边界不同
信息可以在不同层之间传递
高效实现:
使用循环移位 + 掩码
避免创建新窗口
class SwinTransformerBlock(nn.Module):
"""
Swin Transformer 块
包含:
1. 窗口注意力 (W-MSA) 或移位窗口注意力 (SW-MSA)
2. MLP
3. 残差连接
"""
def __init__(self, d_model, num_heads, window_size=7, shift_size=0):
super().__init__()
self.shift_size = shift_size
self.norm1 = nn.LayerNorm(d_model)
self.attn = WindowAttention(d_model, window_size, num_heads)
self.norm2 = nn.LayerNorm(d_model)
self.mlp = nn.Sequential(
nn.Linear(d_model, d_model * 4),
nn.GELU(),
nn.Linear(d_model * 4, d_model)
)
def forward(self, x):
B, N, C = x.shape
H = W = int(N ** 0.5)
# 移位
if self.shift_size > 0:
shifted_x = torch.roll(x, shifts=(-self.shift_size, -self.shift_size), dims=(1, 2))
else:
shifted_x = x
# 窗口注意力
shortcut = x
x = self.norm1(shifted_x)
x = self.attn(x)
x = x + shortcut
# MLP
shortcut = x
x = self.norm2(x)
x = self.mlp(x)
x = x + shortcut
return x
"""
Swin Transformer 的层次结构:
Stage 1: 56×56 patches, 96 维
Stage 2: 28×28 patches, 192 维 (下采样)
Stage 3: 14×14 patches, 384 维 (下采样)
Stage 4: 7×7 patches, 768 维 (下采样)
类似于 CNN 的多尺度特征
可以用于检测和分割
"""
9. ConvNeXt 与现代 ConvNet
9.1 ConvNeXt 设计哲学
论文: "A ConvNet for the 2020s" (Liu et al., 2022)
核心思想:
从 Swin Transformer 借鉴设计哲学
改进传统 ConvNet
达到与 Transformer 相当的性能
设计改进:
1. 更大的卷积核 (7×7)
2. 更少的激活函数
3. 更少的归一化层
4. 使用 GELU 激活
5. 使用 LayerNorm
6. 倒残差结构
7. 下采样分开处理
class ConvNeXtBlock(nn.Module):
"""
ConvNeXt 块
7×7 DWConv → LayerNorm → 1×1 Conv → GELU → 1×1 Conv
理论:
从 Transformer 借鉴设计:
- 大卷积核 (类似大的注意力窗口)
- 更少的激活函数
- LayerNorm 而非 BatchNorm
"""
def __init__(self, in_channels, out_channels):
super().__init__()
# 7×7 深度卷积
self.dwconv = nn.Conv2d(in_channels, in_channels, 7, padding=3, groups=in_channels)
self.norm = nn.LayerNorm(in_channels)
# 1×1 卷积 (MLP)
self.pwconv1 = nn.Linear(in_channels, in_channels * 4)
self.act = nn.GELU()
self.pwconv2 = nn.Linear(in_channels * 4, out_channels)
# 残差连接
self.gamma = nn.Parameter(torch.zeros(1)) if in_channels == out_channels else None
def forward(self, x):
residual = x
x = self.dwconv(x)
x = x.permute(0, 2, 3, 1) # [B, C, H, W] → [B, H, W, C]
x = self.norm(x)
x = self.pwconv1(x)
x = self.act(x)
x = self.pwconv2(x)
x = x.permute(0, 3, 1, 2) # [B, H, W, C] → [B, C, H, W]
if self.gamma is not None:
x = self.gamma * x
return x + residual
"""
ConvNeXt vs Swin Transformer:
ConvNeXt:
- 纯 ConvNet
- 全局卷积核
- 更简单
Swin Transformer:
- 窗口注意力
- 局部 + 全局
- 更灵活
性能:
ConvNeXt 与 Swin 相当
在某些任务上甚至更好
"""
9.2 设计原则总结
现代 ConvNet 设计原则:
1. 大卷积核:
3×3 → 7×7
扩大感受野
2. 更少的激活函数:
每个块只用一个激活函数
类似 Transformer 的 MLP
3. 更少的归一化:
减少 BN 的使用
使用 LayerNorm
4. 倒残差:
先扩展后压缩
类似 MobileNetV2
5. 下采样分开:
使用 stride=2 的卷积下采样
而非池化
这些改进使 ConvNet 在 2022 年重新与 Transformer 竞争
10. 架构设计原则与前沿
10.1 设计原则
神经网络架构设计原则:
1. 感受野:
网络应该有足够的感受野
捕获全局和局部信息
2. 多尺度:
不同层学习不同尺度的特征
浅层: 边缘、纹理
深层: 语义、对象
3. 特征复用:
跳跃连接、密集连接
缓解梯度消失
4. 计算效率:
深度可分离卷积
注意力机制
5. 归一化:
BN/LN/GN 稳定训练
Pre-Norm 更稳定
6. 残差连接:
几乎所有现代架构都使用
保证梯度流动
┌─────────────────────────────────────────────────────────────────────┐
│ CNN vs Transformer 对比 │
├───────────────┬───────────────────┬─────────────────────────────────┤
│ 特性 │ CNN │ Transformer │
├───────────────┼───────────────────┼─────────────────────────────────┤
│ 局部性 │ 天然的局部归纳偏置│ 无局部归纳偏置 │
│ 平移不变性 │ 天然具有 │ 需要位置编码 │
│ 长距离依赖 │ 需要深层网络 │ 全局注意力 │
│ 计算效率 │ O(k²CN) │ O(N²D) │
│ 数据需求 │ 较少 │ 大量数据 │
│ 多尺度 │ 天然的层次结构 │ 需要特殊设计 │
└───────────────┴───────────────────┴─────────────────────────────────┘
趋势:
- 混合架构: 结合 CNN 和 Transformer
- ConvNeXt: 用 Transformer 的设计哲学改进 CNN
- 移动端: CNN 仍然占优
- 服务器: Transformer 性能更好
10.3 前沿方向
架构设计的前沿:
1. 高效注意力:
FlashAttention, 线性注意力
降低 Transformer 的计算复杂度
2. 状态空间模型 (SSM):
Mamba, S4
线性复杂度的序列建模
3. 混合专家 (MoE):
条件计算
大参数量,低计算量
4. 神经架构搜索 (NAS):
自动设计架构
DARTS, EfficientNet
5. 多模态架构:
统一处理图像、文本、音频
CLIP, Flamingo
6. 3D 视觉:
点云处理
3D 卷积/Transformer
附录
A. 架构发展时间线
1998 ──┬── LeNet-5
│
2012 ──┼── AlexNet
│
2014 ──┼── VGGNet / GoogLeNet
│
2015 ──┼── ResNet
│
2017 ──┼── DenseNet / MobileNet
│
2019 ──┼── EfficientNet
│
2020 ──┼── Vision Transformer (ViT)
│
2021 ──┼── Swin Transformer
│
2022 ──┼── ConvNeXt
│
2023+ ──┴── Mamba, MoE, 混合架构
B. 核心公式速查
| 公式 |
含义 |
| y = F(x) + x |
残差连接 |
| DWConv + PWConv |
深度可分离卷积 |
| softmax(QK^T/√d)V |
注意力机制 |
| α · β² · γ² ≈ 2 |
复合缩放约束 |
| x_l = x_0, ..., x_{l-1} |
密集连接 |
C. 推荐资源
- He, K., et al. (2015). Deep Residual Learning for Image Recognition
- Huang, G., et al. (2017). Densely Connected Convolutional Networks
- Howard, A., et al. (2017). MobileNets
- Tan, M., & Le, Q. (2019). EfficientNet
- Dosovitskiy, A., et al. (2020). An Image is Worth 16x16 Words
- Liu, Z., et al. (2021). Swin Transformer
- Liu, Z., et al. (2022). A ConvNet for the 2020s