GoogLeNet(含并行连接的网络)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GoogLeNet(含并行连接的网络)相关的知识,希望对你有一定的参考价值。

参考技术A         GoogLeNet吸收了NiN中网络串联网络的思想,并在此基础上做了很大改进。在随后的几年里,研究人员对GoogLeNet进行了数次改进。

Inception 块

        GoogLeNet中的基础卷积块叫作Inception块,得名于同名电影《盗梦空间》(Inception)。与上一节介绍的NiN块相比,这个基础块在结构上更加复杂,如图所示。

        Inception块里有4条并行的线路。前3条线路使用窗口大小分别是1×1、3×3和5×5的卷积层来抽取不同空间尺寸下的信息,其中中间2个线路会对输入先做1×11\times 11×1卷积来减少输入通道数,以降低模型复杂度。第四条线路则使用3×33\times 33×3最大池化层,后接1×11\times 11×1卷积层来改变通道数。4条线路都使用了合适的填充来使输入与输出的高和宽一致。最后我们将每条线路的输出在通道维上连结,并输入接下来的层中去。

GoogLeNet模型

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

第一模块使用一个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))

        第二模块使用2个卷积层:首先是64通道的1×1卷积层,然后是将通道增大3倍的3×3卷积层。它对应Inception块中的第二条线路。

        b2=nn.Sequential(

                nn.Conv2d(64,64,kernel_size=1),

                nn.Conv2d(64,192,kernel_size=3,padding=1),

                nn.MaxPool2d(kernel_size=3,stride=2,padding=1))

第三模块串联2个完整的Inception块。

    第一个Inception块的输出通道数为64+128+32+32=256,其中4条线路的输出通道数比例为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))

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

                d2l.GlobalAvgPool2d())

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

GoogLeNet模型的计算复杂,而且不如VGG那样便于修改通道数。本节里我们将输入的高和宽从224降到96来简化计算。下面演示各个模块之间的输出的形状变化。

小结

        Inception块相当于一个有4条线路的子网络。它通过不同窗口形状的卷积层和最大池化层来 并行抽取信息 ,并使用1×1卷积层减少通道数从而降低模型复杂度。

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

        GoogLeNet和它的后继者们一度是ImageNet上最高效的模型之一:在类似的测试精度下,它们的计算复杂度往往更低。

原文地址:http://tangshusen.me/Dive-into-DL-PyTorch/#/chapter05_CNN/5.10_batch-norm

Pytorch Note30 更加丰富化结构的网络 GoogLeNet

Pytorch Note30 更加丰富化结构的网络 GoogLeNet


全部笔记的汇总贴: Pytorch Note 快乐星球

GoogLeNet

VGG 是 2014 年 ImageNet 比赛的亚军,那么冠军是谁呢?就是我们马上要讲的 GoogLeNet,这是 Google 的研究人员提出的网络结构,在当时取得了非常大的影响,因为网络的结构变得前所未有,它颠覆了大家对卷积网络的串联的印象和固定做法,采用了一种非常有效的 inception 模块,得到了比 VGG 更深的网络结构,但是却比 VGG 的参数更少,因为其去掉了后面的全连接层,所以参数大大减少,同时有了很高的计算效率。如果想详细了解并查看论文,可以看我的另一篇博客【论文泛读】 GooLeNet:更深的卷积网络

这是 googlenet 的网络示意图,下面我们介绍一下其作为创新的 inception 模块。

Inception 模块

在上面的网络中,我们看到了多个四个并行卷积的层,这些四个卷积并行的层就是 inception 模块,可视化如下

一个 inception 模块的四个并行线路如下:
1.一个 1 x 1 的卷积,一个小的感受野进行卷积提取特征
2.一个 1 x 1 的卷积加上一个 3 x 3 的卷积,1 x 1 的卷积降低输入的特征通道,减少参数计算量,然后接一个 3 x 3 的卷积做一个较大感受野的卷积
3.一个 1 x 1 的卷积加上一个 5 x 5 的卷积,作用和第二个一样
4.一个 3 x 3 的最大池化加上 1 x 1 的卷积,最大池化改变输入的特征排列,1 x 1 的卷积进行特征提取

最后将四个并行线路得到的特征在通道这个维度上拼接在一起,下面我们可以实现一下

代码实现

定义基本层结构

定义一个卷积加一个 relu 激活函数和一个 batchnorm 作为一个基本的层结构

class BasicConv2d(nn.Module):
    def __init__(self, in_channels, out_channels, **kwargs):
        super(BasicConv2d, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, **kwargs)
        self.batch = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        x = self.conv(x)
        x = self.batch(x)
        x = self.relu(x)
        return x

定义Inception

