GoogLeNet,NiN块,完整代码实现

目录

一:回顾

二:网络中的网络(NiN)

全局平均汇聚层的思想:

计算量:

三:NiN块

        注意:

四:小结

五: 含并行连结的网络(GoogLeNet)

 5.1.Inception块

5.2.GoogLeNet模型

​编辑

六:小结

所有项目代码+UI界面


一:回顾

        上一篇我们了解了块的内容,VGG-11使用可复用的卷积块构造网络。不同的VGG模型可通过每个块中卷积层数量输出通道数量的差异来定义块的使用导致网络定义的非常简洁。使用块可以有效地设计复杂的网络。

        在VGG论文中,Simonyan和Ziserman尝试了各种架构。特别是他们发现深层且窄的卷积(即3×3)比较浅层且宽的卷积更有效。VGG网络中的每个卷积块都由连续的几个卷积层和一个池化层组成,卷积层通常具有相同的输出通道数,并使用小的卷积核大小,比如3x3。通过这种方式,VGG网络可以以逐步加深的方式构建复杂的特征,而不需要增加大量的参数。

        此外,使用卷积块的好处还在于可以在不同的网络中重复使用这些块,这样可以更容易地进行模型的设计和调整,而不需要重新构建整个网络。

二:网络中的网络(NiN)

        LeNet、AlexNet和VGG都有一个共同的设计模式:通过一系列的卷积层与汇聚层来提取空间结构特征;然后通过全连接层对特征的表征进行处理。 AlexNet和VGG对LeNet的改进主要在于如何扩大和加深这两个模块。 或者,可以想象在这个过程的早期使用全连接层。然而,如果使用了全连接层,可能会完全放弃表征的空间结构 网络 中的网络NiN)提供了一个非常简单的解决方案:在每个像素的通道上分别使用多层感知机。而这种方法,它使用1x1卷积层和全局平均池化层来替代全连接层,以提取更多的空间结构信息,并减少参数数量。

为什么全连接层没有空间结构信息?

        因为全连接层的神经元与上一层所有神经元都相连,所以无法保留输入数据在空间上的结构信息。这在图像识别中尤其不利,因为图像数据通常具有空间结构,例如图像中的相邻像素之间有空间关系。为了利用图像数据中的空间结构信息,卷积神经网络(CNN)引入了卷积层和池化层,这些层可以保留输入数据在空间上的结构信息。VGG网络将多个卷积层和池化层组成一个块,可以有效地提取输入数据中的特征。同时,使用块的结构可以使网络更深,因为可以通过重复多个块来增加网络深度,而不会过早地出现梯度消失或梯度爆炸问题。因此,VGG引入块的结构可以更好地利用输入数据中的空间结构,并使网络更深。(每个inception后加上最大3*3的汇聚层步幅为2是的高管减半。)

        全局平均池化的一个优点是,通过加强特征映射和类别之间的对应关系,它更适合于卷积结构。因此,特征图可以很容易地解释为类别置信度图。另一个优点是在全局平均池化中没有参数需要优化,从而避免了这一层的过拟合。此外,全局平均池化总结了空间信息,因此对输入的空间平移具有更强的鲁棒性。

全局平均汇聚层的思想:

        对于输出的每一个通道的特征图的所有像素计算一个平均值,经过全局平均池化之后就得到一个 维度=C_{in}=类别数 的特征向量,然后直接输入到softmax

        作用:代替全连接层,可接受任意尺寸的图像

优点:

        1)可以更好的将类别与最后一个卷积层的特征图对应起来(每一个通道对应一种类别,这样每一张特征图都可以看成是该类别对应的类别置信图)

        2)降低参数量,全局平均池化层没有参数,可防止在该层过拟合

        3)整合了全局空间信息,对于输入图片的spatial translation更加鲁棒

首先我们简单理解全局平均池化操作:
        如果有一批特征图,其尺寸为 [ B, C, H, W], 经过全局平均池化之后,尺寸变为[B, C, 1, 1]
也就是说,全局平均池化其实就是对每一个通道图所有像素值求平均值,然后得到一个新的1 * 1的通道图.具体实现 

        假设这个特征图是一个batch中的一张图片,它的shape是(1,30,5,5),其中1表示batch size,30表示通道数,5表示高,5表示宽。对每一个类别都进行全连接层输出,

