当前位置:   article > 正文

Pytorch ----注意力机制与自注意力机制的代码详解与使用_assert kernel_size in (3, 7), 'kernel size must be

assert kernel_size in (3, 7), 'kernel size must be 3 or 7' assertionerror: k

注意力机制的核心重点就是让网络关注到它更需要关注的地方

当我们使用卷积神经网络去处理图片的时候, 我们会更希望卷积神经网络去注意应该注意的地方,而不是什么都关注 ,我们不可能手动去调节需要注意的地方,这个时候,如何让卷积神经网络去自适应的注意重要的物体变得极为重要。注意力机制 就是实现网络自适应注意的一个方式。

一般而言,注意力机制可以分为通道注意力机制,空间注意力机制,以及二者的结合。

 1、SENet

SENet是通道注意力机制的典型实现。 重点是获得输入进来的特征层对应的每一个通道的权值 。通过学习的方式自动获取每个特征通道的重要程度,自动提升有用特征并抑制不重要的特征。

SENet具体实现方式就是:

  • 第一步对输入进来的每个特征层,进行全局平均池化得到一个标量,称之为Squeeze 。
  • 然后进行两次全连接,第一次全连接神经元个数较少 ,第二次全连接神经元个数和输入特征层相同 。先降维在升维好处是一方面降低了网络计算量,一方面增加了网络的非线性能力。(有的框架使用全连接,有的使用1*1的卷积。
  • 在完成两次全连接后,再取一次Sigmoid将权重值固定到0-1之间,此时我们获得了输入特征层每一个通道的权值 (0-1之间)。称之为Excitation。
  • 得到的权值可以看作是经过特征选择后每个通道的重要性,将这个权值乘上每个元素对应的通道,得到新的feature map,从而实现提升重要特征,抑制不重要特征这个功能。

 PyTorch版本的实现

  1. import torch
  2. import torch.nn as nn
  3. #通道注意力机制
  4. class SELayer(nn.Module):
  5. def __init__(self, channel, reduction=16):#传入输入通道数,缩放比例
  6. super(SELayer, self).__init__()
  7. self.avg_pool = nn.AdaptiveAvgPool2d(1)#平均池化高宽为1
  8. self.fc1 = nn.Sequential(
  9. nn.Linear(channel, channel // reduction,bias=False),#降维
  10. nn.ReLU(inplace=True),
  11. nn.Linear(channel // reduction, channel,bias=False),#升维
  12. nn.Sigmoid())
  13. self.fc2 = nn.Sequential(
  14. nn.Conv2d(channel , channel // reduction, 1, bias=False),
  15. nn.ReLU(inplace=True),
  16. nn.Conv2d(channel // reduction , channel , 1, bias=False),
  17. nn.Sigmoid()
  18. )
  19. def forward(self, x):
  20. #b,c,h.w
  21. b, c, _, _ = x.size()#batch \channel\ high\ weight
  22. # b,c,1,1----> b,c
  23. y = self.avg_pool(x).view(b, c)#调整维度、去掉最后两个维度
  24. # b,c- ----> b,c/16 ---- >b,c ----> b,c,1,1
  25. y1 = self.fc1(y).view(b, c, 1, 1)#添加上h,w维度
  26. # b,c,1,1----> b,c
  27. z = self.avg_pool(x) # 平均欧化
  28. # b,c- ----> b,c/16 ---- >b,c
  29. y2 = self.fc2(z) # 降维、升维
  30. return x * y1.expand_as(x)#来扩展张量中某维数据的尺寸,将输入tensor的维度扩展为与指定tensor相同的size

任意的原始网络结构,都可以通过这个Squeeze-Excitation的方式进行feature recalibration,如下图。

添加到其它网络结构的具体实现是:

Global Average Pooling ---> FC----> ReLU ----> FC ----> Sigmoid

第一层的FC会把通道降下来,然后第二层FC再把通道升上去,得到和通道数相同的C个权重,每个权重用于给对应的一个通道进行加权。上图中的r就是缩减系数,实验确定选取16,可以得到较好的性能并且计算量相对较小。 SENet的核心思想在于通过网络根据loss去学习特征权重,使得有效的feature map权重大,无效或效果小的feature map权重小,训练模型达到更好的结果 。

 2、ECANet

ECANet对SENet模块进行了一些改进,实证分析表明降维会对通道关注度的预测产生副作用,而且对所有通道的相关性进行捕获是低效且不必要的,而卷积具有良好的跨通道信息获取能力 。所以提出了一种不降维的局部跨信道交互策略 和 自适应选择一维卷积核大小的方法 ,从而实现了性能上的提优。

ECA模块去除了原来SE模块中的全连接层,直接在全局平均池化之后的特征图上通过一个1D卷积进行学习。 1D卷积的卷积核大小,会影响注意力机制每个权重计算要考虑的通道数量。左图是常规的SE模块,右图是ECA模块。ECA模块用1D卷积替换两次全连接。

 PyTorch版本的实现

  1. class ECAlayer(nn.Module):
  2. def __init__(self, channel, gamma=2,bias=1):
  3. super(ECAlayer, self).__init__()
  4. # x: input features with shape [b, c, h, w]
  5. self.channel=channel
  6. self.gamma=gamma
  7. self.bias=bias
  8. k_size=int(abs((math.log(self.channel , 2)+self.bias)/self.gamma))#(log底数2,c + 1) / 2 ---->(log 2,512 + 1)/2 = 5
  9. k_size= k_size if k_size%2 else k_size+1 #按照输入通道数自适应的计算卷积核大小
  10. self.avg_pool = nn.AdaptiveAvgPool2d(1)
  11. self.conv = nn.Conv1d(1, 1, kernel_size=k_size, padding=(k_size - 1) // 2, bias=False)
  12. self.sigmoid = nn.Sigmoid()
  13. def forward(self, x):
  14. y = self.avg_pool(x) # 基于全局空间信息的特征描述符
  15. # b,c,1,1
  16. # 变换维度,使张量可以进入卷积层
  17. y = self.conv(y.squeeze(-1).transpose(-1, -2))#压缩一个维度后,在转换维度
  18. #b,c,1,1 ----》 b,c,1 ----》 b,1,c 可以理解为输入卷积的batch,只有一个通道所以维度是1,c理解为序列卷积的特征个数
  19. y = y.transpose(-1, -2).unsqueeze(-1)
  20. #b,1,c ----》 b,c,1 ----》 b,c,1,1
  21. # 多尺度信息融合
  22. y = self.sigmoid(y)
  23. return x * y.expand_as(x)

3、CBAMNet

CBAM将通道注意力机制和空间注意力机制进行一个结合,相比于SENet只关注通道的注意力机制 可以取得更好的效果。其实现示意图如下所示,对输入进来的特征层,分别进行通道注意力机制的处理和空间注意力机制的处理 。下图是通道注意力机制和空间注意力机制 的具体实现方式:

通道注意力机制:通道注意力机制的实现可以分为两个部分,对输入进来的单个特征层,分别进行 全局平均池化全局最大池化 。之后对平均池化和最大池化结果,利用共享的全连接层进行处理,再对处理后的结果相加,然后经过sigmoid激活操作,获得输入特征层每一个通道的权值 (0-1之间)。最后将这个权值乘上原输入特征层,生成空间注意力模块需要的输入特征。

空间注意力机制:将通道注意力模块输出的特征图作为本模块的输入,在每一个特征点的通道上取最大值和平均值 。之后将这两个结果基于channel进行堆叠,经过一个卷积操作降维为1个通道,然后经过sigmoid激活操作,获得输入特征层每一个特征点的权值 (0-1之间)。最后将这个权值乘上原输入特征层。

 PyTorch版本的实现

  1. #CBMA 通道注意力机制和空间注意力机制的结合
  2. class ChannelAttention(nn.Module):
  3. def __init__(self, in_planes, ratio=16):
  4. super(ChannelAttention, self).__init__()
  5. self.avg_pool = nn.AdaptiveAvgPool2d(1)#平均池化高宽为1
  6. self.max_pool = nn.AdaptiveMaxPool2d(1)#最大池化高宽为1
  7. # 利用1x1卷积代替全连接
  8. self.fc1 = nn.Conv2d(in_planes, in_planes // ratio, 1, bias=False)
  9. self.relu1 = nn.ReLU()
  10. self.fc2 = nn.Conv2d(in_planes // ratio, in_planes, 1, bias=False)
  11. self.sigmoid = nn.Sigmoid()
  12. def forward(self, x):
  13. # 平均池化---》1*1卷积层降维----》激活函数----》卷积层升维
  14. avg_out = self.fc2(self.relu1(self.fc1(self.avg_pool(x))))
  15. # 最大池化---》1*1卷积层降维----》激活函数----》卷积层升维
  16. max_out = self.fc2(self.relu1(self.fc1(self.max_pool(x))))
  17. out = avg_out + max_out # 加和操作
  18. return self.sigmoid(out)#sigmoid激活操作
  19. class SpatialAttention(nn.Module):
  20. def __init__(self, kernel_size=7):
  21. super(SpatialAttention, self).__init__()
  22. assert kernel_size in (3, 7), 'kernel size must be 3 or 7'
  23. padding = kernel_size//2
  24. #经过一个卷积层,输入维度是2,输出维度是1
  25. self.conv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
  26. self.sigmoid = nn.Sigmoid()#sigmoid激活操作
  27. def forward(self, x):
  28. avg_out = torch.mean(x, dim=1, keepdim=True) # 在通道的维度上,取所有特征点的平均值 b,1,h,w
  29. max_out, _ = torch.max(x, dim=1, keepdim=True)# 在通道的维度上,取所有特征点的最大值 b,1,h,w
  30. x = torch.cat([avg_out, max_out], dim=1)#在第一维度上拼接,变为 b,2,h,w
  31. x = self.conv1(x) # 转换为维度,变为 b,1,h,w
  32. return self.sigmoid(x)#sigmoid激活操作
  33. class cbamblock(nn.Module):
  34. def __init__(self, channel, ratio=16, kernel_size=7):
  35. super(cbamblock, self).__init__()
  36. self.channelattention = ChannelAttention(channel, ratio=ratio)
  37. self.spatialattention = SpatialAttention(kernel_size=kernel_size)
  38. def forward(self, x):
  39. x = x * self.channelattention(x)#将这个权值乘上原输入特征层
  40. x = x * self.spatialattention(x)#将这个权值乘上原输入特征层
  41. return x

 4、Coordinate Attention

全局池化方法通常用于通道注意编码空间信息的全局编码,但由于它将全局空间信息压缩到通道描述符中,导致难以保存位置信息。Coordinate Attention通过精确的位置信息对通道关系和长期依赖性进行编码,它不仅仅能捕获跨通道的信息,还能捕获方向感知和位置感知的信息,这能帮助模型更加精准地定位和识别感兴趣的目标。论文解读:

CVPR2021| 继SE,CBAM后的一种新的注意力机制Coordinate Attention - 知乎

Coordinate Attention是通过在水平方向和垂直方向上进行平均池化,缓解2D全局池化造成的位置信息丢失,再进行transform对空间信息编码,最后把空间信息通过在通道上加权的方式融合。 具体操作分为 Coordinate信息嵌入 Coordinate Attention生成 2个步骤。

  • 对输入进来的 input features (C,H,W),分别在水平方向和垂直方向进行1D平均池化操作,得到两个单独的 1D向量(C,H,1)(C,1,W)。
  • 在空间维度上Concat连接两个attention map的维度(C,1,H+W),方便下面进行卷积操作,并通过一个1x1Conv层来压缩通道,减少计算量。(C/r ,1,H+W)
  • 再是通过BN和Non-linear来编码垂直方向和水平方向的空间信息。(C/r ,1,H+W)
  • 接下来split,在第三维度上拆分成维度为H和W的两个attention map,再各自通过1x1卷积层得到和input feature maps一样的通道数。(C,H,1)(C,1,W)
  • 再sigmoid归一化加权。 最后将两个 attention map 都乘上input feature maps,以强调注意区域的表示。(C,H,W)

 PyTorch版本的实现

  1. #Coordinate Attention协调注意力机制
  2. import torch
  3. from torch import nn
  4. class CABlock(nn.Module):
  5. def __init__(self, channel, h,w, reduction=16):
  6. super(CABlock, self).__init__()
  7. self.h = h
  8. self.w = w
  9. self.avg_pool_x = nn.AdaptiveAvgPool2d((h, 1))
  10. self.avg_pool_y = nn.AdaptiveAvgPool2d((1, w))
  11. self.conv_1x1 = nn.Conv2d(in_channels=channel, out_channels=channel // reduction, kernel_size=1, stride=1,
  12. bias=False)
  13. self.relu = nn.ReLU()
  14. self.bn = nn.BatchNorm2d(channel // reduction)
  15. self.F_h = nn.Conv2d(in_channels=channel // reduction, out_channels=channel, kernel_size=1, stride=1,
  16. bias=False)
  17. self.F_w = nn.Conv2d(in_channels=channel // reduction, out_channels=channel, kernel_size=1, stride=1,
  18. bias=False)
  19. self.sigmoid_h = nn.Sigmoid()
  20. self.sigmoid_w = nn.Sigmoid()
  21. def forward(self, x):
  22. #b,c,h,w
  23. x_h = self.avg_pool_x(x).permute(0, 1, 3, 2)
  24. #b,c,1,h
  25. x_w = self.avg_pool_y(x)
  26. #b,c,1,w
  27. x_cat_conv_relu = self.relu(self.conv_1x1(torch.cat((x_h, x_w), 3)))#在第三维度上进行拼接后,卷积
  28. #b,c,1,h+w
  29. x_cat_conv_split_h, x_cat_conv_split_w = x_cat_conv_relu.split([self.h, self.w], 3)#拆分卷积后的维度
  30. #b,c,1,h b,c,1,w
  31. s_h = self.sigmoid_h(self.F_h(x_cat_conv_split_h.permute(0, 1, 3, 2)))#分别进行sigmoid激活操作
  32. s_w = self.sigmoid_w(self.F_w(x_cat_conv_split_w))
  33. #b,c,h,1 b,c,1,w
  34. out = x * s_h.expand_as(x) * s_w.expand_as(x)
  35. return out

 5、AAnet

self attention是 注意力机制 中的一种,也是transformer中的重要组成部分,利用输入样本自身的关系构建注意力模型。注意力机制 要解决的问题是:当神经网络的输入是多个大小不一样的向量,并且可能因为不同向量之间有一定的关系,而在训练时却无法充分发挥这些关系,导致模型训练结果较差。

self-attention 最终得到的是:给定当前输入样本( 为了更好地理解,把输入进行拆解),产生一个输出,假设这个输出能看到所有输入的样本信息,那么这个输出是序列中所有样本的加权和,根据不同权重选择自己的注意力点。本质上,对于每个输入向量,Self-Attention产生一个向量,该向量在其邻近向量上加权求和,其中权重由单词之间的关系或连通性决定。

注意力机制可以分为三步:一是信息输入;二是计算注意力分布α;三是根据注意力分布α 来计算输入信息的加权平均。

AANet,它由两个模块组成:同尺度聚合模块(ISA)和跨尺度聚合模块(CSA)。AANet可用来代替基于匹配代价体(cost volume)的3D卷积,在加快推理速度的同时保持较高的准确率。 对于AANet而言,其网络结构大致可以分成三部分。第一部分是特征提取,即利用CNN卷积得到不同尺度的特征。

第二部分是“代价聚合”,包括“匹配代价计算”(Multi-Scale Cost Volumes)和“多尺度代价融合”(AAModules)。

第三部分是深度图优化部分

  PyTorch版本的实现(参考)

代码:https://github.com/leaderj1001/Attention-Augmented-Conv2d

论文:https://arxiv.org/pdf/1904.09925.pdf

 论文解读:AANet - 知乎

  1. class AugmentedConv(nn.Module):
  2. def __init__(self, in_channels, out_channels, kernel_size, dk, dv, Nh, shape=0, relative=False, stride=1):
  3. super(AugmentedConv, self).__init__()
  4. self.in_channels = in_channels
  5. self.out_channels = out_channels
  6. self.kernel_size = kernel_size
  7. self.dk = dk
  8. self.dv = dv
  9. self.Nh = Nh
  10. self.shape = shape
  11. self.relative = relative
  12. self.stride = stride
  13. self.padding = (self.kernel_size - 1) // 2
  14. assert self.Nh != 0, "integer division or modulo by zero, Nh >= 1"
  15. assert self.dk % self.Nh == 0, "dk should be divided by Nh. (example: out_channels: 20, dk: 40, Nh: 4)"
  16. assert self.dv % self.Nh == 0, "dv should be divided by Nh. (example: out_channels: 20, dv: 4, Nh: 4)"
  17. assert stride in [1, 2], str(stride) + " Up to 2 strides are allowed."
  18. self.conv_out = nn.Conv2d(self.in_channels, self.out_channels - self.dv, self.kernel_size, stride=stride, padding=self.padding)
  19. self.qkv_conv = nn.Conv2d(self.in_channels, 2 * self.dk + self.dv, kernel_size=self.kernel_size, stride=stride, padding=self.padding)
  20. self.attn_out = nn.Conv2d(self.dv, self.dv, kernel_size=1, stride=1)
  21. if self.relative:
  22. self.key_rel_w = nn.Parameter(torch.randn((2 * self.shape - 1, dk // Nh), requires_grad=True))
  23. self.key_rel_h = nn.Parameter(torch.randn((2 * self.shape - 1, dk // Nh), requires_grad=True))
  24. def forward(self, x):
  25. # Input x (batch_size, channels, height, width)
  26. # batch, _, height, width = x.size()
  27. # conv_out (batch_size, out_channels, height, width)
  28. conv_out = self.conv_out(x)
  29. batch, _, height, width = conv_out.size()
  30. # flat_q, flat_k, flat_v (batch_size, Nh, height * width, dvh or dkh)
  31. # dvh = dv / Nh, dkh = dk / Nh
  32. # q, k, v (batch_size, Nh, height, width, dv or dk)
  33. flat_q, flat_k, flat_v, q, k, v = self.compute_flat_qkv(x, self.dk, self.dv, self.Nh)
  34. logits = torch.matmul(flat_q.transpose(2, 3), flat_k)
  35. if self.relative:
  36. h_rel_logits, w_rel_logits = self.relative_logits(q)
  37. logits += h_rel_logits
  38. logits += w_rel_logits
  39. weights = F.softmax(logits, dim=-1)
  40. # attn_out (batch, Nh, height * width, dvh)
  41. attn_out = torch.matmul(weights, flat_v.transpose(2, 3))
  42. attn_out = torch.reshape(attn_out, (batch, self.Nh, self.dv // self.Nh, height, width))
  43. # combine_heads_2d (batch, out_channels, height, width)
  44. attn_out = self.combine_heads_2d(attn_out)
  45. attn_out = self.attn_out(attn_out)
  46. return torch.cat((conv_out, attn_out), dim=1)

6、注意力机制的应用

注意力机制是一个即插即用的模块,理论上可以放在任何一个特征层后面 ,可以放在主干网络,也可以放在加强特征提取网络。但是放置在主干会导致网络的预训练权重无法使用。例如将注意力模块用在Resnet网络,如果加在block里面,会改变ResNet的网络结构,不能用预训练参数,加在最后一层卷积和第一层卷积不改变网络,可以用预训练参数。如果不使用预训练参数,则可以将SE模块加入到残差单元:

  1. # 网络的第一层加入注意力机制
  2. self.ca = cbamblock(self.in_channel)
  3. # 网络的卷积层的最后一层加入注意力机制
  4. self.ca1 = cbamblock(self.in_channel)
  5. #forword部分
  6. x = self.ca(x) # 注意力机制层
  7. x = self.maxpool(x)
  8. x = self.layer1(x)
  9. x = self.layer2(x)
  10. x = self.layer3(x)
  11. x = self.layer4(x)
  12. x = self.ca1(x)# 注意力机制层
  13. #将SE模块用在Resnet网络,
  14. #只需要将SE模块加入到残差单元(应用在残差学习那一部分)
  15. #在残差单元上,添加通道注意力机制层
  16. self.ca = ECAlayer(out_channel * self.expansion)
  17. #forword部分
  18. out = self.ca(out) # 在残差单元上添加注意力机制层

参考文献:

神经网络学习小记录64——Pytorch 图像处理中注意力机制的解析与代码详解_Bubbliiiing的博客-CSDN博客_pytorch 注意力机制代码

 注意力机制在CNN中使用总结_AI浩的博客-CSDN博客_注意力机制加在什么位置

 网络中的注意力机制-CNN attention_IT捕快的博客-CSDN博客_如何在网络中加入注意力机制

SEnet(Squeeze-and-Excitation Network) 在特征通道之间加入注意力机制
CBAM(Convolutional Block Attention Module) 在特征通道和特征空间两个维度上加入注意力机制
AA-Net(Attention-Augmented-Conv2d Network) 在空间和特征子空间中同时加入注意机制
ECA-Net(Efficient Channel Attention Network) 在局部特征通道之间加入注意力机制

效果对比:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/290970
推荐阅读
相关标签
  

闽ICP备14008679号