1. 程式人生 > >PyTorch學習筆記(9)——nn.Conv2d和其中的padding策略

PyTorch學習筆記(9)——nn.Conv2d和其中的padding策略

一. Caffe、Tensorflow的padding策略

在之前的轉載過的一篇文章——《tensorflow ckpt檔案轉caffemodel時遇到的坑》提到過,caffe的padding方式和tensorflow的padding方式有很大的區別,輸出無法對齊。這是為什麼呢?

下面簡單回顧一下

卷積操作輸出的形狀計算公式是這樣的:

output_shape = (image_shape-filter_shape+2*padding)/stride + 1

因為padding前面的係數是2,所以在padding時,一般是對稱地補,左/右各padding一列 或者 上下各padding一行。

那麼問題來了,如果stride是2,而括號裡算出來的值剛好是奇數怎麼辦?那就再偷偷摸摸補一列padding或者補一行padding。

於是,caffe和tensorflow的區別就出來了。

caffe偷偷摸摸地把一行0補在上面 或者 把一列0補在左邊,tensorflow正好映象對稱,把一行0補在下面或者把一列0補在右邊。這就是導致輸出對齊不了的原因,前面幾層輸出的feature map的中間還能勉強對上,隨著網路結構的加深,到fc之前已經完全對不上了。

也就是說

  • caffe的padding策略是把0補在左上
  • tensorflow的padding策略是把0補在右下

那麼,PyTorch的padding策略是怎樣的呢?在介紹padding策略之前,先簡單的介紹一下PyTorch中的nn.Conv2d

運算元。

二. nn.Conv2d簡單說明

nn.Conv2d的介紹主要譯自官網

nn.Conv2d的功能是:對由多個輸入平面組成的輸入訊號進行二維卷積,以最簡單的例子進行說明:

輸入訊號的形式為(N,Cin,H,W)N表示batch size,Cin表示channel個數,HW分別表示特徵圖的高和寬。

引數說明:

  • stride(步長):控制cross-correlation的步長,可以設為1個int型數或者一個(int, int)型的tuple。
  • padding(補0):控制zero-padding的數目。
  • dilation(擴張)

    :控制kernel點(卷積核點)的間距; 也被稱為 “à trous”演算法. 可以在此github地址檢視:Dilated convolution animations

  • groups(卷積核個數):這個比較好理解,通常來說,卷積個數唯一,但是對某些情況,可以設定範圍在1 —— in_channels中數目的卷積核:

    At groups=1, all inputs are convolved to all outputs. At groups=2, the operation becomes equivalent to having two conv layers side by side, each seeing half the input channels, and producing half the output channels, and both subsequently concatenated. At groups=in_channels, each input channel is convolved with its own set of filters (of size ⌊out_channelsin_channels⌋ ).

注意:kernel_size, stride, padding, dilation 不但可以是一個單個的int——表示在高度和寬度使用這個相同的int作為引數 也可以使用一個(int1, int2)的元組(本質上單個的int就是相同int的(int, int))。在元組中,第1個引數對應高度維度,第2個引數對應寬度維度。

還有一點需要提醒的是:卷積核的size的選擇可能導致input中某幾行(最後幾行)沒有關聯起來,這是因為我們預設使用的模式是valid,而不是full(在tensorflow中也稱為same)。如果想要充分利用input的話,則依賴於使用者對padding以及stride等引數的設定。相比tensorflow,PyTorch需要使用者清楚的知道的自己的卷積核選取對結果的影響。

引數的詳細說明(基於PyTorch0.4.1)

這裡寫圖片描述

下圖中的HoutWout是根據我們在nn.Conv2d中設定的padding,dilation,kernel_size,stride等引數得到的輸出特徵圖的高度和寬度。 這裡寫圖片描述

三. nn.Conv2d中的padding操作

nn.Conv2d簡單介紹完了,現在來講講padding在nn.Conv2d中怎麼實現的,也就是怎麼補的0,或者說補0的策略。

Q1: padding是卷積之後還是卷積之前還是卷積之後實現的?

padding是在卷積之前補0,如果願意的話,可以通過使用torch.nn.Functional.pad來補非0的內容。

Q2:padding補0的預設策略是什麼?

四周都補!如果pad輸入是一個tuple的話,則第一個引數表示高度上面的padding,第2個引數表示寬度上面的 下面將展示一個padding = 1的例子:

