InceptionV3代码实现(Pytorch)

Inception介绍

Inception网络是CNN发展史上一个重要的里程碑。在Inception出现之前,大部分流行CNN仅仅是把卷积层堆叠得越来越多,使网络越来越深,以此希望能够得到更好的性能。但是存在以下问题:

  1. 图像中突出部分的大小差别很大。
  2. 由于信息位置的巨大差异,为卷积操作选择合适的卷积核大小就比较困难。信息分布更全
    局性的图像偏好较大的卷积核,信息分布比较局部的图像偏好较小的卷积核。
  3. 非常深的网络更容易过拟合。将梯度更新传输到整个网络是很困难的。
  4. 简单地堆叠较大的卷积层非常消耗计算资源。

Inception module
解决方案:
为什么不在同一层级上运行具备多个尺寸的滤波器呢?网络本质上会变得稍微「宽一些」,而不是「更深」。作者因此设计了Inception 模块。
Inception模块( Inception module) : 它使用3个不同大小的滤波器(1x1、 3x3、 5x5)对输入执行卷积操作,此外它还会执行最大池化。所有子层的输出最后会被级联起来,并传送至下一个Inception模块。

  • 方面增加了网络的宽度,另一方面增加了网络对尺度的适应性
    在这里插入图片描述

实现降维的Inception模块:如前所述,深度神经网络需要耗费大量计算资源。为了降低算力成
本,作者在3x3和5x5卷积层之前添加额外的1x1卷积层,来限制输入通道的数量。尽管添加额
外的卷积操作似乎是反直觉的,但是1x1卷积比5x5卷积要廉价很多,而且输入通道数量减少也
有利于降低算力成本。
在这里插入图片描述
InceptionV1–Googlenet

  1. Googl eNet采用了Inception模块化(9个)的结构,共22层;
  2. 为了避免梯度消失,网络额外增加了2个辅助的softmax用于向前传导梯度(只用于训练)。

Inception V2在输入的时候增加了BatchNormalization:
所有输出保证在0~1之间。

  • 所有输出数据的均值接近0,标准差接近1的正太分布。使其落入激活函数的敏感区,避免梯度消失,加快收敛。
  • 加快模型收敛速度,并且具有-定的泛化能力。
  • 可以减少dropout的使用。
    在这里插入图片描述
    在这里插入图片描述
  • 作者提出可以用2个连续的3x3卷积层(stride= 1)组成的小网络来代替单个的5x5卷积层,这便是Inception V2结构。
  • 5x5卷积核参数是3x3卷积核的25/9=2.78倍。
    在这里插入图片描述
    InceptionV2
  • 此外,作者将 n * n的卷积核尺寸分解为 1 * n 和 n * 1 两个卷积
    在这里插入图片描述
  • 并联比串联计算效率要高
  • 前面三个原则用来构建三种不同类型的 Inception 模块
    在这里插入图片描述

InceptionV3-网络结构图

  • InceptionV3整合了前面Inception v2中提到的所有升级,还使用了7x7卷积
    在这里插入图片描述
  • 目前,InceptionV3是最常用的网络模型

Inception V3设计思想和Trick:
(1) 分解成小卷积很有效,可以降低参数量,减轻过拟合,增加网络非线性的表达能力。
(2) 卷积网络从输入到输出,应该让图片尺寸逐渐减小,输出通道数逐渐增加,即让空间结
构化,将空间信息转化为高阶抽象的特征信息。
(3) InceptionModule用多个分支提取不同抽象程度的高阶特征的思路很有效,可以丰富网络
的表达能力

InceptionV4
在这里插入图片描述

  1. 左图是基本的Inception v2/v3模块,使用两个3x3卷积代替5x5卷积,并且使用average pooling,该模
    块主要处理尺寸为35x35的feature map;
  2. 中图模块使用1xn和nx1卷积代替nxn卷积,同样使用average pooling,该模块主要处理尺寸为17x17
    的feature map;
  3. 右图将3x3卷积用1x3卷积和3x1卷积代替。

总的来说,Inception v4中基本的Inception module还是沿袭了Inception v2/v3的结构,只是结构看起来更加简洁统一,并且使用更多的Inception modules实验效果也更好。
在这里插入图片描述
Inception模型优势:

  • 采用了1x1卷积核,性价比高,用很少的计算量既可以增加一层的特征变换和非线性变换。
  • 提出Batch Normalization,通过一定的手段,把每层神经元的输入值分布拉到均值0方差1的正态分布,使其落入激活函数的敏感区,避免梯度消失,加快收敛。
  • 引入Inception module, 4个分支结合的结构。
    卷积神经网络迁移学习
  • 现在在工程中最为常用的还是vgg、 resnet、 inception这几种结构, 设计者通常会先直接套用原版的模型对数据进行训练一次,然后选择效果较为好的模型进行微调与模型缩减。
  • 工程上使用的模型必须在精度高的同时速度要快。
  • 常用的模型缩减的方法是减少卷积的个数与减少resnet的模块数。

