TF14——InceptionNet


TF14——InceptionNet

经典卷积网络

image-20220320140919084

VInceptionNet诞生于2014年,当年ImageNet竞赛冠军,Top5错误率为6.67%,InceptionNet引入了Inceoption结构块,在同一层网络结构内使用不同尺寸的卷积核,提升了模型的感知力,使用了批标准化缓解了梯度消失

Inception结构块

InceptionNet的核心是它的基本单元Inception结构块

image-20220320152209246

无论是GoogLeNet也就是Inception v1,还是inceptionNet的后续版本,比如v2、v3、v4都是基于Inception结构块搭建的网络

Inception结构快在用一层网络中,使用了多个尺寸的卷积核,可以提取不同尺寸的特征

  • 通过1*1卷积核,作用到输入特征图的每个像素点,通过设定少于输入特征图深度的1*1卷积核个数,减少了输出特征图深度,起到了降维的作用,减少了参数量和计算量
  • Inception结构块包含四个分支,分别是
    • 经过1*1卷积核输出到卷积连接器,
    • 经过1*1卷积核匹配3*3卷积核输出到卷积连接器,
    • 经过1*1卷积核匹配5*5卷积核输出到卷积连接器,
    • 经过3*3最大池化核配合1*1卷积核输出到卷积连接器,

卷积连接器会把收到的这四路特征数据按深度方向拼接形成Inception结构块的输出

image-20220320152101920

把这个Inception结构块用CBAPD描述出来,用颜色对应上Inception结构块内的操作

image-20220320152501210

  • 第一分支卷积采用16个1*1卷积核,步长为1,全零填充采用BN操作,relu激活函数
  • 第二分支卷积采用16个1*1卷积核降维,步长为1,全零填充采用BN操作,relu激活函数,采用16个3*3卷积核,步长为1,全零填充,采用BN操作,relu激活函数
  • 第三分支卷积采用16个1*1卷积核降维,步长为1,全零填充采用BN操作,relu激活函数,采用16个5*5卷积核,步长为1,全零填充,采用BN操作,relu激活函数
  • 第四分支先采用最大池化,池化核尺寸是3*3,步长为1,全零填充,采用16个1*1卷积核降维,步长为1,全零填充,采用BN操作,relu激活函数

卷积连接器,把这四个分支按照深度方向堆叠在一起,构成Inception结构块的输出,由于Inception结构块中的卷积操作均采用了CBA结构,所以将其定义乘一个新的类ConvBNRelu,可以减少代码长度,增加可读性,

class ConvBNRelu(Model):
    def __init__(self,ch,kernelsz=3,strides=1,padding='same'):
        super(ConvBNrelu,self).__init__()
        self.model = tf.keras.models.Sequential([
            Conv2D(ch,kernelsz,strides=strides,padding=padding),
            BatchNormalization(),
            Activation('relu')
        ])
    def call(self,x):
        x=self.model(x)
        return x

这样实现Inception结构块

image-20220320153804689

  • c1是第一个分支,使用ConvBNRelu卷积操作
  • c2_1,c2_2是第二个分支,使用两次ConvBNRelu卷积操作
  • c3_1,c3_2是第三个分支,使用两次ConvBNRelu卷积操作
  • p4_1,p4_2是第四个分支,使用最大池化和ConvBNRelu卷积操作
  • x1,x2_2,x3_2,x4_2是四个分支的输出,使用tf.concat函数将他们呢堆叠在一起,axis=3指定堆叠的维度是沿深度方向

tf.concat([tensor1,tensor2,tensor3,...],axis)

用来拼接张量,按哪个维度拼接

有了Inception结构快后,就可以搭建一个精简版的InceptionNet

image-20220320154943113

源码:p40_cifar10_inception10.py

class Inception10(Model):
    def __init__(self, num_blocks, num_classes, init_ch=16, **kwargs):
        super(Inception10, self).__init__(**kwargs)
        self.in_channels = init_ch
        self.out_channels = init_ch
        self.num_blocks = num_blocks
        self.init_ch = init_ch
        self.c1 = ConvBNRelu(init_ch)
        self.blocks = tf.keras.models.Sequential()
        for block_id in range(num_blocks):
            for layer_id in range(2):
                if layer_id == 0:
                    block = InceptionBlk(self.out_channels, strides=2)
                else:
                    block = InceptionBlk(self.out_channels, strides=1)
                self.blocks.add(block)
            # enlarger out_channels per block
            self.out_channels *= 2
        self.p1 = GlobalAveragePooling2D()
        self.f1 = Dense(num_classes, activation='softmax')

    def call(self, x):
        x = self.c1(x)
        x = self.blocks(x)
        x = self.p1(x)
        y = self.f1(x)
        return y


model = Inception10(num_blocks=2, num_classes=10)
  • 第一层采用3*3卷积核,步长为1,全零填充,采用BN操作,relu激活,定义类Inception10时,设定了默认init_ch=16,也就是默认输出深度是16,定义ConvBNRelu类时,定义了默认卷积核边长是3,步长是1,全零填充,所以这里直接调用ConBNRelu就可以

  • 随后是4个Inception结构块顺序相连,每两个Inception结构块组成一个block,每个block中的第一个Inception结构块,卷积步长是2,第二个Inception块,卷积步长是1,这使得第一个Inception结构块输出特征图尺寸减半,因此我们把输出特征图深度加深,尽可能保证抽取中信息的承载量一致

  • block_0设置的通道数是16,经过了四个分支,输出的深度为4*16=64,在这里给通道数加倍了,所以block_1通道数是block_0通道数的两倍是32

  • 同样经过了四个分支,输出深度是4*32=128,这128个通道数据会被送入平均池化,送入10个分类的全连接

  • 最后实例化了Inception10类,指定了inceptionNet的block数是2,也就是block_0和block_1,num_classes=10这里指定了网络是几分类,因为cifar10数据集是十分类, 所以这里是10

ps:由于现在的网络规模比较大,把batch_size调整到1024,让训练时,一次喂入神经网络的数据量多一些,以充分发挥显卡的性能,提高训练速度