PyTorch教程-8.8. 设计卷积网络架构

电子说

1.3w人已加入

描述

过去的部分带我们参观了计算机视觉的现代网络设计。我们涵盖的所有工作的共同点是它严重依赖科学家的直觉。许多架构在很大程度上受到了人类创造力的启发,而在很大程度上受到了对深度网络提供的设计空间的系统探索的影响。尽管如此,这种网络工程方法已经取得了巨大的成功。

由于 AlexNet(第 8.1 节)在 ImageNet 上击败了传统的计算机视觉模型,因此通过堆叠卷积块构建非常深的网络变得很流行,所有这些都是由相同的模式设计的。尤其,3×3卷积由 VGG 网络(第 8.2 节)推广。NiN(第 8.3 节)表明即使1×1通过添加局部非线性,卷积可能是有益的。此外,NiN 通过跨所有位置的聚合解决了在网络头部聚合信息的问题。GoogLeNet(8.4节)在其Inception block中加入了多个不同卷积宽度的分支,结合了VGG和NiN的优点。ResNets(第 8.6 节)改变了对身份映射的归纳偏差(来自f(x)=0).这允许非常深的网络。将近十年后,ResNet 设计仍然流行,证明了它的设计。最后,ResNeXt(第 8.6.5 节)添加了分组卷积,在参数和计算之间提供了更好的权衡。挤压和激发网络 (SENets) 是用于视觉的变形金刚的前身,可实现位置之间的高效信息传输(Hu等人,2018 年)。他们通过计算每个通道的全局注意力函数来实现这一点。

到目前为止,我们省略了通过神经架构搜索(NAS)获得的网络(Liu等人,2018 年,Zoph 和 Le,2016 年)。我们之所以选择这样做,是因为它们的成本通常很高,依赖于蛮力搜索、遗传算法、强化学习或某种其他形式的超参数优化。给定一个固定的搜索空间,NAS 使用搜索策略根据返回的性能估计自动选择架构。NAS 的结果是单个网络实例。EfficientNets 是这次搜索的显着成果(Tan 和 Le,2019 年)。

下面我们讨论一个与寻求单一最佳网络完全不同的想法。它在计算上相对便宜,它会在途中产生科学见解,并且在结果质量方面非常有效。让我们回顾一下Radosavovic等人的策略。(2020)设计网络设计空间。该策略结合了手动设计和 NAS 的优势。它通过对网络分布进行操作 并以某种方式优化分布以获得整个网络系列的良好性能来实现这一点。它的结果是RegNets,特别是 RegNetX 和 RegNetY,以及一系列用于设计高性能 CNN 的指导原则。

importtorchfromtorchimportnnfromtorch.nnimportfunctionalasFfromd2limporttorchasd2l

frommxnetimportinit,np,npxfrommxnet.gluonimportnnfromd2limportmxnetasd2lnpx.set_np()

fromflaximportlinenasnnfromd2limportjaxasd2l

importtensorflowastffromd2limporttensorflowasd2l

8.8.1.AnyNet 设计空间

下面的描述紧跟Radosavovic等人的推理 。(2020)加上一些缩写以使其符合本书的范围。首先,我们需要一个供网络系列探索的模板。本章设计的共同点之一是网络由主干、主体和头部组成。.stem 执行初始图像处理,通常通过具有较大窗口大小的卷积。主体由多个块组成,执行从原始图像到对象表示所需的大量转换。最后,头部将其转换为所需的输出,例如通过用于多类分类的 softmax 回归器。反过来,身体由多个阶段组成,以降低的分辨率对图像进行操作。事实上,主干和每个后续阶段都占空间分辨率的四分之一。最后,每个阶段由一个或多个块组成。这种模式对所有网络都很常见,从 VGG 到 ResNeXt。事实上,对于通用 AnyNet 网络的设计,Radosavovic等人。(2020)使用了 ResNeXt 块图 8.6.5。

pytorch

图 8.8.1AnyNet 设计空间。号码(c,r)沿每个箭头指示通道数c和决议r×r当时的图像。从左到右:由主干、主体和头部组成的通用网络结构;身体由四个阶段组成;阶段的详细结构;块的两种替代结构,一种没有下采样,另一种将每个维度的分辨率减半。设计选择包括深度di, 输出通道数ci, 组数gi和瓶颈比ki对于任何阶段i.

让我们详细回顾一下图 8.8.1中概述的结构。如前所述,AnyNet 由主干、主体和头部组成。词干将 RGB 图像(3 通道)作为输入,使用3×3与 stride 的卷积2,然后是批量规范,将分辨率减半r×r到r/2×r/2.此外,它生成c0作为身体输入的通道。

