手動實現卷積神經網路中的卷積操作(conv2d)
寫這個的原因:一來好像沒怎麼搜到別人手動實現,作為補充;二來鞏固一下基礎。
卷積操作示意
先從一張示意圖說起,卷積基礎概念和操作步驟就不囉嗦了,只講這張圖,大意就是,有in-channel,有out-channel,你需要把in-channel都做卷積操作,然後產出out-channel,所以這個w是要層層拆解,w分拆成w0和w1,以對應2個out-channel。w0分拆成3個矩陣w0[:,:,0]、w0[:,:,1]、w0[:,:,2],以對應3個in-channel,因為輸入的3通道終究要合二為一的(進入一個核):y0=w0*x+b0、y1=w1*x+b1,所以w0只有一個b0對應,而不是三個。又因為卷積操作中,一個核要計算的目標是一個sum總值(綠圖中的一個點),而不是9個值,所以3*3的矩陣只要對應1*1的b就夠了。
圖講清楚了,下面開始實現。
下面主要分三步來做:
1.完成conv2d自動卷積,拿結果做參考,拿指定的相同的weights給第二步。
2.自己寫迴圈,進行相應操作。
3.做對比。
第一步:
使用conv2d的程式碼如下:
batch沒什麼用,設1。3通道輸入,原圖尺寸5*5,卷積核3*3,2通道輸出,輸出尺寸3*3,輸出數值27(三個卷積核結果相加:9+9+9),注意,這個conv2d介面預設是不帶bias的!一會如果手動卷積結果想要一模一樣,bias我會設0。
import tensorflow as tf # [batch, in_height, in_width, in_channels] input_arg = tf.Variable(tf.ones([1, 5, 5, 3])) # [filter_height, filter_width, in_channels, out_channels] filter_arg = tf.Variable(tf.ones([3 ,3 , 3 ,2])) conv2d_output = tf.nn.conv2d(input_arg, filter_arg, strides=[1,1,1,1], use_cudnn_on_gpu=False, padding='VALID') with tf.Session() as sess: sess.run(tf.global_variables_initializer()) print('input:\n', sess.run(input_arg)) print('filter:\n', sess.run(filter_arg)) print('output:\n', sess.run(conv2d_output))
第一步結果:
input: [[[[1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.]] [[1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.]] [[1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.]] [[1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.]] [[1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.]]]] filter: [[[[1. 1.] [1. 1.] [1. 1.]] [[1. 1.] [1. 1.] [1. 1.]] [[1. 1.] [1. 1.] [1. 1.]]] [[[1. 1.] [1. 1.] [1. 1.]] [[1. 1.] [1. 1.] [1. 1.]] [[1. 1.] [1. 1.] [1. 1.]]] [[[1. 1.] [1. 1.] [1. 1.]] [[1. 1.] [1. 1.] [1. 1.]] [[1. 1.] [1. 1.] [1. 1.]]]] output: [[[[27. 27.] [27. 27.] [27. 27.]] [[27. 27.] [27. 27.] [27. 27.]] [[27. 27.] [27. 27.] [27. 27.]]]]
因為卷積操作中,channel是最後一維,所以如果最後不是一維,列印之後看起來會有那麼點彆扭。但是這個形狀是沒錯的。
第二步:構思迴圈
padding分valid和same兩種,主要用簡單一些的valid,也就是不填充,這個不填充沒什麼爭議,same的話,為了達到效果,他會分別從後邊或者從前後進行0的填充,因為是封裝的,規律不是很清晰,不太好比較。
如果是valid,那麼在一個維度上,卷積迴圈次數和輸出size大概是i-j+1,i是輸入的size,j是核大小。
Tensor的元素賦值操作比較麻煩,先做一個numpy版(可以先忽略tf相關變數),旨在寫出等效計算過程:
圍繞輸出通道做大迴圈,每個輸出通道都是對所有輸入通道做卷積操作的結果的和,加偏置。注意加偏置的時機:if input == input_channel - 1,一個out_channel對應一個偏置,所以在最後一個input_channel下加biases。
每一個卷積核的視窗操作就是視窗中元素和卷積核元素按位相乘,求和。
import tensorflow as tf
import numpy as np
batch_size = 1
input_height = 5
input_width = 5
filter_height = 3
filter_width = 3
output_height =input_height - filter_height + 1
output_width = input_width - filter_width + 1
input_channel = 3
output_channel = 2
# [batch, in_height, in_width, in_channels]
np_input_arg = np.ones([batch_size, input_height, input_width, input_channel])
# [filter_height, filter_width, in_channels, out_channels]
np_filter_arg = np.ones([filter_height, filter_width, input_channel, output_channel])
np_biases = np.ones([batch_size,1,1,output_channel])
np_final_output = np.zeros([batch_size, output_height, output_width, output_channel])
# manual convolution
for batch in range(batch_size):
for output in range(output_channel):
for input in range(input_channel):
for i in range(output_height):
for j in range(output_width):
# a filter window
filter_sum = 0
# convolution operation: [i,i+1,i+2] * [j,j+1,j+2] [3] * [3] = [9]
for m in range(filter_height):
for n in range(filter_width):
np_final_output[batch][i][j][output] += np_input_arg[batch][i + m][j + n][input] * \
np_filter_arg[m][n][input][output]
if input == input_channel - 1:
np_final_output[batch][i][j][output] += np_biases[batch][0][0][output]
# print('np_final_output[{0}][{1}][{2}][{3}]:{4}'.format(batch,i,j,output, np_final_output[batch][i][j][output]))
print('np_final_output:', np_final_output)
如果np_biases用0,結果與tf.nn.conv2d結果一致:
np_final_output: [[[[27. 27.]
[27. 27.]
[27. 27.]]
[[27. 27.]
[27. 27.]
[27. 27.]]
[[27. 27.]
[27. 27.]
[27. 27.]]]]
如果np_biases(偏置)用1,結果如下:
np_final_output: [[[[28. 28.]
[28. 28.]
[28. 28.]]
[[28. 28.]
[28. 28.]
[28. 28.]]
[[28. 28.]
[28. 28.]
[28. 28.]]]]
todo:Tensor版本的卷積操作:
遇到問題:Tensorflow要寫計算圖,Tensor不能像普通list那樣隨意按元素操作
TypeError: 'Tensor' object does not support item assignment
AttributeError: 'Tensor' object has no attribute 'assign_add'