YOLOv5改进 | 模块缝合 | 当C3遇上全局注意力机制【附完整代码 + 小白必备】

 秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转


💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡


专栏目录: 《YOLOv5入门 + 改进涨点》专栏介绍 & 专栏目录 |目前已有50+篇内容,内含各种Head检测头、损失函数Loss、Backbone、Neck、NMS等创新点改进


非局部网络通过将特定于查询的全局上下文聚合到每个查询位置,为捕获长距离依赖关系提供了一种开创性的方法。然而,通过实验证明,非局部网络建模的全局上下文在图像内不同的查询位置几乎是相同的。因此,利用这一发现创建了一个简化的网络。在本文中,给大家带来的教程是将原来的的网络替换添加GcNet。文章在介绍主要的原理后,将手把手教学如何进行模块的代码添加和修改,并将修改后的完整代码放在文章的最后方便大家一键运行,小白也可轻松上手实践。以帮助您更好地学习深度学习目标检测YOLO系列的挑战。

专栏地址 YOLOv5改进+入门——持续更新各种有效涨点方法 点击即可跳转

目录

1.原理

2. GcNet代码实现

2.1 将GcNet添加到YOLOv5中

2.2 新增yaml文件

2.3 注册模块

2.4 执行程序

3. 完整代码分享

4.GFLOPs

5. 进阶

6. 总结


1.原理

论文地址:GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond点击即可跳转

官方代码:官方代码仓库——点击即可跳转

GCNet(Global Context Network)是一种用于计算机视觉任务的注意力机制模型。该模型旨在提高深度神经网络对全局上下文信息的理解和利用,以改善其在各种视觉任务中的性能。

GCNet 主要解决的问题是,传统的卷积神经网络(CNN)在处理图像时,往往只关注局部信息,而忽略了图像的全局上下文信息。GCNet 引入了全局上下文注意力机制,允许网络在处理图像时动态地调整不同位置的权重,以更好地捕捉全局信息。

GCNet 的主要组成部分:

  1. 全局上下文模块:GCNet 包括一个全局上下文模块,用于从整个图像中提取全局信息。这个模块通常由全局平均池化层(Global Average Pooling)组成,用于将整个特征图压缩成一个全局特征向量。

  2. 通道注意力机制:GCNet 使用通道注意力机制来动态地调整特征图中每个通道的权重,以更好地捕获图像中不同通道的重要性。这有助于网络更加聚焦于对解决当前任务最重要的特征。

  3. 空间注意力机制:除了通道注意力,GCNet 还引入了空间注意力机制,以考虑不同位置之间的关系。这个机制通常通过使用卷积操作来实现,以便网络可以学习到图像中不同位置之间的依赖关系。

  4. 反馈机制:GCNet 通常具有反馈机制,允许网络根据任务的需求动态地调整注意力权重。这种反馈机制通常通过在训练过程中引入反向传播来实现,网络可以根据任务的损失来调整注意力权重。

总的来说,GCNet 通过引入全局上下文注意力机制,允许网络更好地理解和利用图像的全局信息,从而提高了在各种计算机视觉任务中的性能。它的核心思想是通过动态地调整注意力权重来关注图像中最相关的信息,从而提高了网络的表现。

2. GcNet代码实现

2.1 将GcNet添加到YOLOv5中

关键步骤一: 将下面代码粘贴到/projects/yolov5-6.1/models/common.py文件中

