Backbone中添加attention 之CBAM

本文介绍如何在ResNet网络中引入注意力机制,并提供详细的代码实现。注意力机制能够帮助模型聚焦于输入特征的重要部分,提高模型的性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注意力机制(Attention Mechanism)源于对人类视觉的研究。在认知科学中,由于信息处理的瓶颈,人类会选择性地关注所有信息的一部分,同时忽略其他可见的信息。上述机制通常被称为注意力机制。人类视网膜不同的部位具有不同程度的信息处理能力,即敏锐度(Acuity),只有视网膜中央凹部位具有最强的敏锐度。为了合理利用有限的视觉信息处理资源,人类需要选择视觉区域中的特定部分,然后集中关注它。例如,人们在阅读时,通常只有少量要被读取的词会被关注和处理。综上,注意力机制主要有两个方面:决定需要关注输入的哪部分;分配有限的信息处理资源给重要的部分。来至某度百科。

attention在视觉任务中有很多应用场景,每年顶会也有很多关于attention的paper。记录一下往backbone里加attention,不喜勿喷。以resnet为例,其他backbone也可以借鉴一下怎么添加attention。废话少说,直接上代码

import torch.nn as nn
import math

import torch

#3x3卷积会改变feature map 大小(当stride不等于1时),反之
def conv3x3(in_planes, out_planes, stride=1):
    """3x3 convolution with padding"""
    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
                     padding=1, bias=False)

#conv1x1只改变了输出的通道数
def conv1x1(in_planes, out_planes, stride=1):
    """1x1 convolution """
    return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride,
                      bias=False)



class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(BasicBlock, self).__init__()
        #第一个conv3x3的stride是可变的,当取2的时候,会导致特征图的size变成二分之一
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.bn1 = nn.BatchNorm2d(planes)
        self.relu = nn.ReLU(inplace=True)
        #第二个conv3x3的stride=1,只可以改变通道数,不会改变特征图的大小。
        self.conv2 = conv3x3(planes, planes)
        self.bn2 = nn.BatchNorm2d(planes)
        self.downsample = downsample
        self.stride = stride

        if planes == 64:
            self.globalAvgPool = nn.AvgPool2d(56, stride=1)
        elif planes == 128:
            self.globalAvgPool = nn.AvgPool2d(28, stride=1)
        elif planes == 256:
            self.globalAvgPool = nn.AvgPool2d(14, stride=1)
        elif planes == 512:
            self.globalAvgPool = nn.AvgPool2d(7, stride=1)
        self.fc1 = nn.Linear(in_features=planes, out_features=round(planes / 16))
        self.fc2 = nn.Linear(in_features=round(planes / 16), out_features=planes)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        residual = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        if self.downsample is not None:
            residual = self.downsample(x)

        original_out = out
        out = self.globalAvgPool(out)
        out = out.view(out.size(0), -1)
        out = self.fc1(out)
        out = self.relu(out)
        out = self.fc2(out)
        out = self.sigmoid(out)
        out = out.view(out.size(0), out.size(1), 1, 1)
        out = out * original_out

        out += residual
        out = self.relu(out)

        return out


class Bottleneck(nn.Module):
    expansion = 4

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes)
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
                               padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(planes)
        self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(planes * 4)
        self.relu = nn.ReLU(inplace=True)
        if planes == 64:
            self.globalAvgPool = nn.AvgPool2d(56, stride=1)
        elif planes == 128:
            self.globalAvgPool = nn.AvgPool2d(28, stride=1)
        elif planes == 256:
            self.globalAvgPool = nn.AvgPool2d(14, stride=1)
        elif planes == 512:
            self.globalAvgPool = nn.AvgPool2d(7, stride=1)
        self.fc1 = nn.Linear(in_features=planes * 4, out_features=round(planes / 4))
        self.fc2 = nn.Linear(in_features=round(planes / 4), out_features=planes * 4)
        self.sigmoid = nn.Sigmoid()
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        residual = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)

        if self.downsample is not None:
            residual = self.downsample(x)

        original_out = out
        out = self.globalAvgPool(out)
        out = out.view(out.size(0), -1)
        out = self.fc1(out)
        out = self.relu(out)
        out = self.fc2(out)
        out = self.sigmoid(out)
        out = out.view(out.size(0),out.size(1),1,1)
        out = out * original_out

        out += residual
        out = self.relu(out)

        return out
