tensorflow InceptionNet

文章详细介绍了InceptionNet(GoogLeNet)的基本结构和设计思想,包括通过不同尺寸卷积核和1*1卷积来提升网络性能和减少参数量。通过Tensorflow的Keras框架,展示了如何构建InceptionNet的基本单元和整个模型,包括ConvBNRelu和InceptionBlk类的定义,以及模型的训练过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        InceptionNet 即 GoogLeNet,诞生于 2015 年,旨在通过增加网络的宽度来提升网络的能 力,与 VGGNet 通过卷积层堆叠的方式(纵向)相比,是一个不同的方向(横向)。 显然,InceptionNet 模型的构建与 VGGNet 及之前的网络会有所区别,不再是简单的纵 向堆叠,要理解 InceptionNet 的结构,首先要理解它的基本单元,如图下 所示。

借鉴点:一层内使用不同尺寸的卷积核,提升感知力(通过 padding 实现输出特征面积一致); 使用 1 * 1 卷积核,改变输出特征 channel 数(减少网络参数)。  

        可以看到,InceptionNet 的基本单元中,卷积部分是比较统一的 C、B、A 典型结构,即 卷积→BN→激活,激活均采用 Relu 激活函数,同时包含最大池化操作。 在 Tensorflow 框架下利用 Keras 构建 InceptionNet 模型时,可以将 C、B、A 结构封装 在一起,定义成一个新的 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, training=False) #在training=False时,BN通过整个训练集计算均值、方差去做批归一化,training=True时,通过当前batch的均值、方差去做批归一化。推理时 training=False效果好
        return x

        参数 ch 代表特征图的通道数,也即卷积核个数;kernelsz 代表卷积核尺寸;strides 代表 卷积步长;padding 代表是否进行全零填充。

完成了这一步后,就可以开始构建 InceptionNet 的基本单元了,同样利用 class 定义的 方式,定义一个新的 InceptionBlk 类,如下所示。

 

class InceptionBlk(Model):
    def __init__(self, ch, strides=1):
        super(InceptionBlk, self).__init__()
        self.ch = ch
        self.strides = strides
        #第1分支
        self.c1 = ConvBNRelu(ch, kernelsz=1, strides=strides)
        #第2分支
        self.c2_1 = ConvBNRelu(ch, kernelsz=1, strides=strides)
        self.c2_2 = ConvBNRelu(ch, kernelsz=3, strides=1)
        #第3分支
        self.c3_1 = ConvBNRelu(ch, kernelsz=1, strides=strides)
        self.c3_2 = ConvBNRelu(ch, kernelsz=5, strides=1)
        #第4分支
        self.p4_1 = MaxPool2D(3, strides=1, padding='same')
        self.c4_2 = ConvBNRelu(ch, kernelsz=1, strides=strides)

    def call(self, x):
        x1 = self.c1(x)
        x2_1 = self.c2_1(x)
        x2_2 = self.c2_2(x2_1)
        x3_1 = self.c3_1(x)
        x3_2 = self.c3_2(x3_1)
        x4_1 = self.p4_1(x)
        x4_2 = self.c4_2(x4_1)
        # concat along axis=channel
        x = tf.concat([x1, x2_2, x3_2, x4_2], axis=3)
        return x

        参数 ch 仍代表通道数,strides 代表卷积步长,与 ConvBNRelu 类中一致;tf.concat 函数将四 个输出连接在一起,x1、x2_2、x3_2、x4_2 分别代表图 5-27 中的四列输出,结合结构图和 代码很容易看出二者的对应关系。

        可以看到,InceptionNet 的一个显著特点是大量使用了 1 * 1 的卷积核,事实上,最原始 的 InceptionNet 的结构是不包含 1 * 1 卷积的,如图下所示。         图上 InceptionNet 最原始的基本单元 由图 可以更清楚地看出 InceptionNet 最初的设计思想,即通过不同尺寸卷积层和 池化层的横向组合(卷积、池化后的尺寸相同,通道可以相加)来拓宽网络深度,可以增加 网络对尺寸的适应性。但是这样也带来一个问题,所有的卷积核都会在上一层的输出上直接 做卷积运算,会导致参数量和计算量过大(尤其是对于 5 * 5 的卷积核来说)。因此, InceptionNet 在 3 * 3、5 * 5 的卷积运算前、最大池化后均加入了 1 * 1 的卷积层,形成了图 1中的结构,这样可以降低特征的厚度,一定程度上避免参数量过大的问题。

        那么1 * 1的卷积运算是如何降低特征厚度的呢?下面以5 * 5的卷积运算为例说明这个 问题。假设网络上一层的输出为 100 * 100 * 128(H * W * C),通过 32 * 5 * 5(32 个大小 为 5 * 5 的卷积核)的卷积层(步长为 1、全零填充)后,输出为 100 * 100 * 32,卷积层的 参数量为 32 * 5 * 5 * 128 = 102400;如果先通过 32 * 1 * 1 的卷积层(输出为 100 * 100 * 32), 再通过 32 * 5 * 5 的卷积层,输出仍为 100 * 100 * 32,但卷积层的参数量变为 32 * 1 * 1 * 128 + 32 * 5 * 5 * 32 = 29696,仅为原参数量的 30 %左右,这就是小卷积核的降维作用。 InceptionNet 网络的主体就是由其基本单元构成的,其模型结构如图下所示。

