1. Res2Net介绍
1.1 Res2Net的背景和动机
ResNet是一种非常成功的深度卷积神经网络,但它存在一些问题。其中最重要的问题之一是对不同尺度和分辨率特征的建模不足。传统的ResNet块只使用单一的残差连接来传递信息,这意味着网络可能无法有效地捕获不同层次的特征。
Res2Net的动机是通过引入多分支结构和逐级增加的分辨率来提高网络的表达能力。这使得网络能够更好地处理多尺度和多分辨率的特征,从而提高了其在各种计算机视觉任务中的性能。
1.2 Res2Net的基本概念
ResNet中的基本构建块,即残差块(Residual Block)。一个典型的残差块包含两个主要分支:一个跳跃连接(Identity Shortcut)和一个经过多个卷积层的主路径。跳跃连接用于绕过一些卷积层,以确保梯度能够顺畅地传播。
Res2Net的核心思想是将多个分支的信息融合在一个残差块中,以提高网络对不同分辨率的特征的表达能力。具体来说,Res2Net引入了多尺度子网络(Multi-Scale Sub-Networks)来处理不同分辨率的特征,然后将它们的输出级联在一起。这种级联结构允许网络同时学习低分辨率和高分辨率的特征表示,从而提高了网络的感知能力。
Res2Net的核心结构是一个多分支的残差块,每个分支都有自己的卷积层,负责处理不同分辨率的特征。这些分支的输出级联在一起,以获得最终的块输出。多个这样的块可以构建成深层网络,以处理更复杂的任务。
Res2Net的工作原理在前向传播过程中如下:
输入特征首先经过一个初始卷积层,用于提取低级别的特征表示。
接下来,输入特征被送入多个Res2Net块。每个块都包含多个分支,每个分支处理不同分辨率的特征。
每个分支内部包含卷积层、激活函数和规范化层等,用于提取和调整特征。
分支的输出级联在一起,形成块的最终输出。
这个块的输出可以传递到下一个块,也可以连接到网络的其他部分。
如下图:
2. YOLOV5添加Res2Net模块
在models/common.py文件中增加以下模块:
class Bottle2neck(nn.Module): expansion = 1 def __init__(self, inplanes, planes, shortcut, baseWidth=26, scale=4): """ Constructor Args: inplanes: input channel dimensionality planes: output channel dimensionality baseWidth: basic width of conv3x3 scale: number of scale. """ super(Bottle2neck, self).__init__() width = int(math.floor(planes * (baseWidth / 64.0))) self.conv1 = Conv(inplanes, width * scale, k=1) if scale == 1: self.nums = 1 else: self.nums = scale - 1 convs = [] for i in range(self.nums): convs.append(Conv(width, width, k=3)) self.convs = nn.ModuleList(convs) self.conv3 = Conv(width * scale, planes * self.expansion, k=1, act=False) self.silu = nn.SiLU(inplace=True) self.scale = scale self.width = width self.shortcut = shortcut def forward(self, x): if self.shortcut: residual = x out = self.conv1(x) spx = torch.split(out, self.width, 1) for i in range(self.nums): if i == 0: sp = spx[i] else: sp = sp + spx[i] sp = self.convs[i](sp) if i == 0: out = sp else: out = torch.cat((out, sp), 1) if self.scale != 1: out = torch.cat((out, spx[self.nums]), 1) out = self.conv3(out) print(out.shape) if self.shortcut: out += residual out = self.silu(out) return out class C3_Res2Block(C3): # CSP Bottleneck with 3 convolutions def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion super().__init__(c1, c2, n, shortcut, g, e) c_ = int(c2 * e) # hidden channels self.m = nn.Sequential(*(Bottle2neck(c_, c_, shortcut) for _ in range(n)))
在models/yolo.py文件下里的parse_model函数将类名加入进去,如下图:
创建添加Res2Net模块的YOLOv5的yaml配置文件
# Parameters nc: 80 # number of classes depth_multiple: 0.33 # model depth multiple width_multiple: 0.50 # layer channel multiple anchors: - [10,13, 16,30, 33,23] # P3/8 - [30,61, 62,45, 59,119] # P4/16 - [116,90, 156,198, 373,326] # P5/32 # YOLOv5 v6.0 backbone backbone: # [from, number, module, args] [[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2 [-1, 1, Conv, [128, 3, 2]], # 1-P2/4 [-1, 3, C3_Res2Block, [128]], [-1, 1, Conv, [256, 3, 2]], # 3-P3/8 [-1, 6, C3_Res2Block, [256]], [-1, 1, Conv, [512, 3, 2]], # 5-P4/16 [-1, 9, C3_Res2Block, [512]], [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32 [-1, 3, C3_Res2Block, [1024]], [-1, 1, SPPF, [1024, 5]], # 9 ] # YOLOv5 v6.0 head head: [[-1, 1, Conv, [512, 1, 1]], [-1, 1, nn.Upsample, [None, 2, 'nearest']], [[-1, 6], 1, Concat, [1]], # cat backbone P4 [-1, 3, C3_Res2Block, [512, False]], # 13 [-1, 1, Conv, [256, 1, 1]], [-1, 1, nn.Upsample, [None, 2, 'nearest']], [[-1, 4], 1, Concat, [1]], # cat backbone P3 [-1, 3, C3_Res2Block, [256, False]], # 17 (P3/8-small) [-1, 1, Conv, [256, 3, 2]], [[-1, 14], 1, Concat, [1]], # cat head P4 [-1, 3, C3_Res2Block, [512, False]], # 20 (P4/16-medium) [-1, 1, Conv, [512, 3, 2]], [[-1, 10], 1, Concat, [1]], # cat head P5 [-1, 3, C3_Res2Block, [1024, False]], # 23 (P5/32-large) [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5) ]