# 通道注意力机制
class ChannelAttention(nn.Module):
    def __init__(self, in_planes, ratio=16):
        super(ChannelAttention, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.max_pool = nn.AdaptiveMaxPool2d(1)

        self.fc1 = nn.Conv2d(in_planes, in_planes // 16, 1, bias=False)
        self.relu1 = nn.ReLU()
        self.fc2 = nn.Conv2d(in_planes // 16, in_planes, 1, bias=False)

        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        avg_out = self.fc2(self.relu1(self.fc1(self.avg_pool(x))))
        max_out = self.fc2(self.relu1(self.fc1(self.max_pool(x))))
        out = avg_out + max_out
        return self.sigmoid(out)


# 空间注意力机制
class SpatialAttention(nn.Module):
    def __init__(self, kernel_size=7):
        super(SpatialAttention, self).__init__()

        assert kernel_size in (3, 7), 'kernel size must be 3 or 7'
        padding = 3 if kernel_size == 7 else 1

        self.conv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        avg_out = torch.mean(x, dim=1, keepdim=True)
        max_out, _ = torch.max(x, dim=1, keepdim=True)
        x = torch.cat([avg_out, max_out], dim=1)
        x = self.conv1(x)
        return self.sigmoid(x)


class ResNet(nn.Module):

    def __init__(self, block, layers, num_classes=1000, zero_init_residual=False,
                 groups=1, width_per_group=64, replace_stride_with_dilation=None,
                 norm_layer=None):
        super(ResNet, self).__init__()
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        self._norm_layer = norm_layer

        self.inplanes = 64
        self.dilation = 1
        if replace_stride_with_dilation is None:
            # each element in the tuple indicates if we should replace
            # the 2x2 stride with a dilated convolution instead
            replace_stride_with_dilation = [False, False, False]
        if len(replace_stride_with_dilation) != 3:
            raise ValueError("replace_stride_with_dilation should be None "
                             "or a 3-element tuple, got {}".format(replace_stride_with_dilation))
        self.groups = groups
        self.base_width = width_per_group
        self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3,
                               bias=False)
        self.bn1 = norm_layer(self.inplanes)
        self.relu = nn.ReLU(inplace=True)

        # 网络的第一层加入注意力机制
        self.ca = ChannelAttention(self.inplanes)
        self.sa = SpatialAttention()

        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)

        self.layer1 = self._make_layer(block, 64, layers[0])
        self.layer2 = self._make_layer(block, 128, layers[1], stride=2,
                                       dilate=replace_stride_with_dilation[0])
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2,
                                       dilate=replace_stride_with_dilation[1])
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2,
                                       dilate=replace_stride_with_dilation[2])
        # 网络的卷积层的最后一层加入注意力机制
        self.ca1 = ChannelAttention(self.inplanes)
        self.sa1 = SpatialAttention()

        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512 * block.expansion, num_classes)

        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
            elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

        if zero_init_residual:
            for m in self.modules():
                if isinstance(m, Bottleneck):
                    nn.init.constant_(m.bn3.weight, 0)
                elif isinstance(m, BasicBlock):
                    nn.init.constant_(m.bn2.weight, 0)
#每个layer的生成函数_make_layer
    def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
        norm_layer = self._norm_layer
        downsample = None
        previous_dilation = self.dilation
        if dilate:
            self.dilation *= stride
            stride = 1
        if stride != 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                conv1x1(self.inplanes, planes * block.expansion, stride),
                norm_layer(planes * block.expansion),
            )

        layers = []
        #参数block传入:BasicBlock还是BottleNeck作为基本模块
        layers.append(block(self.inplanes, planes))
        self.inplanes = planes * block.expansion
        for _ in range(1, blocks):
            #layer中剩余的block
            layers.append(block(self.inplanes, planes))

        return nn.Sequential(*layers)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)

        x = self.ca(x) * x
        x = self.sa(x) * x

        x = self.maxpool(x)
#layer1-layer4又由若干层基本的block(BasicBlock或者BottleNeck)构成,其中block参决定是BasicBlock还是BottleNeck. layers是一个四维的列表,每个元素分别决定这四层分别包含多少个基本block
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = self.ca1(x) * x
        x = self.sa1(x) * x

        x = self.avgpool(x)
        x = x.reshape(x.size(0), -1)
        x = self.fc(x)

        return x

def count_parameters(model):
    return sum(p.numel() for p in model.parameters() if p.requires_grad)

# Create an instance of the ResNet model
model = ResNet(Bottleneck, [3, 4, 6, 3])

# Count the parameters in the model
total_params = count_parameters(model)

print("Total number of parameters in the ResNet model: {:,}".format(total_params))


