卷積計算是 CNN 最核心的部分,而隨著網絡結構不斷推陳出新(LeNet-5、AlexNet、GoogLeNet、VGG、ResNet、DenseNet、SENet、Xception、ResNeXt、MobileNet、ShuffleNet、NASNet……),卷積類型越來越豐富(二維卷積、三維卷積、空洞卷積、空間可分離卷積、深度可分離卷積、反卷積、變形卷積……)。設計網絡時需要特別留意 padding 策略。
在 TensorFlow 中,padding 參數可選項有兩種:'SAME' 和 'VALID'。在 TF 1.7 API 中 conv2d 是長這樣的:
我們先看一個例子:假設輸入圖片尺寸為 5x5,卷積核尺寸為 3x3,做二維卷積時動圖如下:
其中虛線格子就表示 padding 的像素(值為 0)。經過 padding,實際輸入圖片尺寸變成了 7x7,卷積核在 7x7 範圍內滑動,分別每個位置產生一個輸出像素,最終輸出結果尺寸為 5x5。我們發現,輸出尺寸與原輸入尺寸相同,也就是所謂 'SAME' 方式 padding 名稱的由來。為了保證輸出與輸入尺寸一致,我們可以推出 'SAME' padding 計算公式:
SAME PADDING 計算公式
output_h = input_h
output_w = input_w
pad_h = (kernel_h - 1)/2
pad_w = (kernel_w - 1)/2
前面例子中 kernel_h = kernel_w = 3,所以 pad_h = pad_w = (3 - 1)/2 = 1。
如前所述,使用 'SAME' padding 會在輸入圖片邊界處「無中生有」地加入了一些像素,這很可能破壞原始圖片的內容。而 'VALID' padding 則非常守規矩,卷積核只在「有效的」輸入像素點範圍內滑動。用公式表達就是:
VALID PADDING 計算公式
pad_h = 0
pad_w = 0
output_h = input_h - kernel_h + 1
output_w = input_w - kernel_w + 1
下圖為一個 'VALID' padding 例子,輸入圖片尺寸為 4x4,卷積核尺寸為 3x3,輸出尺寸為 2x2。
以上討論都是在 stride == 1 和 dilation == 1 時的情況。
當 dilation > 1 時,先計算擴張卷積核尺寸:
dilation_kernel_h = (kernel_h - 1) * dilation_h + 1
dilation_kernel_w = (kernel_w - 1) * dilation_w + 1
之後再將 dilation_kernel_h、dilation_kernel_w 代入前面不同 padding 公式即可得到對應的 padding 尺寸和輸出尺寸。一個例子如下:
輸入圖片尺寸為 7x7,卷積核尺寸為 3x3,擴張率為 2x2,使用 'VALID' padding 方法。首先得到擴張後卷積核尺寸為 (3 - 1) * 2 + 1 = 5,即等效為 5x5 卷積核。將該值代入 'VALID' padding 公式,得到輸出尺寸為:7 - 5 + 1 = 3,即 3x3,與圖中顯示一致。
當 stride > 1 時,即使使用 'SAME' padding 方式,輸出尺寸依然小於輸入尺寸,只是沿用了 stride == 1 的稱謂。
SAME PADDING 計算公式(stride > 1)
output_h = ceil(input_h / stride_h)
output_w = ceil(input_w / stride_w)
pad_h = ((output_h - 1) * stride_h + kernel_h - input_h) / 2
pad_w = ((output_w - 1) * stride_w + kernel_w - input_w) / 2
如上圖為 stride = 2 的情況,輸入尺寸為 5x5,卷積核尺寸 3x3。首先通過公式計算得到輸出尺寸:output_h = output_w = ceil(5/2) = ceil(2.5) = 3,即 3x3;然後利用公式計算 pad_h = pad_w = ((3 - 1) * 2 + 3 - 5)/2 = 1,與圖中一致。
當 stride > 1 時,使用 'VALID' padding 方式,計算過程仍滿足卷積核只在「有效的」輸入像素點範圍內滑動的約束。
VALID PADDING 計算公式(stride > 1)
pad_h = 0
pad_w = 0
output_h = ceil((input_h - kernel_h + 1) / stride_h)
output_w = ceil((input_w - kernel_w + 1) / stride_w)
上圖中 input : 5x5,kernel : 3x3,stride : 2x2,output : 2x2,請讀者自行核算。
另外,MaxPool 和 AvePool 的輸出尺寸計算公式也可從上面公式推導得到。
參考:
【1】 https://github.com/vdumoulin/conv_arithmetic
【2】 TensorFlow 源碼 v1.7