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

简介: MobileNet原理+手写python代码实现MobileNet

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


最近看到一个巨牛的人工智能教程,分享一下给大家。教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。平时碎片时间可以当小说看,【点这里可以去膜拜一下大神的“小说”】。

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

转载请注明出处:【huachao1001的专栏:https://blog.csdn.net/huachao1001/article/details/79171447

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原理:


从上图可以很简单的计算到,普通卷积参数总数为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原理:

从上图可以很简单的计算到,普通卷积参数总数为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.]]]

我们通过一个动画演示计算过程:

微信图片_20221214203650.gif

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的结果是一模一样。

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1dx8guzo2jdpp

相关文章
|
8月前
|
测试技术 Python
Python装饰器:为你的代码施展“魔法”
Python装饰器:为你的代码施展“魔法”
381 100
|
8月前
|
开发者 Python
Python列表推导式:一行代码的艺术与力量
Python列表推导式:一行代码的艺术与力量
571 95
|
9月前
|
Python
Python的简洁之道:5个让代码更优雅的技巧
Python的简洁之道:5个让代码更优雅的技巧
380 104
|
9月前
|
开发者 Python
Python神技:用列表推导式让你的代码更优雅
Python神技:用列表推导式让你的代码更优雅
665 99
|
8月前
|
缓存 Python
Python装饰器:为你的代码施展“魔法
Python装饰器:为你的代码施展“魔法
458 88
|
8月前
|
监控 机器人 编译器
如何将python代码打包成exe文件---PyInstaller打包之神
PyInstaller可将Python程序打包为独立可执行文件,无需用户安装Python环境。它自动分析代码依赖,整合解释器、库及资源,支持一键生成exe,方便分发。使用pip安装后,通过简单命令即可完成打包,适合各类项目部署。
1394 68
|
8月前
|
数据可视化 关系型数据库 MySQL
【可视化大屏】全流程讲解用python的pyecharts库实现拖拽可视化大屏的背后原理,简单粗暴!
本文详解基于Python的电影TOP250数据可视化大屏开发全流程,涵盖爬虫、数据存储、分析及可视化。使用requests+BeautifulSoup爬取数据,pandas存入MySQL,pyecharts实现柱状图、饼图、词云图、散点图等多种图表,并通过Page组件拖拽布局组合成大屏,支持多种主题切换,附完整源码与视频讲解。
794 4
【可视化大屏】全流程讲解用python的pyecharts库实现拖拽可视化大屏的背后原理,简单粗暴!
|
9月前
|
设计模式 人工智能 API
AI智能体开发实战:17种核心架构模式详解与Python代码实现
本文系统解析17种智能体架构设计模式,涵盖多智能体协作、思维树、反思优化与工具调用等核心范式,结合LangChain与LangGraph实现代码工作流,并通过真实案例验证效果,助力构建高效AI系统。
967 7
|
8月前
|
机器学习/深度学习 监控 数据挖掘
Python 高效清理 Excel 空白行列:从原理到实战
本文介绍如何使用Python的openpyxl库自动清理Excel中的空白行列。通过代码实现高效识别并删除无数据的行与列,解决文件臃肿、读取错误等问题,提升数据处理效率与准确性,适用于各类批量Excel清理任务。
710 0
|
9月前
|
JSON 缓存 开发者
淘宝商品详情接口(item_get)企业级全解析:参数配置、签名机制与 Python 代码实战
本文详解淘宝开放平台taobao.item_get接口对接全流程,涵盖参数配置、MD5签名生成、Python企业级代码实现及高频问题排查,提供可落地的实战方案,助你高效稳定获取商品数据。

推荐镜像

更多