☰
使用 VGG 块构建深层卷积网络:d2l-en 中 VGG 网络的原理、实现与训练指南
2026/10/3 13:05:08 网站建设 项目流程
  • 文档
  • 教程
  • 人工智能
  • 深度学习
  • NLP
  • 计算机视觉
  • 强化学习

【免费下载链接】d2l-en

Interactive deep learning book with multi-framework code, math, and discussions. Adopted at 500 universities from 70 countries including Stanford, MIT, Harvard, and Cambridge.

项目地址:https://gitcode.com/gh_mirrors/d2/d2l-en
点击查看免费下载

VGG 是牛津大学视觉几何组(Visual Geometry Group)提出的深度卷积神经网络,它首次将"卷积层块 + 下采样"的重复结构作为网络设计的基本单元,开启了现代 CNN 模块化设计时代。本指南以《Dive into Deep Learning》(d2l-en)仓库中 chapter_convolutional-modern/vgg.md 为骨架,完整讲解 VGG 块的设计动机、四框架(MXNet/PyTorch/TensorFlow/JAX)实现、VGG-11 网络组装与 Fashion-MNIST 实战训练,并补充仓库 d2l 工具库中Classifier、layer_summary、Trainer等源码细节,帮助你既会写 VGG 代码,也能理解其背后的设计哲学。

背景:从逐层设计走向模块化架构

AlexNet 用实证证明了深层 CNN 能取得好成绩,但它并没有给出一个通用的设计模板来指导后续研究者搭建新网络。VGG 论文 :cite:Simonyan.Zisserman.2014的贡献恰恰在于提出了这样一个模板:用重复的"块"(block)来组装网络。

这一思路与芯片设计领域 VLSI(超大规模集成)的演进如出一辙——工程师们从放置晶体管,发展到逻辑元件,再到逻辑块 :cite:Mead.1980。神经网络架构设计也经历了同样的抽象化过程:从单个神经元,到整层(layer),再到由多层构成的块(block)。值得一提的是,这一趋势延续至今:研究者已经开始直接复用整个预训练模型来完成相关但不同的任务,这类大型预训练模型通常被称为基础模型(foundation models):cite:bommasani2021opportunities。

使用现代深度学习框架,我们很容易用循环和子程序在代码中实现这些重复结构,这正是本仓库各章节代码的一贯风格。在原文档 vgg.md 中,同一套逻辑分别以 MXNet、PyTorch、TensorFlow 和 JAX 四种后端给出。

VGG 块的动机:为什么"多层卷积 + 一次池化"

传统 CNN 的基本构建单元是"一次卷积 + 一次非线性 + 一次池化"。这种做法的问题是空间分辨率下降太快:每经过一个池化层分辨率就减半,因此网络最多只能堆叠 $\log_2 d$ 层卷积($d$ 为输入维度),否则特征图就耗尽。以 ImageNet 的 $224\times224$ 输入为例,用这种方式无法构造超过 8 层的卷积网络。

VGG 论文的核心思想是:在两次 max-pooling 下采样之间,堆叠多个卷积层,组成一个"块"。作者当时关注的核心问题是:深网络和宽网络哪个更好?一个关键的观察是:

  • 连续两个 $3\times3$ 卷积的感受野,等价于一个 $5\times5$ 卷积;
  • 但后者参数约为 $25 \cdot c^2$,而前者仅需 $3 \cdot 9 \cdot c^2 = 27c^2$,更省参数且引入了更多非线性。

通过详细分析,VGG 作者证明深而窄的网络显著优于浅而宽的网络。这开启了深度学习对"更深网络"的追求,也使"堆叠 $3\times3$ 卷积"成为此后十年深度网络的黄金标准(该设计决策直到近期才被 :cite:liu2022convnet重新审视)。相应地,GPU 上针对小卷积核的快速实现也成为了标配 :cite:lavin2016fast。

VGG 块:定义与四框架实现

一个 VGG 块由两部分组成:

  1. 若干个卷积层:$3\times3$ 卷积核、padding=1,保持高宽不变,后接 ReLU 非线性;
  2. 一个max-pooling 层:$2\times2$ 池化窗口、stride=2,将高宽减半。

vgg_block函数接受两个参数:num_convs(块内卷积层数量)和num_channels(输出通道数)。四种框架的完整实现如下:

MXNet(Gluon)

def vgg_block(num_convs, num_channels): blk = nn.Sequential() for _ in range(num_convs): blk.add(nn.Conv2D(num_channels, kernel_size=3, padding=1, activation='relu')) blk.add(nn.MaxPool2D(pool_size=2, strides=2)) return blk

PyTorch

def vgg_block(num_convs, out_channels): layers = [] for _ in range(num_convs): layers.append(nn.LazyConv2d(out_channels, kernel_size=3, padding=1)) layers.append(nn.ReLU()) layers.append(nn.MaxPool2d(kernel_size=2, stride=2)) return nn.Sequential(*layers)

PyTorch 版本使用nn.LazyConv2d延迟初始化,通道数在首次前向时才确定,这与仓库 d2l/torch.py 中init_cnn、apply_init的惰性初始化机制配合使用。

