在这篇文章中,我们将讨论一种新的网络模型GoogleNet,它和我前面所讨论的模型有所不同,表现在:
GoogLeNet模型于2014年的一篇论文《Going Deeper With Convolutions》提出,其最大的贡献在于Inception模块(Inception有起初、开端的含义),这是一个适合卷积神经网络的构建模块,它选用多个过滤器大小的卷积,将模块转换为多级特征提取器。
Inception模块是一种微架构模块,所谓微架构,就是由深度学习从业者设计的小型构建块,它使得网络能够在增加网络深度的前提下更快地学习,而且更高效。而这些微架构构建块与诸如CONV、POOL等传统类型的层堆叠在一起,可以形成宏架构(macro-architecture)。
Inception模块背后的思想有两层含义:
GoogleNet最初引入的Inception模块如下图所示:
注: 在每个CONV层之后都紧跟一个激活函数(ReLU)。为节省空间,此激活函数并没包含在上面的网络图中。
从图中可以看到,输入层之后有四个不同的路径分支。Inception模块中的第一个分支只是从输入中学习一系列1×1局部特征。
第二条路径首先应用1×1卷积,不仅作为学习局部特征的一种形式,还可以减少维数。较大的卷积(即3×3和5×5)需要更多的计算。因此,如果我们可以通过应用1×1卷积来减少这些较大过滤器的输入维数,就可以减少网络所需的计算量。
第三个分支与第二个分支的逻辑相同,区别在于为了学习5×5过滤器。我们再次通过1×1卷积降低维数,然后将输出馈送到5×5过滤器。
Inception模块的第四个分支以1×1的步幅执行3×3最大池化 - 该分支通常被称为池投影分支。
最后,Inception模块的所有四个分支汇聚在一起,它们沿着通道维度连接在一起。在实现过程中要特别小心(通过零填充)以确保每个分支的输出具有相同的卷大小,从而允许连接输出。
最初的Inception模块是为GoogLeNet设计的,在ImageNet数据集上训练(其中每个输入图像假设为224×224×3)并获得最好的精度。对于较小的数据集(具有较小的图像空间维度),我们可以简化Inception模块,只需要较少的网络参数。比如下图表示的Miniception:
将这些模块堆叠起来,可以组成称之为MiniGoogleNet的模型结构,如下图所示:
有了上面的模型定义,接下来我们就可以使用Keras框架来实现之。但在编码之前,我们先了解一下Keras中的两种类型的模型。
model.add(Dense(32, activation='relu', input_dim=100))
model.add(Dropout(0.25))
inputs = Input(shape=(784,))
# 输入inputs,输出x
x = Dense(64, activation='relu')(inputs)
# 输入x,输出x
x = Dense(64, activation='relu')(x)
因为MiniGoogleNet并不是那种一条路走到黑的模型,所以我们不能选择序列模型,而应该选择函数式API来构建,代码如下:
class MiniGoogleNet:
@staticmethod
def conv_module(x, k, kx, ky, stride, channel_dim, padding="same"):
# define a CONV => BN => RELU pattern
x = Conv2D(k, (kx, ky), strides=stride, padding=padding)(x)
x = BatchNormalization(axis=channel_dim)(x)
x = Activation("relu")(x)
return x
@staticmethod
def inception_module(x, num_k1x1, num_k3x3, channel_dim):
# define two CONV module, then concatenate across the channel dimension
conv_1x1 = MiniGoogleNet.conv_module(x, num_k1x1, 1, 1, (1, 1), channel_dim=channel_dim)
conv_3x3 = MiniGoogleNet.conv_module(x, num_k3x3, 3, 3, (1, 1), channel_dim=channel_dim)
x = concatenate([conv_1x1, conv_3x3], axis=channel_dim)
return x
@staticmethod
def downsample_module(x, k, channel_dim):
# define the CONV module and POOL, then concatenate across the channel dimension
conv_3x3 = MiniGoogleNet.conv_module(x, k, 3, 3, (2, 2), channel_dim=channel_dim, padding="valid")
pool = MaxPooling2D((3, 3), strides=(2, 2))(x)
x = concatenate([conv_3x3, pool], axis=channel_dim)
return x
@staticmethod
def build(width, height, depth, classes):
input_shape = (width, height, depth)
channel_dim = -1
if K.image_data_format() == "channels_first":
input_shape = (depth, width, height)
channel_dim = 1
inputs = Input(shape=input_shape)
x = MiniGoogleNet.conv_module(inputs, 96, 3, 3, (1, 1), channel_dim=channel_dim)
x = MiniGoogleNet.inception_module(x, 32, 32, channel_dim=channel_dim)
x = MiniGoogleNet.inception_module(x, 32, 48, channel_dim=channel_dim)
x = MiniGoogleNet.downsample_module(x, 80, channel_dim=channel_dim)
x = MiniGoogleNet.inception_module(x, 112, 48, channel_dim=channel_dim)
x = MiniGoogleNet.inception_module(x, 96, 64, channel_dim=channel_dim)
x = MiniGoogleNet.inception_module(x, 80, 80, channel_dim=channel_dim)
x = MiniGoogleNet.inception_module(x, 48, 96, channel_dim=channel_dim)
x = MiniGoogleNet.downsample_module(x, 96, channel_dim=channel_dim)
x = MiniGoogleNet.inception_module(x, 176, 160, channel_dim=channel_dim)
x = MiniGoogleNet.inception_module(x, 176, 160, channel_dim=channel_dim)
x = AveragePooling2D((7, 7))(x)
x = Dropout(0.5)(x)
# softmax classifier
x = Flatten()(x)
x = Dense(classes)(x)
x = Activation("softmax")(x)
model = Model(inputs, x, name="googlenet")
return model
接下来就是训练和测试模型,这个在前面的文章中介绍过,其步骤都差不多,所以在这里我也不再罗嗦,有兴趣的同学可以参考我在github上的完整代码。
写下这篇文章,我完成了《Deep Learning for Computer Vision with Python》的学习,其实后面还有一章节是讲残差网络(ResNet),但考虑到ResNet也是采用微架构,其实和GoogleNet差不多,就是模块构建块有些区别,所以就不打算写了。
这套书还有第三部,称为ImageNet Bundle,里面有更多大型项目的例子,考虑到我这边的硬件条件有限,就先不去研究这些复杂的例子。在后面的时间里,我将专注于移动终端上的机器学习,敬请关注。
以上实例均有完整的代码,点击阅读原文,跳转到我在github上建的示例代码。 另外,我在阅读《Deep Learning for Computer Vision with Python》这本书,在微信公众号后台回复“计算机视觉”关键字,可以免费下载这本书的电子版。