计算量:

        全连接层的总参数量为30×5×5×num_outputs(类别数) + num_outputs(偏置)。

        全局平均汇聚层的计算量只与特征图的通道数有关,与高和宽无关。假设该特征图的通道数为C,那么全局平均汇聚层的计算量为C,因为它只需对每个通道执行一次平均汇聚操作即可。

        全局平均汇聚层相对于全连接层的计算量更小,因为它不需要进行大量的乘法和加法操作,而是直接对特征图进行平均操作,全连接层的计算量非常大,而且其中很多参数是冗余的,这些参数很难被充分利用,而且这些冗余的信息很有可能会造成过拟合现象。。同时,全局平均汇聚层还能够减少模型的参数量和计算量,因为它不需要像全连接层那样需要大量的权重参数和偏置参数,从而能够有效地减少过拟合的风险。

        此外,全局平均汇聚层相对于全连接层还具有更好的泛化能力。在全连接层中,每个神经元都与上一层的所有神经元相连,导致模型容易出现过拟合的情况。而全局平均汇聚层则通过对特征图进行平均操作,可以将空间信息压缩成一个点,从而能够更好地保留图像的整体特征,提高模型的泛化能力。

三:NiN块

         NIN是由mlpconv堆叠而成,顶部是GAP层,在mlpconv层之间加入下采样层。下图是有三层mlpconv层的NIN,每个mlpconv层里有3层的mlp。

        注意:

        不是每个 NiN 网络块的最后一层都需要做全局平均汇聚层,这取决于网络设计的具体情况。在原始的 NiN 论文中,作者在最后一个 NiN 网络块的每个卷积层后都接了一个全局平均汇聚层,并且没有使用全连接层。但是,在后续的一些改进中,一些研究人员可能会根据具体的任务需求在网络的最后一层加入全连接层,而不是使用全局平均汇聚层。因此,是否需要在 NiN 网络块的最后一层加入全局平均汇聚层取决于具体的网络设计。

  NIN是基于AlexNet 改进而来,因此卷积层的其它参数设置和alexnet类似 

下面是一块NiN的结构:

        NIN中的MLPConv层包含了多层的MLP,MLPConv中的“MLP”实际上指的是多个卷积层的堆叠,而不是全连接层。每个MLPConv层通常由一个1x1卷积层,一个3x3卷积层和一个1x1卷积层组成,其中1x1卷积层用于降低通道数和提高计算效率。因此,MLPConv层实际上是一种卷积神经网络的组合方式,而不是全连接层下面也有说到,NIN中没有全连接层,全局平均池化层代替了全连接层的作用。如下图所示。NIN块中的普通卷积层和1x1卷积层是用来提取特征的,而全局平均汇聚层是在整个NIN块结束后接在最后一层卷积层之后的一层。

          NiN块以一个普通卷积层开始,后面是两个1×1的卷积层。这两个1×1卷积层充当带有ReLU激活函数的逐像素全连接层。 第一层的卷积窗口形状通常由用户设置。 随后的卷积窗口形状固定为1×1。

import time

import torch
from torch import nn
from d2l import torch as d2l


def nin_block(in_channels, out_channels, kernel_size, strides, padding):
    return nn.Sequential(
        nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding),
        nn.ReLU(),#激活函数
        #因为是1*1的卷积层所以高宽也不便。通道数由1*1的个数决定
        nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),
        nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU())

        最初的NiN网络是在AlexNet后不久提出的,显然从中得到了一些启示。 NiN使用窗口形状为11×11、5×5和3×3的卷积层,输出通道数量与AlexNet中的相同。 每个NiN块后有一个最大汇聚层,汇聚窗口形状为3×3,步幅为2。

        NiN和AlexNet之间的一个显著区别是NiN完全取消了全连接层相反,NiN使用一个NiN块,其输出通道数等于标签类别的数量。最后放一个全局平均汇聚层(global average pooling layer),生成一个对数几率 (logits)NiN设计的一个优点是,它显著减少了模型所需参数的数量。然而,在实践中,这种设计有时会增加训练模型的时间。