TensorFlow(Keras)

def vgg_block(num_convs, num_channels): blk = tf.keras.models.Sequential() for _ in range(num_convs): blk.add( tf.keras.layers.Conv2D(num_channels, kernel_size=3, padding='same', activation='relu')) blk.add(tf.keras.layers.MaxPool2D(pool_size=2, strides=2)) return blk

JAX(Flax)

def vgg_block(num_convs, out_channels): layers = [] for _ in range(num_convs): layers.append(nn.Conv(out_channels, kernel_size=(3, 3), padding=(1, 1))) layers.append(nn.relu) layers.append(lambda x: nn.max_pool(x, window_shape=(2, 2), strides=(2, 2))) return nn.Sequential(layers)

可以看到,四个后端的逻辑完全一致:循环添加 $3\times3$ 卷积 + ReLU,末尾追加 $2\times2$ stride=2 的 max-pooling。

VGG 网络:用arch参数化组装网络族

与 AlexNet 和 LeNet 类似,VGG 网络可以分成两部分:前半部分主要由卷积和池化层组成,后半部分是与 AlexNet 完全相同的全连接层。关键区别在于:卷积层被组织为"保持维度不变的非线性变换组 + 分辨率缩减步骤"的块结构。

在 vgg.md 中,网络通过一个名为arch的元组列表来定义,每个元组对应一个块,包含两个值:卷积层数量与输出通道数——这正是调用vgg_block所需的两个参数。因此 VGG 定义的是一整个网络族而非单一网络:只需遍历arch即可拼装出任意配置的 VGG。

VGG-11 类定义(PyTorch / MXNet / TensorFlow)

class VGG(d2l.Classifier): def __init__(self, arch, lr=0.1, num_classes=10): super().__init__() self.save_hyperparameters() if tab.selected('mxnet'): self.net = nn.Sequential() for (num_convs, num_channels) in arch: self.net.add(vgg_block(num_convs, num_channels)) self.net.add(nn.Dense(4096, activation='relu'), nn.Dropout(0.5), nn.Dense(4096, activation='relu'), nn.Dropout(0.5), nn.Dense(num_classes)) self.net.initialize(init.Xavier()) if tab.selected('pytorch'): conv_blks = [] for (num_convs, out_channels) in arch: conv_blks.append(vgg_block(num_convs, out_channels)) self.net = nn.Sequential( *conv_blks, nn.Flatten(), nn.LazyLinear(4096), nn.ReLU(), nn.Dropout(0.5), nn.LazyLinear(4096), nn.ReLU(), nn.Dropout(0.5), nn.LazyLinear(num_classes)) self.net.apply(d2l.init_cnn) if tab.selected('tensorflow'): self.net = tf.keras.models.Sequential() for (num_convs, num_channels) in arch: self.net.add(vgg_block(num_convs, num_channels)) self.net.add( tf.keras.models.Sequential([ tf.keras.layers.Flatten(), tf.keras.layers.Dense(4096, activation='relu'), tf.keras.layers.Dropout(0.5), tf.keras.layers.Dense(4096, activation='relu'), tf.keras.layers.Dropout(0.5), tf.keras.layers.Dense(num_classes)]))

JAX(Flax)版本

class VGG(d2l.Classifier): arch: list lr: float = 0.1 num_classes: int = 10 training: bool = True def setup(self): conv_blks = [] for (num_convs, out_channels) in self.arch: conv_blks.append(vgg_block(num_convs, out_channels)) self.net = nn.Sequential([ *conv_blks, lambda x: x.reshape((x.shape[0], -1)), # flatten nn.Dense(4096), nn.relu, nn.Dropout(0.5, deterministic=not self.training), nn.Dense(4096), nn.relu, nn.Dropout(0.5, deterministic=not self.training), nn.Dense(self.num_classes)])

为什么叫 VGG-11

原始 VGG 网络包含五个卷积块:前两个块各含 1 个卷积层,后三个块各含 2 个卷积层。第一个块有 64 个输出通道,之后每个块通道数翻倍,直到 512。由于该网络共使用 8 个卷积层和 3 个全连接层,因此常被称为VGG-11。

标准 VGG-11 的arch配置为:

arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))

用 layer_summary 查看逐层输出形状

借助d2l.Classifier基类提供的layer_summary方法(定义于 d2l/torch.py 的Classifier类中,它用随机张量前向遍历self.net并打印每层输出形状),可以直观确认每个块都会将高宽减半:

VGG(arch=((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))).layer_summary( (1, 1, 224, 224))

TensorFlow 与 JAX 后端因数据布局不同,输入形状分别为(1, 224, 224, 1)(TF)与(1, 224, 224, 1)(JAX 需传入training=False)。从输出可以看到:每个块使高宽减半,最终在展平(flatten)前高宽降为 7,随后交由全连接部分处理。VGG 论文还描述了其他变体——事实上,"提出一族在速度与精度间权衡的网络"已成为新架构发布的常态。

训练 VGG-11 于 Fashion-MNIST