InceptionV3代码实现

第一个示例参考文章:

原文链接:GoogLeNet InceptionV3代码复现+超详细注释(PyTorch)
感谢大佬!

第一步:定义基础卷积模块

BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)

作用:卷积层之后总会添加BatchNorm2d进行数据的归一化处理,这使得数据在进行Relu之前不会因为数据过大而导致网络性能的不稳定

  1. num_features:一般输入参数的shape为batch_size * num_features * height*width,即为其中特征的数量,即为输入BN层的通道数;
  2. eps:分母中添加的一个值,目的是为了计算的稳定性,默认为:1e-5,避免分母为0;
  3. momentum:一个用于运行过程中均值和方差的一个估计参数(可以理解是一个稳定系数,类似于SGD中的momentum的系数);
  4. affine:当设为true时,会给定可以学习的系数矩阵gamma和beta
class BasicConv2d(nn.Module):
 
    def __init__(self, in_channels, out_channels, **kwargs):
        super(BasicConv2d, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, bias=False, **kwargs)
        self.bn = nn.BatchNorm2d(out_channels, eps=0.001)
       
    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        return F.relu(x, inplace=True)

第二步:定义Inceptionv3模块

PyTorch提供的有六种基本的Inception模块,分别是InceptionA——InceptionE。

InceptionA

InceptionA
得到输入大小不变,通道数为224+pool_features的特征图。

假如输入为(35, 35, 192)的数据:

第一个branch:
经过branch1x1为带有64个11的卷积核,所以生成第一张特征图(35, 35, 64);
第二个branch:
首先经过branch5x5_1为带有48个1
1的卷积核,所以第二张特征图(35, 35, 48),
然后经过branch5x5_2为带有64个55大小且填充为2的卷积核,特征图大小依旧不变,因此第二张特征图最终为(35, 35, 64);
第三个branch:
首先经过branch3x3dbl_1为带有64个1
1的卷积核,所以第三张特征图(35, 35, 64),
然后经过branch3x3dbl_2为带有96个33大小且填充为1的卷积核,特征图大小依旧不变,因此进一步生成第三张特征图(35, 35, 96),
最后经过branch3x3dbl_3为带有96个3
3大小且填充为1的卷积核,特征图大小和通道数不变,因此第三张特征图最终为(35, 35, 96);
第四个branch:
首先经过avg_pool2d,其中池化核33,步长为1,填充为1,所以第四张特征图大小不变,通道数不变,第四张特征图为(35, 35, 192),
然后经过branch_pool为带有pool_features个的1
1卷积,因此第四张特征图最终为(35, 35, pool_features);
最后将四张特征图进行拼接,最终得到(35,35,64+64+96+pool_features)的特征图。

'''---InceptionA---'''
class InceptionA(nn.Module):
 
    def __init__(self, in_channels, pool_features, conv_block=None):
        super(InceptionA, self).__init__()
        if conv_block is None:
            conv_block = BasicConv2d
        self.branch1x1 = conv_block(in_channels, 64, kernel_size=1)
 
        self.branch5x5_1 = conv_block(in_channels, 48, kernel_size=1)
        self.branch5x5_2 = conv_block(48, 64, kernel_size=5, padding=2)
 
        self.branch3x3dbl_1 = conv_block(in_channels, 64, kernel_size=1)
        self.branch3x3dbl_2 = conv_block(64, 96, kernel_size=3, padding=1)
        self.branch3x3dbl_3 = conv_block(96, 96, kernel_size=3, padding=1)
 
        self.branch_pool = conv_block(in_channels, pool_features, kernel_size=1)
 
    def _forward(self, x):
        branch1x1 = self.branch1x1(x)
 
        branch5x5 = self.branch5x5_1(x)
        branch5x5 = self.branch5x5_2(branch5x5)
 
        branch3x3dbl = self.branch3x3dbl_1(x)
        branch3x3dbl = self.branch3x3dbl_2(branch3x3dbl)
        branch3x3dbl = self.branch3x3dbl_3(branch3x3dbl)
 
        branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)
        branch_pool = self.branch_pool(branch_pool)
 
        outputs = [branch1x1, branch5x5, branch3x3dbl, branch_pool]
        return outputs
 
    def forward(self, x):
        outputs = self._forward(x)
        return torch.cat(outputs, 1)
