是在研究训练过程中遇到的
也就是torch.nn.functional.conv2d实际上是torch.nn.Conv2d的另一个用法,可以直接指定卷积核和偏置的值。 这在通常情况下是用不到的,因为卷积核的值都是训练得到的。但在一些相关运算(correlation)时,需要指定卷积核的值。首先回顾一下nn.Conv2d
# Conv2d的用法
import torch
x1 = torch.rand([1,256,255,255])
conv = torch.nn.Conv2d(in_channels=256,
out_channels=1,
kernel_size=3)
out = conv(x1)
计算过程可由下图得到。具体分析,详见上一篇博客Pytorch中的卷积、空洞卷积和组卷积
对于输入 ( 1 , C i n , H i n , W i n ) (1,C_{in},H_{in},W_{in}) (1,Cin,Hin,Win),输出 C o u t {C_{out}} Cout个维度时,所需要的的卷积核维度为 ( C o u t , C i n , H k , W k ) (C_{out},C_{in},H_{k},W_{k}) (Cout,Cin,Hk,Wk)。先假设BatchSize大小 B B B与这个过程无关,也就是一个Batch中的所有输入 ( B , C i n , H i n , W i n ) (B,C_{in},H_{in},W_{in}) (B,Cin,Hin,Win)都被同一组卷积核卷积,因此上图只画出了其中一个输入的卷积过程。
先看看官方定义
torch.nn.functional.conv2d(input, weight, bias=None, stride=1, padding=0, dilation=1, groups=1) → Tensor
看着和Conv2d一样,但其中的groups在里面是干什么的?不要紧,先看一个最基础的例子,即输入 ( 1 , C i n , H i n , W i n ) (1,C_{in},H_{in},W_{in}) (1,Cin,Hin,Win),输出通道数 C o u t = 1 C_{out}=1 Cout=1。根据上面的分析,可以得到卷积核的维度为 ( 1 , C i n , H k , W k ) (1,C_{in},H_{k},W_{k}) (1,Cin,Hk,Wk)
import torch
import torch.nn.functional as f
z1 = torch.rand([1,256,127,127]).cuda() # 卷积核
x1 = torch.rand([1,256,255,255]).cuda() # 输入
out_1 = f.conv2d(x1, z1)
print(out_1.shape)
# 结果
torch.Size([1, 1, 129, 129])
于是我们可以到一个直观的理解:F.conv2d 中的weight,就是卷积核,其维度是 ( C o u t , C i n , H k , W k ) (C_{out},C_{in},H_{k},W_{k}) (Cout,Cin,Hk,Wk)。
上面这句话是全文的核心。改变weight的第一个维度,直接影响输出大小。
上面那个例子也就是目标跟踪中SiamFC在跟踪时的Correlation过程,
上面是假设BatchSize大小 B B B与这个过程无关,也就是一个Batch中的所有输入 ( B , C i n , H i n , W i n ) (B,C_{in},H_{in},W_{in}) (B,Cin,Hin,Win)都被同一组卷积核卷积。那要不是这样呢,比如SiamFC的训练过程。
输入x维度 ( B , C i n , H i n , W i n ) (B,C_{in},H_{in},W_{in}) (B,Cin,Hin,Win),被用作卷积核的z输入维度 ( B , C i n , H k , W k ) (B,C_{in},H_{k},W_{k}) (B,Cin,Hk,Wk)。想要达成的效果是:
x[0,:,:,:]和z[0,:,:,:]进行卷积,x[1,:,:,:]和z[1,:,:,:]进行卷积,以此类推。每个卷积的过程就是标准卷积。
此时需要用到分组卷积和reshape,针对输入,关于分组卷积详见上一篇博客Pytorch中的卷积、空洞卷积和组卷积
batch = 8
z8 = torch.rand([batch,256,127,127]).cuda()
x8 = torch.rand([batch,256,255,255]).cuda()
x8_ = x8.reshape([1,256*batch,255,255])
out_3 = (f.conv2d(x8_, z8, groups=batch))
print(out_3.shape)
# 结果
torch.Size([1, 8, 129, 129])
然后进行out_3.reshape([8, 1, 129, 129])
,即可。
这里你可能会问reshape不影响顺序,进而会导致错误吗?pytorch中的reshape时是首先填满最内侧维度的,然后才填充外侧维度。这种方式不影响计算结果。
Depth-corr,SiamRPN++中的使用方式。先研究简的跟踪过程:
要求输入x的维度 ( 1 , C i n , H i n , W i n ) (1,C_{in},H_{in},W_{in}) (1,Cin,Hin,Win),z的维度 ( 1 , C i n , H k , W k ) (1,C_{in},H_{k},W_{k}) (1,Cin,Hk,Wk),输出维度 ( 1 , C i n , H o u t , W o u t ) (1,C_{in},H_{out},W_{out}) (1,Cin,Hout,Wout),也就是x的第一个维度和z的第一个维度进行卷积,第二个和第二个,依次类推。。。
有了上面的基础,现在研究起来也变得容易了,还是用分组卷积和reshape,针对卷积核
chanel = 256
z1 = torch.rand([1,chanel,127,127])
x1 = torch.rand([1,chanel,255,255])
z1_dw = z1.reshape([chanel,1,127,127])
out_2 = f.conv2d(x1, z1_dw, groups=chanel)
print(out_2.shape)
# 输出
torch.Size([1, 256, 129, 129])
也就相当于SiamRPN++的训练过程。
输入x维度 ( B , C i n , H i n , W i n ) (B,C_{in},H_{in},W_{in}) (B,Cin,Hin,Win),被用作卷积核的z输入维度 ( B , C i n , H k , W k ) (B,C_{in},H_{k},W_{k}) (B,Cin,Hk,Wk)。想要达成的效果是:
x[0,:,:,:]和z[0,:,:,:]进行卷积,x[1,:,:,:]和z[1,:,:,:]进行卷积,以此类推。每个卷积的过程就是深度可分离卷积。
如果前面看懂了,这里应该就能学以致用了。
z8 = torch.rand([batch,channel,127,127])
x8 = torch.rand([batch,channel,255,255])
x8_ = x8.reshape([1,batch*channel,255,255])
z8_dw = z8.reshape([batch*channel,1,127,127])
out_4 = f.conv2d(x8_, z8_dw, groups=channel*batch)
print(out_1.shape)
# 输出
torch.Size([1, 2048, 129, 129])