深度学习基础(三)NIN_Network In Network

该论文提出了一种新颖的深度网络结构,称为“Network In Network”(NIN),以增强模型对感受野内local patches的辨别能力。与传统的CNNs相比,NIN主要的创新点在于结构内使用的mlpconv layers(multiple layer perceptron convolution layers)和global average pooling。下面先介绍二者:

  • MLP Convolution Layers

                      1540214003(1)

如Fig.1所示,传统卷积网络中的 linear convolution layer由linear filter+nonlinear activation构成,而mlpconv layer内部是一个micro network(在论文中作者选择multilayer perceptron(MLP)作为 micro network)。作者之所以尝试寻找一种新的layer代替 linear convolution layer,是因为传统的卷积层存在着明显的缺陷。一是CNN中的卷积核是 data patch 上的一个广义线性模型(Generalized  linear model,GLM),它的 abstraction 程度较低(这里的abstraction是指特征对同一概念(concept)的变体是不变的)。如果将GLM换成一个更有效的非线性函数逼近器就能够增强 local model 的abstraction能力。二是当latent concepts的samples线性可分时,GLM才能达到很好的abstraction程度,比如concepts的变体全都在GLM定义的分界面一侧,我们在使用传统卷积时实际上是假设 the latent concepts是线性可分的。但是,相同concept的data往往是呈非线性流形(nonlinear manifold)分布的,因而对应那些concepts的表示(representations)通常是输入的高度非线性函数。当latnet concepts的samples是线性可分时,linear convolution的abstraction能力是足够的。线性不可分时,传统的CNN会通过利用一系列完备的filters覆盖latent concepts的所有变体来弥补线性划分的不足。也就是说,对于同一个concept,使用不同的linear filters来检测不同的变体(variations)。但是,单个concept有太多filters的话下一层需要考虑到所有来自前面layers的combinations of variations,这会给下一层增加额外的负担。正如CNN中那样,来自higher layers的filters会在原始输入上映射出更大的区域,这样通过combinig来自低层的lower level concepts会产生一个higher level的concept。因此作者认为,在将lower level concepts combining成higher level concepts之前,在每一个local patch上进行更好的abstraction会很有意义。(patch:每次filter进行卷积时input或faeture maps参数计算的小区域;concept: 应该是希望检测的objects的高级特征,如船舶、飞机等,论文后面说是categories;abstraction: 对同一concept的变体提取的特征不变的特征提取)

为此,作者将GLM替换为一个“micro network”结构,它是一个通用非线性函数逼近器(general nonlinear function approximator)。论文中作者选择多层感知器(multilayer perceptron,MLP)作为micro network,原因有如下两点:

1) 多层感知器与卷积神经网络结构兼容,可以使用反向传播算法进行训练

2) 多层感知器本身可以是一个深度模型(Deep model),这与feature re-use的精神是一致的

mlpconv layer执行的计算如下:

                       1540261874(1)

式中,n是多层感知器层数,从max()也可以看出多层感知器中使用的激活函数是Rectified linear unit。该计算过程等同于在一个传统的卷积层之后连接级联的跨通道参数池化层。每一个池化层先在输入的feature maps上进行加权线性重构,然后将结果输入一个rectifier linear unit激活。跨通道池化得到feature maps接下来会在后面的layers中重复被跨通道池化。这种级联的跨通道参数池化结构(cascaded cross channel parameteric pooling structure)允许复杂的可学习的跨通道信息交互。

跨通道参数池化层就相当于一个卷积核为1*1的 linear convolution layer(包括激活函数)。这种解释有助于直观理解NIN的结构。


此外,论文中还解释了为何micro network不使用maxout network。如下:

maxout network通过对affine feature maps进行最大池化达到减少feature maps数量的目的(affine feature maps是 linear convolution 不带activation function直接计算得到的结果)。最大池化线性函数的结果能够得到一个可以逼近任何凸函数的分段线性逼近器,所以与进行线性划分的传统卷积层相比,maxout network效果更好,因为它能够对位于凸集(Convex set)内的concepts进行正确划分。

但是,maxout network默认在输入空间中latent concepts的samples分布在一个凸集内,这一先验知识却不一定成立。考虑到distributions of the latent concepts的复杂性,我们需要的是一个更加通用的函数逼近器(more general function approximator),比如MLP


  • Global Average Pooling

