第1種解說:(核心最后一張圖,兩種填充方式輸出的形狀尺寸計算公式)
在用tensorflow寫CNN的時候,調用卷積核api的時候,會有填padding方式的參數,找到源碼中的函數定義如下(max pooling也是一樣):
def conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, data_format=None, name=None)
源碼中對于padding參數的說明如下:
padding: Astring
from:"SAME", "VALID"
. The type of padding algorithm to use.
說了padding可以用“SAME”和“VALID”兩種方式,但是對于這兩種方式具體是什么并沒有多加說明。 這里用Stack Overflow中的一份代碼來簡單說明一下,代碼如下:
x = tf.constant([[1., 2., 3.],[4., 5., 6.]])
x = tf.reshape(x, [1, 2, 3, 1]) # give a shape accepted by tf.nn.max_pool
valid_pad = tf.nn.max_pool(x, [1, 2, 2, 1], [1, 2, 2, 1], padding='VALID')
same_pad = tf.nn.max_pool(x, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')print(valid_pad.get_shape())
print(same_pad.get_shape())
# 最后輸出的結果為:
(1, 1, 1, 1)
(1, 1, 2, 1)
可以看出“SAME”的填充方式是比“VALID”的填充方式多了一列。 讓我們來看看變量x是一個2x3的矩陣,max pooling窗口為2x2,兩個維度的strides=2。 第一次由于窗口可以覆蓋(橙色區域做max pool操作),沒什么問題,如下:

接下來就是“SAME”和“VALID”的區別所在,由于步長為2,當向右滑動兩步之后“VALID”發現余下的窗口不到2x2所以就把第三列直接去了,而“SAME”并不會把多出的一列丟棄,但是只有一列了不夠2x2怎么辦?填充!

如上圖所示,“SAME”會增加第四列以保證可以達到2x2,但為了不影響原來的圖像像素信息,一般以0來填充。(這里使用表格的形式展示,markdown不太好控制格式,明白意思就行),這就不難理解不同的padding方式輸出的形狀會有所不同了。
在CNN用在文本中時,一般卷積層設置卷積核的大小為n×k,其中k為輸入向量的維度(即[n,k,input_channel_num,output_channel_num]),這時候我們就需要選擇“VALID”填充方式,這時候窗口僅僅是沿著一個維度掃描而不是兩個維度。可以理解為統計語言模型當中的N-gram。
我們設計網絡結構時需要設置輸入輸出的shape,源碼nn_ops.py中的convolution函數和pool函數給出的計算公式如下:
If padding == "SAME":output_spatial_shape[i] = ceil(input_spatial_shape[i] / strides[i])If padding == "VALID":output_spatial_shape[i] =ceil((input_spatial_shape[i] -(spatial_filter_shape[i]-1) * dilation_rate[i])/ strides[i]).
dilation_rate為一個可選的參數,默認為1,這里我們可以先不管它。 整理一下,對于“VALID”,輸出的形狀計算如下:

參考<https://cloud.tencent.com/developer/article/1012365>
第2種解說:利用tf.nn.conv2d示例來理解 strides, padding效果
這里先再簡單重復一下tf.nn.conv2d使用,其基本參數的使用規范同樣也適用于其他CNN語句
tf.nn.conv2d (input, filter, strides, padding, use_cudnn_on_gpu=None, data_format=None, name=None)
- input : 輸入的要做卷積的圖片,要求為一個張量,shape為 [ batch, in_height, in_weight, in_channel ],其中batch為圖片的數量,in_height 為圖片高度,in_weight 為圖片寬度,in_channel 為圖片的通道數,灰度圖該值為1,彩色圖為3。(也可以用其它值,但是具體含義不是很理解)
- filter: 卷積核,要求也是一個張量,shape為 [ filter_height, filter_weight, in_channel, out_channels ],其中 filter_height 為卷積核高度,filter_weight 為卷積核寬度,in_channel 是圖像通道數 ,和 input 的 in_channel 要保持一致,out_channel 是卷積核數量。
- strides: 卷積時在圖像每一維的步長,這是一個一維的向量,[ 1, strides, strides, 1],第一位和最后一位固定必須是1
- padding: string類型,值為“SAME” 和 “VALID”,表示的是卷積的形式,是否考慮邊界。"SAME"是考慮邊界,不足的時候用0去填充周圍,"VALID"則不考慮
- use_cudnn_on_gpu: bool類型,是否使用cudnn加速,默認為true
import tensorflow as tf
# case 1
# 輸入是1張 3*3 大小的圖片,圖像通道數是5,卷積核是 1*1 大小,數量是1
# 步長是[1,1,1,1]最后得到一個 3*3 的feature map
# 1張圖最后輸出就是一個 shape為[1,3,3,1] 的張量
input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([1,1,5,1]))
op1 = tf.nn.conv2d(input, filter, strides=[1,1,1,1], padding='SAME')# case 2
# 輸入是1張 3*3 大小的圖片,圖像通道數是5,卷積核是 2*2 大小,數量是1
# 步長是[1,1,1,1]最后得到一個 3*3 的feature map
# 1張圖最后輸出就是一個 shape為[1,3,3,1] 的張量
input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([2,2,5,1]))
op2 = tf.nn.conv2d(input, filter, strides=[1,1,1,1], padding='SAME')# case 3
# 輸入是1張 3*3 大小的圖片,圖像通道數是5,卷積核是 3*3 大小,數量是1
# 步長是[1,1,1,1]最后得到一個 1*1 的feature map (不考慮邊界)
# 1張圖最后輸出就是一個 shape為[1,1,1,1] 的張量
input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))
op3 = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID') # case 4
# 輸入是1張 5*5 大小的圖片,圖像通道數是5,卷積核是 3*3 大小,數量是1
# 步長是[1,1,1,1]最后得到一個 3*3 的feature map (不考慮邊界)
# 1張圖最后輸出就是一個 shape為[1,3,3,1] 的張量
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))
op4 = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID') # case 5
# 輸入是1張 5*5 大小的圖片,圖像通道數是5,卷積核是 3*3 大小,數量是1
# 步長是[1,1,1,1]最后得到一個 5*5 的feature map (考慮邊界)
# 1張圖最后輸出就是一個 shape為[1,5,5,1] 的張量
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))
op5 = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME') # case 6
# 輸入是1張 5*5 大小的圖片,圖像通道數是5,卷積核是 3*3 大小,數量是7
# 步長是[1,1,1,1]最后得到一個 5*5 的feature map (考慮邊界)
# 1張圖最后輸出就是一個 shape為[1,5,5,7] 的張量
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))
op6 = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME') # case 7
# 輸入是1張 5*5 大小的圖片,圖像通道數是5,卷積核是 3*3 大小,數量是7
# 步長是[1,2,2,1]最后得到7個 3*3 的feature map (考慮邊界)
# 1張圖最后輸出就是一個 shape為[1,3,3,7] 的張量
input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))
op7 = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME') # case 8
# 輸入是10 張 5*5 大小的圖片,圖像通道數是5,卷積核是 3*3 大小,數量是7
# 步長是[1,2,2,1]最后每張圖得到7個 3*3 的feature map (考慮邊界)
# 10張圖最后輸出就是一個 shape為[10,3,3,7] 的張量
input = tf.Variable(tf.random_normal([10,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))
op8 = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME') init = tf.initialize_all_variables()
with tf.Session() as sess:sess.run(init)print('*' * 20 + ' op1 ' + '*' * 20)print(sess.run(op1))print('*' * 20 + ' op2 ' + '*' * 20)print(sess.run(op2))print('*' * 20 + ' op3 ' + '*' * 20)print(sess.run(op3))print('*' * 20 + ' op4 ' + '*' * 20)print(sess.run(op4))print('*' * 20 + ' op5 ' + '*' * 20)print(sess.run(op5))print('*' * 20 + ' op6 ' + '*' * 20)print(sess.run(op6))print('*' * 20 + ' op7 ' + '*' * 20)print(sess.run(op7))print('*' * 20 + ' op8 ' + '*' * 20)print(sess.run(op8))
# 運行結果
運行結果這里就省略了,太長了,所以不寫這里了。復制語句到Jupyter中運行一下就懂了
參考<理解tf.nn.conv2d方法>