微架构模型:GoogleNet

在这篇文章中,咱们将讨论一种新的网络模型GoogleNet,它和我前面所讨论的模型有所不一样,表如今:git

  1. 移除了全链接层,而采用全局平均池化层(global average pooling)代替,大量减小参数数量,因此相对于AlexNet和VGGNe这种巨型模型,其须要训练的参数少得多,能够节约大量内存。
  2. 采用了微架构,而到目前为止,咱们接触到的模型都是序列(sequential)模型,所谓序列,就是前一层的输出直接输出到下一层。但GoogleNet却采用了微架构,来自一个层的输出能够分红多个不一样的路径而且稍后从新链接到一块儿。

GoogLeNet模型于2014年的一篇论文《Going Deeper With Convolutions》提出,其最大的贡献在于Inception模块(Inception有起初、开端的含义),这是一个适合卷积神经网络的构建模块,它选用多个过滤器大小的卷积,将模块转换为多级特征提取器。github

Inception模块及其变种

Inception模块是一种微架构模块,所谓微架构,就是由深度学习从业者设计的小型构建块,它使得网络可以在增长网络深度的前提下更快地学习,并且更高效。而这些微架构构建块与诸如CONV、POOL等传统类型的层堆叠在一块儿,能够造成宏架构(macro-architecture)。bash

Inception模块背后的思想有两层含义:微信

  1. 在设计卷积层时,咱们可能很难肯定过滤器的大小。设计为5×5过滤器仍是3x3过滤器,若是采用1×1过滤器会不会更好?若是咱们反过来想,为何不都用上,让模型来决定呢? 在Inception模块中,咱们学习全部三个5×五、3×3和1×1过滤器(并行计算它们),将所获得的特征映射沿着通道维度链接起来。GoogLeNet体系结构中的下一层(多是另外一个Inception模块)接收这些链接的混合过滤器并执行相同的过程。总的来讲,这个过程使GoogLeNet可以经过较小的卷积学习局部特征,较大卷积来学习抽象特征。
  2. 经过学习多个过滤器大小,咱们能够将模块转换为多级特征提取器。5×5过滤器具备更大的接收尺寸,能够学习更多抽象功能。根据定义,1×1过滤器学习更多局部特征,而3×3过滤器在二者之间保持平衡。

GoogleNet最初引入的Inception模块以下图所示:网络

注: 在每一个CONV层以后都紧跟一个激活函数(ReLU)。为节省空间,此激活函数并没包含在上面的网络图中。架构

从图中能够看到,输入层以后有四个不一样的路径分支。Inception模块中的第一个分支只是从输入中学习一系列1×1局部特征。框架

第二条路径首先应用1×1卷积,不只做为学习局部特征的一种形式,还能够减小维数。较大的卷积(即3×3和5×5)须要更多的计算。所以,若是咱们能够经过应用1×1卷积来减小这些较大过滤器的输入维数,就能够减小网络所需的计算量。机器学习

第三个分支与第二个分支的逻辑相同,区别在于为了学习5×5过滤器。咱们再次经过1×1卷积下降维数,而后将输出馈送到5×5过滤器。ide

Inception模块的第四个分支以1×1的步幅执行3×3最大池化 - 该分支一般被称为池投影分支。函数

最后,Inception模块的全部四个分支汇聚在一块儿,它们沿着通道维度链接在一块儿。在实现过程当中要特别当心(经过零填充)以确保每一个分支的输出具备相同的卷大小,从而容许链接输出。

Miniception

最初的Inception模块是为GoogLeNet设计的,在ImageNet数据集上训练(其中每一个输入图像假设为224×224×3)并得到最好的精度。对于较小的数据集(具备较小的图像空间维度),咱们能够简化Inception模块,只须要较少的网络参数。好比下图表示的Miniception:

  • :卷积模块,负责执行卷积、批量正则化和激活。

  • :Miniception模块执行两组卷积,一组用于1×1滤波器,另外一组用于3×3滤波器,而后链接结果。在3×3滤波器以前不执行降维,由于咱们将使用CIFAR-10数据集,输入已经很小。

  • :下采样模块,它同时应用卷积和最大池化以下降维度,而后在过滤器维度上链接。

将这些模块堆叠起来,能够组成称之为MiniGoogleNet的模型结构,以下图所示:

实现MiniGoogleNet

有了上面的模型定义,接下来咱们就可使用Keras框架来实现之。但在编码以前,咱们先了解一下Keras中的两种类型的模型。

  • 序列(Sequential)模型: 在咱们以前代码中用到的模型为序列模型,它是最简单的线性结构,从头至尾顺序链接,不分叉。其常见操做是 model.add 进行堆叠。好比:
model.add(Dense(32, activation='relu', input_dim=100))
model.add(Dropout(0.25))
复制代码
  • 函数式API:它比序列模型复杂,能够同时/分阶段输入变量,分阶段输出想要的模型。其常见形式是 output = Layer(parameters)(input) ,输入像函数参数同样传递进去,好比:
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》这本书,在微信公众号后台回复“计算机视觉”关键字,能够免费下载这本书的电子版。

image
相关文章
相关标签/搜索