《动手学深度学习》第二十一天---含并行连结的网络(GoogLeNet)

(一)GoogLeNet简介

在2014年的ImageNet图像识别挑战赛中,一个名叫GoogLeNet的网络结构大放异彩。该模型并不是单纯的将网络加深,而是通过引入Inception的概念。通过多个卷积提取图像不同尺度的信息,最后进行融合,从而更好的表征图像。

(二)Inception结构

Inception 结构的主要思路是怎样用密集成分来近似最优的局部稀疏结构。
它的贡献主要有两个:一是使用1×1的卷积来进行升降维,二是在多个尺寸上同时进行卷积再聚合。
在这里插入图片描述
解读上面的结构:

  1. 采用不同大小的卷积核意味着不同大小的感受野,最后的拼接意味着不同尺度特征的融合
  2. 之所以卷积核大小为1,3和5,是因为可以通过设置padding=0,1,2来让卷积之后的特征维度相等,便于拼接。
  3. 随着层数增加,卷积核的感受野增加,这样3×3和5×5的卷积核会带来巨大的计算量。,因此文章借鉴了NiN采用1×1卷积核来降维。比如同样是输入一组有192个特征,32×32大小,输出256组特征的数据,直接采用3×3卷积,卷积层的参数就是192×256×3×3;若是先用96个输出1×1卷积核降维,再用3×3卷积恢复256组特征,需要192×96×1×1+96×256×3×3,参数量大大减少。此外,1×1卷积通过跨通道信息交互,可以在相同尺寸的感受野中提取更强的非线性(每个卷积层后都紧跟着激活函数)。
  4. Inception既能保持网络结构的稀疏性,又能利用密集矩阵的高计算性能。多个尺寸上进行卷积再聚合可以将稀疏矩阵聚类为较为密集的子矩阵来提高计算性能。具体是怎么实现的呢?
    传统卷积层是输入数据只和一种尺度卷积核卷积(如3×3),输出固定维度数据(256个特征),所有输出特征(256)基本上是均匀分布在尺寸上的(3×3)。但是inception把在多个尺度上面提取特征,输出的特征就不再是均匀分布,而是相关性强的聚集起来(比如1×1的96个特征聚集,5×5的64个特征聚集),这可以理解成多个密集分布的子特征集。这样同样输出256个特征时,inception就可以把相关性强的特征聚集起来,非关键特征被弱化,输出的“冗余”信息较少。
    用这样“纯”的特征集层层传递,最后作为反向计算的输出,自然收敛速度较快。
    如下图:稀疏矩阵分解成密集矩阵加快了计算速度
    稀疏矩阵分解成密集

(三)GoogLeNet简单实现

(1)Inception块
根据上面的Inception块的结构,我们知道有四个线路,Inception块中可以自定义的超参数是每个层的输出通道数,我们以此来控制模型复杂度。

import d2lzh as d2l
from mxnet import gluon, init, nd
from mxnet.gluon import nn

class Inception(nn.Block):
    # c1 - c4为每条线路里的层的输出通道数
    def __init__(self, c1, c2, c3, c4, **kwargs):
        super(Inception, self).__init__(**kwargs)
        # 线路1,单1 x 1卷积层
        self.p1_1 = nn.Conv2D(c1, kernel_size=1, activation='relu')
        # 线路2,1 x 1卷积层后接3 x 3卷积层,通过padding调整使图像输出大小都是相等的
        self.p2_1 = nn.Conv2D(c2[0], kernel_size=1, activation='relu')
        self.p2_2 = nn.Conv2D(c2[1], kernel_size=3, padding=1,
                              activation='relu')
        # 线路3,1 x 1卷积层后接5 x 5卷积层
        self.p3_1 = nn.Conv2D(c3[0], kernel_size=1, activation='relu')
        self.p3_2 = nn.Conv2D(c3[1], kernel_size=5, padding=2,
                              activation='relu')
        # 线路4,3 x 3最大池化层后接1 x 1卷积层
        self.p4_1 = nn.MaxPool2D(pool_size=3, strides=1, padding=1)
        self.p4_2 = nn.Conv2D(c4, kernel_size=1, activation='relu')

    def forward(self, x):
        p1 = self.p1_1(x)
        p2 = self.p2_2(self.p2_1(x))
        p3 = self.p3_2(self.p3_1(x))
        p4 = self.p4_2(self.p4_1(x))
        return nd.concat(p1, p2, p3, p4, dim=1)  # 在通道维上连结输出

(2)GoogLeNet模型

GoogLeNet跟VGG一样,在主体卷积部分中使用5个模块(block),每个模块之间使用步幅为2的3×3 最大池化层来减小输出高宽。

