最近看到一個巨牛的人工智能教程,分享一下給大家帮孔。教程不僅是零基礎雷滋,通俗易懂不撑,而且非常風趣幽默文兢,像看小說一樣!覺得太牛了焕檬,所以分享給大家姆坚。平時碎片時間可以當小說看,【點這里可以去膜拜一下大神的“小說”】实愚。
上一篇文章已經(jīng)介紹過卷積的實現(xiàn)兼呵,這篇文章我們學習反卷積原理,同樣腊敲,在了解反卷積原理后击喂,在后面手寫python代碼實現(xiàn)反卷積。
1 反卷積原理
反卷積原理不太好用文字描述碰辅,這里直接以一個簡單例子描述反卷積過程懂昂。
假設輸入如下:
[[1,0,1],
[0,2,1],
[1,1,0]]
反卷積卷積核如下:
[[ 1, 0, 1],
[-1, 1, 0],
[ 0,-1, 0]]
現(xiàn)在通過stride=2
來進行反卷積,使得尺寸由原來的3*3
變?yōu)?code>6*6.那么在Tensorflow框架中没宾,反卷積的過程如下(不同框架在裁剪這步可能不一樣):
其實通過我繪制的這張圖凌彬,就已經(jīng)把原理講的很清楚了。大致步奏就是循衰,先填充0铲敛,然后進行卷積,卷積過程跟上一篇文章講述的一致会钝。最后一步還要進行裁剪伐蒋。好了,原理講完了迁酸,(#.#)....
2 代碼實現(xiàn)
上一篇文章我們只針對了輸出通道數(shù)為1進行代碼實現(xiàn)咽弦,在這篇文章中,反卷積我們將輸出通道設置為多個胁出,這樣更符合實際場景型型。
先定義輸入和卷積核:
input_data=[
[[1,0,1],
[0,2,1],
[1,1,0]],
[[2,0,2],
[0,1,0],
[1,0,0]],
[[1,1,1],
[2,2,0],
[1,1,1]],
[[1,1,2],
[1,0,1],
[0,2,2]]
]
weights_data=[
[[[ 1, 0, 1],
[-1, 1, 0],
[ 0,-1, 0]],
[[-1, 0, 1],
[ 0, 0, 1],
[ 1, 1, 1]],
[[ 0, 1, 1],
[ 2, 0, 1],
[ 1, 2, 1]],
[[ 1, 1, 1],
[ 0, 2, 1],
[ 1, 0, 1]]],
[[[ 1, 0, 2],
[-2, 1, 1],
[ 1,-1, 0]],
[[-1, 0, 1],
[-1, 2, 1],
[ 1, 1, 1]],
[[ 0, 0, 0],
[ 2, 2, 1],
[ 1,-1, 1]],
[[ 2, 1, 1],
[ 0,-1, 1],
[ 1, 1, 1]]]
]
上面定義的輸入和卷積核,在接下的運算過程如下圖所示:
可以看到實際上全蝶,反卷積和卷積基本一致闹蒜,差別在于寺枉,反卷積需要填充過程,并在最后一步需要裁剪绷落。具體實現(xiàn)代碼如下:
#根據(jù)輸入map([h,w])和卷積核([k,k]),計算卷積后的feature map
import numpy as np
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
#填充0
def fill_zeros(input):
[c,h,w]=input.shape
rs=np.zeros([c,h*2+1,w*2+1],np.float32)
for i in range(c):
for j in range(h):
for k in range(w):
rs[i,2*j+1,2*k+1]=input[i,j,k]
return rs
def my_deconv(input,weights):
#weights shape=[out_c,in_c,h,w]
[out_c,in_c,h,w]=weights.shape
out_h=h*2
out_w=w*2
rs=[]
for i in range(out_c):
w=weights[i]
tmp=np.zeros([out_h,out_w],np.float32)
for j in range(in_c):
conv=compute_conv(input[j],w[j])
#注意裁剪砌烁,最后一行和最后一列去掉
tmp=tmp+conv[0:out_h,0:out_w]
rs.append(tmp)
return rs
def main():
input=np.asarray(input_data,np.float32)
input= fill_zeros(input)
weights=np.asarray(weights_data,np.float32)
deconv=my_deconv(input,weights)
print(np.asarray(deconv))
if __name__=='__main__':
main()
計算卷積代碼筐喳,跟上一篇文章一致。代碼直接看注釋函喉,不再解釋避归。運行結果如下:
[[[ 4. 3. 6. 2. 7. 3.]
[ 4. 3. 3. 2. 7. 5.]
[ 8. 6. 8. 5. 11. 2.]
[ 3. 2. 7. 2. 3. 3.]
[ 5. 5. 11. 3. 9. 3.]
[ 2. 1. 4. 5. 4. 4.]]
[[ 4. 1. 7. 0. 7. 2.]
[ 5. 6. 0. 1. 8. 5.]
[ 8. 0. 8. -2. 14. 2.]
[ 3. 3. 9. 8. 1. 0.]
[ 3. 0. 13. 0. 11. 2.]
[ 3. 5. 3. 1. 3. 0.]]]
為了驗證實現(xiàn)的代碼的正確性,我們使用tensorflow的conv2d_transpose函數(shù)執(zhí)行相同的輸入和卷積核管呵,看看結果是否一致梳毙。驗證代碼如下:
import tensorflow as tf
import numpy as np
def tf_conv2d_transpose(input,weights):
#input_shape=[n,height,width,channel]
input_shape = input.get_shape().as_list()
#weights shape=[height,width,out_c,in_c]
weights_shape=weights.get_shape().as_list()
output_shape=[input_shape[0], input_shape[1]*2 , input_shape[2]*2 , weights_shape[2]]
print("output_shape:",output_shape)
deconv=tf.nn.conv2d_transpose(input,weights,output_shape=output_shape,
strides=[1, 2, 2, 1], padding='SAME')
return deconv
def main():
weights_np=np.asarray(weights_data,np.float32)
#將輸入的每個卷積核旋轉180°
weights_np=np.rot90(weights_np,2,(2,3))
const_input = tf.constant(input_data , tf.float32)
const_weights = tf.constant(weights_np , tf.float32 )
input = tf.Variable(const_input,name="input")
#[c,h,w]------>[h,w,c]
input=tf.transpose(input,perm=(1,2,0))
#[h,w,c]------>[n,h,w,c]
input=tf.expand_dims(input,0)
#weights shape=[out_c,in_c,h,w]
weights = tf.Variable(const_weights,name="weights")
#[out_c,in_c,h,w]------>[h,w,out_c,in_c]
weights=tf.transpose(weights,perm=(2,3,0,1))
#執(zhí)行tensorflow的反卷積
deconv=tf_conv2d_transpose(input,weights)
init=tf.global_variables_initializer()
sess=tf.Session()
sess.run(init)
deconv_val = sess.run(deconv)
hwc=deconv_val[0]
print(hwc)
if __name__=='__main__':
main()
上面代碼中,有幾點需要注意:
- 每個卷積核需要旋轉180°后捐下,再傳入tf.nn.conv2d_transpose函數(shù)中账锹,因為tf.nn.conv2d_transpose內部會旋轉180°,所以提前旋轉坷襟,再經(jīng)過內部旋轉后奸柬,能保證卷積核跟我們所使用的卷積核的數(shù)據(jù)排列一致。
- 我們定義的輸入的shape為[c,h,w]需要轉為tensorflow所使用的[n,h,w,c]婴程。
- 我們定義的卷積核shape為[out_c,in_c,h,w],需要轉為tensorflow反卷積中所使用的[h,w,out_c,in_c]
執(zhí)行上面代碼后廓奕,執(zhí)行結果如下:
[[ 4. 3. 6. 2. 7. 3.]
[ 4. 3. 3. 2. 7. 5.]
[ 8. 6. 8. 5. 11. 2.]
[ 3. 2. 7. 2. 3. 3.]
[ 5. 5. 11. 3. 9. 3.]
[ 2. 1. 4. 5. 4. 4.]]
[[ 4. 1. 7. 0. 7. 2.]
[ 5. 6. 0. 1. 8. 5.]
[ 8. 0. 8. -2. 14. 2.]
[ 3. 3. 9. 8. 1. 0.]
[ 3. 0. 13. 0. 11. 2.]
[ 3. 5. 3. 1. 3. 0.]]
對比結果可以看到,數(shù)據(jù)是一致的排抬,證明前面手寫的python實現(xiàn)的反卷積代碼是正確的懂从。