YOLO11详解(与YOLOv8对比)

1 YOLO:简史

YOLO(You Only Look Once),是由华盛顿大学的Joseph Redmon和Ali Farhadi开发的一个流行的目标检测与图像分割模型。自2015年发布以来,YOLO因其高速度和高精度迅速获得了广泛的关注和使用。

  • 2016 年发布的YOLOv2 通过纳入批量归一化、锚框和维度集群改进了原始模型。
  • 2018 年推出的YOLOv3 使用更高效的骨干网络、多锚和空间金字塔池进一步增强了模型的性能。
  • YOLOv4 was released in 2020, introducing innovations like Mosaic data augmentation, a new anchor-free detection head, and a new loss function.
  • YOLOv5进一步提高了模型的性能,并增加了超参数优化、集成实验跟踪和自动导出为常用导出格式等新功能。
  • YOLOv6于 2022 年由美团开源,目前已用于该公司的许多自主配送机器人。
  • YOLOv7增加了额外的任务,如 COCO 关键点数据集的姿势估计。
  • YOLOv8 released in 2023 by Ultralytics. YOLOv8 introduced new features and improvements for enhanced performance, flexibility, and efficiency, supporting a full range of vision AI tasks,
  • YOLOv9 引入了可编程梯度信息 (PGI) 和广义高效层聚合网络 (GELAN) 等创新方法。
  • YOLOv10是由清华大学的研究人员使用该软件包创建的。 UltralyticsPython 软件包创建的。该版本通过引入端到端头(End-to-End head),消除了非最大抑制(NMS)要求,实现了实时目标检测的进步。
  • YOLO11 🚀 NEW: Ultralytics最新的YOLO模型在多个任务中提供了最先进的(SOTA)性能,涵盖了检测、分割、姿态估计、跟踪和分类等领域。这些模型在各类AI应用和领域中展示了强大的能力。

2 YOLO11与YOLOv8结构对比

2.1 YOLO11模型结构:

# Ultralytics YOLO 🚀, AGPL-3.0 license
# YOLO11 object detection model with P3-P5 outputs. For Usage examples see https://docs.ultralytics.com/tasks/detect
 
# Parameters
nc: 80 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolo11n.yaml' will call yolo11.yaml with scale 'n'
  # [depth, width, max_channels]
  n: [0.50, 0.25, 1024] # summary: 319 layers, 2624080 parameters, 2624064 gradients, 6.6 GFLOPs
  s: [0.50, 0.50, 1024] # summary: 319 layers, 9458752 parameters, 9458736 gradients, 21.7 GFLOPs
  m: [0.50, 1.00, 512] # summary: 409 layers, 20114688 parameters, 20114672 gradients, 68.5 GFLOPs
  l: [1.00, 1.00, 512] # summary: 631 layers, 25372160 parameters, 25372144 gradients, 87.6 GFLOPs
  x: [1.00, 1.50, 512] # summary: 631 layers, 56966176 parameters, 56966160 gradients, 196.0 GFLOPs
 
# YOLO11n backbone
backbone:
  # [from, repeats, module, args]
  - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
  - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
  - [-1, 2, C3k2, [256, False, 0.25]]
  - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
  - [-1, 2, C3k2, [512, False, 0.25]]
  - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
  - [-1, 2, C3k2, [512, True]]
  - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
  - [-1, 2, C3k2, [1024, True]]
  - [-1, 1, SPPF, [1024, 5]] # 9
  - [-1, 2, C2PSA, [1024]] # 10
 
# YOLO11n head
head:
  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [[-1, 6], 1, Concat, [1]] # cat backbone P4
  - [-1, 2, C3k2, [512, False]] # 13
 
  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [[-1, 4], 1, Concat, [1]] # cat backbone P3
  - [-1, 2, C3k2, [256, False]] # 16 (P3/8-small)
 
  - [-1, 1, Conv, [256, 3, 2]]
  - [[-1, 13], 1, Concat, [1]] # cat head P4
  - [-1, 2, C3k2, [512, False]] # 19 (P4/16-medium)
 
  - [-1, 1, Conv, [512, 3, 2]]
  - [[-1, 10], 1, Concat, [1]] # cat head P5
  - [-1, 2, C3k2, [1024, True]] # 22 (P5/32-large)
 
  - [[16, 19, 22], 1, Detect, [nc]] # Detect(P3, P4, P5)

2.2 YOLOv8模型结构:

# Ultralytics YOLO 🚀, AGPL-3.0 license
https://docs.ultralytics.com/tasks/detect
 
# Parameters
nc: 80  # 
 
scales: #
  # [depth, width, max_channels]
  n: [0.33, 0.25, 1024]  
  s: [0.33, 0.50, 1024]  
  m: [0.67, 0.75, 768]  
  l: [1.00, 1.00, 512]   
  x: [1.00, 1.25, 512] 
 