由于该网络旨在与形状的 ImageNet 图像配合使用224×224×3,身体用于将其减少到7×7×c4通过 4 个阶段(回想一下224/21+4=7), 每个最终的步幅为2.最后,head 通过全局平均池采用完全标准的设计,类似于 NiN(第 8.3 节),然后是一个完全连接的层以发出一个n维向量为n-类分类。

大多数相关的设计决策都是网络主体固有的。它分阶段进行,每个阶段都由我们在第 8.6.5 节中讨论的相同类型的 ResNeXt 块组成。那里的设计再次完全通用:我们从一个块开始,通过使用一个步长将分辨率减半2(图8.8.1最右边)。为了匹配这一点,ResNeXt 块的剩余分支需要通过1×1卷积。此块后跟可变数量的附加 ResNeXt 块,这些块使分辨率和通道数均保持不变。请注意,常见的设计实践是在卷积块的设计中添加一个小瓶颈。因此,瓶颈比ki≥1我们提供一些渠道ci/ki在每个阶段的块内i(正如实验所示,这并不是真正有效,应该跳过)。最后,由于我们正在处理 ResNeXt 块,我们还需要选择组数gi对于阶段的分组卷积i.

这个看似通用的设计空间仍然为我们提供了许多参数:我们可以设置块宽度(通道数)c0,…c4,每个阶段的深度(块数)d1,…d4, 瓶颈比k1,…k4和组宽度(组数)g1,…g4.这总共加起来多达 17 个参数,导致不合理的大量配置值得探索。我们需要一些工具来有效地缩小这个巨大的设计空间。这就是设计空间概念美的来源。在我们这样做之前,让我们先实现通用设计。

classAnyNet(d2l.Classifier):defstem(self,num_channels):returnnn.Sequential(nn.LazyConv2d(num_channels,kernel_size=3,stride=2,padding=1),nn.LazyBatchNorm2d(),nn.ReLU())

classAnyNet(d2l.Classifier):defstem(self,num_channels):net=nn.Sequential()net.add(nn.Conv2D(num_channels,kernel_size=3,padding=1,strides=2),nn.BatchNorm(),nn.Activation('relu'))returnnet

classAnyNet(d2l.Classifier):arch:tuplestem_channels:intlr:float=0.1num_classes:int=10training:bool=Truedefsetup(self):self.net=self.create_net()defstem(self,num_channels):returnnn.Sequential([nn.Conv(num_channels,kernel_size=(3,3),strides=(2,2),padding=(1,1)),nn.BatchNorm(notself.training),nn.relu])

classAnyNet(d2l.Classifier):defstem(self,num_channels):returntf.keras.models.Sequential([tf.keras.layers.Conv2D(num_channels,kernel_size=3,strides=2,padding='same'),tf.keras.layers.BatchNormalization(),tf.keras.layers.Activation('relu')])

每个阶段由depthResNeXt 块组成,其中num_channels指定块宽度。请注意,第一个块将输入图像的高度和宽度减半。

@d2l.add_to_class(AnyNet)defstage(self,depth,num_channels,groups,bot_mul):blk=[]foriinrange(depth):ifi==0:blk.append(d2l.ResNeXtBlock(num_channels,groups,bot_mul,use_1x1conv=True,strides=2))else:blk.append(d2l.ResNeXtBlock(num_channels,groups,bot_mul))returnnn.Sequential(*blk)

@d2l.add_to_class(AnyNet)defstage(self,depth,num_channels,groups,bot_mul):net=nn.Sequential()foriinrange(depth):ifi==0:net.add(d2l.ResNeXtBlock(num_channels,groups,bot_mul,use_1x1conv=True,strides=2))else:net.add(d2l.ResNeXtBlock(num_channels,num_channels,groups,bot_mul))returnnet

@d2l.add_to_class(AnyNet)defstage(self,depth,num_channels,groups,bot_mul):blk=[]foriinrange(depth):ifi==0:blk.append(d2l.ResNeXtBlock(num_channels,groups,bot_mul,use_1x1conv=True,strides=(2,2),training=self.training))else:blk.append(d2l.ResNeXtBlock(num_channels,groups,bot_mul,training=self.training))returnnn.Sequential(blk)