net = nn.Sequential(
    nin_block(1, 96, kernel_size=11, strides=4, padding=0),
    nn.MaxPool2d(3, stride=2),
    nin_block(96, 256, kernel_size=5, strides=1, padding=2),
    nn.MaxPool2d(3, stride=2),
    nin_block(256, 384, kernel_size=3, strides=1, padding=1),
    nn.MaxPool2d(3, stride=2),
    nn.Dropout(0.5),
    # 标签类别数是10
    nin_block(384, 10, kernel_size=3, strides=1, padding=1),
    nn.AdaptiveAvgPool2d((1, 1)),#因为最后一个池化层不是MaxPool,所以没有把MaxPool放到模块构造函数里面
    # 将四维的输出转成二维的输出,其形状为(批量大小,10)
    nn.Flatten())

X = torch.rand(size=(1, 1, 224, 224))
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__,'output shape:\t', X.shape)

输出:
Sequential output shape:	 torch.Size([1, 96, 54, 54])
MaxPool2d output shape:	 torch.Size([1, 96, 26, 26])
Sequential output shape:	 torch.Size([1, 256, 26, 26])
MaxPool2d output shape:	 torch.Size([1, 256, 12, 12])
Sequential output shape:	 torch.Size([1, 384, 12, 12])
MaxPool2d output shape:	 torch.Size([1, 384, 5, 5])
Dropout output shape:	 torch.Size([1, 384, 5, 5])
Sequential output shape:	 torch.Size([1, 10, 5, 5])
AdaptiveAvgPool2d output shape:	 torch.Size([1, 10, 1, 1])
Flatten output shape:	 torch.Size([1, 10])

        和以前一样,我们使用Fashion-MNIST来训练模型。训练NiN与训练AlexNet、VGG时相似。

lr, num_epochs, batch_size = 0.01, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
start_time = time.time()
print(start_time)
print(d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu()))
end_time = time.time()
print("训练一共使用了{:.2f}分钟".format((end_time-start_time)/60))
d2l.plt.show()

        由此可以看出,当学习率过小的时候,学习率过小会导致模型收敛缓慢,甚至可能停滞不前,从而影响模型的性能和训练效率。

 将lr调整为0.1,重新观察结果:

四:小结

问:为什么NiN块中有两个或者多个1×1卷积层?

        NiN块中有两个卷积层的原因是为了提高模型的表达能力。第一个卷积层执行特征提取,第二个卷积层执行特征组合。删除其中一个卷积层可能会导致模型表达能力的下降。因为删除其中一个卷积层会减少网络的非线性变换能力,使得网络学习到的特征过于简单,难以区分不同类别之间的差异。

  • NiN使用由一个卷积层和多个1×1卷积层组成的块。该块可以在卷积神经网络中使用,以允许更多的每像素非线性。

  • NiN去除了容易造成过拟合的全连接层,将它们替换为全局平均汇聚层(即在所有位置上进行求和)。该汇聚层通道数量为所需的输出数量(例如,Fashion-MNIST的输出为10)。

  • 移除全连接层可减少过拟合,同时显著减少NiN的参数。

  • NiN的设计影响了许多后续卷积神经网络的设计。