传统的CNNs在网络的lower layers执行卷积。在分类任务中,网络最后一层卷积层的feature maps被向量化,并且随后被送入全连接层与softmax logistic regression layer。这种结构结合了convolutional structure和传统的neural network classifiers,将卷积层视为feature extractors,然后使用传统的方法对得到的特征进行分类。但是,全连接层在训练时容易overfitting,后来采用regularizer方法dropout大幅减轻了过拟合。

在该论文中,作者提出了另外一种叫做global average pooling的方法来代替传统的全连接层。具体是:在最后一层mlpconv layer中为分类任务中的每一个类生成一个feature map;后面不接全连接网络,而是取每个feature map的均值,这样就得到一个长度为N的向量,与类别对应;添加softmax层,前面的向量直接传入计算,这样就得到了每个类别的概率。

与全连接层相比,全局平均池化有三个优点:

1) 全局平均池化强制建立feature maps和categories之间的对应关系,这使它更适用于卷积结构。因此feature maps可以很容易地被转换成categories confidence maps

2)全局平均池化层中没有需要优化的参数,因而能够避免overfitting

3)全局平均池化对空间信息进行汇总,因此它对输入的空间转换更加鲁棒

Global average pooling 可以被视为一个能够将feature maps强制转换为concepts(categories)的confidence maps的structural regularizer,因为mlpconv layers能比GLMs更好地逼近confidence maps


  • Network In Network Structure

论文中有句概括性的话,“NIN is proposed from a more general perspective, the micro network is integrated into CNN structure in persuit of better abstractions for all levels of features”。翻译过来就是,NIN是从一个更一般的角度(非线性划分)提出的,将micro network 整合到CNN结构中是为了更好地abstract所有levels的特征。

NIN的整个结构是由一系列mlpconv layers + global average pooling +  objective cost layer 构成。除此之外,可以像在CNN和maxout network中那样在mlpconv layers之间添加sub-sampling layers。

下面Fig.2是一个NIN结构,由3层mlpconv layers堆叠+1层global average pooling layer构成,每个mlpconv layer内包含一个3层的感知器。:

                       1540262118(1)

Fig.2很好理解,有点困惑的可能是mlpconv layers。前面说到过,在mlpconv layers中先进行一次传统的卷积(filter尺寸随意,比如3*3),然后结果经过MLP计算(1*1卷积)得到结果。下面根据下图介绍具体过程:

1) 红框部分是在进行传统的卷积,可见卷积使用了多个filters。图中竖排圆圈是卷积得到的feature maps在某个位置上所有通道的数值,而不是某个feature map。这也是为了简单直

     观。

2) 图中蓝色框部分是MLP的前两层,最后一层在mlpconv layer输出的feature map上,只有一个节点,即蓝色框后面的小立方体

3)MLP是在多组feature maps的同一位置建立的,而且feature maps每个通道内的元素前一组feature maps中对应元素连接的权重相同(想象1*1卷积)

4)MLP计算过程中得到的feature maps长宽一致。理解不了的话可以从1*1卷积的角度思考

                      1540298326(1)

下面是一幅带参数的NIN,可以帮助我们理解mlpconv layers:

                      1540298375(1)

下图是Fig.2中NIN结构的细节:

                    1540298434(1)

看到网上有人针对上图中的结构写了实现代码就搬过来了。如下,dropout设置为0.5weight decay设置为0.0001,使用data augmentation。在数据的预处理上采用减掉mean再除以std的方法:

import keras
import numpy as np
from keras.datasets import cifar10
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Conv2D, MaxPooling2D, GlobalAveragePooling2D, AveragePooling2D
from keras.initializers import RandomNormal  
from keras import optimizers
from keras.callbacks import LearningRateScheduler, TensorBoard
from keras.layers.normalization import BatchNormalization

batch_size    = 128
epochs        = 164
iterations    = 391
num_classes   = 10
dropout       = 0.5
log_filepath  = './nin'

def color_preprocessing(x_train,x_test):
    x_train = x_train.astype('float32')
    x_test = x_test.astype('float32')
    mean = [125.307, 122.95, 113.865]
    std  = [62.9932, 62.0887, 66.7048]
    for i in range(3):
        x_train[:,:,:,i] = (x_train[:,:,:,i] - mean[i]) / std[i]
        x_test[:,:,:,i] = (x_test[:,:,:,i] - mean[i]) / std[i]

    return x_train, x_test

def scheduler(epoch):
  learning_rate_init = 0.08
  if epoch >= 81:
    learning_rate_init = 0.01
  if epoch >= 122:
    learning_rate_init = 0.001
  return learning_rate_init