InceptionB

InceptionB
得到输入大小减半,通道数为480的特征图,

假如输入为(35, 35, 288)的数据:

第一个branch:
经过branch1x1为带有384个33大小且步长2的卷积核,(35-3+20)/2+1=17所以生成第一张特征图(17, 17, 384);
第二个branch:
首先经过branch3x3dbl_1为带有64个11的卷积核,特征图大小不变,即(35, 35, 64);
然后经过branch3x3dbl_2为带有96个3
3大小填充1的卷积核,特征图大小不变,即(35, 35, 96),
再经过branch3x3dbl_3为带有96个33大小步长2的卷积核,(35-3+20)/2+1=17,即第二张特征图为(17, 17, 96);
第三个branch:
经过max_pool2d,池化核大小3*3,步长为2,所以是二倍最大值下采样,通道数保持不变,第三张特征图为(17, 17, 288);
最后将三张特征图进行拼接,最终得到(17(即Hin/2),17(即Win/2),384+96+288(Cin)=768)的特征图。

'''---InceptionB---'''
class InceptionB(nn.Module):
 
    def __init__(self, in_channels, conv_block=None):
        super(InceptionB, self).__init__()
        if conv_block is None:
            conv_block = BasicConv2d
        self.branch3x3 = conv_block(in_channels, 384, kernel_size=3, stride=2)
 
        self.branch3x3dbl_1 = conv_block(in_channels, 64, kernel_size=1)
        self.branch3x3dbl_2 = conv_block(64, 96, kernel_size=3, padding=1)
        self.branch3x3dbl_3 = conv_block(96, 96, kernel_size=3, stride=2)
 
    def _forward(self, x):
        branch3x3 = self.branch3x3(x)
 
        branch3x3dbl = self.branch3x3dbl_1(x)
        branch3x3dbl = self.branch3x3dbl_2(branch3x3dbl)
        branch3x3dbl = self.branch3x3dbl_3(branch3x3dbl)
 
        branch_pool = F.max_pool2d(x, kernel_size=3, stride=2)
 
        outputs = [branch3x3, branch3x3dbl, branch_pool]
        return outputs
 
    def forward(self, x):
        outputs = self._forward(x)
        return torch.cat(outputs, 1)
InceptionC

InceptionC
得到输入大小不变,通道数为768的特征图。

假如输入为(17,17, 768)的数据:

第一个branch:
首先经过branch1x1为带有192个1*1的卷积核,所以生成第一张特征图(17,17, 192);

第二个branch:
首先经过branch7x7_1为带有c7个11的卷积核,所以第二张特征图(17,17, c7),
然后经过branch7x7_2为带有c7个1
7大小且填充为03的卷积核,特征图大小不变,进一步生成第二张特征图(17,17, c7),
然后经过branch7x7_3为带有192个7
1大小且填充为30的卷积核,特征图大小不变,进一步生成第二张特征图(17,17, 192),因此第二张特征图最终为(17,17, 192);
第三个branch:
首先经过branch7x7dbl_1为带有c7个1
1的卷积核,所以第三张特征图(17,17, c7),
然后经过branch7x7dbl_2为带有c7个71大小且填充为30的卷积核,特征图大小不变,进一步生成第三张特征图(17,17, c7),
然后经过branch7x7dbl_3为带有c7个17大小且填充为03的卷积核,特征图大小不变,进一步生成第三张特征图(17,17, c7),
然后经过branch7x7dbl_4为带有c7个71大小且填充为30的卷积核,特征图大小不变,进一步生成第三张特征图(17,17, c7),
然后经过branch7x7dbl_5为带有192个17大小且填充为03的卷积核,特征图大小不变,因此第二张特征图最终为(17,17, 192);
第四个branch:
首先经过avg_pool2d,其中池化核33,步长为1,填充为1,所以第四张特征图大小不变,通道数不变,第四张特征图为(17,17, 768),
然后经过branch_pool为带有192个的1
1卷积,因此第四张特征图最终为(17,17, 192);
最后将四张特征图进行拼接,最终得到(17, 17, 192+192+192+192=768)的特征图。

