CNN 网络架构的设计

深入探索CNN架构设计的演进历程,从手工设计到网络架构搜索(NAS),理解RegNet的设计空间优化方法

发布于 2025-08-12 更新于 2025-08-124,842 字 11 分钟
CNN 网络架构的设计的文章封面
展开文章目录
  1. 1. AnyNet:系统化的网络设计空间
  2. 1.1 网络架构组成
  3. 1.2 ResNeXt块的参数化设计
  4. 1.3 AnyNet代码实现
  5. 2. 网络设计空间的分布和参数分析
  6. 2.1 设计空间复杂度问题
  7. 2.2 NDS方法的核心假设
  8. 2.3 累积分布函数(CDF)分析
  9. 2.4 渐进式约束实验
  10. 2.5 CNN架构设计原则
  11. 3. RegNet:规则化的网络设计
  12. 3.1 RegNet设计理念
  13. 3.2 网络宽度的线性增长模型
  14. 3.3 RegNetX32实现
  15. 3.4 RegNetX32参数分析
  16. 3.5 RegNetY:注意力增强版本
  17. 4. 模型训练与性能评估
  18. 4.1 训练配置
  19. 4.2 训练结果分析
  20. 5. 总结与展望
  21. 5.1 CNN架构设计的发展历程
  22. 5.2 核心贡献与设计原则
  23. 5.3 技术影响与未来展望
  24. 参考资料

1. AnyNet:系统化的网络设计空间

1.1 网络架构组成

AnyNet网络架构示意图

AnyNet架构详解

茎 (Stem) 的功能与设计:

设计细节:

  • 使用步幅为2的3×3卷积层实现下采样
  • 包含批量归一化层提高训练稳定性
  • 遵循深度CNN早期快速降维的标准实践

主体 (Body) 的多阶段设计:

设计原则:

  • 每阶段空间分辨率减半 (面积减为1/4)
  • 逐步增加特征通道数
  • 使用ResNeXt块作为基本构建单元
  • 第一个块负责维度变换,后续块进行特征精炼

头 (Head) 的标准化设计:

适应性设计:

  • 可根据具体任务调整 (分类、检测、分割)
  • 标准化的接口便于模块化设计
  • 支持不同的输出维度需求

1.2 ResNeXt块的参数化设计

ResNeXt参数详解

瓶颈比率 bb 的作用机制:

当输出通道数为 ww 时,中间通道数为 w/b\lfloor w/b \rfloor

分组卷积组数 gg 的权衡:

AnyNet的超参数空间:

1.3 AnyNet代码实现

from dataclasses import dataclass

import torch.nn as nn
from torch import Tensor

from ResNet import ResNeXtBlock


@dataclass
class StageConfig:
    """网络主体中,每个 Stage 的配置"""
    depth: int  # 块数量
    groups: int  # 分组卷积块的组数
    out_channels: int  # 输出通道数
    bottleneck_ratio: int  # 瓶颈比率


class AnyNet(nn.Module):
    def __init__(self, in_channels: int, stem_out_channels: int, num_classes: int, arch_params: list[StageConfig]):
        """
        :param in_channels: 输入数据的通道数
        :param stem_out_channels: 网络 stem 部分的输出通道数
        :param num_classes: 分类任务的类别数
        :param arch_params: 网络主体的架构参数列表
        """
        super().__init__()

        self.stem = self._create_stem(in_channels, stem_out_channels)  # 创建 Stem

        self.body = nn.Sequential()  # 创建 Body
        next_stage_in_channels = stem_out_channels

        for idx, stage_config in enumerate(arch_params):
            self.body.append(
                self._create_stage(next_stage_in_channels, stage_config.out_channels, depth=stage_config.depth,
                                   groups=stage_config.groups, bottleneck_ratio=stage_config.bottleneck_ratio)
            )
            next_stage_in_channels = stage_config.out_channels

        self.head = self._create_head(next_stage_in_channels, num_classes)  # 创建 Head(分类头)

        self._initialize_weights()

    @classmethod
    def _create_stem(cls, in_channels: int, out_channels: int) -> nn.Sequential:
        stem = nn.Sequential(nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=2, padding=1, bias=False),
                             nn.BatchNorm2d(out_channels), nn.ReLU(inplace=True))
        return stem

    @classmethod
    def _create_stage(cls, in_channels: int, out_channels: int, depth: int, groups: int,
                      bottleneck_ratio: int) -> nn.Sequential:
        """
        创建由多个 ResNeXtBlock 块构成的 Stage

        :param in_channels: 输入通道数
        :param out_channels: 输出通道数
        :param depth: 当前 Stage 的 ResNeXtBlock 块数
        :param groups: 分组卷积的组数
        :param bottleneck_ratio: 瓶颈比率
        """
        stage = nn.Sequential()

        stage.append(  # 第一个块
            ResNeXtBlock(in_channels, out_channels, stride=2, groups=groups, bottleneck_ratio=bottleneck_ratio)
        )

        for _ in range(1, depth):
            stage.append(  # 剩余的块
                ResNeXtBlock(out_channels, out_channels, stride=1, groups=groups, bottleneck_ratio=bottleneck_ratio)
            )

        return stage

    @classmethod
    def _create_head(cls, in_channels: int, num_classes: int) -> nn.Sequential:
        head = nn.Sequential(nn.AdaptiveAvgPool2d((1, 1)), nn.Flatten(), nn.Linear(in_channels, num_classes))
        return head

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

    def forward(self, x: Tensor) -> Tensor:
        """
        :param x: 形状为 [B, C, H, W]
        :return: 形状为 [B, num_classes]
        """
        x = self.stem(x)  # Stem
        x = self.body(x)  # Body
        x = self.head(x)  # Head

        return x
