- 文档
- 教程
- 人工智能
- 深度学习
- 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.
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 块由两部分组成:
- 若干个卷积层:$3\times3$ 卷积核、padding=1,保持高宽不变,后接 ReLU 非线性;
- 一个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 blkPyTorch
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 blkJAX(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展示了通过大量并行计算,用更浅的架构也能获得有竞争力的性能,这一方向值得关注;但本仓库后续章节仍沿着过去十年的主流路径(更深、更模块化)展开。
延伸练习
- 与 AlexNet 相比,VGG 计算更慢且占用更多 GPU 内存:
- 比较 AlexNet 与 VGG 的参数数量;
- 比较卷积层与全连接层的浮点运算量;
- 思考如何降低全连接层带来的计算开销。
- 打印网络各层维度时,只能看到 8 个块(加上若干辅助变换)的信息,尽管网络共有 11 层——剩下的 3 层去哪了?(提示:逐层观察输出形状与
layer_summary的实现。) - 参考 VGG 论文 Table 1 :cite:
Simonyan.Zisserman.2014,构造 VGG-16 或 VGG-19 等其他常见变体。 - 将 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.
相关推荐
使用块的网络(VGG):从可复用卷积块到 VGG-11 的完整实现与训练
使用块的网络(VGG):从可复用卷积块到 VGG 11 的完整实现与训练 本篇技术指南围绕《动手学深度学习》(d2l zh) chapter_convoluti
人工智能深度学习机器学习教程使用块的网络(VGG):用可复用卷积块设计深层卷积神经网络的实战指南
使用块的网络(VGG):用可复用卷积块设计深层卷积神经网络的实战指南 《动手学深度学习》中文仓库(d2l zh)的本章节聚焦于 VGG(Visual Geome
人工智能深度学习机器学习教程用 CNTK 训练 VGG 深度卷积网络:ImageNet 图像分类的 Python 与 BrainScript 实战指南
用 CNTK 训练 VGG 深度卷积网络:ImageNet 图像分类的 Python 与 BrainScript 实战指南 导读 本文围绕 Microsoft
深度学习机器学习人工智能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考