@d2l.add_to_class(AnyNet)defstage(self,depth,num_channels,groups,bot_mul):net=tf.keras.models.Sequential()foriinrange(depth):ifi==0:net.add(d2l.ResNeXtBlock(num_channels,groups,bot_mul,use_1x1conv=True,strides=2))else:net.add(d2l.ResNeXtBlock(num_channels,groups,bot_mul))returnnet

将网络的主干、主体和头部放在一起,我们完成了 AnyNet 的实现。

@d2l.add_to_class(AnyNet)def__init__(self,arch,stem_channels,lr=0.1,num_classes=10):super(AnyNet,self).__init__()self.save_hyperparameters()self.net=nn.Sequential(self.stem(stem_channels))fori,sinenumerate(arch):self.net.add_module(f'stage{i+1}',self.stage(*s))self.net.add_module('head',nn.Sequential(nn.AdaptiveAvgPool2d((1,1)),nn.Flatten(),nn.LazyLinear(num_classes)))self.net.apply(d2l.init_cnn)

@d2l.add_to_class(AnyNet)def__init__(self,arch,stem_channels,lr=0.1,num_classes=10):super(AnyNet,self).__init__()self.save_hyperparameters()self.net=nn.Sequential()self.net.add(self.stem(stem_channels))fori,sinenumerate(arch):self.net.add(self.stage(*s))self.net.add(nn.GlobalAvgPool2D(),nn.Dense(num_classes))self.net.initialize(init.Xavier())

@d2l.add_to_class(AnyNet)defcreate_net(self):net=nn.Sequential([self.stem(self.stem_channels)])fori,sinenumerate(self.arch):net.layers.extend([self.stage(*s)])net.layers.extend([nn.Sequential([lambdax:nn.avg_pool(x,window_shape=x.shape[1:3],strides=x.shape[1:3],padding='valid'),lambdax:x.reshape((x.shape[0],-1)),nn.Dense(self.num_classes)])])returnnet

@d2l.add_to_class(AnyNet)def__init__(self,arch,stem_channels,lr=0.1,num_classes=10):super(AnyNet,self).__init__()self.save_hyperparameters()self.net=tf.keras.models.Sequential(self.stem(stem_channels))fori,sinenumerate(arch):self.net.add(self.stage(*s))self.net.add(tf.keras.models.Sequential([tf.keras.layers.GlobalAvgPool2D(),tf.keras.layers.Dense(units=num_classes)]))

8.8.2.设计空间的分布和参数

正如在第 8.8.1 节中讨论的那样,设计空间的参数是该设计空间中网络的超参数。考虑在 AnyNet 设计空间中识别好的参数的问题。我们可以尝试为给定的计算量(例如,FLOPs 和计算时间)找到单个最佳参数选择。如果我们只允许每个参数有两种可能的选择,我们将不得不探索217=131072组合以找到最佳解决方案。由于其过高的成本,这显然是不可行的。更糟糕的是,就应该如何设计网络而言,我们并没有真正从这个练习中学到任何东西。下次我们添加 X 阶段或移位操作或类似操作时,我们需要从头开始。更糟糕的是,由于训练中的随机性(舍入、混洗、位错误),没有两次运行可能产生完全相同的结果。更好的策略是尝试确定参数选择应如何关联的一般准则。例如,瓶颈率、通道、块、组的数量,或者它们在层之间的变化,理想情况下应该由一组简单的规则来控制。Radosavovic等人的方法。(2019)依赖于以下四个假设:

我们假设通用设计原则确实存在,这样许多满足这些要求的网络应该提供良好的性能。因此,识别网络分布可能是一个很好的策略。换句话说,我们假设大海捞针很多。

在我们评估网络是否良好之前,我们不需要训练网络收敛。相反,使用中间结果作为最终精度的可靠指导就足够了。使用(近似)代理来优化目标被称为多重保真度优化(Forrester等人,2007 年)。因此,根据仅几次通过数据集后获得的准确性进行设计优化,从而显着降低成本。

在较小规模(对于较小网络)下获得的结果可以推广到较大的网络。因此,对结构相似但块数少、通道数少等的网络进行优化。只有到最后,我们才需要验证这样发现的网络是否也能在规模上提供良好的性能。

设计的各个方面可以近似分解,这样就可以在某种程度上独立地推断出它们对结果质量的影响。换句话说,优化问题比较容易。

这些假设使我们能够廉价地测试许多网络。特别是,我们可以从配置空间中均匀采样并评估它们的性能。随后,我们可以通过查看上述网络可以实现的误差/准确度分布来评估参数选择的质量。表示为F(e)给定设计空间网络错误的累积分布函数 (CDF),使用概率分布绘制p.那是,