查看AnyNet模型结构分析
if __name__ == "__main__":
    from torchinfo import summary

    params = [
        StageConfig(depth=3, groups=1, out_channels=256, bottleneck_ratio=4),
        StageConfig(depth=4, groups=1, out_channels=512, bottleneck_ratio=4),
        StageConfig(depth=6, groups=1, out_channels=1024, bottleneck_ratio=4),
        StageConfig(depth=3, groups=1, out_channels=2048, bottleneck_ratio=4),
    ]
    model = AnyNet(in_channels=3, stem_out_channels=64, num_classes=1000, arch_params=params)
    summary(model, input_size=(1, 3, 224, 224))
===============================================================================================
Layer (type:depth-idx)                        Output Shape              Param #
===============================================================================================
AnyNet                                        [1, 1000]                 --
├─Sequential: 1-1                             [1, 64, 112, 112]         --
│    └─Conv2d: 2-1                            [1, 64, 112, 112]         1,728
│    └─BatchNorm2d: 2-2                       [1, 64, 112, 112]         128
│    └─ReLU: 2-3                              [1, 64, 112, 112]         --
├─Sequential: 1-2                             [1, 2048, 7, 7]           --
│    └─Sequential: 2-4                        [1, 256, 56, 56]          --
│    │    └─ResNeXtBlock: 3-1                 [1, 256, 56, 56]          74,112
│    │    └─ResNeXtBlock: 3-2                 [1, 256, 56, 56]          70,400
│    │    └─ResNeXtBlock: 3-3                 [1, 256, 56, 56]          70,400
│    └─Sequential: 2-5                        [1, 512, 28, 28]          --
│    │    └─ResNeXtBlock: 3-4                 [1, 512, 28, 28]          377,856
│    │    └─ResNeXtBlock: 3-5                 [1, 512, 28, 28]          280,064
│    │    └─ResNeXtBlock: 3-6                 [1, 512, 28, 28]          280,064
│    │    └─ResNeXtBlock: 3-7                 [1, 512, 28, 28]          280,064
│    └─Sequential: 2-6                        [1, 1024, 14, 14]         --
│    │    └─ResNeXtBlock: 3-8                 [1, 1024, 14, 14]         1,509,376
│    │    └─ResNeXtBlock: 3-9                 [1, 1024, 14, 14]         1,117,184
│    │    └─ResNeXtBlock: 3-10                [1, 1024, 14, 14]         1,117,184
│    │    └─ResNeXtBlock: 3-11                [1, 1024, 14, 14]         1,117,184
│    │    └─ResNeXtBlock: 3-12                [1, 1024, 14, 14]         1,117,184
│    │    └─ResNeXtBlock: 3-13                [1, 1024, 14, 14]         1,117,184
│    └─Sequential: 2-7                        [1, 2048, 7, 7]           --
│    │    └─ResNeXtBlock: 3-14                [1, 2048, 7, 7]           6,033,408
│    │    └─ResNeXtBlock: 3-15                [1, 2048, 7, 7]           4,462,592
│    │    └─ResNeXtBlock: 3-16                [1, 2048, 7, 7]           4,462,592
├─Sequential: 1-3                             [1, 1000]                 --
│    └─AdaptiveAvgPool2d: 2-8                 [1, 2048, 1, 1]           --
│    └─Flatten: 2-9                           [1, 2048]                 --
│    └─Linear: 2-10                           [1, 1000]                 2,049,000
===============================================================================================
Total params: 25,537,704
Trainable params: 25,537,704
Non-trainable params: 0
Total mult-adds (Units.GIGABYTES): 4.03
===============================================================================================
Input size (MB): 0.60
Forward/backward pass size (MB): 181.04
Params size (MB): 102.15
Estimated Total Size (MB): 283.80
===============================================================================================