# YOLOv8.0n
backbone:
  # [from, repeats, module, args]
  - [-1, 1, Conv, [64, 3, 2]]  # 0-P1/2
  - [-1, 1, Conv, [128, 3, 2]]  # 1-P2/4
  - [-1, 3, C2f, [128, True]]
  - [-1, 1, Conv, [256, 3, 2]]  # 3-P3/8
  - [-1, 6, C2f, [256, True]]
  - [-1, 1, Conv, [512, 3, 2]]  # 5-P4/16
  - [-1, 6, C2f, [512, True]]
  - [-1, 1, Conv, [1024, 3, 2]]  # 7-P5/32
  - [-1, 3, C2f, [1024, True]]
  - [-1, 1, SPPF, [1024, 5]]  # 9
 
# YOLOv8.0n
head:
  - [-1, 1, nn.Upsample, [None, 2, 'nearest']]
  - [[-1, 6], 1, Concat, [1]]  # P4
  - [-1, 3, C2f, [512]]  # 12
 
  - [-1, 1, nn.Upsample, [None, 2, 'nearest']]
  - [[-1, 4], 1, Concat, [1]]  # P3
  - [-1, 3, C2f, [256]]  # 15 (P3/8)
 
  - [-1, 1, Conv, [256, 3, 2]]
  - [[-1, 12], 1, Concat, [1]]  # P4
  - [-1, 3, C2f, [512]]  # 18 (P4/16)
 
  - [-1, 1, Conv, [512, 3, 2]]
  - [[-1, 9], 1, Concat, [1]]  # P5
  - [-1, 3, C2f, [1024]]  # 21 (P5/32)
 
  - [[15, 18, 21], 1, Detect, [nc]]  # (P3, P4, P5)

 

2.3 模型差别

模型差别总体预览 :

2.3.1 使用C2K2模块代替C2f模块

C2f模块

    首先了解C2f模块的代码,这个模块结合了跨阶段部分连接(CSPNet)和 Bottleneck 结构,用于有效的特征提取。:
     __init__方法:
        - 初始化了两个卷积层 cv1和 cv2,并计算出隐藏通道数 self.c
        - 使用 ModuleList 创建多个 Bottleneck 模块,作为中间的卷积操作
        - 参数包括:输入通道数 c1、输出通道数 c2、Bottleneck 模块数 n、是否使用残差连接           shortcut、卷积分组数 g 和扩展因子 e。

      forward方法:
           - 通过 cv1 卷积层将输入特征分成两部分(使用 chunk() 方法),然后通过 Bottleneck                  层处理后一部分特征。
           - 最后使用 cv2 通过 1x1 卷积层将所有分支的输出拼接在一起,得到最终的输出。

        forward_split方法:
           - 与 forward 类似,但使用了 split() 函数来分割特征,而不是 chunk()。

                其中split() 和 chunk()都是 PyTorch 中用于张量切分的函数,但它们的切分方式有所不同。具体来说:       

chunk()根据指定的数量将张量沿着某个维度分成若干块,每块的大小大致相同(可能不是完全相等)。如果无法均匀分割,则有些块会比其他块略大。

torch.chunk(input, chunks, dim=0)

# input:输入的张量。
# chunks:将张量分割成多少块。
# dim:在哪个维度上进行分割。

示例:
x = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
chunks = torch.chunk(x, 2, dim=0)
# chunks 的结果是 [tensor([[1, 2, 3], [4, 5, 6]]), tensor([[7, 8, 9]])]

 split() 根据指定的每个块的大小将张量沿着某个维度分割。你可以指定每个块的大小,因此它的切分方式比chunk()更灵活。

torch.split(input, split_size_or_sections, dim=0)

# input:输入的张量。
# split_size_or_sections:可以是一个整数,表示每块的大小,或者是一个列表,表示每块的大小。
# dim:在哪个维度上进行分割。

x = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
splits = torch.split(x, [1, 2], dim=0)
# splits 的结果是 [tensor([[1, 2, 3]]), tensor([[4, 5, 6], [7, 8, 9]])]

  C2f模块实现代码:

import torch
import torch.nn as nn