(8.8.1)F(e,p)=defPnet∼p{e(net)≤e}.

我们现在的目标是找到一个分布p通过网络使得大多数网络的错误率非常低,并且支持p简洁。当然,这在计算上无法准确执行。我们求助于网络样本Z=def{net1,…netn}(有错误e1,…,en,分别)从p并使用经验 CDFF^(e,Z)反而:

(8.8.2)F^(e,Z)=1n∑i=1n1(ei≤e).

每当一组选择的 CDF 主要(或匹配)另一个 CDF 时,它的参数选择是优越的(或无关紧要的)。因此,Radosavovic等人。(2020)试验共享网络瓶颈比ki=k对于所有阶段i的网络。这摆脱了3的4控制瓶颈比的参数。要评估这是否(负面)影响性能,可以从受约束和不受约束的分布中提取网络并比较相应的 CDF。事实证明,这个约束根本不影响网络分布的准确性,如图8.8.2的第一幅图所示。同样,我们可以选择选择相同的组宽度gi=g发生在网络的各个阶段。同样,这不会影响性能,如图8.8.2的第二个面板所示。这两个步骤结合起来减少了自由参数的数量6.

pytorch

图 8.8.2比较设计空间的误差经验分布函数。AnyNetA是原设计空间;AnyNetB联系瓶颈比率,AnyNetC也联系组宽度,AnyNetD跨阶段增加网络深度。从左到右:(i) 绑定瓶颈比率对性能没有影响,(ii) 绑定组宽度对性能没有影响,(iii) 增加跨阶段的网络宽度(通道)可以提高性能,(iv) 增加跨阶段的网络深度阶段提高性能。图由Radosavovic等人提供 。(2020 年)。

接下来,我们寻找方法来减少阶段宽度和深度的多种潜在选择。一个合理的假设是,随着我们深入,通道的数量应该增加,即ci≥ci−1(wi+1≥wi根据他们在图 8.8.2中的符号),产生AnyNetXD.同样,同样合理的假设是随着阶段的进展,它们应该变得更深,即,di≥di−1, 屈服AnyNetXE.这可以分别在图 8.8.2的第三和第四图中进行实验验证。

8.8.3.注册网

所结果的AnyNetXE设计空间由遵循易于理解的设计原则的简单网络组成:

分享瓶颈比例ki=k对于所有阶段i;

共享组宽度gi=g对于所有阶段i;

增加跨阶段的网络宽度:ci≤ci+1;

跨阶段增加网络深度:di≤di+1.

这给我们留下了最后一组选择:如何为最终的上述参数选择特定值AnyNetXE设计空间。通过研究分布在AnyNetXE可以观察到:理想情况下,网络的宽度随着网络中的块索引线性增加,即cj≈c0+caj, 在哪里j是块索引和斜率ca>0.鉴于我们只能在每个阶段选择不同的块宽度,我们得到了一个分段常数函数,该函数被设计为匹配这种依赖性。其次,实验还表明,瓶颈比为k=1表现最好,即我们建议根本不要使用瓶颈。

我们建议有兴趣的读者仔细阅读Radosavovic等人,以了解有关如何为不同计算量设计特定网络的更多详细信息。(2020 年)。例如,一个有效的 32 层 RegNetX 变体由下式给出k=1(没有瓶颈),g=16(组宽为 16),c1=32和c2=80第一阶段和第二阶段的渠道,分别被选为d1=4和d2=6块深。该设计的惊人见解是它适用,即使在调查更大规模的网络时也是如此。更好的是,它甚至适用于具有全局通道激活的挤压和激励 (SE) 网络设计 (RegNetY)(Hu等人,2018 年)。

classRegNetX32(AnyNet):def__init__(self,lr=0.1,num_classes=10):stem_channels,groups,bot_mul=32,16,1depths,channels=(4,6),(32,80)super().__init__(((depths[0],channels[0],groups,bot_mul),(depths[1],channels[1],groups,bot_mul)),stem_channels,lr,num_classes)

classRegNetX32(AnyNet):def__init__(self,lr=0.1,num_classes=10):stem_channels,groups,bot_mul=32,16,1depths,channels=(4,6),(32,80)super().__init__(((depths[0],channels[0],groups,bot_mul),(depths[1],channels[1],groups,bot_mul)),stem_channels,lr,num_classes)

classRegNetX32(AnyNet):lr:float=0.1num_classes:int=10stem_channels:int=32arch:tuple=((4,32,16,1),(6,80,16,1))

