个人博客:http://www.chenjianqu.com/
原文链接:http://www.chenjianqu.com/show-65.html
论文笔记
1.解决了什么
改进CNN。
2.提出的模型
提出mlpconv,引入了1x1卷积和global average pooling,提出Network In Network(NIN),整个模型未使用全连接。
3.实验结果
在CIFAR-10和CIFAR-100上面获得SOTA结果。
4.待解决的问题
模型不够深,迁移能力有待加强。
卷积神经网络(CNN)
卷积神经网络一般由卷积层、池化层和全连接层组成,如下图:
卷积层的工作原理:卷积核与局部感受野(local receptive field)点积得到特征图(feature map),举例如下:
模型提出
传统卷积层的滤波器/卷积核是局部感受野的广义线性模型(GLM),作者认为GLM的抽象层级很低,使用更有效的非线性函数逼近器(nonlinear function approximator)可以提高卷积层的抽象能力。当隐含概念(latent concepts)的样本是线性可分时,即概念的变体都位于GLM定义的分离平面的一侧时,GLM可以实现很好的特征抽象。所以CNN隐含的假设是latent concepts是线性可分的。
但是实际上相同概念的数据通常存在于非线性流形(nonlinear manifold)上,需要通过高度非线性函数去捕捉这些概念表示。在NIN中,使用微型网络(micro network)结构替代GLM,micro network是一个广义非线性函数逼近器,这里使用多层感知器(MLP)作为micro network。下图是线性卷积层和多层感知器卷积层(MlpConv Layer):
线性卷积层和mlpconv都是将局部感受野(local receptive field)映射到输出特征向量。Mlpconv核使用带非线性激活函数的MLP,跟传统的CNN一样,MLP在各个局部感受野中共享参数的,滑动MLP核可以最终得到输出特征图。NIN通过多个mlpconv的堆叠得到。
在NIN中,卷积层后不接全连接层(FC),而是将最后一个的mlpconv的输出特征图全局平均池化(global average pooling,GAP),而后softmax。
使用GAP有个好处是增加模型的可解释性。传统的CNN中,因为FC层是一个黑盒,所以很难知道类别信息(category level information)是如何传到前面的卷积层的。而GAP强制要求特征图对应类别信息,因此GAP的可解释性更强。此外,相比于FC,GAP的泛化性能更好,更不容易过拟合。
线性卷积层
传统的线性卷积层的计算公式如下:
其中i,j是输出特征图的索引,x_ij是输入图片的局部感受野的索引,k是输出特征图的通道索引,wk是第k个卷积核,max(A,0)代表使用relu激活函数。
前面说过,当隐含概念是线性可分时,线性卷积层对于抽象是足够的。线性卷积层需要通过一组over-complete的卷积核来覆盖所有隐含概念的变体,一个滤波器检测同一概念的变体。然而,一个概念有太多的卷积核会给下一层卷积层带来额外的负担,因为它需要考虑来自上一层的概念所有变化组合。在CNN中,后面的卷积层滤波器对应原始输入的较大区域,滤波器通过联合上一层输出的较低层的特征来生成更高层次的特征。由此可见,每一层抽象程度更高是有利的。
跨通道池化(CCP)
这个概念在论文Maout Networks中有解释。假设你有一个线性模块,该模型有50个输出通道,但是你想要的是5个输出通道。就可以使用cross-channel pooling来对channel进行下采样(可以使用最大池化)。具体说CCP输出特征图中的每个点是10个原始特征图中对应点的最大值。
MLP卷积层
需要用一个通用函数逼近器来抽取局部感受野的特征,最常用的有径向基网络和多层感知器。选择MLP的理由是:1.MLP与卷积神经网络兼容,都使用BP训练;2. 多层感知器本身是一个深度模型,这与特征重用的思想是一致。使用MLP代替原来卷积层的GLM,新的层叫做mlpconv。mlpconv的计算如下:
这里的n是mlp的层数,mlp使用relu作为激活函数。
从跨通道池化的观点看,上式等价于在普通卷积层上级联跨通道参数池化层(cascaded cross channel parametric pooling,CCCP),每一个池化层对输入特征图进行加权线性重组,然后通过一个非线性激活函数。直白地说CCCP就是1x1卷积。这种结构允许复杂和可学习的跨通道信息交互。
全局平均池化(GAP)
传统的CNN模型先是使用堆叠的卷积层提取特征,输入全连接层(FC)进行分类。这种结构沿袭自LeNet5,把卷积层作为特征抽取器,全连接层作为分类器。但是FC层参数数量太多,很容易过拟合,会影响模型的泛化性能。因此需要用Dropout增加模型的泛化性。
这里提出GAP代替传统的FC层。主要思想是零每个分类对应最后一层mlpconv的输出特征图。对每个特征图取平均,后将得到的池化后的向量softmax得到分类概率。GAP的优点:
加强了特征映射和类别之间的对应,更适合卷积神经网络,特征图可以被解释类别置信度。
GAP层不用优化参数,可以避免过拟合。
GAP对空间信息进行汇总,因此对输入数据的空间变换有更好的鲁棒性。
可以将GAP看做一个结构正则化器,显性地强制特征图映射为概念置信度。
Network In Network(NIN)
NIN是由mlpconv堆叠而成,顶部是GAP层,在mlpconv层之间加入下采样层。下图是有三层mlpconv层的NIN,每个mlpconv层里有3层的mlp。
NIN是基于AlexNet 改进而来,因此卷积层的其它参数设置和alexnet类似。
NIN训练
使用四个基准数据集评估NIN模型:CIFAR-10 CIFAR-100 SVHN MNIST。
模型架构:三层mlpconv堆叠而成,每个mlpconv层后接最大池化层,size=2,stride=2。最后接GAP。
Dropout:除了最后的mlpconv层外,其他层均使用dropout。
正则化:所有权重应用L2正则化。
数据集预处理和及其它训练过程与AlexNet类似。
NIN对比实验
1. CIFAR-10数据集
对这个数据集,作者应用了相同全局对比度归一化和ZCA白化进行处理,这和Maxout论文里的类似。训练时先通过验证集调整local receptive field size和weight decay,当确定好这两个超参数后,重新训练网络。
首先测试有无dropout的影响,结果如下:
从上图可以看到,在mlpconv层之间引入dropout可以减少20%以上的测试误差,因此这里所有测试的模型都加入了dropout。NIN无数据增强的情况下最终得到10.41%的测试错误率,加入数据增强后达到8.81%。如下图:
2. CIFAR-100数据集
3. Street View House Number数据集
4. MNIST数据集
GAP对比实验
GAP和FC类似,都是对向量化的特征映射进行线性转换,不同的地方是变换矩阵的不同。GAP的变换矩阵is prefixed and it is non-zero only on block diagonal elements which share the same value.(没理解)。FC有稠密的变换矩阵,变换矩阵的值由BP优化。FC和GAP对比如下:
作者还探索了GAP在传统的CNN模型上是否也有更好的效果。比如将LeNet5改造,使其最后一个卷积层输出10通道特征图,然后用GAP替换FC+Dropout。在CIFAR-10上测试。
结果对比:仅仅FC的测试错误率为17.56%,FC+Dropout的测试错误率为15.99%,GAP的测试错误率为16.46%。由此可知GAP具有正则化的作用。之所以比FC+Dropout差,作者说是因为GAP对线性卷积层的要求高,需要带ReLU的线性滤波器去建模类别置信度映射。
NIN可视化
对CIFAR-10上的模型的特征图进行可视化。如下图:
很明显最大激活的的特征图对应ground truth。可视化再次证明了NIN的有效性,它通过mlpconv层进行更强的局部接受域建模,全球平均池加强了类别级特征映射的学习。
代码实现
使用tensorflow.slim,基于CIFAR-10数据集训练NIN网络。
-
CIFAR-10数据集预处理
import numpy as np from keras.datasets import cifar10 import keras num_classes=10 (x_train, y_train), (x_test, y_test) = cifar10.load_data() y_train = keras.utils.to_categorical(y_train, num_classes) # one-hot 编码 y_test = keras.utils.to_categorical(y_test, num_classes) # one-hot 编码 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 # 数据集减去均值,除以标准差 x_train, x_test = color_preprocessing(x_train, x_test)
2.模型定义
import tensorflow as tf from tensorflow.contrib.layers import xavier_initializer from tensorflow import name_scope as namespace slim = tf.contrib.slim REGULARIZER=0.0005 def NIN(inputs): with slim.arg_scope([slim.conv2d], stride=1, activation_fn=tf.nn.relu, padding='SAME', weights_initializer=xavier_initializer(), weights_regularizer=slim.l2_regularizer(REGULARIZER), biases_regularizer=slim.l2_regularizer(REGULARIZER), biases_initializer=tf.zeros_initializer()): net=inputs with namespace('mlpconv_1'): net = slim.conv2d(net, kernel_size=5,num_outputs=192,scope='mlpconv_1_conv') net = slim.conv2d(net, kernel_size=1,num_outputs=160,scope='mlpconv_1_conv1x1_1') net = slim.conv2d(net, kernel_size=1,num_outputs=96,scope='mlpconv_1_conv1x1_2') net=slim.max_pool2d(net,[3,3],2,padding='SAME',scope='maxpooling1') net = slim.dropout(net, 0.5, scope='dropout1') with namespace('mlpconv_2'): net = slim.conv2d(net, kernel_size=5,num_outputs=192,scope='mlpconv_2_conv') net = slim.conv2d(net, kernel_size=1,num_outputs=192,scope='mlpconv_2_conv1x1_1') net = slim.conv2d(net, kernel_size=1,num_outputs=192,scope='mlpconv_2_conv1x1_2') net=slim.max_pool2d(net,[3,3],2,padding='SAME',scope='maxpooling2') net = slim.dropout(net, 0.5, scope='dropout2') with namespace('mlpconv_3'): net = slim.conv2d(net, kernel_size=3,num_outputs=192,scope='mlpconv_3_conv') net = slim.conv2d(net, kernel_size=1,num_outputs=192,scope='mlpconv_3_conv1x1_1') net = slim.conv2d(net, kernel_size=1,num_outputs=10,scope='mlpconv_3_conv1x1_2') #GAP net= tf.reduce_mean(net, [1, 2], name='GAP', keep_dims=True) outputs=tf.squeeze(net,axis=[1,2],name='reshape',) return outputs
3.模型配置
from tensorflow import name_scope as namespace BATCH_SIZE=128 DATA_LEN=x_train.shape[0] x = tf.placeholder(tf.float32, shape=[None, 32, 32, 3], name='input') y_ = tf.placeholder(tf.float32, [None, 10], name='labels') global_step=tf.Variable(0,trainable=False) y=NIN(x) with namespace('loss'): #softmax并计算交叉熵 #print(y.get_shape().as_list() ) ce_loss = slim.losses.softmax_cross_entropy(y, y_) #交叉熵损失 regularization_loss = tf.add_n(slim.losses.get_regularization_losses())#正则损失 loss=ce_loss+regularization_loss with namespace('train'): #使用指数衰减学习率 learning_rate=tf.train.exponential_decay( 0.01,#初始学习率 global_step, DATA_LEN/BATCH_SIZE,#多少次更新一次学习率 0.99,#学习率衰减率 staircase=True#学习率阶梯下降 ) train_step=tf.train.MomentumOptimizer(learning_rate,0.9,#动量系数 ).minimize(loss,global_step=global_step) with namespace('acc'): correct_prediction=tf.equal(tf.argmax(y,1),tf.argmax(y_,1)) accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32)) tf.summary.scalar('loss',loss) tf.summary.scalar('accuracy',accuracy) merged=tf.summary.merge_all();
4.模型训练,keras的数据生成器太好用了。
#定义数据生成器 from keras.preprocessing import image import os import time epochs=30 TEST_STEP=int(x_test.shape[0]/BATCH_SIZE)-1 datagen = image.ImageDataGenerator( horizontal_flip=True, width_shift_range=0.125, height_shift_range=0.125, fill_mode='constant', cval=0.) datagen.fit(x_train) tg=datagen.flow(x_train, y_train, batch_size=BATCH_SIZE) with tf.Session() as sess: init_op=tf.global_variables_initializer() sess.run(init_op) writer=tf.summary.FileWriter('D:/Jupyter/cv/NIN_log',sess.graph) saver=tf.train.Saver() for i in range(epochs): start = time.clock() for j in range(int(DATA_LEN/BATCH_SIZE)): next_data,next_label=next(tg) summary,_,step=sess.run([merged,train_step,global_step],feed_dict={x:next_data,y_:next_label}) writer.add_summary(summary,step) loss_sum=0.0 acc_sum=0.0 for k in range(TEST_STEP): loss_value,acc=sess.run([loss,accuracy], feed_dict={x:x_test[k*BATCH_SIZE:(k+1)*BATCH_SIZE], y_:y_test[k*BATCH_SIZE:(k+1)*BATCH_SIZE]} ) loss_sum+=loss_value acc_sum+=acc elapsed = (time.clock() - start) print('epochs:%d loss:%f acc:%f time:%f'%(i,loss_sum/TEST_STEP,acc_sum/TEST_STEP,elapsed)) model_save_path = os.path.join(r'D:/Jupyter/cv/NIN_log', 'model.ckpt') saver.save(sess, save_path=model_save_path, global_step=i+1) writer.close()
训练30轮的结果:
参考文献
[1]Min Lin, Qiang Chen, Shuicheng Yan.Network In Network.
[2] bryant_meng.【Keras-NIN】CIFAR-10. https://blog.csdn.net/bryant_meng/article/details/80979211. 2018-11-23