最近看到一個巨牛的人工智能教程,分享一下給大家杖虾。教程不僅是零基礎烂瘫,通俗易懂媒熊,而且非常風趣幽默奇适,像看小說一樣!覺得太牛了芦鳍,所以分享給大家嚷往。平時碎片時間可以當小說看,【點這里可以去膜拜一下大神的“小說”】柠衅。
MobileNet是針對移動端優(yōu)化的卷積皮仁,所以當需要壓縮模型時,可以考慮使用MobileNet替換卷積菲宴。下面我們開始學習MobileNet原理贷祈,并且先通過Tensorflow函數(shù)接口實現(xiàn)MobileNet,再手寫python代碼實現(xiàn)MobileNet喝峦。
轉載請注明出處:【huachao1001的簡書:http://www.reibang.com/p/7bef96816f7d】
1 對比普通卷積和MobileNet原理
MobileNet是用于替換普通卷積势誊,相比普通卷積,MobileNet參數(shù)更少谣蠢,計算速度更快粟耻。我們先看一下輸入為(h=12,w=12,c=4)查近,卷積為3*3,輸出為(h=12,w=12,c=2)前向計算中挤忙,普通卷積的參數(shù)量霜威、乘法計算次數(shù)。普通卷積如下圖所示:
從上圖可以很簡單的計算到册烈,普通卷積參數(shù)總數(shù)為72個戈泼,需要做10368次乘法計算。
相比普通卷積赏僧,MobileNet采用的方法是矮冬,將卷積分解為2個操作:depthwise和pointwise。pointwise比較容易理解次哈,就是普通的卷積核為11的卷積胎署。depthwise采用的方法不是普通卷積方式,我們知道窑滞,對于輸入通道數(shù)為4的feature map在計算卷積時琼牧,輸出的每個通道都需要對應4個33卷積核參數(shù)。這一步是最主要的耗時哀卫,為了提升計算速度巨坊,MobileNet把每個輸入feature map對應一個33卷積核,輸出通道數(shù)不變此改,即為4趾撵。而真正對通道數(shù)做改變的是在pointwise,也就是11的卷積共啃。
注意:上面面論述針對的是輸入為(h=12,w=12,c=4)占调,卷積為3*3,輸出為(h=12,w=12,c=2) 這種情況舉例說明移剪。
下面圖很清晰的理解mobilenet原理:
從上圖可以很簡單的計算到究珊,普通卷積參數(shù)總數(shù)為44個,需要做6336次乘法計算纵苛〗虽蹋可以看到,mobilenet的參數(shù)和乘法計算次數(shù)明顯比普通卷積要小攻人。這還僅僅是我列舉的簡單例子陈瘦,在實際網(wǎng)絡中解滓,幾十層的網(wǎng)絡很常見循帐,feature map也是遠遠大于12124给赞。根據(jù)我的經(jīng)驗,普通100M的網(wǎng)絡模型烙博,將所有卷積替換成mobilenet后瑟蜈,能降到20M以下烟逊,計算速度更是不在一個量級。
2 Tensorflow中使用MobileNet
在Tensorflow中铺根,有depthwise對應的函數(shù)接口宪躯,直接調用就可以了。由于pointwise就是普通的卷積核大小為1*1的卷積位迂,而卷積的原理访雪,我們在《Tensorflow卷積實現(xiàn)原理+手寫python代碼實現(xiàn)卷積》一文中已經(jīng)講的很清楚了。所以我們只要關注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):
#每個通道里面的二維數(shù)組轉為[w*h,1]即1列
line = tf.reshape(chw_tensor[i],[h*w,1])
cols.append(line)
#橫向連接精置,即將所有豎直數(shù)組橫向排列連接
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代碼實現(xiàn)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)
#將輸入在指定該區(qū)域賦值,即除了4個邊界后锣杂,剩下的區(qū)域
padding_fm[1:h+1,1:w+1]=fm
#對每個點為中心的區(qū)域遍歷
for i in range(1,h+1):
for j in range(1,w+1):
#取出當前點為中心的k*k區(qū)域
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的區(qū)別
#對每個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的區(qū)別
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的結果是一模一樣踱蠢。