五: 含并行连结的网络(GoogLeNet)

        在2014年的ImageNet图像识别挑战赛中,一个名叫GoogLeNet (2015)的网络架构大放异彩。 GoogLeNet吸收了NiN中串联网络的思想,并在此基础上做了改进。 这篇论文的一个重点是解决了什么样大小的卷积核最合适的问题。 毕竟,以前流行的网络使用小到1×1,大到11×11的卷积核。 本文的一个观点是,有时使用不同大小的卷积核组合是有利的。 本节将介绍一个稍微简化的GoogLeNet版本:我们省略了一些为稳定训练而添加的特殊特性,现在有了更好的训练方法,这些特性不是必要的。

 5.1.Inception块

        在GoogLeNet中,基本的卷积块被称为Inception块(Inception block)。这很可能得名于电影《盗梦空间》(Inception),因为电影中的一句话“我们需要走得更深”(“We need to go deeper”)。  

        NIN块中的普通卷积层和1x1卷积层是用来提取特征的,而全局平均汇聚层是在整个NIN块结束后接在最后一层卷积层之后的一层。全局平均汇聚层的作用是对整个特征图进行汇聚操作,将每个通道上的特征值取平均,最终输出每个通道的平均值,从而降低特征图的维度,减少参数量和计算量。而Inception块则是在NIN块的基础上进一步发展而来的因为GoogleNet用了很多NiN的思想,大量地使用1x1卷积减少参数量。GoogLeNet使用了全局平均汇聚层而不使用全连接层。在GoogLeNet的最后一个Inception块之后,输出的特征图会经过一个全局平均汇聚层,将每个通道上的特征值取平均,最终输出每个通道的平均值。它包含了多个不同大小和不同种类的卷积核,并采用了并行连接的方式来提取特征,最后通过拼接的方式将不同的特征组合在一起,形成一个更加丰富和多样化的特征表达。

                                                          图1  Inception块的架构。

        图1所示,Inception块由四条并行路径组成。前三条路径使用窗口大小为1×1、3×3和5×5的卷积层,从不同空间大小中提取信息。 中间的两条路径在输入上执行1×1卷积,以减少通道数,从而降低模型的复杂性。 第四条路径使用3×3最大汇聚层,然后使用1×1卷积层来改变通道数。 这四条路径都使用合适的填充来使输入与输出的高和宽一致最后我们将每条线路的输出在通道维度上连结,并构成Inception块的输出(c_1+c_2+c_3+c_4。在Inception块中,通常调整的超参数是每层输出通道数。(Inception模块包括了多个不同尺寸的卷积核和池化层,并行地进行特征提取,从而提高了网络的性能。)。

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

class Inception(nn.Module):
    # c1--c4是每条路径的输出通道数
    def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):
        super(Inception, self).__init__(**kwargs)
        # 线路1,单1x1卷积层
        self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
        # 线路2,1x1卷积层后接3x3卷积层
        self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)
        #c2[0]是上一次的输出,c2[1]是这一次的输入
        self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
        # 线路3,1x1卷积层后接5x5卷积层
        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,3x3最大汇聚层后接1x1卷积层
        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这个网络如此有效呢? 首先我们考虑一下滤波器(filter)的组合,它们可以用各种滤波器尺寸探索图像,这意味着不同大小的滤波器可以有效地识别不同范围的图像细节。 同时,我们可以为不同的滤波器分配不同数量的参数。

5.2.GoogLeNet模型

        如图2所示,GoogLeNet一共使用9个Inception块和全局平均汇聚层的堆叠来生成其估计值。Inception块之间的最大汇聚层可降低维度。 第一个模块类似于AlexNet和LeNet,Inception块的组合从VGG继承,全局平均汇聚层避免了在最后使用全连接层。

                                                             图2 GoogleNet架构

现在,我们逐一实现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.ReLU(),
                   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(因为是在dim=0上拼接,所以维度是通道数加起来的总和),四个路径之间的输出通道数量比为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))

        第四模块更加复杂, 它串联了5个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))

        第五模块包含输出通道数为256+320+128+128=832和384+384+128+128=1024的两个Inception块。 其中每条路径通道数的分配思路和第三、第四模块中的一致,只是在具体数值上有所不同。 需要注意的是,第五模块的后面紧跟输出层,该模块同NiN一样使用全局平均汇聚层,将每个通道的高和宽变成1。 最后我们将输出变成二维数组,再接上一个输出个数为标签类别数的全连接层。

b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),
                   Inception(832, 384, (192, 384), (48, 128), 128),
                   nn.AdaptiveAvgPool2d((1,1)),
                   nn.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)

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

lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
print(d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu()))
d2l.plt.show()

六:小结

  • Inception块相当于一个有4条路径的子网络。它通过不同窗口形状的卷积层和最大汇聚层来并行抽取信息并使用1×1卷积层减少每像素级别上的通道维数从而降低模型复杂度

  • GoogLeNet将多个设计精细的Inception块与其他层(卷积层、全连接层)串联起来。其中Inception块的通道数分配之比是在ImageNet数据集上通过大量的实验得来的。

  • GoogLeNet和它的后继者们一度是ImageNet上最有效的模型之一:它以较低的计算复杂度提供了类似的测试精度。

所有项目代码+UI界面

视频,笔记和代码,以及注释都已经上传网盘,放在主页置顶文章

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值