美文网首页人工智能
MobileNet原理+手写python代码实现MobileNe

MobileNet原理+手写python代码实现MobileNe

作者: huachao1001 | 来源:发表于2018-06-30 13:00 被阅读216次

    MobileNet是针对移动端优化的卷积,所以当需要压缩模型时,可以考虑使用MobileNet替换卷积。下面我们开始学习MobileNet原理,并且先通过Tensorflow函数接口实现MobileNet,再手写python代码实现MobileNet。

    转载请注明出处:【huachao1001的简书:https://www.jianshu.com/p/7bef96816f7d】

    1 对比普通卷积和MobileNet原理

    MobileNet是用于替换普通卷积,相比普通卷积,MobileNet参数更少,计算速度更快。我们先看一下输入为(h=12,w=12,c=4),卷积为3*3,输出为(h=12,w=12,c=2)前向计算中,普通卷积的参数量、乘法计算次数。普通卷积如下图所示:


    普通卷积

    从上图可以很简单的计算到,普通卷积参数总数为72个,需要做10368次乘法计算。

    相比普通卷积,MobileNet采用的方法是,将卷积分解为2个操作:depthwise和pointwise。pointwise比较容易理解,就是普通的卷积核为11的卷积。depthwise采用的方法不是普通卷积方式,我们知道,对于输入通道数为4的feature map在计算卷积时,输出的每个通道都需要对应4个33卷积核参数。这一步是最主要的耗时,为了提升计算速度,MobileNet把每个输入feature map对应一个33卷积核,输出通道数不变,即为4。而真正对通道数做改变的是在pointwise,也就是11的卷积。

    注意:上面面论述针对的是输入为(h=12,w=12,c=4),卷积为3*3,输出为(h=12,w=12,c=2) 这种情况举例说明。

    下面图很清晰的理解mobilenet原理:

    mobilenet

    从上图可以很简单的计算到,普通卷积参数总数为44个,需要做6336次乘法计算。可以看到,mobilenet的参数和乘法计算次数明显比普通卷积要小。这还仅仅是我列举的简单例子,在实际网络中,几十层的网络很常见,feature map也是远远大于12124。根据我的经验,普通100M的网络模型,将所有卷积替换成mobilenet后,能降到20M以下,计算速度更是不在一个量级。

    2 Tensorflow中使用MobileNet

    在Tensorflow中,有depthwise对应的函数接口,直接调用就可以了。由于pointwise就是普通的卷积核大小为1*1的卷积,而卷积的原理,我们在《Tensorflow卷积实现原理+手写python代码实现卷积》一文中已经讲的很清楚了。所以我们只要关注depthwise即可。
    在Tensorflow中,depthwise操作接口是:

    tf.nn.depthwise_conv2d(
        input,
        filter,
        strides,
        padding,
        rate=None,
        name=None,
        data_format=None
    )
    

    假设我们的输入和卷积核如下:

     #输入,shape=[c,h,w]=[2,5,5]
    input_data=[
                  [[1,0,1,2,1],
                   [0,2,1,0,1],
                   [1,1,0,2,0],
                   [2,2,1,1,0],
                   [2,0,1,2,0]],
    
                   [[2,0,2,1,1],
                    [0,1,0,0,2],
                    [1,0,0,2,1],
                    [1,1,2,1,0],
                    [1,0,1,1,1]],
    
                ]
    #卷积核,shape=[in_c,k,k]=[2,3,3]
    weights_data=[ 
                   [[ 1, 0, 1],
                    [-1, 1, 0],
                    [ 0,-1, 0]],
                   [[-1, 0, 1],
                    [ 0, 0, 1],
                    [ 1, 1, 1]] 
                 ]
    

    下面我们贴上完整调用depthwise的代码:

    import tensorflow as tf
    def get_shape(tensor):
        [s1,s2,s3]= tensor.get_shape() 
        s1=int(s1)
        s2=int(s2)
        s3=int(s3)
        return s1,s2,s3
    def chw2hwc(chw_tensor): 
        [c,h,w]=get_shape(chw_tensor) 
        cols=[]
    
        for i in range(c):
            #每个通道里面的二维数组转为[w*h,1]即1列 
            line = tf.reshape(chw_tensor[i],[h*w,1])
            cols.append(line)
    
        #横向连接,即将所有竖直数组横向排列连接
        input = tf.concat(cols,1)#[w*h,c]
        #[w*h,c]-->[h,w,c]
        input = tf.reshape(input,[h,w,c])
        return input
    
    def hwc2chw(hwc_tensor):
        [h,w,c]=get_shape(hwc_tensor) 
        cs=[] 
        for i in range(c): 
            #[h,w]-->[1,h,w] 
            channel=tf.expand_dims(hwc_tensor[:,:,i],0)
            cs.append(channel)
        #[1,h,w]...[1,h,w]---->[c,h,w]
        input = tf.concat(cs,0)#[c,h,w]
        return input
    def tf_depthwise(input,weights ):
        depthwise=tf.nn.depthwise_conv2d( input, weights, [1, 1, 1, 1], padding='SAME' ) 
        return depthwise
    def main(): 
        const_input = tf.constant(input_data , tf.float32)
        const_weights = tf.constant(weights_data , tf.float32 ) 
        input = tf.Variable(const_input,name="input")
        #[2,5,5]------>[5,5,2]
        input=chw2hwc(input)
        #[5,5,2]------>[1,5,5,2]
        input=tf.expand_dims(input,0) 
        weights = tf.Variable(const_weights,name="weights")
        #[2,3,3]-->[3,3,2]
        weights=chw2hwc(weights)
        #[3,3,2]-->[3,3,2,1]
        weights=tf.expand_dims(weights,3) 
        print(weights.get_shape().as_list())
    
        #[b,h,w,c]
        conv=tf_depthwise(input,weights )
        rs=hwc2chw(conv[0]) 
    
        init=tf.global_variables_initializer()
        sess=tf.Session()
        sess.run(init)
        conv_val = sess.run(rs)
    
        print(conv_val) 
    
    
    if __name__=='__main__':
        main()
    

    打印结果如下:

    [[[ 1. -3.  0.  1. -2.]
      [-1.  3.  1. -1.  3.]
      [ 1. -1.  0.  3. -2.]
      [ 1.  1.  1. -2.  1.]
      [ 4.  1.  4.  2. -1.]]
    
     [[ 1.  3.  2.  3.  2.]
      [ 2.  1.  3.  4.  2.]
      [ 3.  4.  5.  6.  1.]
      [ 2.  3.  5.  4.  0.]
      [ 1.  2.  1. -1. -1.]]]
    

    我们通过一个动画演示计算过程:
    [图片上传失败...(image-f2e073-1530334796526)]

    3 手写python代码实现depthwise

    import numpy as np
    input_data=[
                  [[1,0,1,2,1],
                   [0,2,1,0,1],
                   [1,1,0,2,0],
                   [2,2,1,1,0],
                   [2,0,1,2,0]],
    
                   [[2,0,2,1,1],
                    [0,1,0,0,2],
                    [1,0,0,2,1],
                    [1,1,2,1,0],
                    [1,0,1,1,1]] 
                ]
    weights_data=[ 
                   [[ 1, 0, 1],
                    [-1, 1, 0],
                    [ 0,-1, 0]],
                   [[-1, 0, 1],
                    [ 0, 0, 1],
                    [ 1, 1, 1]] 
    
               ]
    
    #fm:[h,w]
    #kernel:[k,k]
    #return rs:[h,w] 
    def compute_conv(fm,kernel):
        [h,w]=fm.shape
        [k,_]=kernel.shape 
        r=int(k/2)
        #定义边界填充0后的map
        padding_fm=np.zeros([h+2,w+2],np.float32)
        #保存计算结果
        rs=np.zeros([h,w],np.float32)
        #将输入在指定该区域赋值,即除了4个边界后,剩下的区域
        padding_fm[1:h+1,1:w+1]=fm 
        #对每个点为中心的区域遍历
        for i in range(1,h+1):
            for j in range(1,w+1): 
                #取出当前点为中心的k*k区域
                roi=padding_fm[i-r:i+r+1,j-r:j+r+1]
                #计算当前点的卷积,对k*k个点点乘后求和
                rs[i-1][j-1]=np.sum(roi*kernel)
    
        return rs
    
    def my_depthwise(chw_input,chw_weights):
        [c,_,_]=chw_input.shape
        [_,k,_]=chw_weights.shape
        #outputs=np.zeros([h,w],np.float32)
        outputs=[] #注意跟conv的区别
        #对每个feature map遍历,从而对每个feature map进行卷积
        for i in range(c):
            #feature map==>[h,w]
            f_map=chw_input[i]
            #kernel ==>[k,k]
            w=chw_weights[i]
             
            rs =compute_conv(f_map,w)
            #outputs=outputs+rs   
            outputs.append(rs) #注意跟conv的区别
        return np.array( outputs)
    
    def main():  
    
        #shape=[c,h,w]
        input = np.asarray(input_data,np.float32)
        #shape=[in_c,k,k]
        weights =  np.asarray(weights_data,np.float32) 
        rs=my_depthwise(input,weights) 
        print(rs) 
    
    
    if __name__=='__main__':
        main() 
    

    同样,注释写的很清楚,不再解释代码。运行结果如下:

    [[[ 1. -3.  0.  1. -2.]
      [-1.  3.  1. -1.  3.]
      [ 1. -1.  0.  3. -2.]
      [ 1.  1.  1. -2.  1.]
      [ 4.  1.  4.  2. -1.]]
    
     [[ 1.  3.  2.  3.  2.]
      [ 2.  1.  3.  4.  2.]
      [ 3.  4.  5.  6.  1.]
      [ 2.  3.  5.  4.  0.]
      [ 1.  2.  1. -1. -1.]]]
    

    可以看到,跟tensorflow的结果是一模一样。

    相关文章

      网友评论

      本文标题:MobileNet原理+手写python代码实现MobileNe

      本文链接:https://www.haomeiwen.com/subject/avetuftx.html