一、先來介紹一下卷積操作conv2d的實現
該函數的官方文檔如下:
tf.nn.conv2d(
input,
filter,
strides,
padding,
use_cudnn_on_gpu=True,
data_format='NHWC',
dilations=[1, 1, 1, 1],
name=None
)
其中,第一個參數 input ,是輸入的預處理圖像,它要求是一個四維的張量,每個維度對應爲 【batchsize, 輸入圖像寬度,輸入圖像高度,輸入圖像深度】 ,數據類型只能用浮點型
第二個參數 filter 是卷積核的大小,也要求是四維張量,對應爲 【卷積核寬度,卷積核高度,輸入的圖像深度,輸出的圖像深度】
第三個參數是設定步長,是一個有四個元素的向量,對應爲 【1,水平方向步長,垂直方向步長,1】
第四個參數是一個指示填充方式的參數,只能選擇 ‘SAME’或者‘VALID’ ,前者說明進行填充,後者說明不用填充。
其他的參數一般情況下默認即可,如需改變請參考官方原文解釋:https://tensorflow.google.cn/api_docs/python/tf/nn/conv2d
原來感覺這個函數挺簡單的,直接使用就行,但是,當遇到一些特殊情況時,就有必要更進一步瞭解一下對於細節的處理方式,其實主要就是輸入輸出圖像尺寸的對應問題。下面從幾個不同的例子來進行說明。
1、首先,來看當使用 SAME方式填充,步長爲一 的情況。
import tensorflow as tf
x1 = tf.constant(1,shape=[1,5,5,1],dtype=tf.float32)
x2 = tf.constant(1,shape=[1,6,6,1],dtype=tf.float32)
kernel = tf.constant(1,shape=[3,3,1,1],dtype=tf.float32)
conv1 = tf.nn.conv2d(x1,kernel,[1,1,1,1],padding='SAME')
conv2 = tf.nn.conv2d(x2,kernel,[1,1,1,1],padding='SAME')
with tf.Session() as sess:
a,b = sess.run([conv1,conv2])
print(a.shape)
print(b.shape)
輸出
(1, 5, 5, 1)
(1, 6, 6, 1)
可以發現,此時是保持輸入輸出的圖像尺寸不發生變化,因爲進行了填充,邊界一週填充的數量是由卷積核的大小決定的, p=(f-1)/2。 這也是在一般情況下最常用的處理方式。
2、當使用 VALID填充,步長爲一
…………同上……………………
conv1 = tf.nn.conv2d(x1,kernel,[1,1,1,1],padding='VALID')
conv2 = tf.nn.conv2d(x2,kernel,[1,1,1,1],padding='VALID')
………………
輸出:
(1, 3, 3, 1)
(1, 4, 4, 1)
可以發現,此時是輸入圖像的尺寸均減小了2,這是因爲沒有填充邊界導致的,也不難理解。
3、再來看用 SAME填充,步長爲二 的情況
import tensorflow as tf
x1 = tf.constant(1,shape=[1,5,5,1],dtype=tf.float32)
x2 = tf.constant(1,shape=[1,6,6,1],dtype=tf.float32)
kernel = tf.constant(1,shape=[3,3,1,1],dtype=tf.float32)
conv1 = tf.nn.conv2d(x1,kernel,[1,2,2,1],padding='SAME')
conv2 = tf.nn.conv2d(x2,kernel,[1,2,2,1],padding='SAME')
with tf.Session() as sess:
a,b = sess.run([conv1,conv2])
print(a.shape)
print(b.shape)
輸出
(1, 3, 3, 1)
(1, 3, 3, 1)
此時,就會發現一個有趣的問題,雖然輸入的圖像尺寸不同,但是卻得到了同樣尺寸的輸出(輸出的結果是不一樣的),這是爲什麼呢?
5X5時比較好理解,因爲在一圈補零後,(5-3+2)/2+1=3,所以輸出爲3;
但是,6X6的時候就會出現問題,這個情況按照理論上來說是不允許的,在CS231n課程中也提出這個情況是 not fit,但tensorflow中卻不會報錯,而是向下取整得到了輸出尺寸的大小。那此時具體在tensorflow內是如何實現計算的呢?
根據對結果的分析,可以發現此時在補零的時候,並沒有對一圈都進行補零,而只是在圖像的右側和下側進行了填充,因此對於的尺寸就是 (6-3+1)/2+1=3,這也就是內部的實現機理。一般情況下,因爲卷積核等都是我們自己進行設計,儘量避免這種情況最好。
兩個圖像具體的輸出值爲:
4 6 4 9 9 6
6 9 6 9 9 6
4 6 4 6 6 4
4、最後看一下 用VALID填充,步長爲二
import tensorflow as tf
x1 = tf.constant(1,shape=[1,5,5,1],dtype=tf.float32)
x2 = tf.constant([[[[0.,1.,1.,1.,1.,0.],
[1.,1.,1.,1.,1.,0.],
[1.,1.,1.,1.,1.,0.],
[1.,1.,1.,1.,1.,0.],
[1.,1.,1.,1.,1.,0.],
[0.,0.,0.,0.,0.,0.]]]],shape=[1,6,6,1],dtype=tf.float32)
kernel = tf.constant(1,shape=[3,3,1,1],dtype=tf.float32)
conv1 = tf.nn.conv2d(x1,kernel,[1,2,2,1],padding='VALID')
conv2 = tf.nn.conv2d(x2,kernel,[1,2,2,1],padding='VALID')
with tf.Session() as sess:
a,b = sess.run([conv1,conv2])
print(a.shape,a)
print(b.shape,b)
輸出:
(1, 2, 2, 1)
[[[[9.]
[9.]]
[[9.]
[9.]]]]
(1, 2, 2, 1)
[[[[8.]
[9.]]
[[9.]
[9.]]]]
顯然,可以看出,兩者輸出的尺寸還是一樣,而此時是如何實現的呢?
對於5X5的圖像,和之前一樣,(5-3)/2+1=2,因爲此時沒有進行填充
而對於6X6的呢?在這裏,我們從其最終輸出的結果可以發現,tensorflow是將輸入的最後一行和最右側那一列直接去掉進行的計算,從而得到的尺寸 (6-1-3)/2+1=2。