class C2f(nn.Module):
    """CSP Bottleneck with 2 convolutions."""
    
    def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):
        """
        初始化函数,定义C2f模块的参数和层次结构。

        参数:
        - c1: 输入通道数
        - c2: 输出通道数
        - n: Bottleneck模块的数量
        - shortcut: 是否使用残差连接
        - g: 卷积层中的组数(用于分组卷积)
        - e: 扩展因子,用于计算 Bottleneck 中的隐藏通道数
        """
        super().__init__()
        self.c = int(c2 * e)  # 计算隐藏通道数,通常是输出通道数的某个比例
        self.cv1 = Conv(c1, 2 * self.c, 1, 1)  # 1x1 卷积层,用于减少通道数,输出2倍隐藏通道数
        self.cv2 = Conv((2 + n) * self.c, c2, 1)  # 最终的1x1卷积层,恢复到原输出通道数,可能使用FReLU激活函数
        # 创建多个 Bottleneck 模块,每个模块有一个卷积层,使用shortcut和分组卷积
        self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))

    def forward(self, x):
        """
        前向传播函数,定义C2f模块的计算流程。

        参数:
        - x: 输入的张量(Tensor)

        返回:
        - 输出的张量(Tensor)
        """
        # 使用1x1卷积将输入分成两部分,并沿通道维度进行分割
        y = list(self.cv1(x).chunk(2, 1))  # 将输入切分为两块(沿通道维度)
        # 对每个Bottleneck模块处理y的后一部分,并添加到列表中
        y.extend(m(y[-1]) for m in self.m)  # 将每个 Bottleneck 的输出添加到列表中
        # 将所有分支的输出拼接,经过最后的1x1卷积生成最终输出
        return self.cv2(torch.cat(y, 1))  # 在通道维度上拼接

    def forward_split(self, x):
        """
        前向传播的另一种方式,使用 split() 方法代替 chunk() 进行特征切分。

        参数:
        - x: 输入的张量(Tensor)

        返回:
        - 输出的张量(Tensor)
        """
        # 使用1x1卷积将输入分成两部分,并使用 split() 进行分割
        y = list(self.cv1(x).split((self.c, self.c), 1))  # 使用 split 按照指定尺寸切分
        # 对每个Bottleneck模块处理y的后一部分,并添加到列表中
        y.extend(m(y[-1]) for m in self.m)  # 将每个 Bottleneck 的输出添加到列表中
        # 将所有分支的输出拼接,经过最后的1x1卷积生成最终输出
        return self.cv2(torch.cat(y, 1))  # 在通道维度上拼接

     接下来了解C3k2模块的代码,C3k2模块是 CSP Bottleneck 的一种加速实现版本,它结合了卷积和分组卷积的特性,并允许选择使用自定义卷积核大小的 C3k模块,或者标准的 Bottleneck模块。它的主要设计目的是在保持高效特征提取的同时,提供更灵活的配置以适应不同的计算需求:

C3k2 模块

 

  1. CSP Bottleneck 的加速版本

    • CSP (Cross Stage Partial Network)网络最早是在 YOLOv4 中引入的,它通过减少冗余的梯度流动,使得模型的计算更加高效,同时保持了强大的特征表示能力。
    • C3k2模块继承了 CSP 的基本思想,通过使用 Bottleneck或者 C3k模块来进行特征提取。
  2. 两个卷积操作(2 convolutions)

    • C3k2的命名反映了它的结构,其中的 "k2" 代表两个卷积操作。相比传统的 CSP Bottleneck,它通过减少卷积操作的复杂度来加速特征提取过程。
  3. 自定义卷积核大小(C3k)

    • 如果设置 C3k=True,C3k2模块会使用 C3k,这是一个具有可定制卷积核大小的 Bottleneck 模块。通过允许用户选择卷积核的大小,C3k2提供了更大的灵活性,可以针对不同任务和计算资源进行优化。
    • 如果 C3k=False,则使用标准的 Bottleneck 模块。
  4. 扩展因子(Expansion Factor)

    • C3k2使用了扩展因子 e,这意味着模块内部的卷积通道数将根据输出通道数 c2e 的值进行缩放。扩展因子通常用于控制瓶颈层中的特征维度,决定了特征的精简和扩展程度。
  5. 分组卷积(Group Convolution)

    • C3k2中的卷积层可以支持分组卷积(通过参数 g 控制),这种技术通过将卷积操作分成多个组,从而减少计算量并加速推理过程。这在处理高维特征时尤为重要,特别是在需要进行实时推理的应用中。
  6. 残差连接(Shortcut)

    • C3k2还支持残差连接(shortcut参数)。残差连接使得输入可以跳过卷积操作,直接加到输出上,从而避免梯度消失问题,并在深层网络中提高训练效果。 

 C3k2模块实现代码: 

import torch
import torch.nn as nn