'''---InceptionC---'''
class InceptionC(nn.Module):
 
    def __init__(self, in_channels, channels_7x7, conv_block=None):
        super(InceptionC, self).__init__()
        if conv_block is None:
            conv_block = BasicConv2d
        self.branch1x1 = conv_block(in_channels, 192, kernel_size=1)
 
        c7 = channels_7x7
        self.branch7x7_1 = conv_block(in_channels, c7, kernel_size=1)
        self.branch7x7_2 = conv_block(c7, c7, kernel_size=(1, 7), padding=(0, 3))
        self.branch7x7_3 = conv_block(c7, 192, kernel_size=(7, 1), padding=(3, 0))
 
        self.branch7x7dbl_1 = conv_block(in_channels, c7, kernel_size=1)
        self.branch7x7dbl_2 = conv_block(c7, c7, kernel_size=(7, 1), padding=(3, 0))
        self.branch7x7dbl_3 = conv_block(c7, c7, kernel_size=(1, 7), padding=(0, 3))
        self.branch7x7dbl_4 = conv_block(c7, c7, kernel_size=(7, 1), padding=(3, 0))
        self.branch7x7dbl_5 = conv_block(c7, 192, kernel_size=(1, 7), padding=(0, 3))
 
        self.branch_pool = conv_block(in_channels, 192, kernel_size=1)
 
    def _forward(self, x):
        branch1x1 = self.branch1x1(x)
 
        branch7x7 = self.branch7x7_1(x)
        branch7x7 = self.branch7x7_2(branch7x7)
        branch7x7 = self.branch7x7_3(branch7x7)
 
        branch7x7dbl = self.branch7x7dbl_1(x)
        branch7x7dbl = self.branch7x7dbl_2(branch7x7dbl)
        branch7x7dbl = self.branch7x7dbl_3(branch7x7dbl)
        branch7x7dbl = self.branch7x7dbl_4(branch7x7dbl)
        branch7x7dbl = self.branch7x7dbl_5(branch7x7dbl)
 
        branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)
        branch_pool = self.branch_pool(branch_pool)
 
        outputs = [branch1x1, branch7x7, branch7x7dbl, branch_pool]
        return outputs
 
    def forward(self, x):
        outputs = self._forward(x)
        return torch.cat(outputs, 1)
InceptionD

InceptionD
得到输入大小减半,通道数512的特征图,

假如输入为(17, 17, 768)的数据:

第一个branch:
首先经过branch3x3_1为带有192个11的卷积核,所以生成第一张特征图(17, 17, 192);
然后经过branch3x3_2为带有320个3
3大小步长为2的卷积核,(17-3+20)/2+1=8,最终第一张特征图(8, 8, 320);
第二个branch:
首先经过branch7x7x3_1为带有192个1
1的卷积核,特征图大小不变,即(17, 17, 192);
然后经过branch7x7x3_2为带有192个17大小且填充为03的卷积核,特征图大小不变,进一步生成第三张特征图(17,17, 192);
再经过branch7x7x3_3为带有192个71大小且填充为30的卷积核,特征图大小不变,进一步生成第三张特征图(17,17, 192);
最后经过branch7x7x3_4为带有192个3*3大小步长为2的卷积核,最终第一张特征图(8, 8, 192);
第三个branch:

首先经过max_pool2d,池化核大小3*3,步长为2,所以是二倍最大值下采样,通道数保持不变,第三张特征图为(8, 8, 768);
最后将三张特征图进行拼接,最终得到(8(即Hin/2),8(即Win/2),320+192+768(Cin)=1280)的特征图。

'''---InceptionD---'''
class InceptionD(nn.Module):
 
    def __init__(self, in_channels, conv_block=None):
        super(InceptionD, self).__init__()
        if conv_block is None:
            conv_block = BasicConv2d
        self.branch3x3_1 = conv_block(in_channels, 192, kernel_size=1)
        self.branch3x3_2 = conv_block(192, 320, kernel_size=3, stride=2)
 
        self.branch7x7x3_1 = conv_block(in_channels, 192, kernel_size=1)
        self.branch7x7x3_2 = conv_block(192, 192, kernel_size=(1, 7), padding=(0, 3))
        self.branch7x7x3_3 = conv_block(192, 192, kernel_size=(7, 1), padding=(3, 0))
        self.branch7x7x3_4 = conv_block(192, 192, kernel_size=3, stride=2)
 
    def _forward(self, x):
        branch3x3 = self.branch3x3_1(x)
        branch3x3 = self.branch3x3_2(branch3x3)
 
        branch7x7x3 = self.branch7x7x3_1(x)
        branch7x7x3 = self.branch7x7x3_2(branch7x7x3)
        branch7x7x3 = self.branch7x7x3_3(branch7x7x3)
        branch7x7x3 = self.branch7x7x3_4(branch7x7x3)
 
        branch_pool = F.max_pool2d(x, kernel_size=3, stride=2)
        outputs = [branch3x3, branch7x7x3, branch_pool]
        return outputs
 
    def forward(self, x
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hanley_Yeung

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值