一、Inception网络的作用
2012年AlexNet做出历史突破以来,直到GoogLeNet出来之前,主流的网络结构突破大致是网络更深(层数),网络更宽(神经元数),这些网络有以下的缺点:
- 参数太多,容易过拟合,若训练数据集有限;
- 网络越大计算复杂度越大,难以应用;
- 网络越深,梯度越往后穿越容易消失(梯度弥散),难以优化模型
那么解决上述问题的方法当然就是增加网络深度和宽度的同时减少参数,Inception就是在这样的情况下应运而生。
二、Inception的网络结构
Inception v1的网络,将1x1,3x3,5x5的conv和3x3的pooling,堆叠在一起,一方面增加了网络的width,另一方面增加了网络对尺度的适应性,结构图如下:
上图是论文中提出的最原始的版本,所有的卷积核都在上一层的所有输出上来做,那5×5的卷积核所需的计算量就太大了,造成了特征图厚度很大。为了避免这一现象提出的inception具有如下结构,在3x3前,5x5前,max pooling后分别加上了1x1的卷积核起到了降低特征图厚度的作用,因为1*1的卷积核可以设定卷积核个数,来降低原始数据卷积后的渠道数从而降低计算难度,也就是Inception v1的网络结构。,如下图:
三、InceptionV1结构的实现
先看一下结构以及结构内部的内容:
每个卷积单元内部,都采用了same卷积-BN-relu激活的结构,只是卷积核的大小、步长不一致,所以可以定义一个返回这样卷积结构单元的函数来简化代码,代码如下:
# 创建卷积结构单元 def covunit(filters,kernel_size,unit_input): \'\'\' :param filters: 卷积核格式 :param kernel_size: 卷积核大小 :param unit_input: 卷积核输入,tensor :return:卷积后输出的tensor \'\'\' unit_cov=tf.keras.layers.Conv2D(filters,kernel_size,padding=\'same\')(unit_input) unit_bn=tf.keras.layers.BatchNormalization()(unit_cov) unit_act=tf.keras.layers.Activation(\'relu\')(unit_bn) return unit_act
每个部分生成的张量需要用凭借函数tf.concat()函数按渠道方向合并在一起,以下是函数介绍:
tf.concat()
- value:list,将需要拼接的张量放在列表中。
- axis:axis与pandas中的一致,但是由于tensor计算中shape=(batch,row,col,channel),所以axis=0表示batch方向,axis=3表示按渠道方向,其中axis=-1表示最后一个维度。
代码如下:
import tensorflow as tf import numpy as np gpus = tf.config.list_physical_devices(\'GPU\') tf.config.experimental.set_visible_devices(devices=gpus[2:8], device_type=\'GPU\') # os.environ["CUDA_VISIBLE_DEVICES"] = "0" # 读取数据集,32*32像素3通道图片,标签为10类 (train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.cifar10.load_data() # 零均值归一化 def normalize(X_train, X_test): X_train = X_train / 255. X_test = X_test / 255. mean = np.mean(X_train, axis=(0, 1, 2, 3)) # 均值 std = np.std(X_train, axis=(0, 1, 2, 3)) # 标准差 X_train = (X_train - mean) / (std + 1e-7) X_test = (X_test - mean) / (std + 1e-7) return X_train, X_test # 预处理 def preprocess(x, y): x = tf.cast(x, tf.float32) y = tf.cast(y, tf.int32) y = tf.squeeze(y, axis=1) # 将(50000, 1)的数组转化为(50000)的Tensor y = tf.one_hot(y, depth=10) return x, y # 训练数据标准化 train_images, test_images = normalize(train_images, test_images) # 预处理 train_db = tf.data.Dataset.from_tensor_slices((train_images, train_labels)) train_db = train_db.shuffle(50000).batch(100).map(preprocess) # 每个批次128个训练样本 test_db = tf.data.Dataset.from_tensor_slices((test_images, test_labels)) test_db = test_db.shuffle(10000).batch(100).map(preprocess) # 每个批次128个测试样本 # 创建卷积结构单元的函数 def covunit(filters, kernel_size, unit_input): \'\'\' :param filters: 卷积核格式 :param kernel_size: 卷积核大小 :param unit_input: 卷积核输入,tensor :return:卷积后输出的tensor \'\'\' unit_cov = tf.keras.layers.Conv2D(filters, kernel_size, padding=\'same\')(unit_input) unit_bn = tf.keras.layers.BatchNormalization()(unit_cov) unit_act = tf.keras.layers.Activation(\'relu\')(unit_bn) return unit_act # Inceptionblock函数,网络结构中可能有多个这样的结构块,所以需要如此操作 def Inceptionblock(filters, block_input): \'\'\' :param filters: 该Inception结构每个卷积部分的filters个数 :param block_input: Inception结构的输入 :return: Inceptionblock结构输出的tensor \'\'\' # 第一部分:16*1*1卷积,输入为block_input unit1 = covunit(filters=filters, kernel_size=(1, 1), unit_input=block_input) # 第二部分:16*1*1卷积,输入为block_input;16*3*3卷积输入为上一个unit的输出 unit2_1 = covunit(filters=filters, kernel_size=(1, 1), unit_input=block_input) unit2_2 = covunit(filters=filters, kernel_size=(3, 3), unit_input=unit2_1) # 第三部分:16*1*1卷积,输入为block_input;16*5*5卷积输入为上一个卷积的输出 # 其中5*5的卷积可以用2次3*3的卷积操作替换,有相同的感受野但是参数变少 unit3_1 = covunit(filters=filters, kernel_size=(1, 1), unit_input=block_input) unit3_2 = covunit(filters=filters, kernel_size=(5, 5), unit_input=unit3_1) # 第四部分:3*3maxpool,输入为block_input;16*1*1卷积,输入为maxpool的输出 unit4_1 = tf.keras.layers.MaxPooling2D(pool_size=(3, 3), strides=(1, 1), padding=\'same\')(block_input) unit4_2 = covunit(filters=filters, kernel_size=(1, 1), unit_input=unit4_1) # 将每个部分的输出tensor在渠道维度上拼接在一起,卷积计算中张量的shape=(batch,row,col,channel),所以axis=3是按channel合并 block_out = tf.concat(values=[unit1, unit2_2, unit3_2, unit4_2], axis=3) return block_out # 创建Inception网络 net_input = tf.keras.Input((32, 32, 3)) Inceptionblock1 = Inceptionblock(16, net_input) # Inception网络结构 l1_cov = tf.keras.layers.Conv2D(16, (3, 3), padding=\'same\')(Inceptionblock1) l1_pool = tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2))(l1_cov) l2_f = tf.keras.layers.Flatten()(l1_pool) # 全连接层 l2_Drop = tf.keras.layers.Dropout(0.2)(l2_f) l2_Den = tf.keras.layers.Dense(100, \'relu\')(l2_Drop) net_output = tf.keras.layers.Dense(10, \'softmax\')(l2_Den) InceptionNet = tf.keras.Model(inputs=net_input, outputs=net_output) # 构建模型 # 查看模型的结构 InceptionNet.summary() # 模型编译 InceptionNet.compile( optimizer=tf.keras.optimizers.Adam(0.00001), loss=tf.keras.losses.CategoricalCrossentropy(from_logits=True), metrics=[\'accuracy\'] ) InceptionNet.fit(train_db)