InceptionNet提出了1x1卷积核
1.Inceptionnet的借鉴点
2.实现
四个分支送到卷积连接器的特征数据尺寸相同,卷积连接器会把收到的这四路特征数据按深度方向拼接,形成Inception结构块的输出
由于都是使用CBA的结构,直接定义一个ConvRelu类
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 %左右,这就是小卷积核的降维作用
为什么经过卷积核后图片大小不变?
因为是全0填充的。
参数 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 很好地解决了)。
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()