传统的卷积操作形状是规则的,如下式
简单地说就是对特征图上的一块小区域进行加权平均,再输出相应值,其形状是规则的方形。作者觉得这个感受野太规则,不能很好地捕捉特征,所以在此基础上对卷积又加了偏置:
1、Pytorch里可变形卷积的初始化方式
开始是对一些常规参数的设置(kernel_size,stride,padding),后续定义了self.conv(最终输出的卷积层,设置输入通道数和输出通道数),self.p_conv(偏置层,学习之前公式(2)中说的偏移量),self.m_conv(权重学习层)。register_backward_hook是为了方便查看这几层学出来的结果,对网络结构无影响。
self.p_conv和self.m_conv输入通道由我们自己设置,self.p_conv输出通道为2*kernel_size*kernel_size代表了卷积核中所有元素的偏移坐标(因为同时存在x和y的偏移,所以要乘以2),而self.p_conv输出通道为(kernel_size*kernel_size)代表了卷积核每个元素的权重。他们的kernel_size为3,stride可以由我们自己设置(这里涉及之前公式(1,2)对于p0的查找)stride默认值为1
2、前传函数
首先数据先经过self.p_conv学习出offset(坐标偏移量),如果调制设置为true的话就同时学习出偏置。如之前提到的这两层的步长都是由自己设置的,所以他们所学习出来的特征图每个元素恰好与卷积核中心是一一对应的。如下图
由图片可以知道通过p_conv后的特征图(上图全红的矩形)上每个元素恰好与卷积核中心是一一对应,也就是说通过该特征图的尺寸,卷积核的尺寸,步长可以推算出在卷积过程中卷积核的中心坐标。 如上图,我们可知卷积操作次数为6。
第一次卷积,卷积核的中心坐标为(1,1),后续所有卷积核中心坐标。
接下来通过self._get_p()这个函数获取所有卷积核中心坐标p0具体操作,首先通过函数get_p_n()生成了卷积的相对坐标:
把卷积的中心点定义为原点,其他点坐标都相对于原点而言,比如self.kernel_size为3,通过torch.meshgrid生成从(-1,-1)到(1,1)9个坐标。将坐标的x和y分别存储,然后再将x,y以(1,2N,1,1)的形式返回,这样我们就获取了一个卷积核的所有相对坐标。
接下来获取卷积核在特征图上对应的中心坐标,也就是p0,代码实现如下
流程如同之前所说,通过torch.meshgrid生成所有中心坐标,通过kernel_size推断初始坐标通过self.stride推断所有中心坐标。这个_get_p_0的函数和该图。
完全是一一对应的,输入参数的h,w就是通过p_conv后的特征图的尺寸信息,接下来讲获取的张量通过repeat()扩展成(1,2N,h,w),然后再将我们获取的相对坐标信息与中心坐标相加就获得了我们卷积核的所有坐标,即公式(1)。
_get_p()中的相关操作如下:
卷积坐标加上之前学习出的offset后就是论文提出的公式(2)也就是加上了偏置后的卷积操作。比如p(在N=0时)p_0就是中心坐标,而p_n=(-1,-1),所以此时的p就是卷积核中心坐标加上(-1,-1),再加上offset。同理可得N=1,N=2...分别代表了一个卷积核上各个元素。