2. 网络设计空间的分布和参数分析

2.1 设计空间复杂度问题

2.2 NDS方法的核心假设

2.3 累积分布函数(CDF)分析

CDF分析方法

CDF的数学表示:

从设计空间分布 D\mathcal{D} 中采样 nn 个网络,错误率为 e1,e2,,ene_1, e_2, \ldots, e_n

F^(e)=1ni=1nI(eie)\hat{F}(e) = \frac{1}{n} \sum_{i=1}^{n} \mathbb{I}(e_i \leq e)

其中 I()\mathbb{I}(\cdot) 为指示函数:

  • eiee_i \leq e 时,I(eie)=1\mathbb{I}(e_i \leq e) = 1
  • ei>ee_i > e 时,I(eie)=0\mathbb{I}(e_i \leq e) = 0

CDF曲线的解读规则:

网络采样与评估:

2.4 渐进式约束实验

NDS方法的渐进式约束实验结果

NDS方法通过逐步引入参数约束,比较约束前后的CDF曲线变化来确定设计原则的重要性:

实验阶段

AnyNetXA → AnyNetXB:统一瓶颈比率

AnyNetXB → AnyNetXC:统一分组卷积组数

AnyNetXC:通道数变化模式比较

AnyNetXD:阶段深度变化模式

2.5 CNN架构设计原则


3. RegNet:规则化的网络设计

3.1 RegNet设计理念

3.2 网络宽度的线性增长模型

线性增长模型

线性增长的理论基础:

RegNetX系列网络:

RegNet vs 传统设计:

方面传统手工设计NAS方法RegNet方法
设计复杂度极高中等
计算成本极高中等
通用性
可解释性中等
设计洞察有限有限丰富

3.3 RegNetX32实现

class RegNetX32(AnyNet):
    def __init__(self, in_channels, num_classes: int):
        super().__init__(
            in_channels=in_channels, stem_out_channels=32, num_classes=num_classes,
            arch_params=[
                StageConfig(depth=4, groups=16, out_channels=32, bottleneck_ratio=1),
                StageConfig(depth=6, groups=16, out_channels=80, bottleneck_ratio=1)
            ]
        )
查看RegNetX32模型结构分析
if __name__ == "__main__":
    from torchinfo import summary

    model = RegNetX32(in_channels=1, num_classes=10)
    summary(model, input_size=(1, 1, 96, 96))
===============================================================================================
Layer (type:depth-idx)                        Output Shape              Param #
===============================================================================================
RegNetX32                                     [1, 10]                   --
├─Sequential: 1-1                             [1, 32, 48, 48]           --
│    └─Conv2d: 2-1                            [1, 32, 48, 48]           288
│    └─BatchNorm2d: 2-2                       [1, 32, 48, 48]           64
│    └─ReLU: 2-3                              [1, 32, 48, 48]           --
├─Sequential: 1-2                             [1, 80, 12, 12]           --
│    └─Sequential: 2-4                        [1, 32, 24, 24]           --
│    │    └─ResNeXtBlock: 3-1                 [1, 32, 24, 24]           2,304
│    │    └─ResNeXtBlock: 3-2                 [1, 32, 24, 24]           1,280
│    │    └─ResNeXtBlock: 3-3                 [1, 32, 24, 24]           1,280
│    │    └─ResNeXtBlock: 3-4                 [1, 32, 24, 24]           1,280
│    └─Sequential: 2-5                        [1, 80, 12, 12]           --
│    │    └─ResNeXtBlock: 3-5                 [1, 80, 12, 12]           7,824
│    │    └─ResNeXtBlock: 3-6                 [1, 80, 12, 12]           7,520
│    │    └─ResNeXtBlock: 3-7                 [1, 80, 12, 12]           7,520
│    │    └─ResNeXtBlock: 3-8                 [1, 80, 12, 12]           7,520
│    │    └─ResNeXtBlock: 3-9                 [1, 80, 12, 12]           7,520
│    │    └─ResNeXtBlock: 3-10                [1, 80, 12, 12]           7,520
├─Sequential: 1-3                             [1, 10]                   --
│    └─AdaptiveAvgPool2d: 2-6                 [1, 80, 1, 1]             --
│    └─Flatten: 2-7                           [1, 80]                   --
│    └─Linear: 2-8                            [1, 10]                   810
===============================================================================================
Total params: 52,730
Trainable params: 52,730
Non-trainable params: 0
Total mult-adds (Units.MEGABYTES): 10.19
===============================================================================================
Input size (MB): 0.04
Forward/backward pass size (MB): 6.49
Params size (MB): 0.21
Estimated Total Size (MB): 6.74
===============================================================================================

