tensorflow 中卷積 conv2d 的實現及特殊情況下的處理方式

一、先來介紹一下卷積操作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。

 

總而言之,輸出的尺寸=(輸入尺寸-核尺寸+2*一圈填充的數量)/步長+1

一圈填充的數量在SAME模式下,就等於  (核尺寸-1)/2,在VALID模式下等於0

當除以步長不能得到整數時,若是SAME模式,則只在右側和下側進行補零,最終得到向下取整之後的尺寸;在VALID模式下,直接忽略掉最右側和下側的像素值,也得到向下取整之後的尺寸。

但是在實際操作中還是儘量避免這些不能得到整數的情況。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章