目录
- 一、注意力机制介绍
- 1、什么是注意力机制?
- 2、注意力机制的分类
- 3、注意力机制的核心
- 二、GCN 模块
- 1、GCN 模块的原理
- 2、实验结果
- 3、应用示例
- 三、DAN模块
- 1、DAN模块的原理
- 2、实验结果
- 3、应用示例
大家好,我是哪吒。
🏆本文收录于,目标检测YOLO改进指南。
本专栏均为全网独家首发,内附代码,可直接使用,改进的方法均是2023年最近的模型、方法和注意力机制。每一篇都做了实验,并附有实验结果分析,模型对比。
在机器学习和自然语言处理领域,随着数据的不断增长和任务的复杂性提高,传统的模型在处理长序列或大型输入时面临一些困难。传统模型无法有效地区分每个输入的重要性,导致模型难以捕捉到与当前任务相关的关键信息。为了解决这个问题,注意力机制(Attention Mechanism)应运而生。
一、注意力机制介绍
1、什么是注意力机制?
注意力机制(Attention Mechanism)是一种在机器学习和自然语言处理领域中广泛应用的重要概念。它的出现解决了模型在处理长序列或大型输入时的困难,使得模型能够更加关注与当前任务相关的信息,从而提高模型的性能和效果。
本文将详细介绍注意力机制的原理、应用示例以及应用示例。
2、注意力机制的分类
类别 | 描述 |
---|---|
全局注意力机制(Global Attention) | 在计算注意力权重时,考虑输入序列中的所有位置或元素,适用于需要全局信息的任务。 |
局部注意力机制(Local Attention) | 在计算注意力权重时,只考虑输入序列中的局部区域或邻近元素,适用于需要关注局部信息的任务。 |
自注意力机制(Self Attention) | 在计算注意力权重时,根据输入序列内部的关系来决定每个位置的注意力权重,适用于序列中元素之间存在依赖关系的任务。 |
Bahdanau 注意力机制 | 全局注意力机制的一种变体,通过引入可学习的对齐模型,对输入序列的每个位置计算注意力权重。 |
Luong 注意力机制 | 全局注意力机制的另一种变体,通过引入不同的计算方式,对输入序列的每个位置计算注意力权重。 |
Transformer 注意力机制 | 自注意力机制在Transformer模型中的具体实现,用于对输入序列中的元素进行关联建模和特征提取。 |
3、注意力机制的核心
注意力机制的核心思想是根据输入的上下文信息来动态地计算每个输入的权重。这个过程可以分为三个关键步骤:计算注意力权重、对输入进行加权和输出。首先,计算注意力权重是通过将输入与模型的当前状态进行比较,从而得到每个输入的注意力分数。这些注意力分数反映了每个输入对当前任务的重要性。对输入进行加权是将每个输入乘以其对应的注意力分数,从而根据其重要性对输入进行加权。最后,将加权后的输入进行求和或者拼接,得到最终的输出。注意力机制的关键之处在于它允许模型在不同的时间步或位置上关注不同的输入,从而捕捉到与任务相关的信息。
🏆YOLOv5/v7 添加注意力机制,30多种模块分析①,SE模块,SK模块
🏆YOLOv5/v7 添加注意力机制,30多种模块分析②,BAM模块,CBAM模块
二、GCN 模块
1、GCN 模块的原理
GCN(Global Context Network)模块是一种用于计算机视觉领域的深度学习模型中的注意力机制。它由 Tsinghua 大学的 Cao et al. 在 2019 年提出,旨在通过给神经网络提供全局上下文信息来提高图像分类、分割、检测等任务的性能。
GCN模块的核心思想是利用自适应的全局平均池化(Adaptive Global Average Pooling),根据每个通道的重要性对其进行加权,将全局范围内的并行卷积特征映射融合成一个全局语义向量,从而增强模型对局部和全局特征的感知能力。
GCN模块的具体实现如下所示:
import torch.nn as nn
import torch.nn.functional as F
class ContextBlock2d(nn.Module):
def __init__(self, in_channels, ratio, pooling_type='att', fusion_types=('channel_add', )):
super(ContextBlock2d, self).__init__()
assert pooling_type in ['avg', 'att']
assert isinstance(fusion_types, (list, tuple))
self.in_channels = in_channels
self.ratio = ratio
self.planes = int(in_channels // ratio)
self.pooling_type = pooling_type
self.fusion_types = fusion_types
if 'channel_add' in fusion_types:
self.channel_add_conv = nn.Sequential(
nn.Conv2d(self.in_channels, self.in_channels, kernel_size=1),
nn.LayerNorm([self.in_channels, 1, 1])
)
if 'channel_mul' in fusion_types:
self.channel_mul_conv = nn.Sequential(
nn.Conv2d(self.in_channels, self.in_channels, kernel_size=1),
nn.LayerNorm([self.in_channels, 1, 1]),
nn.Sigmoid()
)
if 'spatial' in fusion_types:
self.spatial_conv = nn.Sequential(
nn.Conv2d(self.in_channels, self.planes, kernel_size=1),
nn.LayerNorm([self.planes, 1, 1])
)
if pooling_type == 'att':
self.conv_mask = nn.Conv2d(self.in_channels, 1, kernel_size=1)
def spatial_pool(self, x):
batch, channel, height, width = x.size()
input_x = x
# [N, C, H * W]
input_x = input_x.view(batch, channel, height * width)
# [N, C, 1, 1]
spatial_output = F.adaptive_avg_pool2d(x, output_size=(1, 1))
# [N, C, 1, 1]
spatial_output = self.spatial_conv(spatial_output)
# [N, C, 1, 1]
spatial_output = F.relu(spatial_output, inplace=True)
# [N, C, 1, 1]
spatial_output = F.interpolate(spatial_output, size=(height, width), mode='nearest')
# [N, 1, H, W]
output = F.softmax(spatial_output, dim=1)
return output
def forward(self, x):
batch, channel, height, width = x.size()
# calculate the input tensor for calculating correlation matrix
input_x = x
if self.pooling_type == 'avg':
# N x C x 1 x 1
context_mask = F.adaptive_avg_pool2d(x, output_size=(1, 1))
elif self.pooling_type == 'att':
# N x C x 1 x 1
context_mask = F.relu(self.conv_mask(x))
# N x C x 1 x 1
context_mask = F.interpolate(context_mask, size=(height, width), mode='nearest')
# N x C x H x W
context = x * context_mask
if 'channel_mul' in self.fusion_types:
# N x C x 1 x 1
avg_context = torch.sum(context, dim=(2, 3), keepdim=True) / (height * width)
# N x C x H x W
context = context * avg_context
if 'channel_add' in self.fusion_types:
# N x C x H x W
channel_add_term = self.channel_add_conv(context)
# N x C x H x W
context = context + channel_add_term
output = context
if 'spatial' in self.fusion_types:
# N x 1 x H x W
spatial_attention = self.spatial_pool(x)
# N x C x H x W
output = output * spatial_attention
return output
在这个实现中,ContextBlock2d 类接受输入张量 x
,并提供了以下四种融合策略:
-
channel_add:通过一个卷积层和 LayerNorm 实现的通道级别加法操作。
-
channel_mul:通过一个卷积层、Sigmoid 激活函数和 LayerNorm 实现的通道级别乘法操作。
-
spatial:通过自适应平均池化和一组卷积层实现的空间级别的特征融合。
-
att:通过一组卷积层和 Softmax 函数实现的注意力机制。
GCN模块的实现中用到了以下技巧:
- 自适应平均池化:对于不同大小的输入,使用自适应的池化核大小以得到固定大小的输出。
- Sigmoid 激活函数:对于 channel_mul 融合策略,使用 Sigmoid 函数将权重限制在 (0, 1) 范围内。
- LayerNorm:对于 channel_add 和 channel_mul 融合策略,使用 LayerNorm 对特征图进行归一化操作。
- Softmax 函数:对于 att 融合策略,使用 Softmax 函数计算注意力值。
2、实验结果
在Kinetics验证集上,使用R50作为骨干的Slow-only基线下,GCNet和NLNet的结果如下:
method | Top-1 Acc | Top-5 Acc | #params(M) | FLOPs(G) |
---|---|---|---|---|
baseline | 74.94 | 91.90 | 32.45 | 39.29 |
+5 NL | 75.95 | 92.29 | 39.81 | 59.60 |
+5 SNL | 75.76 | 92.44 | 36.13 | 39.32 |
+5 GC | 75.85 | 92.25 | 34.30 | 39.31 |
+all GC | 76.00 | 92.34 | 42.45 | 39.35 |
3、应用示例
在 YOLOv5 中,GCNet 模块被应用于 CSPDarknet53 特征提取器中,以增强模型的感受野和上下文信息。具体来说,GCNet 模块是通过在通道维度上进行全局上下文编码来实现的。
下面是在 YOLOv5 中使用 GCNet 模块的应用示例:
import torch.nn as nn
from models.common import Conv
class GCNet(nn.Module):
def __init__(self, in_channels, reduction=16):
super(GCNet, self).__init__()
self.conv1x1 = Conv(in_channels, in_channels // reduction, 1)
self.conv3x3 = Conv(in_channels, in_channels // reduction, 3, padding=1)
self.global_context = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
Conv(in_channels, in_channels // reduction, 1),
nn.ReLU(inplace=True),
Conv(in_channels // reduction, in_channels, 1),
nn.Sigmoid()
)
def forward(self, x):
feat1 = self.conv1x1(x)
feat2 = self.conv3x3(x)
gc = self.global_context(feat2)
feat = feat1 * gc.expand_as(feat1) + feat2
return feat
class CSPBlock(nn.Module):
def __init__(self, in_channels, out_channels, num_blocks, darknet_lite=False):
super(CSPBlock, self).__init__()
self.conv1 = Conv(in_channels, out_channels, 1)
self.conv2 = Conv(in_channels, out_channels, 1)
self.conv3 = Conv(out_channels * 2, out_channels, 1)
self.conv4 = Conv(out_channels * 2, out_channels * 2, 3, padding=1, groups=out_channels * 2)
self.conv5 = Conv(out_channels * 2, out_channels, 1)
self.layers = nn.Sequential(*[
ResBlock(out_channels, darknet_lite) for _ in range(num_blocks)
])
self.gc_block = nn.Sequential(
Conv(out_channels, out_channels // 2, 1),
GCNet(out_channels // 2),
Conv(out_channels, out_channels, 1)
)
def forward(self, x):
feat1 = self.conv1(x)
feat2 = self.conv2(x)
feat2 = self.layers(feat2)
feat2 = self.gc_block(feat2)
feat2 = torch.cat([feat2, feat1], dim=1)
feat2 = self.conv3(feat2)
feat2 = self.conv4(feat2)
feat2 = self.conv5(feat2)
return feat2
在上述代码中,我们首先定义了一个 GCNet 类和一个 CSPBlock 类。GCNet 类是实现全局上下文编码的模块,而 CSPBlock 是 YOLOv5 中的一个基本块。
在 CSPBlock 中,我们使用 GCNet 模块来增强模型的感受野和上下文信息。具体来说,我们将 GCNet 模块放置于 CSPBlock 的末尾,并将其输入特征图和经过卷积操作的另一份特征图进行拼接,最后再通过几个卷积层输出特征图。这样做可以使模型更好地处理不同尺度的物体。
三、DAN模块
1、DAN模块的原理
DANet(Dual Attention Network)模块是一种新型的注意力机制,广泛应用于计算机视觉领域中的图像分割任务。该模块由位置注意力模块和通道注意力模块组成,能够自适应地对输入图像中的关键区域进行加强,从而提高了图像分割的精度。
在位置注意力模块中,通过构建一个全局上下文信息嵌入层来获取位置感知初始特征,然后使用空间转换网络(Spatial Transform Network,STN)来自适应地调整这些特征的空间位置,以使其更好地匹配目标对象的形状和大小。进一步地,通过使用一个门控方案,位置注意力模块可以选择性地增强或抑制每个特征通道的激活,以便更好地突出目标对象。
在通道注意力模块中,首先提取特征图的全局信息,并通过一个门控方案将其与特征图中每个通道的激活相乘,以得到通道加权的响应。接着,在通道特征响应映射(Channel Feature Response Map,CFRM)中,使用类似于SENet(Squeeze and Excitation Network)的方式来生成通道注意力图。最后,将通道注意力图与特征图相乘,以获得增强后的特征响应。
2、实验结果
在PASCAL VOC 2012和Cityscapes等多个数据集上进行的大量实验表明,DANet模块相对于其他主流方法具有更好的图像分割性能。例如,在Cityscapes数据集中,使用DANet模块的分割网络在Mean IoU指标上达到了81.5%,优于其他方法。
3、应用示例
以下是一个使用DANet模块的应用示例片段,其中包括了DANet模块的定义和在YOLOv5中的应用:
import torch.nn as nn
class DANet(nn.Module):
def __init__(self, in_channels):
super(DANet, self).__init__()
self.conv1 = nn.Conv2d(in_channels, in_channels // 4, kernel_size=1)
self.conv2 = nn.Conv2d(in_channels, in_channels // 4, kernel_size=1)
self.conv3 = nn.Conv2d(in_channels, in_channels, kernel_size=1)
self.softmax = nn.Softmax(dim=-1)
self.gamma = nn.Parameter(torch.zeros(1))
def forward(self, x):
batch_size, channels, height, width = x.size()
proj_query = self.conv1(x).view(batch_size, -1, width * height).permute(0, 2, 1)
proj_key = self.conv2(x).view(batch_size, -1, width * height)
energy = torch.bmm(proj_query, proj_key)
attention = self.softmax(energy)
proj_value = self.conv3(x).view(batch_size, -1, width * height)
out = torch.bmm(proj_value, attention.permute(0, 2, 1))
out = out.view(batch_size, channels, height, width)
out = self.gamma * out + x
return out
# 在YOLOv5中使用DANet模块
class YOLOv5(nn.Module):
def __init__(self):
super(YOLOv5, self).__init__()
# ... 略去其他层的定义
self.da_conv1 = nn.Sequential(
nn.Conv2d(256, 512, kernel_size=3, stride=2, padding=1),
nn.BatchNorm2d(512),
nn.LeakyReLU(negative_slope=0.1),
DANet(in_channels=512), # 添加DANet模块
)
# ... 略去其他层的定义
def forward(self, x):
# ... 略去前面的网络部分
x = self.da_conv1(x)
# ... 略去后续的网络部分
return x
在上述代码中,我们首先定义了一个DANet模块。在该模块中,将输入张量x通过三个卷积层,得到了三个特征张量:proj_query
、proj_key
和proj_value。其中,proj_query
和proj_key
用来计算注意力权重矩阵(attention),而proj_value
作为输出特征的候选。
在DANet模块的forward函数
中,我们首先计算了proj_query和proj_key之间的点积,得到了能量矩阵energy,然后对energy
进行softmax
操作,得到了注意力权重矩阵attention
。最后,我们将proj_value
和attention
进行矩阵乘法操作,得到了加权后的输出特征张量out。
在YOLOv5模型中,我们通过在卷积层之间插入DANet模块来提高特征表示能力。例如,在上述代码中,我们定义了一个包含DANet模块的卷积层da_conv1
,并在其中使用了DANet模块来增强特征表示能力。
参考论文:
- https://arxiv.org/abs/1904.11492
- https://arxiv.org/abs/1809.02983
🏆本文收录于,目标检测YOLO改进指南。
本专栏均为全网独家首发,🚀内附代码,可直接使用,改进的方法均是2023年最近的模型、方法和注意力机制。每一篇都做了实验,并附有实验结果分析,模型对比。
🏆华为OD机试(JAVA)真题(A卷+B卷)
每一题都有详细的答题思路、详细的代码注释、样例测试,订阅后,专栏内的文章都可看,可加入华为OD刷题群(私信即可),发现新题目,随时更新,全天CSDN在线答疑。
🏆哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师。
🏆往期回顾:
1、YOLOv5/v7 添加注意力机制,30多种模块分析①,SE模块,SK模块
2、YOLOv5/v7 添加注意力机制,30多种模块分析②,BAM模块,CBAM模块
3、YOLOv5结合BiFPN,如何替换YOLOv5的Neck实现更强的检测能力?
4、YOLOv5结合BiFPN:BiFPN网络结构调整,BiFPN训练模型训练技巧
5、YOLOv7升级换代:EfficientNet骨干网络助力更精准目标检测