b1 = nn.Sequential()  #  第一个模块,采用7×7的卷积核,步幅为2,长宽均为各侧填充3
b1.add(nn.Conv2D(64, kernel_size=7, strides=2, padding=3, activation='relu'),
            #Conv2D默认使用的是小于的最大值(floor)
            nn.MaxPool2D(pool_size=3, strides=2, padding=1))   
            #  MaxPool2D默认采用小于的最大值(floor)

在这里插入图片描述
当输入为1×96×96时,第一模块卷积层输出为
64×{(96-7+2+3×2)/2}×{(96-7+2+3×2)/2}=64×48×48,第一模块最大池化层输出为
64×{(48-3+2+1×2)/2}×{(48-3+2+1×2)/2}=64×24×24

 b2 = nn.Sequential()   #  第二个模块,采用一个1×1的卷积层,后接激活函数,再采用一个3×3的卷积层,同样后面接激活函数。它对应Inception块中的第二条线路。
 b2.add(nn.Conv2D(64, kernel_size=1, activation='relu'),  #  1×1卷积核用于降维,减少参数
           nn.Conv2D(192, kernel_size=3, padding=1, activation='relu'),
           nn.MaxPool2D(pool_size=3, strides=2, padding=1))

在这里插入图片描述
对于第一模块的输出64×24×24作为第二模块的输入,1×1的卷积核输出为64×24×24,
3×3的卷积核输出为192×(24-3+1+1×2)×(24-3+1+1×2)=192×24×24
最大池化层的输出为192×{(24-3+2+1×2)/2}×{(24-3+2+1×2)/2}=192×12×12

b3 = nn.Sequential()   #第三个模块由两个Inception块和一个最大池化层组成
b3.add(Inception(64, (96, 128), (16, 32), 32),
# 第一个输出通道数为64+128+32+32=256,其中第二三条线路分别把通道压缩了96/192,16/192后再到第二个卷积核。
       Inception(128, (128, 192), (32, 96), 64),
#第二个输出通道数为128+192+96+64=480,其中第二三条线路分别把通道压缩了128/256,32/256后再到第二个卷积核。
       nn.MaxPool2D(pool_size=3, strides=2, padding=1))

在这里插入图片描述
通道由输入的192到第二个Inception输出时变为了480,图像大小由输入的12×12,到第二个Inception输出时变为仍然为12×12,但是经过最大池化层后变为6×6,所以第二模块输出为480×6×6

b4 = nn.Sequential()    #  第四个模块由四个Inception块和最大池化层构成
b4.add(Inception(192, (96, 208), (16, 48), 64),
       Inception(160, (112, 224), (24, 64), 64),
       Inception(128, (128, 256), (24, 64), 64),
       Inception(112, (144, 288), (32, 64), 64),
       Inception(256, (160, 320), (32, 128), 128),
       nn.MaxPool2D(pool_size=3, strides=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块中都略有不同。
在这里插入图片描述
由上面的分析也可以得到输出通道为832,并且由于输入第四模块的图像为6×6,经过Inception不变,经过最大池化层减半,所以第四层输出为832×3×3

b5 = nn.Sequential()
b5.add(Inception(256, (160, 320), (32, 128), 128),
       Inception(384, (192, 384), (48, 128), 128),
       nn.GlobalAvgPool2D())   #  使用全局平均池化层来将每个通道的高和宽变成1

net = nn.Sequential()
net.add(b1, b2, b3, b4, b5, nn.Dense(10))   #  接上一个输出个数为标签类别数的全连接层。

第五模块有输出通道数为256+320+128+128=832和384+384+128+128=1024的两个Inception块。通道分配思路同第四块。
在这里插入图片描述由于最后利用全局平均池把输出的高宽变成1,所以第五个模块输出为1024×1×1。
最后输出标签为10,所以输出为10×1

X = nd.random.uniform(shape=(1, 1, 96, 96))
net.initialize()
for layer in net:
    X = layer(X)
    print(layer.name, 'output shape:\t', X.shape)

在这里插入图片描述
这里附上完整的结构图:
图中有两个辅助的softmax分支,作用有两个:一是避免梯度消失,用于向前传导梯度。反向传播时要是有一层求导为0,链式结果为0。二是将中间某一层输出作用分类,起到模型融合作用—loss=loss_2+0.3×loss_1+0.3×loss_0。测试时辅助分支会被去掉。
在这里插入图片描述(3)获取数据和训练模型

lr, num_epochs, batch_size, ctx = 0.1, 5, 128, d2l.try_gpu()
net.initialize(force_reinit=True, ctx=ctx, init=init.Xavier())
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': lr})
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch5(net, train_iter, test_iter, batch_size, trainer, ctx,
              num_epochs)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值