3.4 RegNetX32参数分析

3.5 RegNetY:注意力增强版本

RegNetY特性

挤压-激励(SE)模块:

RegNetX vs RegNetY性能对比:

指标RegNetXRegNetY提升
参数量基准+5~10%小幅增加
计算量基准+2~5%微量增加
准确率基准+1~3%显著提升
推理速度基准-2~5%轻微下降

RegNetX vs RegNetY选择指南:


4. 模型训练与性能评估

4.1 训练配置

if __name__ == "__main__":
    import torch
    from torch import optim

    from training_tools import fashionMNIST_loader, Trainer

    BATCH_SIZE = 128
    EPOCHS_NUM = 10
    LEARNING_RATE = 0.05

    model = RegNetX32(in_channels=1, num_classes=10)
    train_loader, test_loader = fashionMNIST_loader(BATCH_SIZE, resize=96)
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(model.parameters(), LEARNING_RATE)
    platform = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    with Trainer(model, train_loader, test_loader, criterion, optimizer, platform) as trainer:
        trainer.train(EPOCHS_NUM)

4.2 训练结果分析

查看RegNetX32完整训练过程
第 001/10 轮,训练损失:0.9418,训练精度:66.48%,测试损失:4.7452,测试精度:21.97%
第 002/10 轮,训练损失:0.4853,训练精度:82.67%,测试损失:1.0351,测试精度:65.65%
第 003/10 轮,训练损失:0.3922,训练精度:86.08%,测试损失:1.5781,测试精度:56.69%
第 004/10 轮,训练损失:0.3465,训练精度:87.70%,测试损失:1.8860,测试精度:55.66%
第 005/10 轮,训练损失:0.3207,训练精度:88.58%,测试损失:0.5745,测试精度:79.57%
第 006/10 轮,训练损失:0.3003,训练精度:89.22%,测试损失:0.3773,测试精度:87.31%
第 007/10 轮,训练损失:0.2861,训练精度:89.77%,测试损失:0.6627,测试精度:78.40%
第 008/10 轮,训练损失:0.2742,训练精度:90.25%,测试损失:0.3845,测试精度:86.95%
第 009/10 轮,训练损失:0.2613,训练精度:90.56%,测试损失:0.3343,测试精度:88.11%
第 010/10 轮,训练损失:0.2536,训练精度:90.89%,测试损失:0.3222,测试精度:88.16%

RegNetX32训练过程可视化

性能分析

RegNetX32训练表现:

与其他CNN架构的对比:

模型参数量最佳测试精度训练稳定性设计复杂度
LeNet~60K~85%
AlexNet~60M~88%
VGG~138M~90%
ResNet~25M~93%
RegNetX3253K88.16%

RegNet的应用价值:


5. 总结与展望

5.1 CNN架构设计的发展历程

5.2 核心贡献与设计原则

5.3 技术影响与未来展望


参考资料

如果这篇记录对你有帮助,可以留下一句回应。

前往留言

DISCUSSION

讨论与回应

欢迎补充细节、指出问题,或分享与这篇文章有关的经验。

昵称与邮箱为必填项,邮箱仅用于头像和回复通知,不会公开。网址可以留空。

正在准备留言区…

演示赞赏界面

谢谢你愿意支持长期写作。

第一阶段不会发起支付或收集任何信息。接入真实赞赏渠道后,这里会展示清楚的金额、渠道和完成状态。

输入关键词开始搜索 · 按 Esc 关闭
打开完整搜索页 →