class ContextBlock2d(nn.Module):
    """ContextBlock2d
    Parameters
    ----------
    inplanes : int
        Number of in_channels.
    pool : string
        spatial att or global pooling (default:'att').
    fusions : list
    Reference:
        Yue Cao, et al. "GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond."
    """

    def __init__(self, inplanes, pool='att', fusions=None):
        super(ContextBlock2d, self).__init__()
        if fusions is None:
            fusions = ['channel_add', 'channel_mul']
        assert pool in ['avg', 'att']
        assert all([f in ['channel_add', 'channel_mul'] for f in fusions])
        assert len(fusions) > 0, 'at least one fusion should be used'
        self.inplanes = inplanes
        self.planes = inplanes // 4
        self.pool = pool
        self.fusions = fusions
        if 'att' in pool:
            self.conv_mask = nn.Conv2d(inplanes, 1, kernel_size=1)
            self.softmax = nn.Softmax(dim=2)
        else:
            self.avg_pool = nn.AdaptiveAvgPool2d(1)
        if 'channel_add' in fusions:
            self.channel_add_conv = nn.Sequential(nn.Conv2d(self.inplanes, self.planes, kernel_size=1),
                                                  nn.LayerNorm([self.planes, 1, 1]), nn.SiLU(inplace=True),
                                                  nn.Conv2d(self.planes, self.inplanes, kernel_size=1))
        else:
            self.channel_add_conv = None
        if 'channel_mul' in fusions:
            self.channel_mul_conv = nn.Sequential(nn.Conv2d(self.inplanes, self.planes, kernel_size=1),
                                                  nn.LayerNorm([self.planes, 1, 1]), nn.SiLU(inplace=True),
                                                  nn.Conv2d(self.planes, self.inplanes, kernel_size=1))
        else:
            self.channel_mul_conv = None
        self.reset_parameters()

    def reset_parameters(self):
        if self.pool == 'att':
            kaiming_init(self.conv_mask, mode='fan_in')
            self.conv_mask.inited = True

        if self.channel_add_conv is not None:
            last_zero_init(self.channel_add_conv)
        if self.channel_mul_conv is not None:
            last_zero_init(self.channel_mul_conv)

    def spatial_pool(self, x):
        batch, channel, height, width = x.size()
        if self.pool == 'att':
            input_x = x
            # [N, C, H * W]
            input_x = input_x.view(batch, channel, height * width)
            # [N, 1, C, H * W]
            input_x = input_x.unsqueeze(1)
            # [N, 1, H, W]
            context_mask = self.conv_mask(x)
            # [N, 1, H * W]
            context_mask = context_mask.view(batch, 1, height * width)
            # [N, 1, H * W]
            context_mask = self.softmax(context_mask)
            # [N, 1, H * W, 1]
            context_mask = context_mask.unsqueeze(3)
            # [N, 1, C, 1]
            context = torch.matmul(input_x, context_mask)
            # [N, C, 1, 1]
            context = context.view(batch, channel, 1, 1)
        else:
            # [N, C, 1, 1]
            context = self.avg_pool(x)

        return context

    def forward(self, x):
        # [N, C, 1, 1]
        context = self.spatial_pool(x)

        if self.channel_mul_conv is not None:
            # [N, C, 1, 1]
            channel_mul_term = torch.sigmoid(self.channel_mul_conv(context))
            out = x * channel_mul_term
        else:
            out = x
        if self.channel_add_conv is not None:
            # [N, C, 1, 1]
            channel_add_term = self.channel_add_conv(context)
            out = out + channel_add_term
        return out
    
def constant_init(module, val, bias=0):
    if hasattr(module, 'weight') and module.weight is not None:
        nn.init.constant_(module.weight, val)
    if hasattr(module, 'bias') and module.bias is not None:
        nn.init.constant_(module.bias, bias)

def kaiming_init(module, a=0, mode='fan_out', nonlinearity='relu', bias=0, distribution='normal'):
    assert distribution in ['uniform', 'normal']
    if hasattr(module, 'weight') and module.weight is not None:
        if distribution == 'uniform':
            nn.init.kaiming_uniform_(module.weight, a=a, mode=mode, nonlinearity=nonlinearity)
        else:
            nn.init.kaiming_normal_(module.weight, a=a, mode=mode, nonlinearity=nonlinearity)
    if hasattr(module, 'bias') and module.bias is not None:
        nn.init.constant_(module.bias, bias)

def last_zero_init(m):
    if isinstance(m, nn.Sequential):
        constant_init(m[-1], val=0)
        m[-1].inited = True
    else:
        constant_init(m, val=0)
        m.inited = True

class C3GC(nn.Module):
    # C3 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(C3GC, self).__init__()
        c_ = int(c2 * e)  # hidden channels
        self.gc = ContextBlock2d(c1)
        self.cv1 = Conv(c1, c_)
        self.cv2 = Conv(c1, c_)
        self.cv3 = Conv(2 * c_, c2)  # act=FReLU(c2)
        self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])

    def forward(self, x):
        out = torch.cat((self.m(self.cv1(x)), self.cv2(self.gc(x))), dim=1)
        out = self.cv3(out)
        return out

GCNet的主要流程可以概括为以下几个步骤:

  1. 输入图像的特征提取

    • 首先,将输入图像通过一个预训练的卷积神经网络(如ResNet、VGG等)进行特征提取,得到一个特征图。

  2. 全局上下文模块

    • 将特征图输入到全局上下文模块中。

    • 在全局上下文模块中,使用全局平均池化(Global Average Pooling)对整个特征图进行操作,得到一个全局上下文特征向量。

  3. 通道注意力机制

    • 将全局上下文特征向量与原始特征图结合起来。

    • 对结合后的特征进行通道注意力机制的处理,动态地调整特征图中每个通道的权重,以便网络能够自适应地关注对解决当前任务最重要的特征通道。

  4. 空间注意力机制

    • 在通道注意力机制之后,还可以引入空间注意力机制。

    • 通过卷积操作,在特征图的空间维度上学习到不同位置之间的依赖关系,从而更好地捕捉图像中的结构信息。

  5. 特征融合和输出

    • 将经过通道注意力和空间注意力机制处理后的特征图输入到后续的网络层中,可能是全连接层或者其他类型的层。

    • 最终,通过输出层生成网络的最终预测结果,如分类标签、图像分割等。

