平行串联的网络(GoogLeNet)——【torch学习笔记】

平行串联的网络(GoogLeNet)

引用翻译:《动手学深度学习》

2014年,Szegedy等人在ImageNet挑战赛中获胜,提出了一个结合NiN和重复块范式的优势的结构。该论文的一个重点是解决哪些大小的卷积核是最好的问题。毕竟,以前流行的网络采用了小到1×1,大到11×11的选择。本文的一个见解是,有时采用不同大小的核的组合可能是有利的。在这一节中,我们将介绍GoogLeNet,介绍原始模型的一个略微简化的版本–我们省略了一些特设的功能,这些功能是为了稳定训练而添加的,但现在有了更好的训练算法,这些功能就没有必要了。

一、起始区块

GoogLeNet中的基本卷积块被称为Inception块,可能是由于电影《盗梦空间》中的一句话(“We Need To Go Deeper”)而得名,这句话引发了病毒式的回忆。

在这里插入图片描述

如上图所示,起始块由四个平行路径组成。前三条路径使用窗口大小为1×1、3×3和5×5的卷积层来提取不同空间大小的信息。中间两条路径对输入进行1×1卷积,以减少输入通道的数量,降低模型的复杂性。第四条路径使用一个3×3的最大集合层,然后是一个1×1的卷积层来改变通道的数量。这四条路径都使用适当的填充,使输入和输出具有相同的高度和宽度。最后,每条路径的输出沿通道维度串联起来,构成该块的输出。Inception块的常用参数是每层输出通道的数量。

import sys
sys.path.insert(0, '..')

import d2l
import torch
import torch.nn as nn
import torch.nn.functional as F

class Inception(nn.Module):
    # c1 - c4是路径中每层的输出通道数
    def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):
        super(Inception, self).__init__(**kwargs)
        # 路径1是一个单一的1 x 1卷积
        self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
        # 路径2是一个1×1的卷积层,然后是一个3×3的卷积层
        self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)
        self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
        # 路径3是一个1×1的卷积层,然后是一个5×5的卷积层
        self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)
        self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
        # 路径4是一个3 x 3的最大集合层,然后是一个1 x 1的卷积层。
        self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
        self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1)

    def forward(self, x):
        p1 = F.relu(self.p1_1(x))
        p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
        p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
        p4 = F.relu(self.p4_2(self.p4_1(x)))
        # 将通道维度上的输出串联起来
        return torch.cat((p1, p2, p3, p4), dim=1)

为了获得一些关于这个网络为什么工作得这么好的直觉,考虑一下过滤器的组合。它们在不同的范围内探索图像。这意味着不同范围的细节可以被不同的过滤器有效识别。同时,我们可以为不同的范围分配不同数量的参数(例如,为短距离分配更多的参数,但不能完全忽略长距离)。

二、GoogLeNet模型

GoogLeNet使用一个总共有9个起始块的堆栈和全局平均池来产生其估计值。起始块之间的最大池化降低了维度。第一部分与AlexNet和LeNet相同,块的堆叠继承自VGG,全局平均池化避免了在最后出现完全连接层的堆叠。该架构描述如下。

在这里插入图片描述

我们现在可以逐件实现GoogLeNet。第一个组件使用一个64通道的7×7卷积层。

b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
                   nn.ReLU(),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

第二个组件使用两个卷积层:首先是一个64通道的1×1卷积层,然后是一个3×3卷积层,将通道数量增加到三倍。这对应于Inception块中的第二条路径。

b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
                   nn.ReLU(),
                   nn.Conv2d(64, 192, kernel_size=3, padding=1),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

第三个组件将两个完整的Inception块串联起来。第一个Inception块的输出通道数为64+128+32+32=256 ,与四条路径的输出通道之比为64:128:32:32=2:4:1:1。第二和第三条路径首先将输入通道的数量分别减少到96/192=1/2和16/192=1/12,然后连接第二卷积层。第二Inception块的输出通道数增加到128+192+96+64=480,每条路径的输出通道数的比例为128:192:96:64=4:6:3:2。第二和第三条路径首先将输入通道的数量分别减少到128/256=1/2和32/256=1/8。