def build_model():
  model = Sequential()

  model.add(Conv2D(192, (5, 5), padding='same', kernel_regularizer=keras.regularizers.l2(0.0001), kernel_initializer=RandomNormal(stddev = 0.01), input_shape=x_train.shape[1:]))
  model.add(Activation('relu'))
  model.add(Conv2D(160, (1, 1), padding='same', kernel_regularizer=keras.regularizers.l2(0.0001), kernel_initializer=RandomNormal(stddev = 0.05)))
  model.add(Activation('relu'))
  model.add(Conv2D(96, (1, 1), padding='same', kernel_regularizer=keras.regularizers.l2(0.0001), kernel_initializer=RandomNormal(stddev = 0.05)))
  model.add(Activation('relu'))
  model.add(MaxPooling2D(pool_size=(3, 3),strides=(2,2),padding = 'same'))

  model.add(Dropout(dropout))

  model.add(Conv2D(192, (5, 5), padding='same', kernel_regularizer=keras.regularizers.l2(0.0001), kernel_initializer=RandomNormal(stddev = 0.05)))
  model.add(Activation('relu'))
  model.add(Conv2D(192, (1, 1),padding='same', kernel_regularizer=keras.regularizers.l2(0.0001), kernel_initializer=RandomNormal(stddev = 0.05)))
  model.add(Activation('relu'))
  model.add(Conv2D(192, (1, 1),padding='same', kernel_regularizer=keras.regularizers.l2(0.0001), kernel_initializer=RandomNormal(stddev = 0.05)))
  model.add(Activation('relu'))
  model.add(MaxPooling2D(pool_size=(3, 3),strides=(2,2),padding = 'same'))

  model.add(Dropout(dropout))

  model.add(Conv2D(192, (3, 3), padding='same', kernel_regularizer=keras.regularizers.l2(0.0001), kernel_initializer=RandomNormal(stddev = 0.05)))
  model.add(Activation('relu'))
  model.add(Conv2D(192, (1, 1), padding='same', kernel_regularizer=keras.regularizers.l2(0.0001), kernel_initializer=RandomNormal(stddev = 0.05)))
  model.add(Activation('relu'))
  model.add(Conv2D(10, (1, 1), padding='same', kernel_regularizer=keras.regularizers.l2(0.0001), kernel_initializer=RandomNormal(stddev = 0.05)))
  model.add(Activation('relu'))

  model.add(GlobalAveragePooling2D())
  model.add(Activation('softmax'))

  sgd = optimizers.SGD(lr=.1, momentum=0.9, nesterov=True)
  model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])
  return model

if __name__ == '__main__':

    # load data
    (x_train, y_train), (x_test, y_test) = cifar10.load_data()
    y_train = keras.utils.to_categorical(y_train, num_classes)
    y_test = keras.utils.to_categorical(y_test, num_classes)

    x_train, x_test = color_preprocessing(x_train, x_test)

    # build network
    model = build_model()
    print(model.summary())

    # set callback
    tb_cb = TensorBoard(log_dir=log_filepath, histogram_freq=0)
    change_lr = LearningRateScheduler(scheduler)
    cbks = [change_lr,tb_cb]

    # set data augmentation
    print('Using real-time data augmentation.')
    datagen = ImageDataGenerator(horizontal_flip=True,width_shift_range=0.125,height_shift_range=0.125,fill_mode='constant',cval=0.)
    datagen.fit(x_train)

    # start training
    model.fit_generator(datagen.flow(x_train, y_train,batch_size=batch_size),steps_per_epoch=iterations,epochs=epochs,callbacks=cbks,validation_data=(x_test, y_test))
    model.save('nin.h5')

此外,作者又在这份代码的基础上加入了batch normalization改进该网络,见此。具体是其它地方完全不动,只在conv和activation之间加入一个bn层,如下:

model.add(Conv2D(192, (5, 5), padding='same', kernel_regularizer=keras.regularizers.l2(0.0001), kernel_initializer=RandomNormal(stddev = 0.01), input_shape=x_train.shape[1:]))
 model.add(BatchNormalization())
 model.add(Activation('relu'))


  • Experiment

实验内容见论文或下方的翻译链接


  • 参考文献

如何理解NIN网络中的mlpconv层

多层感知机:Multi-Layer Perceptron

[翻译]Network In Network

给妹纸的深度学习教学(2)——拿NIN试水

posted @ 2019-03-24 14:36  Lilu1223  Views(1867)  Comments(0Edit  收藏  举报