在整个流程中,GCNet通过全局上下文模块和注意力机制,使网络能够更好地理解和利用图像中的全局信息,并自适应地关注对解决当前任务最重要的特征通道和位置信息,从而提高了网络在各种图像处理任务中的性能和效果。

2.2 新增yaml文件

关键步骤二在下/projects/yolov5-6.1/models下新建文件 yolov5_C3GC.yaml并将下面代码复制进去

  • OD【目标检测 】
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license

# Parameters
nc: 80  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.25  # 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, C3GC, [ 128 ] ],
    [ -1, 1, Conv, [ 256, 3, 2 ] ],  # 3-P3/8
    [ -1, 6, C3GC, [ 256 ] ],
    [ -1, 1, Conv, [ 512, 3, 2 ] ],  # 5-P4/16
    [ -1, 9, C3GC, [ 512 ] ],
    [ -1, 1, Conv, [ 1024, 3, 2 ] ],  # 7-P5/32
    [ -1, 3, C3GC, [ 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, C3GC, [ 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, C3GC, [ 256, False ] ],  # 17 (P3/8-small)

    [ -1, 1, Conv, [ 256, 3, 2 ] ],
    [ [ -1, 14 ], 1, Concat, [ 1 ] ],  # cat head P4
    [ -1, 3, C3GC, [ 512, False ] ],  # 20 (P4/16-medium)

    [ -1, 1, Conv, [ 512, 3, 2 ] ],
    [ [ -1, 10 ], 1, Concat, [ 1 ] ],  # cat head P5
    [ -1, 3, C3GC, [ 1024, False ] ],  # 23 (P5/32-large)

    [ [ 17, 20, 23 ], 1, Detect, [ nc, anchors ] ],  # Detect(P3, P4, P5)
  ]
  • Seg【语义分割】 
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license

# Parameters
nc: 80  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.25  # 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, C3GC, [ 128 ] ],
    [ -1, 1, Conv, [ 256, 3, 2 ] ],  # 3-P3/8
    [ -1, 6, C3GC, [ 256 ] ],
    [ -1, 1, Conv, [ 512, 3, 2 ] ],  # 5-P4/16
    [ -1, 9, C3GC, [ 512 ] ],
    [ -1, 1, Conv, [ 1024, 3, 2 ] ],  # 7-P5/32
    [ -1, 3, C3GC, [ 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, C3GC, [ 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, C3GC, [ 256, False ] ],  # 17 (P3/8-small)

    [ -1, 1, Conv, [ 256, 3, 2 ] ],
    [ [ -1, 14 ], 1, Concat, [ 1 ] ],  # cat head P4
    [ -1, 3, C3GC, [ 512, False ] ],  # 20 (P4/16-medium)

    [ -1, 1, Conv, [ 512, 3, 2 ] ],
    [ [ -1, 10 ], 1, Concat, [ 1 ] ],  # cat head P5
    [ -1, 3, C3GC, [ 1024, False ] ],  # 23 (P5/32-large)

    [ [ 17, 20, 23 ], 1, Segment, [nc, anchors, 32, 256]], # Detect(P3, P4, P5)
  ]

温馨提示:本文只是对yolov5l基础上添加swin模块,如果要对yolov8n/l/m/x进行添加则只需要指定对应的depth_multiple 和 width_multiple。 


# YOLOv5n
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.25  # layer channel multiple
 
# YOLOv5s
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # layer channel multiple
 
# YOLOv5l 
depth_multiple: 1.0  # model depth multiple
width_multiple: 1.0  # layer channel multiple
 
# YOLOv5m
depth_multiple: 0.67  # model depth multiple
width_multiple: 0.75  # layer channel multiple
 
# YOLOv5x
depth_multiple: 1.33  # model depth multiple
width_multiple: 1.25  # layer channel multiple

2.3 注册模块

关键步骤在yolo.py中的parse_model注册添加 ‘C3GC’

2.4 执行程序

在train.py中,将cfg的参数路径设置为yolov5_C3GC.yaml的路径

建议大家写绝对路径,确保一定能找到

🚀运行程序,如果出现下面的内容则说明添加成功🚀

  from  n    params  module                                  arguments
  0                -1  1      1760  models.common.Conv                      [3, 16, 6, 2, 2]
  1                -1  1      4672  models.common.Conv                      [16, 32, 3, 2]
  2                -1  1      5969  models.common.C3GC                      [32, 32, 1]
  3                -1  1     18560  models.common.Conv                      [32, 64, 3, 2]
  4                -1  2     33569  models.common.C3GC                      [64, 64, 2]
  5                -1  1     73984  models.common.Conv                      [64, 128, 3, 2]
  6                -1  3    173889  models.common.C3GC                      [128, 128, 3]
  7                -1  1    295424  models.common.Conv                      [128, 256, 3, 2]
  8                -1  1    363137  models.common.C3GC                      [256, 256, 1]
  9                -1  1    164608  models.common.SPPF                      [256, 256, 5]
 10                -1  1     33024  models.common.Conv                      [256, 128, 1, 1]
 11                -1  1         0  torch.nn.modules.upsampling.Upsample    [None, 2, 'nearest']
 12           [-1, 6]  1         0  models.common.Concat                    [1]
 13                -1  1    157569  models.common.C3GC                      [256, 128, 1, False]
 14                -1  1      8320  models.common.Conv                      [128, 64, 1, 1]
 15                -1  1         0  torch.nn.modules.upsampling.Upsample    [None, 2, 'nearest']
 16           [-1, 4]  1         0  models.common.Concat                    [1]
 17                -1  1     39873  models.common.C3GC                      [128, 64, 1, False]
 18                -1  1     36992  models.common.Conv                      [64, 64, 3, 2]
 19          [-1, 14]  1         0  models.common.Concat                    [1]
 20                -1  1     91457  models.common.C3GC                      [128, 128, 1, False]
 21                -1  1    147712  models.common.Conv                      [128, 128, 3, 2]
 22          [-1, 10]  1         0  models.common.Concat                    [1]
 23                -1  1    363137  models.common.C3GC                      [256, 256, 1, False]
 24      [17, 20, 23]  1    115005  Detect                                  [80, [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]], [64, 128, 256]]
YOLOv5_C3GC summary: 318 layers, 2128661 parameters, 2128661 gradients, 4.8 GFLOPs

3. 完整代码分享

https://pan.baidu.com/s/15a2JywncqIacuKaLAxyG7A?pwd=ccbt

提取码: ccbt 

4.GFLOPs

关于GFLOPs的计算方式可以查看百面算法工程师 | 卷积基础知识——Convolution

未改进的GFLOPs

改进后的GFLOPs  

5. 进阶

你能在不同的位置添加全局注意力机制吗?

6. 总结

GCNet(Global Context Network)是一种引入全局上下文信息和注意力机制的图像处理模型。其流程首先通过全局上下文模块提取整个图像的全局信息,然后通过通道注意力机制动态调整特征图中每个通道的权重,使网络能够自适应地关注对解决当前任务最重要的特征通道。接着,GCNet引入空间注意力机制,学习不同位置之间的依赖关系,以更好地捕捉图像中的结构信息。通过全局上下文模块和注意力机制的综合利用,GCNet使网络能够更充分地理解和利用图像中的全局信息,从而提高了在各种图像处理任务中的性能和效果。

  • 29
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
最佳缝合线算法(Seam Carving)是一种图像处理算法,用于在保持图像宽高比的情况下,缩小或扩大图像的大小。 以下是实现最佳缝合线算法的代码: 1. 计算能量值 ```python import numpy as np import cv2 def energy(image): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3) sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3) return np.abs(sobel_x) + np.abs(sobel_y) ``` 2. 计算最小能量路径 ```python def find_seam(image): e = energy(image) m = np.zeros_like(e) m[0] = e[0] for i in range(1, m.shape[0]): for j in range(m.shape[1]): if j == 0: m[i][j] = e[i][j] + min(m[i-1][j], m[i-1][j+1]) elif j == m.shape[1]-1: m[i][j] = e[i][j] + min(m[i-1][j], m[i-1][j-1]) else: m[i][j] = e[i][j] + min(m[i-1][j-1], m[i-1][j], m[i-1][j+1]) seam = [] j = np.argmin(m[-1]) seam.append((len(e)-1, j)) for i in reversed(range(len(e)-1)): if j == 0: j = np.argmin(m[i][j:j+2]) elif j == m.shape[1]-1: j = np.argmin(m[i][j-1:j+1]) + j - 1 else: j = np.argmin(m[i][j-1:j+2]) + j - 1 seam.append((i, j)) return seam[::-1] ``` 3. 删除最小能量路径 ```python def remove_seam(image, seam): h, w = image.shape[:2] mask = np.ones((h, w), dtype=np.bool) for i, j in seam: mask[i, j] = False return image[mask].reshape((h, w-1, 3)) ``` 4. 缩小图像 ```python def resize(image, size): h, w = image.shape[:2] dy, dx = h - size[1], w - size[0] if dx > 0: for i in range(dx): seam = find_seam(image) image = remove_seam(image, seam) if dy > 0: image = np.rot90(image, 1, (0, 1)) for i in range(dy): seam = find_seam(image) image = remove_seam(image, seam) image = np.rot90(image, 3, (0, 1)) return image ``` 上述代码实现了最佳缝合线算法,可以通过调用 `resize` 函数对图片进行缩放。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kay_545

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值