四、GoogLeNet
GoogLeNet在加深度的同时做了结构上的创新,引入了一个叫做Inception的结构来代替之前的卷积加激活的经典组件。
1.Inception块
GoogLeNet中的基础卷积块叫做Inception块,其结构较为复杂
Inception块里有4条并行的线路,前三条线路使用窗口大小分别是1 ×\times× 1、3 ×\times× 3、5 ×\times× 5的卷积层来抽取不同空间尺寸下的信息,其中中间2个线路会对输入先做1 ×\times× 1卷积来减少输入通道数,以降低模型复杂度。第4条线路则使用3 ×\times× 3最大池化层,后接1 ×\times× 1卷积层来改变通道数。4条线路都使用了合适的填充来使输入与输出的高宽一致。最后我们将每条线路的输出在通道维上连结,并向后进行传播
(1)1 ×\times× 1卷积
它和其他卷积核的唯一区别是 没有考虑在特征图局部信息之间的关系
1 ×\times× 1卷积的作用
- 实现跨通道的交互和信息整合
- 卷积核通道数的降维(通道数减少)和升维,减少网络参数
在tf.keras中实现Inception模块,各个卷积层卷积核的个数通过参数来控制
python
# 定义Inception模块
class Inception(tf.keras.layers.Layer):
def __init__(self, c1, c2, c3, c4):
super().__init__()
#线路1
self.p1_1 = tf.keras.layers.Conv2D(
c1, kernel_size = 1, activation = 'relu', padding = 'same'
)
# 线路2
self.p2_1 = tf.keras.layers.Conv2D(
c2[0], kernel_size = 1, activation = 'relu', padding = 'same'
)
self.p2_2 = tf.keras.layers.Conv2D(
c1[1], kernel_size = 3, activation = 'relu', padding = 'same'
)
# 线路3
self.p3_1 = tf.keras.layers.Conv2D(
c3[0], kernel_size = 1, activation = 'relu', padding = 'same'
)
self.p3_1 = tf.keras.layers.Conv2D(
c3[1], kernel_size = 5, activation = 'relu', padding = 'same'
)
# 线路4
self.p4_1 = tf.keras.layers.MaxPool2D(
pool_size = 3, padding = 'same', strides = 1
)
self.p3_1 = tf.keras.layers.Conv2D(
c4, kernel_size = 1, activation = 'relu', padding = 'same'
)
# 完成前向传播过程
def call(self, x):
p1 = self.p1_1(x)
p2 = self.p2_2(self.p2_1(x))
p3 = self.p3_2(self.p3_1(x))
p4 = self.p4_2(self.p4_1(x))
# 在通道维上concat输出
outputs = tf.concat([p1, p2, p3, p4], axis = -1)
return outputs
指定通道数,对Inception模块进行实例化
python
Inception(64, (96, 128), (16, 32), 32) # 每个卷积层的卷积核数
2.GoogLeNet模型
GoogLeNet主要由Inception块构成
整个网络架构分为五个模块(B1, B2, B3, B4, B5 ),每个模块之间使用步幅为2的3 ×\times×3最大池化层来减小输出高宽
(1)B1模块
第一模块使用了一个64通道的7 ×\times× 7卷积层
python
# 定义模型的输入
inputs = tf.keras.Input(shape = (224, 224, 3), name = 'input')
x = tf.keras.layers.Conv2D(64, kernel_size = 7, strides = 2, padding = 'same', activation = 'relu')(inputs)
x = tf.keras.layers.MaxPool2D(pool_size = 3, strides = 2, padding = 'same')(x)
(2)B2模块
第二个模块使用2个卷积层:首先是64通道的1 ×\times× 1卷积层,然后是将通道增大3倍的3 ×\times× 3卷积层
python
# B2模块
x = tf.keras.layers.Conv2D(64, kernel_size = 1, padding = 'same', activation = 'relu')(x)
x = tf.keras.layers.Conv2D(192, kernel_size = 3, padding = 'same', activation = 'relu')(x)
x = tf.keras.layers.MaxPool2D(pool_size = 3, padding = 'same', strides = 2)(x)
(3)B3模块
第三个模块串联2个完整的Inception块。第一个Inception块的通道数是64+128+32+32=25664+128+32+32=25664+128+32+32=256,第二个Inception块输出通道数增至128+192+96+64=480128+192+96+64=480128+192+96+64=480
python
# B3模块
x = Inception(64, (96, 128), (16, 32), 32)(x)
x = Inception(128, (128, 192), (32, 96), 64)(x)
x = tf.keras.layers.MaxPool2D(pool_size = 3, strides = 2, padding = 'same')(x)
(4)B4模块
第四模块更加复杂,它串联了5个Inception模块,其输出通道数分别是
192+208+48+64=512192+208+48+64=512192+208+48+64=512
160+224+64+64=512160+224+64+64=512160+224+64+64=512
128+256+64+64=512128+256+64+64=512128+256+64+64=512
112+288+64+64=528112+288+64+64=528112+288+64+64=528
26+320+128+128=83226+320+128+128=83226+320+128+128=832
并且增加了辅助分类器(放在第一个Inception后面和最后一个Inception前面),根据实验发现网络的中间层具有很强的识别能力,为了利用中间层抽象的特征,在某些中间层中添加含有多层的分类器,如下图所示:
辅助分类器实现:
python
def aux_classifier(x, filter_size):
# x是输入数据,filter_size:卷积层卷积核个数,全连接层神经元个数
# 池化层
x = tf.keras.layers.AveragePooling2D(
pool_size = 5, strides = 3, padding = 'same'
)(x)
# 1x1卷积层
x = tf.keras.layers.Conv2D(
filters = filter_size[0], kernel_size = 1, strides = 1, padding = 'valid', activation = 'relu'
)(x)
# 展平
x = tf.keras.layers.Flatten()(x)
# 全连接层
x = tf.keras.layers.Dense(units = filter_size[1], activation = 'relu')(x)
# Softmax输出层
x = tf.keras.layers.Dense(units = 10, activation = 'softmax')(x)
return x
B4模块实现
python
# B4模块
#Inception
x = Inception(192, (96, 208), (16, 48), 64)(x)
# 辅助输出1
aux_output_1 = aux_classifier(x, [128, 1024])
# Inception
x = Inception(160, (112, 224), (24, 64), 64)(x)
# Inception
x = Inception(128, (128, 256), (24, 64), 64)(x)
# Inception
x = Inception(112, (144, 288), (32, 64), 64)(x)
# 辅助输出2
aux_output_2 = aux_classifier(x, [128, 1024])
# Inception
x = Inception(256, (160, 320), (32, 128), 128)(x)
# 最大池化
x = tf.keras.layers.MaxPool2D(pool_size = 3, strides = 2, padding = 'same')(x)
(5)B5模块
它串联了2个Inception模块,其输出通道数分别是
256+320+128+128=832256+320+128+128=832256+320+128+128=832
384+384+128+128=1024384+384+128+128=1024384+384+128+128=1024
后面紧跟输出层,该模块使用全局平均池化层(GPA)来将每个通道的高和宽变成1。最后输出变成二维数组后接 输出个数为标签类别数 的全连接层。
全局平均池化层(GPA)
用来替代全连接层,将特征图每一通道中所有像素值相加后求平均,得到就是GPA的结果,再将其送入后续网络中进行计算
实现过程:
python
# B5模块
# Inception
x = Inception(256, (160, 320), (32, 128), 128)(x)
# Inception
x = Inception(384, (192, 384), (48, 128), 128)(x)
# GPA
x = tf.keras.layers.GlobalAvgPool2D()(x)
# 输出层
main_outputs = tf.keras.layers.Dense(10, activation = 'softmax')(x)
(6)最终
构建GooLeNet模型并通过summary来看下模型的结构:
python
# 使用Model来创建模型,指明输入和输出
model = tf.keras.Model(inputs = inputs, outputs = [main_outputs, aux_output_1, aux_output_2])
model.summary()
3.手写数字识别
(1)数据读取
python
import numpy as np
from tensorflow.keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
# N H W C
train_images = np.reshape(train_images, (train_images.shape[0], train_images.shape[1], train_images.shape[2], 1))
test_images = np.reshape(test_images, (test_images.shape[0], test_images.shape[1], test_images.shape[2], 1))
# 定义两个方法随机抽取部分样本演示
def get_train(size):
index = np.random.randint(0, np.shape(train_images)[0], size)
resize_images = tf.image.resize_with_pad(train_images[index], 224, 224, )
return resize_images.numpy(), train_labels[index]
def get_test(size):
index = np.random.randint(0, np.shape(test_images)[0], size)
resize_images = tf.image.resize_with_pad(test_images[index], 224, 224, )
return resize_images.numpy(), test_labels[index]
# 获取训练样本和测试样本
train_image, train_label = get_train(256)
test_image, test_label = get_test(128)
(2)模型编译
- 有三个输出,需指定权重
python
# 优化器,损失函数,评价指标
model.compile(optimizer = tf.keras.optimizers.SGD(learning_rate = 0.01),
loss = tf.keras.losses.sparse_categorical_crossentropy,
metrics = ['accuracy'],
loss_weights = [1, 0.3, 0.3])
(3)模型训练
python
model.fit(train_images, train_labels, batch_size = 128, epochs = 3, verbose = 1, validation_split = 0.1)
(4)模型评估
python
model.evaluate(test_images, test_labels, verbose = 1)
4.延伸版本
(1)InceptionV2
在InceptionV2中将大卷积核拆分为小卷积核,将V1中的5 ×\times× 5卷积用两个3 ×\times× 3的卷积代替,从而增加网络的深度,减少了参数
(2)InceptionV3
将n ×\times× n卷积分割为1 ×\times× n和n ×\times× 1两个卷积,例如,一个3 ×\times× 3的卷积首先执行一个1 ×\times× 3的卷积,然后执行一个3 ×\times× 1的卷积,这种方法的参数量和计算量均降低