虽然 AlexNet 提供了深度 CNN 可以取得良好结果的经验证据,但它没有提供通用模板来指导后续研究人员设计新网络。在接下来的部分中,我们将介绍几个常用于设计深度网络的启发式概念。
该领域的进展反映了芯片设计中 VLSI(超大规模集成)的进展,工程师从将晶体管放置到逻辑元件再到逻辑块(Mead,1980 年)。同样,神经网络架构的设计也变得越来越抽象,研究人员从单个神经元的角度思考到整个层,现在转向块,重复层的模式。十年后,这已经发展到研究人员使用整个训练模型将它们重新用于不同但相关的任务。此类大型预训练模型通常称为 基础模型 (Bommasani等人,2021 年)。
回到网络设计。使用块的想法首先出现于牛津大学的视觉几何组 (VGG),在他们同名的VGG网络中(Simonyan 和 Zisserman,2014 年)。通过使用循环和子例程,可以使用任何现代深度学习框架轻松地在代码中实现这些重复结构。
8.2.1. VGG 块
CNN 的基本构建块是以下序列:(i) 带有填充的卷积层以保持分辨率,(ii) 非线性,例如 ReLU,(iii) 池化层,例如最大池化以减少解决。这种方法的问题之一是空间分辨率下降得非常快。特别是,这强加了一个硬限制log2d网络上所有维度之前的卷积层(d) 用完了。例如,在 ImageNet 的情况下,以这种方式不可能有超过 8 个卷积层。
Simonyan 和 Zisserman ( 2014 )的关键思想是以 块的形式通过最大池化在下采样之间使用多个卷积。他们主要感兴趣的是深度网络还是宽网络表现更好。例如,连续应用两个 3×3卷积接触与单个相同的像素 5×5卷积确实如此。同时,后者使用了大约同样多的参数(25⋅c2) 三个 3×3卷积做(3⋅9⋅c2). 在相当详细的分析中,他们表明深度和狭窄的网络明显优于浅层网络。这将深度学习置于对具有超过 100 层的典型应用的更深网络的追求上。堆叠3×3卷积已成为后来的深度网络的黄金标准(最近Liu等人( 2022 )才重新考虑的设计决策)。因此,小卷积的快速实现已成为 GPU 的主要内容 (Lavin 和 Gray,2016 年)。
回到 VGG:一个 VGG 块由一系列卷积组成 3×3填充为 1 的内核(保持高度和宽度)后跟一个2×2步长为 2 的最大池化层(每个块后将高度和宽度减半)。在下面的代码中,我们定义了一个函数vgg_block
来实现一个 VGG 块。
下面的函数有两个参数,对应于卷积层数num_convs
和输出通道数 num_channels
。
8.2.2. VGG网络
与 AlexNet 和 LeNet 一样,VGG 网络可以分为两部分:第一部分主要由卷积层和池化层组成,第二部分由与 AlexNet 相同的全连接层组成。关键区别在于卷积层在保持维数不变的非线性变换中分组,然后是分辨率降低步骤,如图 8.2.1所示。
网络的卷积部分连续连接 图 8.2.1中的几个 VGG 块(也在vgg_block
函数中定义)。这种卷积分组是一种在过去十年中几乎保持不变的模式,尽管操作的具体选择已经发生了相当大的修改。该变量 conv_arch
由一个元组列表(每个块一个)组成,其中每个元组包含两个值:卷积层数和输出通道数,它们正是调用函数所需的参数vgg_block
。因此,VGG 定义了一个网络家族,而不仅仅是一个特定的表现形式。要构建一个特定的网络,我们只需迭代arch
以组成块。
class VGG(d2l.Classifier):
def __init__(self, arch, lr=0.1, num_classes=10):
super().__init__()
self.save_hyperparameters()
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)
class VGG(d2l.Classifier):
def __init__(self, arch, lr=0.1, num_classes=10):
super().__init__()
self.save_hyperparameters()
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=