VGG-11 比 AlexNet 计算量更大,因此仓库中的训练示例使用通道数更小的变体,这对 Fashion-MNIST 来说绰绰有余:

model = VGG(arch=((1, 16), (1, 32), (2, 64), (2, 128), (2, 128)), lr=0.01) trainer = d2l.Trainer(max_epochs=10, num_gpus=1) data = d2l.FashionMNIST(batch_size=128, resize=(224, 224)) if tab.selected('pytorch'): model.apply_init([next(iter(data.get_dataloader(True)))[0]], d2l.init_cnn) trainer.fit(model, data)

TensorFlow 后端写法略有差异:

trainer = d2l.Trainer(max_epochs=10) data = d2l.FashionMNIST(batch_size=128, resize=(224, 224)) with d2l.try_gpu(): model = VGG(arch=((1, 16), (1, 32), (2, 64), (2, 128), (2, 128)), lr=0.01) trainer.fit(model, data)

这里有几个值得注意的实战细节(均有仓库源码可印证):

  • d2l.FashionMNIST(batch_size=128, resize=(224, 224)):数据模块定义于 d2l/torch.py 的FashionMNIST类,构造时通过transforms.Resize(resize)将原本 $28\times28$ 的图片放大到 $224\times224$,get_dataloader返回带num_workers的 DataLoader;
  • d2l.init_cnn:定义于 d2l/torch.py,对nn.Linear和nn.Conv2d施加xavier_uniform_初始化;PyTorch 的惰性层(LazyConv2d/LazyLinear)需要先用真实数据触发一次前向(model.apply_init)才能完成参数创建并应用初始化,这与 d2l/torch.py 中apply_init的实现一致;
  • d2l.Trainer(max_epochs=10, num_gpus=1):训练器定义于 d2l/torch.py,其fit会依次执行数据准备、模型准备、优化器配置与多轮fit_epoch;num_gpus在Trainer中控制设备搬运与model.to(gpu);
  • 训练过程与 AlexNet 类似(见 chapter_convolutional-modern/alexnet.md),验证损失与训练损失接近,说明过拟合程度较小。

对于 JAX 后端,trainer.fit配合model.apply_init(定义于 d2l/jax.py,通过self.init(key, *dummy_input)初始化参数)使用;JAX 版本的Dropout需要deterministic标志区分训练/推理模式,因此示例中传入training=False以关闭随机性。

总结:VGG 为什么是"第一个真正现代的 CNN"

可以说 VGG 是第一个真正意义上的现代卷积神经网络:

  • AlexNet 引入了许多让深度学习大规模生效的组件,但VGG 引入了关键特性:多卷积层组成的块结构,以及"深而窄优于浅而宽"的偏好;
  • VGG 是第一个真正以"参数化网络族"形式存在的模型,实践者可以在复杂度与速度之间自由权衡;
  • 现代框架在这里大放异彩:不再需要生成 XML 配置文件来指定网络,而是通过简单的 Python 代码直接组装网络。

近期的 ParNet :cite:Goyal.Bochkovskiy.Deng.ea.2021展示了通过大量并行计算,用更浅的架构也能获得有竞争力的性能,这一方向值得关注;但本仓库后续章节仍沿着过去十年的主流路径(更深、更模块化)展开。

延伸练习

  1. 与 AlexNet 相比,VGG 计算更慢且占用更多 GPU 内存:
    • 比较 AlexNet 与 VGG 的参数数量;
    • 比较卷积层与全连接层的浮点运算量;
    • 思考如何降低全连接层带来的计算开销。
  2. 打印网络各层维度时,只能看到 8 个块(加上若干辅助变换)的信息,尽管网络共有 11 层——剩下的 3 层去哪了?(提示:逐层观察输出形状与layer_summary的实现。)
  3. 参考 VGG 论文 Table 1 :cite:Simonyan.Zisserman.2014,构造 VGG-16 或 VGG-19 等其他常见变体。
  4. 将 Fashion-MNIST 从 $28\times28$ 八倍上采样到 $224\times224$ 非常浪费。尝试修改网络架构与分辨率转换(例如改用 56 或 84 的输入尺寸),能否在精度不下降的前提下做到?可参考 VGG 论文中关于"下采样前增加更多非线性"的思路。

相关章节导航

  • 现代卷积神经网络章节索引:了解 VGG 在 CNN 发展脉络中的位置;
  • AlexNet 章节:VGG 训练流程的直接参照;
  • LeNet 章节:VGG 块与经典 CNN 组件的衔接;
  • d2l 工具库:Classifier、Trainer、FashionMNIST、init_cnn等基类与工具函数的完整源码。
  • 文档
  • 教程
  • 人工智能
  • 深度学习
  • NLP
  • 计算机视觉
  • 强化学习

【免费下载链接】d2l-en

Interactive deep learning book with multi-framework code, math, and discussions. Adopted at 500 universities from 70 countries including Stanford, MIT, Harvard, and Cambridge.

项目地址:https://gitcode.com/gh_mirrors/d2/d2l-en
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询