Pytorch——Inception结构

178 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情


前言

上一篇文章中,我们介绍了如何去定义一个ResNet网络,如何去定义一个ResNet的基本单元以及和另一种非常经典的神经网络结构——Mobilenet。

今天,我们来介绍另一种非常重要的网络结构——Inception.


  • 1.1 Inception介绍

Inception网络结构是由google提出并且应用在了Inception v1、Inception v2 和 Inception v3、Inception v4等一系列需要去加宽网络宽度,进而提高模型性能的网络结构中。

  • 1.2 搭建Inception的模块

Inception这样的模块,从本质上看,相比于ResNet而言,多了更多的分支,在这些更多的分支上,分别进行了不同的卷积核的操作。

在实际设计模型的时候,基本上有两种方式,一种是套用现成的网络结构,另外一种就是需要去针对现成的模型结构去做一些剪枝,这些模型剪枝的时候就会对网络结构进行修改

对于InceptionNet的网络结构,最重要的是模块就是Inception,这个模块可以理解成一个网中网的结构,或者可以理解成多个分支并联的结构

input: A

B1 = f1(A)

B2 = f2(A)

B3 = f3(A)

  • resnet的结构:

    • B = g(A) + f(A)
    • 为了保证A和f(A)分特征图大小是一致的,可能需要对A进行卷积运算。
  • Inception的结构:

    • concat([B1, B2, B3]),
    • 在Inception中,为了节省计算量,
    • 会引入对卷积核的拆分,比如说,我们在使用5x5,7x7的卷积核的时候,
    • 在inceptionv2中一个5x5的卷积核会拆成两个3x3的卷积核的串联
    • 一个7x7的卷积核会拆成三个3x3的卷积核的串联
    • 在inceptionv3中会将卷积核进一步拆成1x3和3x1的卷积核,这个时候就能够压缩计算量

定义Inception这种结构单元,还是比较复杂的,因为要写很多并行的代码,就比较麻烦,而且并行化在芯片实现的时候比较麻烦,因为branch的分支很多,所以,在芯片里,我们使用比较多的还是ResNet这种比较干净的网络结构和MobileNet这种比较轻量型的网络结构;当然在服务端跑这种比较大型的网络结构是可以考虑Inception这种网络结构 的。它的效果还是不错的。

每一个网络我们是可以把它切成几个模块的,我们在读网络,尤其是在一些大型的网络结构的时候,我们一般是把这个大型的网络结构分成几个**模块**,每个模块又有几个小的**基本单元**来组成,通常将一个模块称之为**block**。
import torch
import torch.nn as nn

def ConvBNRelu(in_channel, out_channel, kernel_size):
    return nn.Sequential(
        nn.Conv2d(in_channel, out_channel,
                  kernel_size=kernel_size,
                  stride=1,
                  padding=kernel_size // 2),
        nn.BatchNorm2d(out_channel),
        nn.ReLU()
    )


class BaseInception(nn.Module):
    def __init__(self,
                 in_channel,
                 out_channel_list,
                 reduce_channel_list):
        super(BaseInception, self).__init__()

        self.branch1_conv = ConvBNRelu(in_channel,
                                  out_channel_list[0],
                                  1)

        self.branch2_conv1 = ConvBNRelu(in_channel,
                                    reduce_channel_list[0],
                                    1)
        self.branch2_conv2 = ConvBNRelu(reduce_channel_list[0],
                                        out_channel_list[1],
                                        3)

        self.branch3_conv1 = ConvBNRelu(in_channel,
                                        reduce_channel_list[1],
                                        1)
        self.branch3_conv2 = ConvBNRelu(reduce_channel_list[1],
                                        out_channel_list[2],
                                        5)

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

        self.branch4_conv = ConvBNRelu(in_channel,
                                       out_channel_list[3],
                                       3)
    def forward(self, x):
        out1 = self.branch1_conv(x)

        out2 = self.branch2_conv1(x)
        out2 = self.branch2_conv2(out2)

        out3 = self.branch3_conv1(x)
        out3 = self.branch3_conv2(out3)

        out4 = self.branch4_pool(x)
        out4 = self.branch4_conv(out4)

        out = torch.cat([out1, out2, out3, out4], dim=1)
        return out
class InceptionNet(nn.Module):
    def __init__(self):
        super(InceptionNet, self).__init__()
        self.block1 = nn.Sequential(
            nn.Conv2d(3, 64,
                      kernel_size=7,
                      stride=2,
                      padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU()
        )

        self.block2 = nn.Sequential(
            nn.Conv2d(64, 128,
                      kernel_size=3,
                      stride=2,
                      padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
        )

        self.block3 = nn.Sequential(
            BaseInception(in_channel=128,
                          out_channel_list=[64, 64,
                                            64, 64],
                          reduce_channel_list=[16, 16]),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        )

        self.block4 = nn.Sequential(
            BaseInception(in_channel=256,
                          out_channel_list=[96, 96,
                                            96, 96],
                          reduce_channel_list=[32, 32]),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        )

        self.fc = nn.Linear(384, 10)

    def forward(self, x):
        out = self.block1(x)
        out = self.block2(out)
        out = self.block3(out)
        out = self.block4(out)
        out = torch.nn.functional.avg_pool2d(out, 2)
        out = out.view(out.size(0), -1)
        out = self.fc(out)
        return out

def InceptionNetSmall():
    return InceptionNet()

注:cifar10数据训练的代码参考我之前的文章Pytorch——Cifar10图像分类中的训练模型的代码,只需要修改一下net即可。

  • 这个网络是可以收敛到80%多的,因为我们的网络结构定义的还是比较简单的,相比于GoogLeNet而言,我们裁剪掉了很多东西,所以,我们在学习新的网络结构的时候,还是去抓住它的核心理念,它的核心的地方在哪。

    • VGGNet主要是一个串联的结构,定义的时候主要采用3x3的小卷积核来进行卷积运算;

    • ResNet主要是一个跳连的结构;

    • InceptionNet主要是增加网络的宽度,采用不同的分支去增加网络的宽度,另外就是采用1x1的卷积核去压缩计算量,

这种采用1x1的卷积核去压缩计算量的方式在设计ResNetv1,ResNetv2的时候也被用到了,先对标准的结构进行channel的压缩,压缩之后在用3x3的卷积核进行运算,这样压缩了计算量,同时加大了网络的深度,由一层网络变成了两层,网络的非线性表达能力也会有所提升,相比于直接采用卷积核来做,在计算量上有了节省,而且在性能上基本上没什么损失的。

9JQ4ZCQY3M({Q$KEN%9BFQX.png