Res2Net
论文:https://arxiv.org/abs/1904.01169
代码:https://github.com/Res2Net/Res2Net-PretrainedModels
目前现在计算机视觉主流的任务如分类/检测/分割,都需要backbone骨干网络提取特征,在目前的诸多backbone中,都希望通过增加多尺度表征能力来进行性能提升,目前现在大部分网络是在一层一层上使用的多尺度。而本文的主角Res2net通过提出一种全新的CNN模块,通过在残差块里面构建多通道的残差连接,来代替单个3*3的卷积核,res2Net在更细粒度级别表示多尺度特征,同时还具有相当好的泛化性能,res2net能够嵌入到目前主流的CNN网络中去,例如resnet,resnext,DLA,以及senet,作者通过大量的消融试验证明了res2net的有效性
上图a是ResNet网络,图b是Res2Net,可以看出后者明显在残差单元(residual block)中插入更多带层级的残差连接结构(hierarchical residual-like connections)。具体来说,作者将一组3×3的卷积核替换为更小的一组过滤器,同时以分层的类残差方式来连接不同的过滤器组。
那么整个res2net的核心就是这个Bottle2neck
if scale == 1:
self.nums = 1
else:
self.nums = scale -1
if stype == 'stage':
self.pool = nn.AvgPool2d(kernel_size=3, stride = stride, padding=1)
convs = []
bns = []
for i in range(self.nums):
convs.append(nn.Conv2d(width, width,
kernel_size=3, stride = stride, padding=1, bias=False))
bns.append(nn.BatchNorm2d(width))
通过这个for循环,共有scale-1个3*3的卷积核,但是由于通道channel使用width分离了,可以理解成
的宽度,所以参数数量大大减少,同样的,每一个3*3后都跟一个bn层。
最后就是关键部分Bottle2neck的forward:
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
spx = torch.split(out, self.width, 1)
for i in range(self.nums):
if i==0 or self.stype=='stage':
sp = spx[i]
else:
sp = sp + spx[i]
sp = self.convs[i](sp)
sp = self.relu(self.bns[i](sp))
if i==0:
out = sp
else:
out = torch.cat((out, sp), 1)
if self.scale != 1 and self.stype=='normal':
out = torch.cat((out, spx[self.nums]),1)
elif self.scale != 1 and self.stype=='stage':
out = torch.cat((out, self.pool(spx[self.nums])),1)
out = self.conv3(out)
out = self.bn3(out)
if self.downsample is not None:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
这个代码整体上其实就是整个res2net最核心的地方,前面没什么好说的,简单的一个CBR,后面就是怎么把原来resnet的3*3卷积,换成res2net的新残差块
这一段开始通过torch.split拆分特征向量,除了第一个branch不过3x3卷积之外,拆出来的每个branch通过3x3卷积之后与下一个branch进行cat之后一边往下走,一边再与下一个branch做cat。
毫无疑问,这样做之后输出的channel数量就是width*scale,最后接上一个1x1的卷积再次进行特征融合。这样res2net的核心结构就完成了,可以看出超参就2个,一个width一个scale,作者用了一种类似FPN的方式把resnet单个3x3卷积,替换成了这样的相对更加细粒度的卷积,虽然还是3x3,但是由于通道做了一个拆分,实际上参数量没有增加的很多
最后就没有什么好说的了,分类网络中很常见的一个pooling+fc
x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
这里直接拉论文的结论,可以从上面看出来相比较resnet-50,res2net-50在参数和实时性上面其实没什么增加,但是精度提升非常明显。同时res2net可以很好地用在其他网络上做一个硬提升,例如resnext,senet等等。