1.Inceptionnet的借鉴点

Inception结构快在同一层网络中使用多个尺寸的卷积核,可以提取不同尺寸的特征,提升感知力(通过 padding 实现输出特征面积一致);使用 1 * 1 卷积核,作用到输入特征图的每个像素点,通过设定少于输入特征图深度的1*1卷积核的个数,减少了输出特征图的深度,起到了降维的作用,减少了参数量和计算量,即改变输出特征 channel 数(减少网络参数)
InceptionNet 即 GoogLeNet,诞生于 2015 年,旨在通过增加网络的宽度来提升网络的能力,与 VGGNet 通过卷积层堆叠的方式(纵向)相比,是一个不同的方向(横向)。
显然,InceptionNet 模型的构建与 VGGNet 及之前的网络会有所区别,不再是简单的纵向堆叠,要理解 InceptionNet 的结构,首先要理解它的基本单元,如图 5-27 所示。

InceptionNet提出了1x1卷积核

 2.实现

InceptionNet提出了1x1卷积核

 四个分支送到卷积连接器的特征数据尺寸相同,卷积连接器会把收到的这四路特征数据按深度方向拼接,形成Inception结构块的输出

由于都是使用CBA的结构,直接定义一个ConvRelu类

InceptionNet提出了1x1卷积核

 x1, x2_1, x2_2, x3_1, x3_2, x4_1, x4_2是四个分支的输出,使用tf.concat函数将它们按深度方向堆叠在一起,axis=3指定堆叠的维度是按深度方向

3.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提出了1x1卷积核

为什么经过卷积核后图片大小不变?

因为是全0填充的。

InceptionNet提出了1x1卷积核

                                 InceptionNet提出了1x1卷积核

                                      InceptionNet提出了1x1卷积核

 InceptionNet提出了1x1卷积核

InceptionNet提出了1x1卷积核

 参数 num_blocks 代表 InceptionNet 的 Block 数,每个 Block 由两个基本单元构成,每经过一个 Block,特征图尺寸变为 1/2,通道数变为 2 倍; num_classes 代表分类数,对于 cifar10数据集来说即为 10; init_ch 代表初始通道数,也即 InceptionNet 基本单元的初始卷积核个数。

InceptionNet 网络不再像 VGGNet 一样有三层全连接层(全连接层的参数量占 VGGNet总参数量的 90 %), 而是采用“全局平均池化+全连接层”的方式, 这减少了大量的参数。

这里介绍一下全局平均池化,在 tf.keras 中用 GlobalAveragePooling2D 函数实现,相比于平均池化(在特征图上以窗口的形式滑动,取窗口内的平均值为采样值),全局平均池化不再以窗口滑动的形式取均值,而是直接针对特征图取平均值,即每个特征图输出一个值。通过这种方式,每个特征图都与分类概率直接联系起来,这替代了全连接层的功能,并且不产生额外的训练参数,减小了过拟合的可能,但需要注意的是,使用全局平均池化会导致网络收敛的速度变慢

总体来看, InceptionNet 采取了多尺寸卷积再聚合的方式拓宽网络结构,并通过 1 * 1的卷积运算来减小参数量,取得了比较好的效果,与同年诞生的 VGGNet 相比,提供了卷积神经网络构建的另一种思路。但 InceptionNet 的问题是,当网络深度不断增加时,训练会十分困难,甚至无法收敛(这一点被 ResNet 很好地解决了)。 

 InceptionNet提出了1x1卷积核

 InceptionNet提出了1x1卷积核

4.代码

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
        self.c1 = ConvBNRelu(ch, kernelsz=1, strides=strides)
        self.c2_1 = ConvBNRelu(ch, kernelsz=1, strides=strides)
        self.c2_2 = ConvBNRelu(ch, kernelsz=3, strides=1)
        self.c3_1 = ConvBNRelu(ch, kernelsz=1, strides=strides)
        self.c3_2 = ConvBNRelu(ch, kernelsz=5, strides=1)
        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):
        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)

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
              metrics=['sparse_categorical_accuracy'])

checkpoint_save_path = "./checkpoint/Inception10.ckpt"
if os.path.exists(checkpoint_save_path + '.index'):
    print('-------------load the model-----------------')
    model.load_weights(checkpoint_save_path)

cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
                                                 save_weights_only=True,
                                                 save_best_only=True)

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

# print(model.trainable_variables)
file = open('./weights.txt', 'w')
for v in model.trainable_variables:
    file.write(str(v.name) + 'n')
    file.write(str(v.shape) + 'n')
    file.write(str(v.numpy()) + 'n')
file.close()

###############################################    show   ###############################################

# 显示训练集和验证集的acc和loss曲线
acc = history.history['sparse_categorical_accuracy']
val_acc = history.history['val_sparse_categorical_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

plt.subplot(1, 2, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.title('Training and Validation Loss')
plt.legend()
plt.show()