class C3k2(nn.Module):
    """
    C3k2 模块是带有 2 个卷积操作的加速版 CSP Bottleneck 模块,并且可以选择性地使用 C3k 块。
    """
    
    def __init__(self, c1, c2, n=1, c3k=False, e=0.5, g=1, shortcut=True):
        """
        初始化 C3k2 模块。

        参数:
        - c1: 输入通道数
        - c2: 输出通道数
        - n: Bottleneck 模块的重复次数
        - c3k: 是否使用 C3k 模块
        - e: 扩展因子,决定隐藏通道数
        - g: 分组卷积参数
        - shortcut: 是否使用残差连接
        """
        super(C3k2, self).__init__()
        self.c = int(c2 * e)  # 计算隐藏通道数
        # 定义 1x1 卷积层,用于将输入调整到 2 倍的隐藏通道数
        self.cv1 = nn.Conv2d(c1, 2 * self.c, kernel_size=1, stride=1, bias=False)
        self.cv2 = nn.Conv2d(2 * self.c, c2, kernel_size=1, stride=1, bias=False)
        
        # 定义模块列表,使用 C3k 或 Bottleneck 模块
        self.m = nn.ModuleList(
            C3k(self.c, self.c, 2, shortcut, g) if c3k else Bottleneck(self.c, self.c, shortcut, g) for _ in range(n)
        )
    
    def forward(self, x):
        """
        前向传播函数,处理输入张量。

        参数:
        - x: 输入张量
        
        返回:
        - 经过 C3k2 模块处理后的输出张量
        """
        # 将 cv1 的输出分成两部分
        y = list(self.cv1(x).chunk(2, dim=1))  # 使用 chunk 操作将通道维度分成两部分
        # 对每个模块列表中的模块进行处理,并将结果添加到 y 中
        y.extend(m(y[-1]) for m in self.m)
        # 将所有部分拼接并通过 cv2 恢复通道数
        return self.cv2(torch.cat(y, dim=1))


class Bottleneck(nn.Module):
    """
    Bottleneck 模块,用于特征提取,包含 1x1 和 3x3 卷积层,并带有残差连接。
    """
    
    def __init__(self, in_channels, out_channels, shortcut=True, g=1, e=0.5):
        """
        初始化 Bottleneck 模块。

        参数:
        - in_channels: 输入通道数
        - out_channels: 输出通道数
        - shortcut: 是否使用残差连接
        - g: 分组卷积参数
        - e: 扩展因子,控制 Bottleneck 中的隐藏通道数
        """
        super(Bottleneck, self).__init__()
        hidden_channels = int(out_channels * e)
        
        # 1x1 卷积用于降维
        self.conv1 = nn.Conv2d(in_channels, hidden_channels, kernel_size=1, stride=1, bias=False)
        self.bn1 = nn.BatchNorm2d(hidden_channels)
        
        # 3x3 卷积用于提取特征,使用自定义卷积核大小
        self.conv2 = nn.Conv2d(hidden_channels, out_channels, kernel_size=3, stride=1, padding=1, groups=g, bias=False)
        self.bn2 = nn.BatchNorm2d(out_channels)
        
        # 判断是否需要残差连接
        self.shortcut = nn.Identity() if in_channels == out_channels and shortcut else None

    def forward(self, x):
        """
        前向传播函数,处理输入张量。

        参数:
        - x: 输入张量
        
        返回:
        - 经过 Bottleneck 模块处理后的输出张量
        """
        residual = x
        x = self.conv1(x)
        x = self.bn1(x)
        x = torch.relu(x)
        x = self.conv2(x)
        x = self.bn2(x)
        
        if self.shortcut is not None:
            x += residual
        
        return torch.relu(x)


class C3k(nn.Module):
    """
    C3k 模块,提供自定义卷积核大小的 CSP Bottleneck 模块。
    """
    
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, k=3):
        """
        初始化 C3k 模块。

        参数:
        - c1: 输入通道数
        - c2: 输出通道数
        - n: Bottleneck 模块的重复次数
        - shortcut: 是否使用残差连接
        - g: 分组卷积参数
        - k: 卷积核大小
        """
        super(C3k, self).__init__()
        hidden_channels = int(c2 * 0.5)  # 默认扩展因子为 0.5
        
        # 定义卷积层
        self.cv1 = nn.Conv2d(c1, hidden_channels, kernel_size=1, stride=1)
        self.cv2 = nn.Conv2d(c1, hidden_channels, kernel_size=1, stride=1)
        self.cv3 = nn.Conv2d(2 * hidden_channels, c2, kernel_size=1, stride=1)
        
        # 定义 Bottleneck 模块序列,使用自定义卷积核大小
        self.m = nn.Sequential(
            *[Bottleneck(hidden_channels, hidden_channels, shortcut, g=g, k=(k, k), e=1.0) for _ in range(n)]
        )
    
    def forward(self, x):
        """
        前向传播函数,处理输入张量。

        参数:
        - x: 输入张量
        
        返回:
        - 经过 C3k 模块处理后的输出张量
        """
        return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim=1))


# 示例使用
if __name__ == "__main__":
    # 假设输入张量形状为 [batch_size, channels, height, width]
    x = torch.randn(1, 64, 128, 128)
    model = C3k2(in_channels=64, out_channels=128, n=3, c3k=False, e=0.5, shortcut=True)
    output = model(x)
    print(output.shape)  # 输出张量的形状应为 [1, 128, 128, 128]

 

其中CSP Bottleneck 模块通常包含以下几个部分:

 分支机制:输入被分为两部分:一个直接传递,另一个通过 Bottleneck 进行处理