b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32),
                    Inception(256, 128, (128, 192), (32, 96), 64),
                    nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

第四个区块更为复杂。它串联了五个Inception块,它们分别有192+208+48+64=512,160+224+64+64=512,128+256+64+64=512,112+288+64+64=528,以及256+320+128+128=832个输出通道。分配给这些路径的通道数与第三模块相似:带有3×3卷积层的第二条路径输出的通道数最多,其次是只有1×1卷积层的第一条路径,带有5×5卷积层的第三条路径,以及带有3×3最大汇集层的第四条路径。第二和第三条路径将首先根据比例减少通道的数量。这些比例在不同的Inception块中略有不同。

b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64),
                   Inception(512, 160, (112, 224), (24, 64), 64),
                   Inception(512, 128, (128, 256), (24, 64), 64),
                   Inception(512, 112, (144, 288), (32, 64), 64),
                   Inception(528, 256, (160, 320), (32, 128), 128),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

第五块有两个Inception块,有256+320+128+128=832和384+384+128+128=1024个输出通道。分配给每个路径的通道数与第三和第四模块的相同,但在具体数值上有所不同。应该注意的是,第五块后面是输出层。这个模块使用全局平均池化层,将每个通道的高度和宽度改为1,就像在NiN中一样。最后,我们把输出变成一个二维数组,然后是一个全连接层,其输出的数量是标签类的数量。

class Flatten(nn.Module):
    def forward(self, input):
        return input.view(input.size(0), -1)

b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),
                   Inception(832, 384, (192, 384), (48, 128), 128),
                   nn.AdaptiveMaxPool2d((1,1)),
                   Flatten())

net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10))

GoogLeNet模型在计算上很复杂,所以修改通道的数量不像VGG那样容易。为了在Fashion-MNIST上有一个合理的训练时间,我们把输入的高度和宽度从224减少到96。这就简化了计算过程。下面展示了不同模块之间输出形状的变化。

X = torch.rand(size=(1, 1, 96, 96))
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__,'output shape:\t', X.shape)
Sequential output shape:	 torch.Size([1, 64, 24, 24])
Sequential output shape:	 torch.Size([1, 192, 12, 12])
Sequential output shape:	 torch.Size([1, 480, 6, 6])
Sequential output shape:	 torch.Size([1, 832, 3, 3])
Sequential output shape:	 torch.Size([1, 1024])
Linear output shape:	 torch.Size([1, 10])

三、数据采集和训练

和以前一样,我们使用Fashion-MNIST数据集来训练我们的模型。在调用训练程序之前,我们将其转换为96×96像素的分辨率。

lr, num_epochs, batch_size, device = 0.1, 5, 16, d2l.try_gpu()

# 初始化权重
def init_weights(m):
    if type(m) == nn.Linear or type(m) == nn.Conv2d:
        torch.nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)

# 损失函数
criterion = nn.CrossEntropyLoss()

# 加载fashion数据集
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)

d2l.train_ch5(net, train_iter, test_iter, criterion, num_epochs, batch_size, device, lr)
training on cpu

四、摘要

  • Inception块相当于一个有四个路径的子网络。它通过不同窗形的卷积层和最大池化层平行地提取信息。 1×1卷积层在每个像素层面上降低了通道的维度。最大池化降低了分辨率。

  • GoogLeNet将多个精心设计的Inception块与其他层串联起来。在Inception块中分配的通道数量的比例是通过在ImageNet数据集上的大量实验得到的。

  • GoogLeNet以及它的后续版本是ImageNet上最有效的模型之一,以较低的计算复杂性提供了类似的测试精度。

五、练习

1、GoogLeNet有几个迭代版本。尝试实现和运行它们。其中一些包括以下内容。

  • 添加一个批处理规范化层,如本章后面所述[2]。

  • 对Inception块进行调整[3]。

  • 使用 "标签平滑 "进行模型正则化[3]。

  • 把它包括在残差连接中,如本章后面所述[4]。

2、GoogLeNet发挥作用的最小图像尺寸是多少?

3、比较AlexNet、VGG和NiN与GoogLeNet的模型参数大小。后两种网络结构是如何大幅减少模型参数大小的?

4、为什么我们最初需要一个大范围的卷积?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值