目录
一.什么是注意力机制(Attention Mechanism)
三.自注意力机制(Self-Attention Mechanism)
四.多头自注意力机制(Multi-head Self-Attention Machanism)
五.通道注意力机制(Channel attention force mechanism)
六.空间注意力机制(Spatial attention force mechanism)
一.什么是注意力机制(Attention Mechanism)
深度学习中的注意力机制(Attention Mechanism)是一种模仿人类视觉和认知系统的方法,它允许神经网络在处理输入数据时集中注意力于相关的部分。通过引入注意力机制,神经网络能够自动地学习并选择性地关注输入中的重要信息,提高模型的性能和泛化能力。
注意力机制从本质上讲和人类的选择性注意力机制类似,核心目标也是从众多信息中选出对当前任务目标更加关键的信息。深度学习中,注意力机制通常应用于序列数据(如文本、语音或图像序列)的处理。其中,最典型的注意力机制包括自注意力机制、空间注意力机制和时间注意力机制。这些注意力机制允许模型对输入序列的不同位置分配不同的权重,以便在处理每个序列元素时专注于最相关的部分。
二.什么是注意力(Attention)
注意力attention本质其实就是一个加权求和。
对于k个d维的特征向量(i=1,2,3....,k),若想要通过加权平均的方法整合这k个特征向量的信息,得到一个最终向量,则需要注意力attention将合理的计算出来。
加权平均方法:
=
如何通过attention计算出:
1.设置一个打分函数,针对每个,计算出一个得分值。而打分的依据,就是和attention所关注的对象(的相关程度,相关程度越高则得分值越大。
2.对得到的K个得分值(i=1,2,3....,k),经过一个softmax函数,得到最后的权重值,如下所示:
=
softmax函数将各个输出节点的输出值范围映射到[0, 1],并且约束各个输出节点的输出值的和为1
三.自注意力机制(Self-Attention Mechanism)
自注意力机制的基本思想是,在处理序列数据时,每个元素都可以与序列中的其他元素建立关联,而不仅仅是依赖于相邻位置的元素。它通过计算元素之间的相对重要性来自适应地捕捉元素之间的长程依赖关系。
具体而言,对于序列中的每个元素,自注意力机制计算其与其他元素之间的相似度,并将这些相似度归一化为注意力权重。然后,通过将每个元素与对应的注意力权重进行加权求和,可以得到自注意力机制的输出。
重要参数介绍:
在开始计算前,我们需要了解计算中最重要的三个参数,分别是q(query)、k(key)、v(value)。其中q(query)的作用是用来与其他单词进行匹配,而k(key)的作用是用来与q进行匹配,v(value)则是当前单词或字的重要信息表示。
3.1.对输入数据进行Embedding操作
比如说输入的数据是“使用自注意力机制”,其中代表的是“使用”,代表的是“自注意力”,代表的是“机制”,通过相应的Embedding操作(eg.Word2Vec)将输入数据转换为对应的向量,,。
3.2.q,k操作
进过Embedding操作后,,,作为注意力计算的输入数据。
此时,每个输入数据向量都需要分别与,,三个矩阵相乘,得到对应的,,
得到每个输入数据的,,值后,则可以计算不同向量之间的关联程度。若要计算和,之间的关联程序,则需要用和,进行匹配计算。
匹配计算的公式有很多,以如下公式为例:
= · /
其中代表的是和的矩阵维度
经过匹配公式计算后,得到,,
此时,再对上述得到的数据进行softmax操作,得到,,值。
3.3.v操作
在v操作中,会将上述由softmax计算出的结果值,,和,,分别相乘。
并以此类推,最终得到,,的值 ,即,,经过自注意力机制计算得到的最终的结果。
由此可见,自注意力机制通过计算序列中不同位置之间的相关性( , 操作),为每个位置分配一个权重,然后对序列进行加权求和( 操作)。
3.4.代码实现
import torch
import torch.nn as nn
# 定义自注意力模块
class SelfAttention(nn.Module):
def __init__(self, embed_dim):
super(SelfAttention, self).__init__()
# 相当于设置Q,K,V矩阵,用于与输入数据向量进行矩阵相乘
# Q,K,V计算矩阵的参数需要后期训练
self.query = nn.Linear(embed_dim, embed_dim)
self.key = nn.Linear(embed_dim, embed_dim)
self.value = nn.Linear(embed_dim, embed_dim)
def forward(self, x):
# 输入输出经过一个全连接层计算得到对应的q,k,v值
q = self.query(x)
k = self.key(x)
v = self.value(x)
# matmul 函数返回两个数组的矩阵乘积
# q,k矩阵点乘得到a(1,i),即为注意力矩阵
# 此处需要对矩阵k进行维度变化处理,即第二维度和第三维度进行交换
attn_weights = torch.matmul(q, k.transpose(1, 2))
# a(1,i)经过softmax层计算得到(~)a(1,i)
attn_weights = nn.functional.softmax(attn_weights, dim=-1)
# (~)a(1,i)在与v矩阵相乘得到最终的b(i)值
attended_values = torch.matmul(attn_weights, v)
return attended_values
# 定义自注意力分类器模型
class SelfAttentionClassifier(nn.Module):
def __init__(self, embed_dim, hidden_dim, num_classes):
super(SelfAttentionClassifier, self).__init__()
# 传入参数embed_dim实例化SelfAttention类,并命名为attention
self.attention = SelfAttention(embed_dim)
# 两个全连接层
self.fc1 = nn.Linear(embed_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, num_classes)
def forward(self, x):
# attention是已经实例化的SelfAttention类对象
# 所以此处是调用其中的forward函数,得到b(i)值
attended_values = self.attention(x)
x = attended_values.mean(dim=1) # 对每个位置的向量求平均
x = self.fc1(x)
x = torch.relu(x)
x = self.fc2(x)
return x
四.多头自注意力机制(Multi-head Self-Attention Machanism)
多头注意力机制是在自注意力机制的基础上发展起来的,是自注意力机制的变体,旨在增强模型的表达能力和泛化能力。它通过使用多个独立的注意力头,分别计算注意力权重,并将它们的结果进行拼接或加权求和,从而获得更丰富的表示。
与自注意力机制每个输入向量只有一个,,对应不同,多头自注意力机制会在点乘一个,,后再次分配多个,,用于点乘。
4.1.q,k操作
此处以两个,,为例:
如图所示,将先点乘矩阵得到,此后会再为 分配两个head,通过点乘和得到和,具体公式如下:
= ·
= ·
= ·
对于k,v的操作相同。
而此时的注意力矩阵的计算,也就相应的改变为与和进行点乘计算:
= · ·
点乘得到的结果再进行softmax操作:
= softmax( )
4.2.v操作
多头自注意力机制的v操作与自注意力机制的v操作类似:
计算出和后,将 和进行拼接,即向量的首尾相连,然后通过线性转换(即不含非线性激活层的单层全连接神经网络)得到。
4.3.代码实现
import torch
import torch.nn as nn
# 定义多头自注意力模块
class MultiHeadSelfAttention(nn.Module):
def __init__(self, embed_dim, num_heads):
super(MultiHeadSelfAttention, self).__init__()
# 头数
self.num_heads = num_heads
# 头维度
self.head_dim = embed_dim // num_heads
# 设置矩阵为后续运算准备
self.query = nn.Linear(embed_dim, embed_dim)
self.key = nn.Linear(embed_dim, embed_dim)
self.value = nn.Linear(embed_dim, embed_dim)
self.fc = nn.Linear(embed_dim, embed_dim)
def forward(self, x):
batch_size, seq_len, embed_dim = x.size()
# 将输入向量拆分为多个头
# 实例化网络对象
# 通过view()函数将矩阵设置为想要的形状
q = self.query(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
k = self.key(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
v = self.value(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
# 计算注意力权重
attn_weights = torch.matmul(q, k.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.head_dim, dtype=torch.float))
attn_weights = torch.softmax(attn_weights, dim=-1)
# 注意力加权求和
# 得到b(i,1)值
attended_values = torch.matmul(attn_weights, v).transpose(1, 2).contiguous().view(batch_size, seq_len, embed_dim)
# 经过线性变换和残差连接
# 得到最终的b(i)值
x = self.fc(attended_values) + x
return x
# 定义多头自注意力分类器模型
class MultiHeadSelfAttentionClassifier(nn.Module):
def __init__(self, embed_dim, num_heads, hidden_dim, num_classes):
super(MultiHeadSelfAttentionClassifier, self).__init__()
# 实例化多头自注意力机制网络对象
self.attention = MultiHeadSelfAttention(embed_dim, num_heads)
self.fc1 = nn.Linear(embed_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, num_classes)
def forward(self, x):
# x经过attention网络计算得到最终的b(i)值
x = self.attention(x)
x = x.mean(dim=1) # 对每个位置的向量求平均
x = self.fc1(x)
x = torch.relu(x)
x = self.fc2(x)
return x
五.通道注意力机制(Channel attention force mechanism)
通道注意力机制是一种对特征通道之间的相互依赖性建模来自适应地重新缩放每个通道的特征的方法。它可以让网络专注于更有用的信道并增强辨别学习能力。通道注意力机制的基本思想是使用一些网络去计算一个权重,把这个权重与 feature map 进行运算,得到加强注意力后的 feature map。
因此通道注意力机制可以用于图像超分辨率、图像分类等任务中。
下面以SENet作为示例,介绍通道注意力机制。
5.1.SENet介绍
通俗的来说SENet的核心思想在于通过网络根据loss去学习特征权重,使得有效的feature map权重大,无效或效果小的feature map权重小的方式训练模型达到更好的结果。
SE block嵌在原有的一些分类网络中不可避免地增加了一些参数和计算量,但是在效果面前还是可以接受的 。Sequeeze-and-Excitation(SE) block并不是一个完整的网络结构,而是一个子结构,可以嵌到其他分类或检测模型中。
上图为SENet的结构图,其中该网络结构中最重要的步骤为Squeeze和Excitation。
具体步骤如下:
- 给定一个输入 x,其特征通道数为 ,通过一系列卷积等一般变换后得到一个特征通道数为 的特征。与传统的 CNN 不一样的是,接下来通过三个操作来重标定前面得到的特征。
- 首先是 Squeeze 操作,顺着空间维度来进行特征压缩,将每个二维的特征通道变成一个实数,这个实数某种程度上具有全局的感受野,并且输出的维度和输入的特征通道数相匹配。它表征着在特征通道上响应的全局分布,而且使得靠近输入的层也可以获得全局的感受野,这一点在很多任务中都是非常有用的。
- 其次是 Excitation 操作,它是一个类似于循环神经网络中门的机制。通过参数 w 来为每个特征通道生成权重,其中参数 w 被学习用来显式地建模特征通道间的相关性。
- 最后是一个 Reweigh 的操作,将 Excitation 的输出的权重看做是经过特征选择后的每个特征通道的重要性,然后通过乘法逐通道加权到先前的特征上,完成在通道维度上的对原始特征的重标定。
Squeeze操作对应的公式:
该公式的计算相当于对输入数据进行一次global average pooling操作,可以将H*W*C的输入转换成1*1*C的输出,将通道的数据转换为标量值。
Excitation操作对应的公式:
Squeeze操作后得到的结果先与点乘,得到的结果经过一个Relu层;然后与点乘,最后再经过一个sigmoid函数,得到最终的s值,其维度也是1*1*C。
该公式中的,的维度分别为C/r * C和C*C/r,其中r是一个缩放参数,其目的是为了减少channel个数从而减少计算量。
Reweigh操作对应的公式:
经过Squeeze和Excitation操作得到权重矩阵后,与最初的输入数据点乘,相当于中的每个值都乘以中对应的权重值,即可得到最后经过权重计算的输出值。
5.2.代码实现
import torch
import torch.nn as nn
class SELayer(nn.Module):
def __init__(self, channel, reduction=16):
super(SELayer, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Sequential(
# 对应W1,维度为C/r * C
nn.Linear(channel, channel // reduction),
nn.ReLU(inplace=True),
# 对应W2,维度为C*C/r
nn.Linear(channel // reduction, channel),
nn.Sigmoid()
)
def forward(self, x):
b, c, _, _ = x.size()
# squeeze操作
y = self.avg_pool(x).view(b, c)
# excitation操作
y = self.fc(y).view(b, c, 1, 1)
# y为得到的权重矩阵
# reweigh操作
return x * y
class SENet(nn.Module):
def __init__(self, num_classes=1000, reduction=16):
super(SENet, self).__init__()
# 卷积层设置
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
# squeeze 和 excitation层设置--类对象实例化
self.se = SELayer(64, reduction=reduction)
self.fc = nn.Linear(64, num_classes)
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.se(x)
# view()函数的参数为-1意味着函数将自动计算该维度的大小
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
六.空间注意力机制(Spatial attention force mechanism)
空间注意力机制和通道注意力机制具有异曲同工之妙,通道注意力机制旨在捕捉通道的重要性的程度,空间注意力机制旨在通过引入注意力模块,使模型能够自适应地学习不同区域的注意力权重。这样,模型可以更加关注重要的图像区域,而忽略不重要的区域。
下面以CBAM(Convolutional Block Attention Module)作为示例,介绍空间注意力机制。
5.1.CBAM介绍
卷积注意力模块(CBAM)是一种用于前馈卷积神经网络的简单而有效的注意力模块。 给定一个中间特征图,CBAM模块会沿着两个独立的维度(通道和空间)依次推断注意力图,然后将注意力图与输入特征图相乘以进行自适应特征优化。
由于CBAM是轻量级的通用模块,因此可以忽略的该模块的开销而将其无缝集成到任何CNN架构中,并且可以与基础CNN一起进行端到端训练。
CBAM模块由两个注意力模块组成:通道注意力模块(Channel Attention Module)和空间注意力模块(Spatial Attention Module)。
其中空间注意力模块将Channel attention模块输出的特征图作为本模块的输入特征图。首先做一个基于channel的global max pooling 和global average pooling,然后将这2个结果基于channel 做concat操作(将多个张量沿着一个新的维度进行拼接)。然后经过一个卷积操作,降维为1个channel。再经过sigmoid生成spatial attention feature。最后将该feature和该模块的输入feature做乘法,得到最终生成的特征。
5.2.代码实现
import torch
import torch.nn as nn
# 通道注意力模块
class ChannelAttention(nn.Module):
def __init__(self, in_channels, reduction_ratio=16):
super(ChannelAttention, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Sequential(
nn.Linear(in_channels, in_channels // reduction_ratio),
nn.ReLU(inplace=True),
nn.Linear(in_channels // reduction_ratio, in_channels)
)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
b, c, _, _ = x.size()
# squeeze操作
y = self.avg_pool(x).view(b, c)
# excitation操作
y = self.fc(y).view(b, c, 1, 1)
# reweigh操作
return x * self.sigmoid(y)
# 空间注意力模块
class SpatialAttention(nn.Module):
def __init__(self):
super(SpatialAttention, self).__init__()
self.conv = nn.Conv2d(2, 1, kernel_size=7, padding=3)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
max_pool = torch.max(x, dim=1, keepdim=True)[0]
avg_pool = torch.mean(x, dim=1, keepdim=True)
# 张量max_pool和avg_pool沿着第二个维度拼接形成新的一个张量
y = torch.cat([max_pool, avg_pool], dim=1)
# 张量连接后channel为2
# 通过卷积将channel值降维为1
y = self.conv(y)
# reweigh操作
return x * self.sigmoid(y)
class CBAM(nn.Module):
def __init__(self, in_channels, reduction_ratio=16):
super(CBAM, self).__init__()
# 通道注意力模块和空间注意力模块实例化
self.channel_attention = ChannelAttention(in_channels, reduction_ratio)
self.spatial_attention = SpatialAttention()
def forward(self, x):
x = self.channel_attention(x)
x = self.spatial_attention(x)
return x
参考文章:
注意力机制综述(图解完整版附代码) - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/631398525
[1709.01507] Squeeze-and-Excitation Networks (arxiv.org)https://arxiv.org/abs/1709.01507 Sanghyun_Woo_Convolutional_Block_Attention_ECCV_2018_paper.pdf (thecvf.com)https://openaccess.thecvf.com/content_ECCV_2018/papers/Sanghyun_Woo_Convolutional_Block_Attention_ECCV_2018_paper.pdf