Bottleneck 单元:Bottleneck 是由 1x1 卷积层和 3x3 卷积层组成的结构,1x1 卷积层用于减少通道数(降低计算复杂度),3x3 卷积层用于提取局部特征

特征融合:最终通过拼接(concatenation)或逐元素相加(element-wise addition)将两条路径的特征融合在一起,作为输出。

 Bottleneck 模块实现代码:

import torch
import torch.nn as nn

class Bottleneck(nn.Module):
    """
    ResNet 中 Bottleneck 模块的实现,包含 1x1、3x3 和 1x1 卷积层,并带有残差连接。
    """
    def __init__(self, in_channels, out_channels, bottleneck_channels=None, shortcut=True, groups=1, expansion=0.5):
        """
        初始化 Bottleneck 模块。

        参数:
        - in_channels: 输入通道数
        - out_channels: 输出通道数
        - bottleneck_channels: Bottleneck 中的隐藏通道数
        - shortcut: 是否使用残差连接
        - groups: 分组卷积参数
        - expansion: 扩展因子,控制 Bottleneck 中的通道数
        """
        super().__init__()
        bottleneck_channels = bottleneck_channels or int(out_channels * expansion)
        # 1x1 卷积,降维操作
        self.conv1 = nn.Conv2d(in_channels, bottleneck_channels, kernel_size=1, stride=1, bias=False)
        # 3x3 卷积,特征提取
        self.conv2 = nn.Conv2d(bottleneck_channels, bottleneck_channels, kernel_size=3, stride=1, padding=1, groups=groups, bias=False)
        # 1x1 卷积,升维操作
        self.conv3 = nn.Conv2d(bottleneck_channels, out_channels, kernel_size=1, stride=1, bias=False)
        # Batch Normalization
        self.bn1 = nn.BatchNorm2d(bottleneck_channels)
        self.bn2 = nn.BatchNorm2d(bottleneck_channels)
        self.bn3 = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(inplace=True)
        self.shortcut = shortcut and in_channels == out_channels  # 是否使用残差连接

    def forward(self, x):
        """
        前向传播函数。

        参数:
        - x: 输入的张量
        返回:
        - 经过 Bottleneck 处理后的输出张量
        """
        residual = x  # 原始输入,用于残差连接

        # 通过 1x1 卷积进行降维
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)

        # 通过 3x3 卷积进行特征提取
        x = self.conv2(x)
        x = self.bn2(x)
        x = self.relu(x)

        # 通过 1x1 卷积升维
        x = self.conv3

 

2.3.2 YOLO11在SPPF层后新增C2PSA层        

C2PSA 模块是基于 PSA(Pyramid Squeeze Attention)注意力机制的卷积模块,负责处理输入张量并通过注意力机制增强特征表示。该模块涉及到卷积操作、分割和多头注意力机制。

PSA 模块:

 

 PSA 模块实现代码:

import torch
import torch.nn as nn
import math

