关于Padding实现的一些细节

Padding的值

Padding是指在向量的每一维前后填充一定大小的边界,其常用于在卷积和池化这种具有“滤波”窗口的运算中,以调整输出大小、防止个别数据的丢弃

需要注意的是,Padding并不是真的在向量的前后填充了值,而只是标记了padding的位置

卷积和池化在计算时,会先确定滤波窗口的位置,窗口中如果包含了padding,则padding部分并不参与计算

《关于Padding实现的一些细节》
《关于Padding实现的一些细节》

举一个例子来说明这样处理的合理性

假设在做最大池化,向量的形状如上图,末尾有一padding

如果padding有具体的值,比如说0,则这个窗口的输出就为0,padding的值影响了输出的结果,而舍弃了源数据,这与paddding运算的目的相悖

如果padding的值是一个非常小的值,假设是负无穷,在上面的情形下看似不影响结果,但是无法适用于更多的情况,比如卷积,比如平均池化。所以padding不参与计算也有利于实现上的解耦,显然也更节省内存

Padding模式

Padding运算作用于输入向量的每一维,每一维的操作都是一致的,所以理解Padding的操作,只需要理解一维向量的padding过程

假设一个一维向量,输入形状为input_size,经过滤波操作后的输出形状为output_size,滤波窗口为filter_size,需要padding的个数为padding_needed,滤波窗口滑动步长为stride,则之间满足关系:

《关于Padding实现的一些细节》

由公式可知,指定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多一些?

Padding前后

tensorflow特别指出,当padding个数为奇数时,需要在后面多padding一些

caffe则跟tensorflow相反,caffe采用对称padding,相当于会将多余的padding分配给前面

《关于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的值

    原文作者:JamesPlur
    原文地址: https://zhuanlan.zhihu.com/p/73118626
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