classRegNetX32(AnyNet):def__init__(self,lr=0.1,num_classes=10):stem_channels,groups,bot_mul=32,16,1depths,channels=(4,6),(32,80)super().__init__(((depths[0],channels[0],groups,bot_mul),(depths[1],channels[1],groups,bot_mul)),stem_channels,lr,num_classes)

我们可以看到,每个 RegNetX 阶段都逐渐降低分辨率并增加输出通道。

RegNetX32().layer_summary((1,1,96,96))

Sequentialoutputshape:torch.Size([1,32,48,48])Sequentialoutputshape:torch.Size([1,32,24,24])Sequentialoutputshape:torch.Size([1,80,12,12])Sequentialoutputshape:torch.Size([1,10])

RegNetX32().layer_summary((1,1,96,96))

Sequentialoutputshape:(1,32,48,48)Sequentialoutputshape:(1,32,24,24)Sequentialoutputshape:(1,80,12,12)GlobalAvgPool2Doutputshape:(1,80,1,1)Denseoutputshape:(1,10)

RegNetX32(training=False).layer_summary((1,96,96,1))

Sequentialoutputshape:(1,48,48,32)Sequentialoutputshape:(1,24,24,32)Sequentialoutputshape:(1,12,12,80)Sequentialoutputshape:(1,10)

RegNetX32().layer_summary((1,96,96,1))

Sequentialoutputshape:(1,48,48,32)Sequentialoutputshape:(1,24,24,32)Sequentialoutputshape:(1,12,12,80)Sequentialoutputshape:(1,10)

8.8.4.训练

在 Fashion-MNIST 数据集上训练 32 层 RegNetX 就像以前一样。

model=RegNetX32(lr=0.05)trainer=d2l.Trainer(max_epochs=10,num_gpus=1)data=d2l.FashionMNIST(batch_size=128,resize=(96,96))trainer.fit(model,data)

pytorch

model=RegNetX32(lr=0.05)trainer=d2l.Trainer(max_epochs=10,num_gpus=1)data=d2l.FashionMNIST(batch_size=128,resize=(96,96))trainer.fit(model,data)

pytorch

model=RegNetX32(lr=0.05)trainer=d2l.Trainer(max_epochs=10,num_gpus=1)data=d2l.FashionMNIST(batch_size=128,resize=(96,96))trainer.fit(model,data)

pytorch

trainer=d2l.Trainer(max_epochs=10)data=d2l.FashionMNIST(batch_size=128,resize=(96,96))withd2l.try_gpu():model=RegNetX32(lr=0.01)trainer.fit(model,data)

pytorch

8.8.5。讨论

具有理想的归纳偏差(假设或偏好),如视觉的局部和平移不变性(第 7.1 节),CNN 一直是该领域的主导架构。自 LeNet 以来一直如此,直到最近 Transformers(第 11.7 节)(Dosovitskiy等人,2021 年,Touvron等人,2021 年)开始在准确性方面超越 CNN。虽然最近在视觉 Transformer 方面取得的大部分进展都可以反向移植到 CNN 中(Liu等人,2022 年),只有在更高的计算成本下才有可能。同样重要的是,最近的硬件优化(NVIDIA Ampere 和 Hopper)只是扩大了支持变形金刚的差距。

值得注意的是,与 CNN 相比,Transformer 对局部性和平移不变性的归纳偏差程度要低得多。这并不是最不重要的,因为大型图像集的可用性,例如 LAION-400m 和 LAION-5B(Schuhmann等人,2022 年),其中有多达 50 亿张学习结构的图像。令人惊讶的是,在这方面的一些更相关的工作甚至包括 MLP(Tolstikhin等人,2021 年)。

总之,视觉 Transformers(第 11.8 节)目前在大规模图像分类中的最先进性能方面处于领先地位,表明可扩展性胜过归纳偏差(Dosovitskiy等人,2021 年)。这包括使用多头自注意力(第 11.5节)预训练大型 Transformers(第11.9节)。我们邀请读者深入研究这些章节以进行更详细的讨论。

8.8.6.练习

将阶段数增加到 4。你能设计一个性能更好的更深的 RegNetX 吗?

通过用 ResNet 块替换 ResNeXt 块来去除 ResNeXt-ify RegNets。你的新模型表现如何?

通过违反RegNetX 的设计原则来实现“VioNet”系列的多个实例。他们的表现如何?哪一个(di,ci,gi,bi) 是最重要的因素?

您的目标是设计“完美”的 MLP。你能用上面介绍的设计原则找到好的架构吗?是否可以从小型网络推断到大型网络?

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分