這裡寫圖片描述 顯然,padding=1的效果是:原來的輸入層基礎上,上下左右各補了一行!除此之外我們看到,上下左右都是0.9862,那麼,這個東西是啥呢?為什麼不是0呢?

為了這個問題,我甚至還去PyTorch論壇上獻醜了,估計大家可能也知道是咋回事了… 是的!是Bias!我問的問題是這樣的: Calculation detail in nn.Conv2d

Hello, I just can’t figure out the way nn.Conv2d calculate the output . 

The result calculated from torch is not the same as some machine learning course had taught.

For example, likes the code below:

>> m = torch.nn.Conv2d(1, 1, 3, padding=0)
>> m(input)
tensor([[[[ 0.5142,  0.3803,  0.2687],
          [-0.4321,  1.1637,  1.0675],
          [ 0.1742,  0.0869, -0.4451]]]], grad_fn=<ThnnConv2DBackward>)
>> input
tensor([[[[ 0.7504,  0.1157,  1.4940, -0.2619, -0.4732],
          [ 0.1497,  0.0805,  2.0829, -0.0925, -1.3367],
          [ 1.7471,  0.5205, -0.8532, -0.7358, -1.3931],
          [ 0.1159, -0.2376,  1.2683, -0.0959, -1.3171],
          [-0.1620, -1.8539,  0.0893, -0.0568, -0.0758]]]])
>> m.weight
Parameter containing:
tensor([[[[ 0.2405,  0.3018,  0.0011],
          [-0.1691, -0.0701, -0.0334],
          [-0.0429,  0.2668, -0.2152]]]], requires_grad=True)

for the left top element 0.5142, it’s not the output equals to

>> import numpy as np
>> w = np.array([[0.2405,  0.3018,  0.0011], 
>>              [-0.1691, -0.0701, -0.0334], 
>>              [-0.0429,  0.2668, -0.2152]])
# top-left 3x3 matrix of 5x5
>> x = np.array([[ 0.7504,  0.1157,  1.4940], 
>>               [ 0.1497,  0.0805,  2.0829], 
>>               [1.7471,  0.5205, -0.8532]])
>> print(np.sum(w*x))
#  0.364034 != 0.5142
0.36403412999999996

My Question here is: Why Could the output not equal to 0.5142?

Further more, when i add paramter padding into nn.Conv2d, 
The outcome seems obscure to me as below, thanks a lot for explain that to me.Thank you!

>> input
tensor([[[[ 0.7504,  0.1157,  1.4940, -0.2619, -0.4732],
          [ 0.1497,  0.0805,  2.0829, -0.0925, -1.3367],
          [ 1.7471,  0.5205, -0.8532, -0.7358, -1.3931],
          [ 0.1159, -0.2376,  1.2683, -0.0959, -1.3171],
          [-0.1620, -1.8539,  0.0893, -0.0568, -0.0758]]]])
# set padding from 0 to 1 equals to (1, 1)
>> m1 = torch.nn.Conv2d(1, 1, 1, padding=1)
>> m1(input)
tensor([[[[0.9862, 0.9862, 0.9862, 0.9862, 0.9862, 0.9862, 0.9862],
          [0.9862, 1.0771, 1.0002, 1.1672, 0.9544, 0.9288, 0.9862],
          [0.9862, 1.0043, 0.9959, 1.2385, 0.9749, 0.8242, 0.9862],
          [0.9862, 1.1978, 1.0492, 0.8828, 0.8970, 0.8174, 0.9862],
          [0.9862, 1.0002, 0.9574, 1.1398, 0.9745, 0.8266, 0.9862],
          [0.9862, 0.9665, 0.7615, 0.9970, 0.9793, 0.9770, 0.9862],
          [0.9862, 0.9862, 0.9862, 0.9862, 0.9862, 0.9862, 0.9862]]]],
       grad_fn=<ThnnConv2DBackward>)

The confused point is that how 0.9862 be calculated? 
And what is the default padding strategy in nn.Conv2d?

Thank you for reading and answer!

答案也很簡單——我沒考慮bias! 這裡寫圖片描述

根據下圖,Q2中神祕的0.9862的來歷我們就很清楚了,是bias的值。 這裡寫圖片描述