MobileNet-v1:
MobileNet主要用于移动端计算模型,是将传统的卷积操作改为两层的卷积操作,在保证准确率的条件下,计算时间减少为原来的1/9,计算参数减少为原来的1/7.
MobileNet模型的核心就是将原本标准的卷积操作因式分解成一个depthwise convolution和一个1*1的pointwise convolution操作。简单讲就是将原来一个卷积层分成两个卷积层,其中前面一个卷积层的每个filter都只跟input的每个channel进行卷积,然后后面一个卷积层则负责combining,即将上一层卷积的结果进行合并。
depthwise convolution:
比如输入的图片是Dk*Dk*M(Dk是图片大小,M是输入的渠道数),那么有M个Dw*Dw的卷积核,分别去跟M个渠道进行卷积,输出Df*Df*M结果
pointwise convolution:
对Df*Df*M进行卷积合并,有1*1*N的卷积,进行合并常规的卷积,输出Df*Df*N的结果
上面经过这两个卷积操作,从一个Dk*Dk*M=>Df*Df*N,相当于用Dw*Dw*N的卷积核进行常规卷积的结果,但计算量从原来的DF*DF*DK*DK*M*N减少为DF*DF*DK*DK*M+DF*DF*M*N.
网络结构:
第一层为常规卷积,后面接着都为depthwise convolution+pointwise convolution,最后两层为Pool层和全连接层,总共28层.
MobileNet-v2:
两点改变:
(1) . Inverted residuals(倒置残差) : 通常的residuals block是通过1*1的卷积核将渠道减小,再经过3*3卷积,最后再通过1*1的卷积核从而将渠道数扩大回去,从而减少计算量.而Inverted residuals刚好相反,通过先扩大渠道数再进行卷积,最后再缩小回原来的渠道数.基本思想就是,通过将渠道数扩大,从而在中间层学到更多的特征,最后再总结筛选出优秀的特征出来.
(2) . Linear bottlenecks : 为了避免Relu函数对特征的损失,在最后经过1*1的卷积缩小渠道数后,放弃采用Relu激活函数,采用线性激活函数,在进行Eltwise sum操作.基本思想便是:在经历了1*1的卷积,降低渠道数,这个已经使某些信息丢失,而外,当特征渠道数变小,大部分的值都会趋向小于0,最后经过Relu激活函数,将再导致丢失一些特征信息.
MobileNet-v1 和 MobileNet-v2的对比:
MobileNet-v2 和 ResNet对比:
MobileNet_v2模型结构:
里面有两个地方弄错了:
(1) : block_7_3的第一个pw的卷积核由1*1*96改为1*1*960
(2) : block_11的输入图片由1^2*num_class改为1^2*1280
tensorflow相关实现代码:
#mobilenet_v1
def mobilenet_v1(x, input_filters, output_filters, kernel, strides , mode):
with tf.variable_scope('mobilenet_v1'):
depthwise_filter = tf.Variable(tf.truncated_normal([kernel,kernel,input_filters,1], stddev=0.1), name='dw_weight')
pointwise_filter = tf.Variable(tf.truncated_normal([1,1,input_filters,output_filters], stddev=0.1), name='pw_weight')
x_padded = tf.pad(x, [[0, 0], [kernel // 2, kernel // 2], [kernel // 2, kernel // 2], [0, 0]], mode=mode)
return tf.nn.separable_conv2d(x_padded, depthwise_filter, pointwise_filter, strides = [1, strides, strides, 1], padding = 'VALID')
#mobilenet_v2
def mobilenet_v2(x, input_filters, output_filters, kernel, strides , mode):
with tf.variable_scope('mobilenet_v2'):
x_padded = tf.pad(x, [[0, 0], [kernel // 2, kernel // 2], [kernel // 2, kernel // 2], [0, 0]], mode=mode)
pw_wight_1 = tf.Variable(tf.truncated_normal([1,1,input_filters,input_filters*4], stddev=0.1), name='pw_weight_1')
pw_1 = relu(tf.nn.conv2d(x_padded, pw_wight_1, strides=[1, 1, 1, 1], padding='VALID', name='pw_1'))
depthwise_filter = tf.Variable(tf.truncated_normal([kernel,kernel,input_filters*4 ,1], stddev=0.1), name='dw_weight')
pointwise_filter = tf.Variable(tf.truncated_normal([1,1,input_filters*4,output_filters], stddev=0.1), name='pw_weight')
return tf.nn.separable_conv2d(pw_1, depthwise_filter, pointwise_filter, strides = [1, strides, strides, 1], padding = 'VALID')