<think>嗯,用户想在YOLOv8的tasks.py文件里集成CBAM模块。首先,我得回忆一下CBAM的结构。CBAM包括通道注意力和空间注意力两个部分,对吧?通道注意力通常使用全局平均池化和最大池化,然后通过MLP生成权重。空间注意力则是用卷积层处理通道的聚合信息。 然后,我需要确定在tasks.py中的哪个位置添加CBAM。根据引用[2],用户提到在__init__.py和conv.py中添加CBAM,并在task.py中修改。可能是在构建模型的时候,需要在某个卷积层后面插入CBAM模块。比如,在定义卷积块之后,追加CBAM。 接下来,用户可能需要示例代码。根据引用[2]中的步骤,步骤3是修改task.py文件,但具体的代码没有给出。所以需要参考如何在YOLO的模型定义中插入新模块。通常,YOLO的模型结构通过yaml文件配置,但用户可能希望直接在代码中添加,而不是通过配置文件。不过引用[3]提到在yaml文件中新增模块即可,但用户这次是想在tasks.py里集成,所以可能需要修改构建模型的部分。 比如,在tasks.py中,可能有一个函数用来创建卷积层,可以在那里添加CBAM。例如,在Conv类之后,定义CBAM类,然后在构建模型时,在适当的位置调用这个类。需要确保CBAM的输入输出通道数匹配,特别是空间注意力的部分,输入通道数应该为1。 另外,用户提供的引用中有CBAM的代码示例,可能需要在conv.py中添加,但用户现在想在tasks.py中实现。可能需要将CBAM类的定义放在tasks.py里,或者在适当的位置导入。不过根据引用[1],代码示例是从ultralytics.nn.CBAM导入CBAM,所以可能用户的项目中已经有CBAM的实现,但需要集成到tasks.py中。 可能的步骤是:在tasks.py中导入CBAM模块,然后在模型构建的过程中,在需要的地方添加CBAM层。比如,在构建卷积块后,添加CBAM。例如,在某个阶段的卷积之后插入。 不过用户的具体问题是如何在tasks.py中实现CBAM模块的集成,所以需要提供代码示例。例如,在tasks.py中添加CBAM类的定义,然后在解析模型结构时,将CBAM作为可用的模块。或者,修改现有的构建流程,允许在yaml配置中使用CBAM,这可能需要修改解析代码。 根据引用[4]中的训练示例,用户需要修改yaml文件来加入CBAM,但用户现在想在tasks.py中直接集成,所以可能需要调整模型构建的代码,使得CBAM可以被识别和调用。 总结来说,步骤可能包括: 1. 在tasks.py中定义CBAM类,包含通道和空间注意力。 2. 在解析模型结构的代码部分,允许识别CBAM模块,并将其添加到模型中。 3. 确保输入输出通道正确,特别是在空间注意力层,将通道数转换为1。 需要检查代码结构,确定在tasks.py中的哪个函数或类负责构建层,然后将CBAM插入适当的位置。例如,在构建卷积层后,添加CBAM模块。 可能还需要考虑如何将CBAM模块连接到现有的网络结构中,比如在某个卷积层之后,或者在残差块中。用户可能需要根据YOLOv8的具体结构来确定插入点,比如在Backbone的某个阶段后加入CBAM。 因此,示例代码可能包括在tasks.py中添加CBAM类的定义,并在构建模型时,在适当的位置实例化该类,并将其添加到网络层中。</think>### 在 `tasks.py` 文件中集成 CBAM 模块的步骤 #### 1. **添加 CBAM 类定义** 在 `tasks.py` 文件中定义 CBAM 模块类,包含通道注意力(Channel Attention)和空间注意力(Spatial Attention)子模块: ```python import torch import torch.nn as nn class CBAM(nn.Module): def __init__(self, in_channels, reduction_ratio=16): super(CBAM, self).__init__() # 通道注意力 self.channel_attention = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(in_channels, in_channels // reduction_ratio, 1), nn.ReLU(), nn.Conv2d(in_channels // reduction_ratio, in_channels, 1), nn.Sigmoid() ) # 空间注意力 self.spatial_attention = nn.Sequential( nn.Conv2d(2, 1, kernel_size=7, padding=3), nn.Sigmoid() ) def forward(self, x): # 通道注意力计算 channel_weights = self.channel_attention(x) x = x * channel_weights # 空间注意力计算 max_pool = torch.max(x, dim=1, keepdim=True)[0] avg_pool = torch.mean(x, dim=1, keepdim=True) spatial_weights = self.spatial_attention(torch.cat([max_pool, avg_pool], dim=1)) return x * spatial_weights ``` #### 2. **修改模型构建逻辑** 在 `tasks.py` 的模型构建函数(例如 `parse_model`)中添加 CBAM 模块的调用逻辑,例如在卷积层后插入 CBAM: ```python def parse_model(d, ch): # ... 原有代码 ... layers = [] for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): m = eval(m) if isinstance(m, str) else m for j, a in enumerate(args): try: args[j] = eval(a) if isinstance(a, str) else a except: pass if m in [Conv, Bottleneck]: c1, c2 = ch[f], args[0] args = [c1, c2, *args[1:]] # 添加 CBAM 模块支持 elif m == CBAM: args = [ch[f]] # 输入通道数 # ... 原有代码 ... layers.append(m(*args)) ch.append(c2) # ... 原有代码 ... ``` #### 3. **在 YAML 配置中调用 CBAM** 在模型配置文件(如 `yolov8n.yaml`)的对应位置插入 `CBAM` 模块: ```yaml backbone: # ... 其他层 ... - [-1, 1, CBAM, []] # 在指定位置插入 CBAM ``` --- ### 代码说明 1. **通道注意力**:通过全局平均池化和全连接层生成通道权重,增强重要特征通道[^2]。 2. **空间注意力**:结合最大池化和平均池化,通过卷积生成空间权重,突出关键区域。 3. **集成到模型**:通过修改 `parse_model` 函数,将 CBAM 作为可调用的模块插入网络结构中。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

VisionX Lab

你的鼓励将是我更新的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值