# Squeeze-and-Excitation (SE) 模块,用于特征加权
class SEWeightModule(nn.Module):
    """
    Squeeze-and-Excitation (SE) 模块:
    通过全局池化和两层全连接网络,对特征通道进行重新加权。
    """
    def __init__(self, channels, reduction=16):
        """
        初始化 SE 模块。

        参数:
        - channels: 输入的通道数
        - reduction: 降维比率,通常是 16
        """
        super(SEWeightModule, self).__init__()
        # 全局平均池化,将空间维度压缩为 1x1
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        # 第一个 1x1 卷积层用于减少通道数,通道数为 channels // reduction
        self.fc1 = nn.Conv2d(channels, channels // reduction, kernel_size=1, padding=0)
        self.relu = nn.ReLU(inplace=True)
        # 第二个 1x1 卷积层用于恢复通道数
        self.fc2 = nn.Conv2d(channels // reduction, channels, kernel_size=1, padding=0)
        # 使用 sigmoid 激活函数生成注意力权重
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        """
        前向传播函数。

        参数:
        - x: 输入张量 (batch_size, channels, height, width)
        
        返回:
        - weight: 通道权重 (batch_size, channels, 1, 1)
        """
        # 全局平均池化
        out = self.avg_pool(x)
        # 通过全连接层减少通道数
        out = self.fc1(out)
        out = self.relu(out)
        # 恢复通道数并生成权重
        out = self.fc2(out)
        weight = self.sigmoid(out)
        return weight

# 标准卷积定义函数
def conv(in_planes, out_planes, kernel_size=3, stride=1, padding=1, dilation=1, groups=1):
    """
    定义带有填充和分组卷积的标准卷积层。

    参数:
    - in_planes: 输入通道数
    - out_planes: 输出通道数
    - kernel_size: 卷积核大小
    - stride: 步长
    - padding: 填充
    - dilation: 扩张
    - groups: 分组卷积数

    返回:
    - 卷积层
    """
    return nn.Conv2d(in_planes, out_planes, kernel_size=kernel_size, stride=stride,
                     padding=padding, dilation=dilation, groups=groups, bias=False)

# 1x1 卷积定义
def conv1x1(in_planes, out_planes, stride=1):
    """
    定义 1x1 卷积层。

    参数:
    - in_planes: 输入通道数
    - out_planes: 输出通道数
    - stride: 步长

    返回:
    - 1x1 卷积层
    """
    return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)

# PSA (Pyramid Squeeze Attention) 模块定义
class PSAModule(nn.Module):
    """
    Pyramid Squeeze Attention (PSA) 模块:
    通过多种不同卷积核大小和分组卷积提取多尺度特征,并使用 SE 模块进行特征加权。
    """
    def __init__(self, inplans, planes, conv_kernels=[3, 5, 7, 9], stride=1, conv_groups=[1, 4, 8, 16]):
        """
        初始化 PSA 模块。

        参数:
        - inplans: 输入通道数
        - planes: 输出通道数
        - conv_kernels: 使用的卷积核大小列表
        - stride: 卷积的步长
        - conv_groups: 分组卷积数量列表
        """
        super(PSAModule, self).__init__()
        # 定义四个不同卷积核大小和分组数的卷积层
        self.conv_1 = conv(inplans, planes // 4, kernel_size=conv_kernels[0], padding=conv_kernels[0] // 2,
                           stride=stride, groups=conv_groups[0])
        self.conv_2 = conv(inplans, planes // 4, kernel_size=conv_kernels[1], padding=conv_kernels[1] // 2,
                           stride=stride, groups=conv_groups[1])
        self.conv_3 = conv(inplans, planes // 4, kernel_size=conv_kernels[2], padding=conv_kernels[2] // 2,
                           stride=stride, groups=conv_groups[2])
        self.conv_4 = conv(inplans, planes // 4, kernel_size=conv_kernels[3], padding=conv_kernels[3] // 2,
                           stride=stride, groups=conv_groups[3])
        # 使用 SE 模块进行加权
        self.se = SEWeightModule(planes // 4)
        self.split_channel = planes // 4
        self.softmax = nn.Softmax(dim=1)

    def forward(self, x):
        """
        前向传播函数。

        参数:
        - x: 输入张量 (batch_size, channels, height, width)

        返回:
        - 加权后的输出张量
        """
        batch_size = x.shape[0]
        # 四个不同卷积核的特征提取
        x1 = self.conv_1(x)
        x2 = self.conv_2(x)
        x3 = self.conv_3(x)
        x4 = self.conv_4(x)

        # 将四个分支特征拼接
        feats = torch.cat((x1, x2, x3, x4), dim=1)
        feats = feats.view(batch_size, 4, self.split_channel, feats.shape[2], feats.shape[3])

        # 使用 SE 模块加权
        x1_se = self.se(x1)
        x2_se = self.se(x2)
        x3_se = self.se(x3)
        x4_se = self.se(x4)

        # 将所有加权后的特征拼接
        x_se = torch.cat((x1_se, x2_se, x3_se, x4_se), dim=1)
        attention_vectors = x_se.view(batch_size, 4, self.split_channel, 1, 1)
        attention_vectors = self.softmax(attention_vectors)

        # 使用注意力权重重新加权特征
        feats_weight = feats * attention_vectors
        for i in range(4):
            x_se_weight_fp = feats_weight[:, i, :, :, :]
            if i == 0:
                out = x_se_weight_fp
            else:
                out = torch.cat((out, x_se_weight_fp), 1)

        return out


  C2PSA 模块:

C2PSA 模块实现代码:

import torch
import torch.nn as nn

class C2PSA(nn.Module):
    """
    C2PSA 模块实现,结合了卷积和 Pyramid Squeeze Attention (PSA) 注意力机制,
    用于增强特征表示能力。
    """

    def __init__(self, c1, c2, n=1, e=0.5):
        """
        初始化 C2PSA 模块。

        参数:
        - c1: 输入通道数,确保与 c2 相等
        - c2: 输出通道数
        - n: 重复 PSABlock 的次数
        - e: 扩展因子,控制隐藏通道数的大小
        """
        super().__init__()
        assert c1 == c2  # 确保输入通道数与输出通道数相同
        self.c = int(c1 * e)  # 计算隐藏通道数
        # 1x1 卷积层,将输入通道数变为 2 倍的隐藏通道数
        self.cv1 = Conv(c1, 2 * self.c, 1, 1)
        # 最终的 1x1 卷积层,将通道数恢复到原始的输出通道数
        self.cv2 = Conv(2 * self.c, c1, 1, 1)

        # 使用 PSA Block 处理分割后的特征,定义一个包含多个 PSABlock 的序列
        # PSA Block 中的注意力比率设置为 0.5,num_heads 控制多头注意力的数量
        self.m = nn.Sequential(
            *(PSABlock(self.c, attn_ratio=0.5, num_heads=self.c // 64) for _ in range(n))
        )

    def forward(self, x):
        """
        前向传播函数,处理输入张量 x,通过 PSA blocks 后返回处理后的张量。

        参数:
        - x: 输入的张量

        返回:
        - 经过处理的输出张量
        """
        # 输入张量 x 经过第一个 1x1 卷积层 self.cv1 处理
        # 使用 split 将通道维度按隐藏通道数 self.c 分割为两个部分 a 和 b
        a, b = self.cv1(x).split((self.c, self.c), dim=1)

        # 通过 PSA blocks 处理 b 分支
        b = self.m(b)

        # 将经过处理的 b 分支和未处理的 a 分支在通道维度拼接
        # 然后经过第二个 1x1 卷积层恢复原通道数
        return self.cv2(torch.cat((a, b), dim=1))

 

__init__ 方法:

  1. 参数

    • c1:输入通道数。
    • c2:输出通道数,代码中通过 assert c1 == c2 确保输入输出通道数相等。
    • n:模块重复的次数。
    • e:扩展因子,用于计算隐藏通道数。
  2. 变量

    • self.c:隐藏通道数,计算方式为 c1 * e
    • self.cv1:1x1 卷积操作,用于将输入通道数变为 2 倍的隐藏通道数。该操作减少了特征维度。
    • self.cv2:最终的卷积操作,将通道数恢复到原始输出通道 c1
  3. PSA Block

    • self.m 是通过 PSABlock 组成的序列,PSABlock 是 PSA 注意力机制的核心部分。每个 PSABlock 中,计算注意力比率 attn_ratio=0.5,以及 num_heads=self.c // 64,表示多头注意力的数量(每 64 个通道作为一组头部)。

forward 方法:

  1. 张量分割

    • a, b = self.cv1(x).split((self.c, self.c), dim=1):输入的张量首先经过 cv1 处理,然后通过 split 函数将其在通道维度上分割成两部分,每部分具有 self.c 个通道。
  2. PSA Block 处理

    • b = self.m(b):通过 PSABlock 模块处理第二部分特征 b,这些模块包含注意力机制,用于增强特征表示。
  3. 拼接与输出

    • torch.cat((a, b), dim=1):最后,将分割后的两个张量 ab 在通道维度上拼接。
    • 拼接后的张量通过 cv2 进行处理,恢复到输出通道数。

 3 使用方法

YOLO11与YOLOv8的使用方法基一致,只需要安装ultralytics的依赖库,以及深度学习模型相关的各类依赖库即可使用。

首先,需要确保 Python 环境中安装了训练所需的库:

pip install ultralytics

 其次,需要准备一个符合 YOLO 格式的数据集,主要包括以下几部分:

  • 图片文件:训练数据的图片,通常为 .jpg.png 格式。
  • 标签文件:每张图片对应的标签文件,通常为 .txt 格式。每个文件包含物体的类别及边界框信息,格式如下:
<class_index> <x_center> <y_center> <width> <height>


# class_index 是物体的类别编号(从 0 开始)。
# x_center 和 y_center 是物体边界框中心点的归一化坐标(相对于图像宽高)。
# width 和 height 是物体边界框的归一化宽度和高度。

/path/to/dataset/
    ├── images/
    │   ├── train/
    │   │   ├── img1.jpg
    │   │   ├── img2.jpg
    │   └── val/
    │       ├── img3.jpg
    └── labels/
        ├── train/
        │   ├── img1.txt
        │   ├── img2.txt
        └── val/
            ├── img3.txt

第三,配置数据集文件,需要一个 .yaml 配置文件,用来指定训练集、验证集的路径和类别信息。

# 数据集配置
path: /path/to/dataset  # 数据集的根目录
train: images/train  # 训练集图片路径
val: images/val  # 验证集图片路径

# 类别信息
nc: 3  # 类别数量
names: ['class1', 'class2', 'class3']  # 类别名称

第四,配置模型文件,需要一个模型结构的 .yaml 文件。可以参考当前的模型文件结构,或基于 当前模型做进一步修改。

# Ultralytics YOLO 🚀, AGPL-3.0 license
# YOLO11 object detection model with P3-P5 outputs. For Usage examples see https://docs.ultralytics.com/tasks/detect
 
# Parameters
nc: 80 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolo11n.yaml' will call yolo11.yaml with scale 'n'
  # [depth, width, max_channels]
  n: [0.50, 0.25, 1024] # summary: 319 layers, 2624080 parameters, 2624064 gradients, 6.6 GFLOPs
  s: [0.50, 0.50, 1024] # summary: 319 layers, 9458752 parameters, 9458736 gradients, 21.7 GFLOPs
  m: [0.50, 1.00, 512] # summary: 409 layers, 20114688 parameters, 20114672 gradients, 68.5 GFLOPs
  l: [1.00, 1.00, 512] # summary: 631 layers, 25372160 parameters, 25372144 gradients, 87.6 GFLOPs
  x: [1.00, 1.50, 512] # summary: 631 layers, 56966176 parameters, 56966160 gradients, 196.0 GFLOPs
 
# YOLO11n backbone
backbone:
  # [from, repeats, module, args]
  - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
  - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
  - [-1, 2, C3k2, [256, False, 0.25]]
  - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
  - [-1, 2, C3k2, [512, False, 0.25]]
  - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
  - [-1, 2, C3k2, [512, True]]
  - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
  - [-1, 2, C3k2, [1024, True]]
  - [-1, 1, SPPF, [1024, 5]] # 9
  - [-1, 2, C2PSA, [1024]] # 10
 
# YOLO11n head
head:
  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [[-1, 6], 1, Concat, [1]] # cat backbone P4
  - [-1, 2, C3k2, [512, False]] # 13
 
  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [[-1, 4], 1, Concat, [1]] # cat backbone P3
  - [-1, 2, C3k2, [256, False]] # 16 (P3/8-small)
 
  - [-1, 1, Conv, [256, 3, 2]]
  - [[-1, 13], 1, Concat, [1]] # cat head P4
  - [-1, 2, C3k2, [512, False]] # 19 (P4/16-medium)
 
  - [-1, 1, Conv, [512, 3, 2]]
  - [[-1, 10], 1, Concat, [1]] # cat head P5
  - [-1, 2, C3k2, [1024, True]] # 22 (P5/32-large)
 
  - [[16, 19, 22], 1, Detect, [nc]] # Detect(P3, P4, P5)
# Ultralytics YOLO 🚀, AGPL-3.0 license
https://docs.ultralytics.com/tasks/detect
 
# Parameters
nc: 80  # 
 
scales: #
  # [depth, width, max_channels]
  n: [0.33, 0.25, 1024]  
  s: [0.33, 0.50, 1024]  
  m: [0.67, 0.75, 768]  
  l: [1.00, 1.00, 512]   
  x: [1.00, 1.25, 512] 
 
# YOLOv8.0n
backbone:
  # [from, repeats, module, args]
  - [-1, 1, Conv, [64, 3, 2]]  # 0-P1/2
  - [-1, 1, Conv, [128, 3, 2]]  # 1-P2/4
  - [-1, 3, C2f, [128, True]]
  - [-1, 1, Conv, [256, 3, 2]]  # 3-P3/8
  - [-1, 6, C2f, [256, True]]
  - [-1, 1, Conv, [512, 3, 2]]  # 5-P4/16
  - [-1, 6, C2f, [512, True]]
  - [-1, 1, Conv, [1024, 3, 2]]  # 7-P5/32
  - [-1, 3, C2f, [1024, True]]
  - [-1, 1, SPPF, [1024, 5]]  # 9
 
# YOLOv8.0n
head:
  - [-1, 1, nn.Upsample, [None, 2, 'nearest']]
  - [[-1, 6], 1, Concat, [1]]  # P4
  - [-1, 3, C2f, [512]]  # 12
 
  - [-1, 1, nn.Upsample, [None, 2, 'nearest']]
  - [[-1, 4], 1, Concat, [1]]  # P3
  - [-1, 3, C2f, [256]]  # 15 (P3/8)
 
  - [-1, 1, Conv, [256, 3, 2]]
  - [[-1, 12], 1, Concat, [1]]  # P4
  - [-1, 3, C2f, [512]]  # 18 (P4/16)
 
  - [-1, 1, Conv, [512, 3, 2]]
  - [[-1, 9], 1, Concat, [1]]  # P5
  - [-1, 3, C2f, [1024]]  # 21 (P5/32)
 
  - [[15, 18, 21], 1, Detect, [nc]]  # (P3, P4, P5)

 最后,使用命令行或创建train.py文件进行训练
命令行:

#yolov11n,需要scale则改变后缀,如yolo11s.yaml
yolo train model=yolo11n.yaml data=dataset.yaml epochs=100 imgsz=640
#yolov8n,需要scale则改变后缀,如yolov8s.yaml
yolo train model=yolov8n.yaml data=dataset.yaml epochs=100 imgsz=640

train.py

from ultralytics import YOLO


def train(version):
    # 确保你 yolov8{version}.yaml 和 yolov8{version}.pt 文件的路径正确
    model = YOLO(f"path/yolov8{version}.yaml").load(f"path/yolov8{version}.pt")
    # 同时,需要将 data.yaml 文件路径正确替换为你的数据集路径
    results = model.train(data="path/data.yaml", epochs=100, imgsz=640, batch=-1, device=0)


if __name__ == "__main__":
    train("s") # 可以传入 "n", "s", "m", "l", "x" 等不同版本的 YOLOv8

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值