头图

欢迎关注我的公众号 [极智视界],回复001获取Google编程规范

O_o>_<   o_OO_o~_~o_O

  本文记录了 pytorch 与 darknet 计算卷积输出 shape 方式对比,两者有一些区别。

   卷积算子是深度学习中最常用的算子,在神经网络中,往往需要涉及到算子 shape 形状的推导,这里主要说一下 cov 层的 shape 推导,拿 pytorch 和 darknet 进行对比。

1、pytorch 卷积输出 shape 推导

   看源码,答案在 torch/nn/modules/conv.py 脚本 class Conv2d(_ConvNd) 类的注释中:

Shape:
    - Input: :math:`(N, C_{in}, H_{in}, W_{in})`
    - Output: :math:`(N, C_{out}, H_{out}, W_{out})` where

      .. math::
          H_{out} = \left\lfloor\frac{H_{in}  + 2 \times \text{padding}[0] - \text{dilation}[0]
                    \times (\text{kernel\_size}[0] - 1) - 1}{\text{stride}[0]} + 1\right\rfloor

      .. math::
          W_{out} = \left\lfloor\frac{W_{in}  + 2 \times \text{padding}[1] - \text{dilation}[1]
                    \times (\text{kernel\_size}[1] - 1) - 1}{\text{stride}[1]} + 1\right\rfloor

  以上的计算公式为 LaTeX 格式,这是一种基于 TEX 的排版系统,由美国计算机学家莱斯利-兰伯特发明,利用这种格式,能够快速生成复杂表格和数学公式。不过这种格式对于我们来说可读性比较差,这里用富本编辑器的 公式 功能转换一下,来看 H_{out}:

  这里考虑了空洞卷积的膨胀系数,如果把 dilation 去掉,shape 推导会是这样的:

  以上是 pytorch 中关于卷积 shape 的推导方式,下面来看一下 darknet 中的情况。

2、darknet 卷积输出 shape 推导

   同样看源码,在 parser.c 中有:

int size = option_find_int(options, "size", 1);
int pad = option_find_int_quiet(options, "pad", 0);
int padding = option_find_int_quiet(options, "padding", 0);
if(pad) padding = size/2;

   其中 option_find_int 和 option_find_int_quiet 均为链表查找函数,这里有点不一样的地方,可以把 pad 理解为填充标志位,实际填充值为 padding,从以上代码可以看出实际用的 padding 并不是直接从模型文件中读出来的 padding,而是通过标志位 pad 判断处理后得到的 padding。

   如上处理后再进行卷积输出 shape 的计算,代码在 convolutional_layer.c 中:

/// 调用接口
/// int out_h = convolutional_out_height(l);
/// int out_w = convolutional_out_width(l);

int convolutional_out_height(convolutional_layer l)
{
    return (l.h + 2*l.pad - l.size) / l.stride_y + 1;
}

int convolutional_out_width(convolutional_layer l)
{
    return (l.w + 2*l.pad - l.size) / l.stride_x + 1;
}

   将以上的 out_h 计算过程可视化一下:

  看到这里可能还有一个疑惑,怎么直接用的是 l.pad,而不是 l.padding 呢,答案在这里:

l.pad = padding;

   这样就说得通了,再来看个案例,如下图:

  来看框出来的层,以 n c h w 布局的话输入为 [n , 16, 256, 256],从可视化看到经过这个卷积后的 shape 依旧为 [n, 16, 256, 256],如果我们以 pytorch 的计算方式:Hout = (Hin + 2 * padding - (size - 1) - 1) / stride + 1 来计算的话,你可能会认为 pad 即为 padding,则 Hout = (256 + 2 - (1 - 1) -1) / 1 + 1 = 258,此时得到的结果显然不对,故这种计算方式存在问题。我们改用 darknet 中的计算方式,即把 pad 当作标志位,先计算 padding,这里算子参数里没有写 padding,则令 padding = 0,计算过程如下:

size = 1
padding = 0
pad = 1
    
/// if(pad) padding = size / 2;
padding = 1 / 2 = 0

/// Hout = (Hin + 2 * padding - size) / stride + 1
Hout = (256 + 2 * 0 - 1) + 1 = 256

   对于 C 语言中的 / 是普通的除法,得到的为浮点数时,直接把小数点后面的舍去,即向下取整。

  这样结果就能对起来了。

   以上分享了 pytorch 和 darknet 中关于卷积输出 shape 的计算方式对比,也是容易碰到的小坑,希望我的分享对你有一点帮助。


【公众号传送】
【经验分享】pytorch 与 darknet 计算卷积输出 shape 方式对比


极智视界
13 声望4 粉丝