class Inception10(Model):
    def __init__(self, num_blocks, num_classes, init_ch=16, **kwargs):#输出深度16
        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

 代码实现:

import tensorflow as tf
import os
import numpy as np
from matplotlib import pyplot as plt
from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, MaxPool2D, Dropout, Flatten, Dense, \
    GlobalAveragePooling2D
from tensorflow.keras import Model

np.set_printoptions(threshold=np.inf)

cifar10 = tf.keras.datasets.cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0


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, training=False) #在training=False时,BN通过整个训练集计算均值、方差去做批归一化,training=True时,通过当前batch的均值、方差去做批归一化。推理时 training=False效果好
        return x


class InceptionBlk(Model):
    def __init__(self, ch, strides=1):
        super(InceptionBlk, self).__init__()
        self.ch = ch
        self.strides = strides
        #第1分支
        self.c1 = ConvBNRelu(ch, kernelsz=1, strides=strides)
        #第2分支
        self.c2_1 = ConvBNRelu(ch, kernelsz=1, strides=strides)
        self.c2_2 = ConvBNRelu(ch, kernelsz=3, strides=1)
        #第3分支
        self.c3_1 = ConvBNRelu(ch, kernelsz=1, strides=strides)
        self.c3_2 = ConvBNRelu(ch, kernelsz=5, strides=1)
        #第4分支
        self.p4_1 = MaxPool2D(3, strides=1, padding='same')
        self.c4_2 = ConvBNRelu(ch, kernelsz=1, strides=strides)

    def call(self, x):
        x1 = self.c1(x)
        x2_1 = self.c2_1(x)
        x2_2 = self.c2_2(x2_1)
        x3_1 = self.c3_1(x)
        x3_2 = self.c3_2(x3_1)
        x4_1 = self.p4_1(x)
        x4_2 = self.c4_2(x4_1)
        # concat along axis=channel
        x = tf.concat([x1, x2_2, x3_2, x4_2], axis=3)
        return x


class Inception10(Model):
    def __init__(self, num_blocks, num_classes, init_ch=16, **kwargs):#输出深度16
        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)
optimizer = tf.keras.optimizers.Adam()
model.compile(optimizer=optimizer ,
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
              metrics=['sparse_categorical_accuracy'])


history = model.fit(x_train, y_train, batch_size=32, epochs=5, validation_data=(x_test, y_test), validation_freq=1)
model.summary()

         

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

XMM-struggle

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

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

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

打赏作者

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

抵扣说明:

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

余额充值