Padding是指在向量的每一維前後填充一定大小的邊界,其常用於在卷積和池化這種具有「濾波」窗口的運算中,以調整輸出大小、防止個別數據的丟棄
需要注意的是,Padding並不是真的在向量的前後填充了值,而只是標記了padding的位置
卷積和池化在計算時,會先確定濾波窗口的位置,窗口中如果包含了padding,則padding部分並不參與計算
舉一個例子來說明這樣處理的合理性
假設在做最大池化,向量的形狀如上圖,末尾有一padding
如果padding有具體的值,比如說0,則這個窗口的輸出就為0,padding的值影響了輸出的結果,而捨棄了源數據,這與paddding運算的目的相悖
如果padding的值是一個非常小的值,假設是負無窮,在上面的情形下看似不影響結果,但是無法適用於更多的情況,比如卷積,比如平均池化。所以padding不參與計算也有利於實現上的解耦,顯然也更節省內存
Padding運算作用於輸入向量的每一維,每一維的操作都是一致的,所以理解Padding的操作,只需要理解一維向量的padding過程
假設一個一維向量,輸入形狀為input_size,經過濾波操作後的輸出形狀為output_size,濾波窗口為filter_size,需要padding的個數為padding_needed,濾波窗口滑動步長為stride,則之間滿足關係:
由公式可知,指定padding_needed可以確定output_size的值,反過來,如果已知輸出的形狀,則進而可以確定padding的數量。
這是兩種處理padding的方案,pytorch採用的是第一種,即在卷積或池化時先確定padding數量,自動推導輸出形狀;tensorflow和caffe採用的是更為人熟知的第二種,即先根據Valid還是Same確定輸出大小,再自動確定padding的數量
Valid和Same是預設的兩種padding模式,Valid指不padding,same指輸出大小儘可能和輸入大小成比例
下面是tensorflow計算padding的代碼:
void GetWindowedOutputSize(int64_t input_size, int32_t filter_size, int32_t dilation_rate, int32_t stride, const std::string& padding_type, int64_t* output_size,int32_t* padding_before, int32_t* padding_after) { CHECK_GT(stride, 0); CHECK_GE(dilation_rate, 1);
int32_t effective_filter_size = (filter_size - 1) * dilation_rate + 1; if (padding_type == "valid") { if (output_size) { *output_size = (input_size - effective_filter_size + stride) / stride; } if (padding_before) { *padding_before = 0; } if (padding_after) { *padding_after = 0; } } else if (padding_type == "same") { int64_t tmp_output_size = (input_size + stride - 1) / stride; if (output_size) { *output_size = tmp_output_size; } const int32_t padding_needed = std::max( 0, static_cast<int32_t>((tmp_output_size - 1) * stride + effective_filter_size - input_size)); // For odd values of total padding, add more padding at the right // side of the given dimension. if (padding_before) { *padding_before = padding_needed / 2; } if (padding_after) { *padding_after = padding_needed - padding_needed / 2; } } else { UNIMPLEMENTED(); } if (output_size) { CHECK_GE((*output_size), 0); } }
其中考慮了空洞卷積的情況(dilation),所以稍微複雜一些
另外需要格外注意的,代碼中計算了padding_after和padding_before兩個值,是指要將padding_needed平均分配到向量的兩側。這就引出一個問題,當padding_needed是奇數時,是在前pad多一些,還是在後面pad多一些?
tensorflow特別指出,當padding個數為奇數時,需要在後面多padding一些
caffe則跟tensorflow相反,caffe採用對稱padding,相當於會將多餘的padding分配給前面
為了兼容兩種框架,需要根據配置對padding的策略進行調整
下面解釋CUDNN如何調整padding分配的策略
以池化舉例,CUDNN池化kernel的參數有:
handle Input. Handle to a previously created cuDNN context. poolingDesc Input. Handle to a previously initialized pooling descriptor. alpha, beta Input. Pointers to scaling factors (in host memory) used to blend the computation result with prior value in the output layer as follows: dstValue = alpha[0]*result + beta[0]*priorDstValue. Please refer to this section for additional details. xDesc Input. Handle to the previously initialized input tensor descriptor. x Input. Data pointer to GPU memory associated with the tensor descriptor xDesc. yDesc Input. Handle to the previously initialized output tensor descriptor. y Output. Data pointer to GPU memory associated with the output tensor descriptor yDesc.
CUDNN首先需要先制定輸入形狀和輸出形狀,xDesc指定了輸入向量的形狀,ydesc指定了輸出向量的形狀
然後CUDNN又通過poolingdesc指定了每一維padding_before的值
所以根據公式可以自然推導padding_after的值