class inception(nn.Module):
    def __init__(self, in_channel, out1_1, out2_1, out2_3, out3_1, out3_5, out4_1):
        super(inception, self).__init__()
        # 第一条线路
        self.branch1x1 = conv_relu(in_channel, out1_1, 1)
        
        # 第二条线路
        self.branch3x3 = nn.Sequential( 
            conv_relu(in_channel, out2_1, 1),
            conv_relu(out2_1, out2_3, 3, padding=1)
        )
        
        # 第三条线路
        self.branch5x5 = nn.Sequential(
            conv_relu(in_channel, out3_1, 1),
            conv_relu(out3_1, out3_5, 5, padding=2)
        )
        
        # 第四条线路
        self.branch_pool = nn.Sequential(
            nn.MaxPool2d(3, stride=1, padding=1),
            conv_relu(in_channel, out4_1, 1)
        )
        
    def forward(self, x):
        f1 = self.branch1x1(x)
        f2 = self.branch3x3(x)
        f3 = self.branch5x5(x)
        f4 = self.branch_pool(x)
        output = torch.cat((f1, f2, f3, f4), dim=1)
        return output

定义辅助分类器Aux

class InceptionAux(nn.Module):
    def __init__(self, in_channels, num_classes):
        super(InceptionAux, self).__init__()
        self.averagePool = nn.AvgPool2d(kernel_size=5, stride=3)
        self.conv = BasicConv2d(in_channels, 128, kernel_size=1)  # output[batch, 128, 4, 4]

        self.fc1 = nn.Linear(2048, 1024)
        self.fc2 = nn.Linear(1024, num_classes)

    def forward(self, x):

        x = self.averagePool(x)
        x = self.conv(x)
        x = torch.flatten(x, 1)
        x = F.dropout(x, 0.5, training=self.training)
        x = F.relu(self.fc1(x), inplace=True)
        x = F.dropout(x, 0.5, training=self.training)
        x = self.fc2(x)
        return x

定义GoogLetNet

class GoogLeNet(nn.Module):
    def __init__(self, num_classes=1000, aux_logits=True, init_weights=False):
        super(GoogLeNet, self).__init__()
        self.aux_logits = aux_logits

        self.conv1 = BasicConv2d(3, 64, kernel_size=7, stride=2, padding=3)
        self.maxpool1 = nn.MaxPool2d(3, stride=2, ceil_mode=True)

        self.conv2 = BasicConv2d(64, 64, kernel_size=1)
        self.conv3 = BasicConv2d(64, 192, kernel_size=3, padding=1)
        self.maxpool2 = nn.MaxPool2d(3, stride=2, ceil_mode=True)

        self.inception3a = Inception(192, 64, 96, 128, 16, 32, 32)
        self.inception3b = Inception(256, 128, 128, 192, 32, 96, 64)
        self.maxpool3 = nn.MaxPool2d(3, stride=2, ceil_mode=True)

        self.inception4a = Inception(480, 192, 96, 208, 16, 48, 64)
        self.inception4b = Inception(512, 160, 112, 224, 24, 64, 64)
        self.inception4c = Inception(512, 128, 128, 256, 24, 64, 64)
        self.inception4d = Inception(512, 112, 144, 288, 32, 64, 64)
        self.inception4e = Inception(528, 256, 160, 320, 32, 128, 128)
        self.maxpool4 = nn.MaxPool2d(3, stride=2, ceil_mode=True)

        self.inception5a = Inception(832, 256, 160, 320, 32, 128, 128)
        self.inception5b = Inception(832, 384, 192, 384, 48, 128, 128)

        if self.aux_logits:
            self.aux1 = InceptionAux(512, num_classes)
            self.aux2 = InceptionAux(528, num_classes)

        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.dropout = nn.Dropout(0.4)
        self.fc = nn.Linear(1024, num_classes)
        if init_weights:
            self._initialize_weights()

    def forward(self, x):
        x = self.conv1(x)
        x = self.maxpool1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = self.maxpool2(x)

        x = self.inception3a(x)
        x = self.inception3b(x)
        x = self.maxpool3(x)
        x = self.inception4a(x)
        if self.training and self.aux_logits:    # eval model lose this layer
            aux1 = self.aux1(x)

        x = self.inception4b(x)
        x = self.inception4c(x)
        x = self.inception4d(x)
        if self.training and self.aux_logits:    # eval model lose this layer
            aux2 = self.aux2(x)

        x = self.inception4e(x)
        x = self.maxpool4(x)
        x = self.inception5a(x)
        x = self.inception5b(x)

        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.dropout(x)
        x = self.fc(x)
        if self.training and self.aux_logits:   # eval model lose this layer
            return x, aux2, aux1
        return x

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)

其实随着时间的发展,Inception已经出现了多个版本,但是我们这里是最原始的Inceptionv1版本

  • v1:最早的版本
  • v2:加入 batch normalization 加快训练
  • v3:对 inception 模块做了调整
  • v4:基于 ResNet 加入了 残差连接

如果想更加了解,可以持续关注我的博客

以上是关于GoogLeNet(含并行连接的网络)的主要内容,如果未能解决你的问题,请参考以下文章

GoogLeNet

现代卷积神经网络(GoogleNet),并使用GoogleNet进行实战CIFAR10分类

VGGNet随笔

GoogLeNet 与 Inception

卷积神经网络Inception Net

解读(GoogLeNet)Going deeper with convolutions