yolov26改进 | 主干/Backbone篇 | 利用目标检测轻量化网络MobileNetV2替换Backbone(yolov26系列全系轻量化)
开始讲解之前推荐一下我的专栏本专栏的内容支持(分类、检测、分割、追踪、关键点检测),专栏目前为限时折扣欢迎大家订阅本专栏本专栏每周更新5-7篇最新机制更有包含我所有改进的文件和交流群提供给大家本人定期在群内分享发表论文方法和经验。一、本文介绍本文给大家带来的改进机制是经典轻量级网络——MobileNetV2。该网络专为移动端和嵌入式视觉应用设计在MobileNetV1深度可分离卷积的基础上进一步引入了倒残差结构和线性瓶颈层。其首先通过逐点卷积扩展特征通道再利用轻量级深度卷积提取空间信息最后通过线性卷积压缩通道从而在降低参数量和计算量的同时保留更加完整的特征信息。与MobileNetV1相比MobileNetV2具有更强的特征提取能力并在模型精度、推理速度和部署效率之间取得了更好的平衡。其通过去除狭窄瓶颈层中的非线性激活减少了特征信息损失适合应用于目标检测、图像分类、面部属性识别和细粒度识别等多种视觉任务。对于希望降低YOLO模型计算开销、提高推理速度并实现边缘端部署的读者来说MobileNetV2是一种非常实用的轻量化改进方案。本文基于MobileNetV2官方结构进行了适配与修改提供了对应YOLOv26系列不同模型规模的全系轻量化版本。文章将结合完整代码和配置文件手把手讲解如何使用MobileNetV2替换原有特征提取网络帮助大家快速完成模型搭建、训练和实际应用。专栏链接YOLOv26有效涨点专栏包含Conv、注意力机制、主干/Backbone、损失函数、优化器、后处理等改进机制目录一、本文介绍二、MobileNetV2的框架原理2.1 MobileNetV2的基本原理2.1.1 反转残差结构2.1.2 线性瓶颈层2.1.3 SSDLite框架三、MobileNetV2的核心代码四、手把手教你添加MobileNetV2网络结构4.1 修改一4.2 修改二4.3 修改三4.4 修改四4.5 修改五4.6 修改六4.7 修改七4.8 修改八4.9 修改九五、MobileNetV2的yaml文件5.1 yaml文件5.2 训练文件的代码六、成功运行记录七、本文总结二、MobileNetV2的框架原理​​官方论文地址官方论文地址官方代码地址官方代码地址​2.1 MobileNetV2的基本原理MobileNetV2是在MobileNetV1基础上提出来的其不光具有V1的全部改进还提出了采用反转残差结构和线性瓶颈层。这种结构通过轻量级的深度卷积和线性卷积过滤特征同时去除狭窄层中的非线性以维持表征能力。MobileNetV2通过这种设计提高了性能并在多种任务和基准测试上表现出色。此外它提出了一种新的框架SSDLite用于移动设备上的目标检测并展示了如何构建移动语义分割模型Mobile DeepLabv3。这种方法允许输入/输出域与变换的表达力解耦为进一步分析提供了方便的框架。MobileNetV2的主要创新点包括1. 反转残差结构使用轻量级的深度卷积作为扩展层来提高特征过滤的效率。2. 线性瓶颈层在狭窄的层中去除非线性激活函数以保持网络的表征能力。3. SSDLite框架用于移动设备上的高效目标检测它是一种简化和优化的SSD框架。2.1.1 反转残差结构反转残差结构是MobileNetV2的关键特性它采用轻量级的深度可分离卷积作为扩展层。这种结构首先使用1x1的卷积将输入特征图的通道数扩大然后应用深度可分离卷积对这些扩展的特征图进行空间特征提取最后再次通过1x1的卷积将通道数减少恢复到原来的尺寸。这样的设计有效地提高了网络处理特征的效率同时减少了参数数量和计算成本。通过这种方式MobileNetV2能够在保持模型轻量的同时提供足够的模型表现力适用于移动和嵌入式设备上的高效计算。​上图展示了残差块和反转残差块之间的区别(a) 残差传统的残差块通过直接连接输入和输出来促进特征的传递通常包含具有高通道数的层和ReLU激活函数。(b) 反转残差块在反转残差块中连接是在瓶颈层之间即通道数较少的层而且去除了非线性激活函数以保持特征的表达力。这种设计通常首先用一个扩展层增加通道数然后应用深度卷积处理特征并且在最后一个线性层减少通道数。2.1.2线性瓶颈层线性瓶颈层是MobileNetV2架构中的另一个关键特性。在这种结构中传统的非线性激活函数被有意地从瓶颈层中去除。瓶颈层是指那些通道数较少的卷积层它们位于扩展层和压缩层之间。这样做的目的是为了减少信息在通过狭窄层时的损失因为非线性操作可能会破坏特征中的一些信息。通过保持这些层的线性网络能够维持更丰富的特征表示这对于提高模型的整体性能至关重要。总结就是在一些卷积层里面把激活函数删除掉了类似于v8中的Bottleneck模块将其中的激活函数删除掉。2.1.3SSDLite框架SSDLite是一个轻量级的目标检测框架专为移动设备优化。它是SSD框架的简化版本通过使用深度可分离卷积替换SSD中的标准卷积显著减少了计算量和模型的大小。SSDLite继承了SSD的单次检测机制使得模型在进行目标检测时既高效又准确。这种设计使SSDLite非常适合在资源受限的设备上进行实时目标检测任务。​上图展示了可分离卷积块的演变。其中(a) 展示了常规的卷积。(b) 展示了可分离卷积块这种块首先使用深度卷积分别处理每个输入通道然后用一个1x1的卷积组合这些特征。(c) 展示了带有线性瓶颈的可分离卷积它在瓶颈层中移除了非线性激活函数以保持特征的表达力。(d) 展示了带有扩展层的瓶颈结构它使用一个扩展层放大特征空间然后再用深度卷积和1x1卷积进行处理。对角线阴影的纹理表示不包含非线性的层最后的浅色层表示下一个块的开始。请注意当堆叠时2d和2c是等效的块。三、MobileNetV2的核心代码下面的代码是整个MobileNetV2的核心代码大家如果想学习可以和上面的框架原理对比着看一看估计会有一定的收获使用方式看章节四。import torch from torch import nn __all__ [MobileNetV2_n, MobileNetV2_s, MobileNetV2_m] class ConvNormReLUBlock(nn.Module): def __init__( self, in_channels: int, out_channels: int, kernel_size: list, stride: int 1, padding: int 0, groups: int 1, bias: bool False, activation: bool nn.ReLU6, ): Constructs a block containing a combination of convolution, batchnorm and relu Args: in_channels (int): input channels out_channels (int): output channels kernel_size (list): kernel size parameter for convolution stride (int, optional): stride parameter for convolution. Defaults to 1. padding (int, optional): padding parameter for convolution. Defaults to 0. groups (int, optional): number of blocked connections from input channel to output channel for convolution. Defaults to 1. bias (bool, optional): whether to enable bias in convolution. Defaults to False. activation (bool, optional): activation function to use. Defaults to nn.ReLU6. super().__init__() self.conv nn.Conv2d( in_channels, out_channels, kernel_size, stridestride, paddingpadding, groupsgroups, biasbias, ) self.bn nn.BatchNorm2d(out_channels) self.activation activation() def forward(self, x): Perform forward pass. x self.conv(x) x self.bn(x) x self.activation(x) return x class InverseResidualBlock(nn.Module): def __init__( self, in_channels: int, out_channels: int, expansion_factor: int 6, stride: int 1, ): Constructs a inverse residual block with depthwise seperable convolution Args: in_channels (int): input channels out_channels (int): output channels expansion_factor (int, optional): Calculating the input output channel for depthwise convolution by multiplying the expansion factor with input channels. Defaults to 6. stride (int, optional): stride paramemeter for depthwise convolution. Defaults to 1. CSDN:Snu77 super().__init__() hidden_channels in_channels * expansion_factor self.residual in_channels out_channels and stride 1 self.conv1 ( ConvNormReLUBlock(in_channels, hidden_channels, (1, 1)) if in_channels ! hidden_channels else nn.Identity() # If its not the first layer, then we need to add a 1x1 convolutional layer to expand the number of channels ) self.depthwise_conv ConvNormReLUBlock( hidden_channels, hidden_channels, (3, 3), stridestride, padding1, groupshidden_channels, ) self.conv2 ConvNormReLUBlock( hidden_channels, out_channels, (1, 1), activationnn.Identity ) def forward(self, x): Perform forward pass. identity x x self.conv1(x) x self.depthwise_conv(x) x self.conv2(x) if self.residual: x torch.add(x, identity) return x class MobileNetV2(nn.Module): def __init__( self, input_channel: int 3, depth_multiplier: float 1, ): Constructs MobileNetV2 architecture Args: n_classes (int, optional): output neuron in last layer. Defaults to 1000. input_channel (int, optional): input channels in first conv layer. Defaults to 3. dropout (float, optional): dropout in last layer. Defaults to 0.2. super().__init__() # The configuration of MobileNetV2 # input channels, expansion factor, output channels, repeat, stride, config ( (32, 1, 16, 1, 1), (16, 6, 24, 2, 2), (24, 6, 32, 3, 2), (32, 6, 64, 4, 2), (64, 6, 96, 3, 1), (96, 6, 160, 3, 2), (160, 6, 320, 1, 1), ) layers [ ConvNormReLUBlock(input_channel, int(32 * depth_multiplier), (3, 3), stride2, padding1) ] # 遍历配置并添加 InverseResidualBlock 层 for in_channels, expansion_factor, out_channels, repeat, stride in config: # repeat不放缩了已经足够轻量化了 for _ in range(repeat): layers.append( InverseResidualBlock( in_channelsint(in_channels * depth_multiplier), out_channelsint(out_channels * depth_multiplier), expansion_factorexpansion_factor, stridestride, ) ) in_channels out_channels # 更新输入通道 stride 1 # 重复层的 stride 设为 1 # 将层列表转换为 nn.Sequential self.model nn.Sequential(*layers) self.width_list [i.size(1) for i in self.forward(torch.randn(1, 3, 640, 640))] def forward(self, x): Perform forward pass. unique_tensors {} for model in self.model: x model(x) width, height x.shape[2], x.shape[3] unique_tensors[(width, height)] x result_list list(unique_tensors.values())[-4:] return result_list def MobileNetV2_n(width_mult0.5): model MobileNetV2(depth_multiplier0.25) return model def MobileNetV2_s(width_mult1.0): model MobileNetV2(depth_multiplier0.5) return model def MobileNetV2_m(width_mult1.5): model MobileNetV2(depth_multiplier1) return model if __name__ __main__: # Generating Sample image image_size (1, 3, 224, 224) image torch.rand(*image_size) # Model mobilenet_v2 MobileNetV2() out mobilenet_v2(image) for i in range(len(out)): print(out[i].size())四、手把手教你添加MobileNetV2网络结构4.1 修改一我们复制网络结构代码到“ultralytics/nn”目录下创建一个py文件复制粘贴进去 我这里起的名字是MobileNetV2。​4.2 修改二第二步我们在该目录下创建一个新的py文件名字为__init__.py(用群内的文件的话已经有了无需新建)然后在其内部导入我们的检测头如下图所示。4.3 修改三第三步我门中到如下文件ultralytics/nn/tasks.py进行导入和注册我们的模块(用群内的文件的话已经有了无需重新导入直接开始第四步即可)从今天开始以后的教程就都统一成这个样子了因为我默认大家用了我群内的文件来进行修改4.4 修改四添加如下两行代码​4.5 修改五找到1600多行大概把具体看图片按照图片来修改就行添加红框内的部分注意没有()只是函数名我这里只添加了部分的版本大家有兴趣这个MobileNetV2还有更多的版本可以添加看我给的代码函数头即可。​elif m in {自行添加对应的模型即可下面都是一样的}: m m() c2 m.width_list # 返回通道列表 backbone True4.6 修改六按图修改。​if isinstance(c2, list): m_ m m_.backbone True else: m_ nn.Sequential(*(m(*args) for _ in range(n))) if n 1 else m(*args) # module t str(m)[8:-2].replace(__main__., ) # module type m.np sum(x.numel() for x in m_.parameters()) # number params m_.i, m_.f, m_.type i 4 if backbone else i, f, t # attach index, from index, type4.7 修改七如下的也需要修改全部按照我的来。​代码如下把原先的代码替换了即可。if verbose: LOGGER.info(f{i:3}{str(f):20}{n_:3}{m.np:10.0f} {t:45}{str(args):30}) # print save.extend(x % (i 4 if backbone else i) for x in ([f] if isinstance(f, int) else f) if x ! -1) # append to savelist layers.append(m_) if i 0: ch [] if isinstance(c2, list): ch.extend(c2) if len(c2) ! 5: ch.insert(0, 0) else: ch.append(c2)4.8 修改八修改七和前面的都不太一样需要修改前向传播中的一个部分 已经离开了parse_model方法了。可以在图片中看代码行数没有离开task.py文件都是同一个文件。 同时这个部分有好几个前向传播都很相似大家不要看错了是160多行左右的不同仓库版本可能有些差异同时我后面提供了代码大家直接复制粘贴即可不会修改联系博主获取视频教程。​​代码如下-def _predict_once(self, x, profileFalse, visualizeFalse, embedNone): Perform a forward pass through the network. Args: x (torch.Tensor): The input tensor to the model. profile (bool): Print the computation time of each layer if True. visualize (bool): Save the feature maps of the model if True. embed (list, optional): A list of layer indices to return embeddings from. Returns: (torch.Tensor): The last output of the model. y, dt, embeddings [], [], [] # outputs embed frozenset(embed) if embed is not None else {-1} max_idx max(embed) for m in self.model: if m.f ! -1: # if not from previous layer x y[m.f] if isinstance(m.f, int) else [x if j -1 else y[j] for j in m.f] # from earlier layers if profile: self._profile_one_layer(m, x, dt) if hasattr(m, backbone): x m(x) if len(x) ! 5: # 0 - 5 x.insert(0, None) for index, i in enumerate(x): if index in self.save: y.append(i) else: y.append(None) x x[-1] # 最后一个输出传给下一层 else: x m(x) # run y.append(x if m.i in self.save else None) # save output if visualize: feature_visualization(x, m.type, m.i, save_dirvisualize) if embed and m.i in embed: embeddings.append(nn.functional.adaptive_avg_pool2d(x, (1, 1)).squeeze(-1).squeeze(-1)) # flatten if m.i max_idx: return torch.unbind(torch.cat(embeddings, 1), dim0) return x4.9 修改九我们找到如下文件ultralytics/utils/torch_utils.py按照如下的图片进行修改否则容易打印不出来计算量。​五、MobileNetV2的yaml文件5.1 yaml文件训练信息YOLO26-Backbone-MobileNetV2 summary: 352 layers, 1,545,508 parameters, 1,545,508 gradients, 3.3 GFLOPs# Ultralytics AGPL-3.0 License - https://ultralytics.com/license # Ultralytics YOLO26 object detection model with P3/8 - P5/32 outputs # Model docs: https://docs.ultralytics.com/models/yolo26 # Task docs: https://docs.ultralytics.com/tasks/detect # Parameters nc: 80 # number of classes end2end: True # whether to use end-to-end mode reg_max: 1 # DFL bins scales: # model compound scaling constants, i.e. modelyolo26n.yaml will call yolo26.yaml with scale n # [depth, width, max_channels] n: [0.50, 0.25, 1024] # summary: 260 layers, 2,572,280 parameters, 2,572,280 gradients, 6.1 GFLOPs s: [0.50, 0.50, 1024] # summary: 260 layers, 10,009,784 parameters, 10,009,784 gradients, 22.8 GFLOPs m: [0.50, 1.00, 512] # summary: 280 layers, 21,896,248 parameters, 21,896,248 gradients, 75.4 GFLOPs l: [1.00, 1.00, 512] # summary: 392 layers, 26,299,704 parameters, 26,299,704 gradients, 93.8 GFLOPs x: [1.00, 1.50, 512] # summary: 392 layers, 58,993,368 parameters, 58,993,368 gradients, 209.5 GFLOPs # 共四个版本 MobileNetV2_n, MobileNetV2_m, MobileNetV2_s # YOLO26 backbone backbone: # [from, repeats, module, args] - [-1, 1, MobileNetV2_n, []] # 0-4 P1/2 # - [-1, 1, MobileNetV2_m, []] # 0-4 P1/2 # - [-1, 1, MobileNetV2_s, []] # 0-4 P1/2 - [-1, 1, SPPF, [1024, 5, 3, True]] # 5 - [-1, 2, C2PSA, [1024]] # 6 # YOLO26 head head: - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 3], 1, Concat, [1]] # cat backbone P4 - [-1, 2, C3k2, [512, True]] # 9 - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 2], 1, Concat, [1]] # cat backbone P3 - [-1, 2, C3k2, [256, True]] # 12 (P3/8-small) - [-1, 1, Conv, [256, 3, 2]] - [[-1, 9], 1, Concat, [1]] # cat head P4 - [-1, 2, C3k2, [512, True]] # 15 (P4/16-medium) - [-1, 1, Conv, [512, 3, 2]] - [[-1, 6], 1, Concat, [1]] # cat head P5 - [-1, 2, C3k2, [1024, True, 0.5, True]] # 18 (P5/32-large) - [[12, 15, 18], 1, Detect, [nc]] # Detect(P3, P4, P5)5.2 训练文件的代码可以复制我的运行文件进行运行。import warnings warnings.filterwarnings(ignore) from ultralytics import YOLO if __name__ __main__: model YOLO(替换你的yaml文件地址) model.load(yolov8n.pt) model.train(datar你的数据集的地址, cacheFalse, imgsz640, epochs150, batch4, close_mosaic0, workers0, device0, optimizerSGD ampFalse, )六、成功运行记录下面是成功运行的截图已经完成了有1个epochs的训练图片太大截不全第2个epochs了。​​​七、本文总结到此本文的正式分享内容就结束了在这里给大家推荐我的YOLOv26改进有效涨点专栏本专栏目前为新开的平均质量分98分后期我会根据各种最新的前沿顶会进行论文复现也会对一些老的改进机制进行补充如果大家觉得本文帮助到你了订阅本专栏关注后续更多的更新~专栏链接YOLOv26有效涨点专栏包含Conv、注意力机制、主干/Backbone、损失函数、优化器、后处理等改进机制​​​​​