上一篇 | 下一篇 |
---|---|
注意力机制(3/4集) | 待编写 |
一、代码
在 pytorch 中,貌似只有一个多头注意力机制包,其他类型的注意力机制都需要自定义。
这里先给出一些常用的注意力机制代码,位置编码等代码会在后续的Transformer
讲解中给出。
下方代码的核心都是:生成权重。并且 Q 、 K 、 V Q、K、V Q、K、V 三个参数一般是用在自注意力机制里面的,其他类型的注意力机制有所不同。
首先,在图像领域,不考虑 batch_size
的话,一个图片有
3
3
3 维,分别是 (c,h,w)
(通道数,高,宽)。立体结构图如下:
其中空间维就是 h ∗ w h*w h∗w 这个平面维度,通道维就是 c c c 这个维度。
①自注意力机制(Self-Attention)
原理之前已经详细给出,这里就不再赘述,只给出精简的做法及图解。
做法及图解:
令 ▇ N=batch_size, L=Seq_Len, H=heads, D=heads_dim
▇ 。
-
获取输入 X X X 的尺寸信息:其中单个
batch_size
的尺寸为(seq_len, embed_size)
,其中embed_size=input_size
。图解如下: -
生成初步的 Q 、 K 、 V Q、K、V Q、K、V :通过三个不同的线性层可生成初步的 Q 、 K 、 V Q、K、V Q、K、V ,三者形状均为
(batch_size, seq_len, embed_size)
。 -
多头注意力情况下,会生成新的 Q 、 K 、 V Q、K、V Q、K、V :初步的 Q 、 K 、 V Q、K、V Q、K、V 会沿着特征维度(
embed_size
维度)被分割成heads
份,则新的 Q 、 K 、 V Q、K、V Q、K、V 尺寸均为(batch_size, seq_len, heads, head_dim)
,简化为(N, L, H, D)
。图解如下(仅展示单个
batch_size
): -
计算注意力分数:(用爱因斯坦求和约定函数来求矩阵点积)单个
batch_size
的等价 “矩阵乘法+嵌套 for 循环” 计算图解为:(因为爱因斯坦求和约定函数太抽象,不容易画图)
接着进行缩放,即结果的每个元素除以 d k \sqrt{d_k} dk ,尺寸不变。
-
计算注意力权重:沿
seq_len
维度应用 s o f t m a x softmax softmax ,得到attention
。 -
注意力加权:即
attention
与V
相乘。图解如下(仅展示单个
batch_size
): -
恢复输入尺寸:由于先前分割了多头注意力,所以这里要将多头注意力结果沿
D
维度重新拼接成原尺寸(batch_size, seq_len, embed_size)
。
代码:
特征维度就是下方代码中的 embed_size
,其实就是 input_size
。
import torch.nn.functional as F
class SelfAttention(nn.Module):
def __init__(self, embed_size, heads=8):
super().__init__()
self.embed_size = embed_size
self.heads = heads
self.head_dim = embed_size // heads
self.values = nn.Linear(embed_size, embed_size)
self.keys = nn.Linear(embed_size, embed_size)
self.queries = nn.Linear(embed_size, embed_size)
self.fc_out = nn.Linear(embed_size, embed_size)
def forward(self, x):
# x shape: (batch_size, seq_len, embed_size)
batch_size, seq_len, _ = x.size()
# 生成Q, K, V, 形状均为(batch_size, seq_len, heads, head_dim)
Q = self.queries(x).view(batch_size, seq_len, self.heads, self.head_dim)
K = self.keys(x).view(batch_size, seq_len, self.heads, self.head_dim)
V = self.values(x).view(batch_size, seq_len, self.heads, self.head_dim)
# 计算注意力分数
# n=batch_size, l=seq_len, h=heads, d=head_dim
energy = torch.einsum("nlhd,nlhd->nhll", [Q, K]) # 爱因斯坦求和约定,计算 Q 和 K 的点积, 得到能量矩阵
energy = energy / (self.head_dim ** 0.5) # 缩放(即除以根号下dk)
attention = F.softmax(energy, dim=3) # 沿 seq_len 维度应用 softmax
# 注意力加权
out = torch.einsum("nhll,nlhd->nlhd", [attention, V])
out = out.reshape(batch_size, seq_len, self.embed_size)
return self.fc_out(out)
上述代码参数讲解:
embed_size
:输入特征的维度(如 512 ),其实就是input_size
。heads
:多头注意力的头数(如 8 ),通过并行化提升模型对不同表示子空间的关注能力。head_dim
:每个注意力头的维度,计算为embed_size // heads
(如 512/8=64 )。- 线性投影层:
values
、keys
、queries
:将输入映射到 Q 、 K 、 V Q、K、V Q、K、V ,保持输出维度为embed_size
。fc_out
:将多头注意力结果合并回原始维度。
energy
:能量矩阵,形状为(n,h,l,l)
。
上述代码中某些函数讲解:
-
torch.einsum
:爱因斯坦求和,用来求多维张量点积(是 t r a n s f o r m e r transformer transformer 的核心操作之一)。-
核心思想:用下标字母表示张量的维度,并通过重复字母表示求和,省略显式的求和符号(如 ∑ \sum ∑)。
-
(其实有点难以理解,难以捋清,如果看不懂的话,就用矩阵乘法+嵌套 for 循环,下面我已给出)
-
函数语法:
torch.einsum("表达式字符串", 张量1, 张量2, ...)
。-
表达式字符串:用来规定输入张量、输出张量的维度,形如
"nlhd,nlhd->nhll"
,同时规定了计算方式。其中->
前的字母表示输入张量的维度,每个张量的维度用逗号分隔;->
后的字母表示输出张量的维度,若省略,则对重复字母求和,输出无重复字母的维度。 -
输入张量:在表达式字符串后面用逗号隔开,形如
张量1, 张量2, ...
。 -
字母选择:使用任意 小写字母 (如
i, j, k
)表示张量的元素下标,也代表了维度,但需保证同一维度在不同张量中字母一致。 -
计算方式:重复字母对应的维度中,对应元素求和,保留输入张量维度字母中的非重复维度字母,例如
"ik,kj->ij"
,则对输入张量的k
维度求和,保留i,j
两个维度。举例如下:有两个矩阵进行内积(点积),分别为 A ( I × K ) 、 B ( K × J ) \large A_{(I×K)}、B_{(K×J)} A(I×K)、B(K×J) ,其元素为 A ( i , k ) 、 B ( k , j ) \large A(i,k)、B(k,j) A(i,k)、B(k,j) ,对维度 k k k 进行求和运算,生成矩阵为 C ( I × J ) \large C_{(I×J)} C(I×J) ,其元素为 C ( i , j ) \large C(i,j) C(i,j) 。则有
C ( i , j ) = ∑ k = 0 K − 1 A ( i , k ) ⋅ B ( k , j ) ———————————————— [ 1 2 3 4 ] ∗ [ 5 6 7 8 ] = [ 19 29 43 50 ] C(i,j)=\sum_{k=0}^{K-1}A(i,k)·B(k,j)\\ ————————————————\\ \left[ \begin{matrix} 1 & 2\\ 3 & 4 \\ \end{matrix} \right]*\left[ \begin{matrix} 5 & 6\\ 7 & 8 \\ \end{matrix} \right]=\left[ \begin{matrix} 19 & 29\\ 43 & 50 \\ \end{matrix} \right] C(i,j)=k=0∑K−1A(i,k)⋅B(k,j)————————————————[1324]∗[5768]=[19432950]
-
-
等价于 “矩阵乘法+嵌套 for 循环” 代码:
import torch # 定义输入张量 N, L, H, D = 2, 3, 4, 5 Q = torch.randn(N, L, H, D) K = torch.randn(N, L, H, D) # —————————使用 einsum 计算———————————————————————————————————— energy_einsum = torch.einsum("nlhd,nlhd->nhll", [Q, K]) # —————————等价矩阵乘法验证————————————————————————————————————— energy_manual = [] for n in range(N): for h in range(H): # 提取第n个样本、第h个头的 Q 和 K Q_head = Q[n, :, h, :] # 形状 (L, D) K_head = K[n, :, h, :] # 形状 (L, D) # 计算点积矩阵 energy_head = torch.mm(Q_head, K_head.T) # 形状 (L, L) energy_manual.append(energy_head) energy_manual = torch.stack(energy_manual).view(N, H, L, L) # ——————————————————————————————————————————————————————————— # 检查一致性 print(torch.allclose(energy_einsum, energy_manual, atol=1e-6)) # 应输出 True
-
②通道注意力机制(SEAttention)
该算法的作用是:获得输入特征层的每一个通道的权值,以此找到最需要关注的通道。
~~ 压缩成一个长条 ~~
结构图:
适用场景:
- 图像领域:图像的多维通道往往意味着颜色,想要提取出最显著的颜色特征,就用此算法。
- 序列领域:可将样本制作成图像一样的立体块…
做法:
- 先在输入特征层的高和宽上做平均池化,将单个输入数据池化成
c×1×1
的特征长条; - 接着使用两次全连接层,第一次全连接层压缩通道数,第二次全连接层恢复通道数。
- 在完成两次全连接后,再取一次
Sigmoid
将值固定到0-1
之间,此时我们获得了输入特征层每一个通道的权值(0-1
之间), - 在获得这个权值后,我们将这个权值乘上原输入特征层即可。
代码:
class SEAttention(nn.Module):
def __init__(self, channel, reduction=16):
super(SEAttention, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Sequential(
nn.Linear(channel, channel // reduction),
nn.ReLU(inplace=True),
nn.Linear(channel // reduction, channel),
nn.Sigmoid()
)
def forward(self, x):
b, c, _, _ = x.size() # x shape: (b, c, h, w)
y = self.avg_pool(x).view(b, c) # 全局空间压缩, b,c,h,w -> b,c,1,1 -> b,c
y = self.fc(y).view(b, c, 1, 1) # 通道重校准, b,c -> b,c//reduction -> b,c -> b,c,1,1
return x * y.expand_as(x) # 特征加权
上述代码参数讲解:
channel
:输入数据的通道数;reduction
:该参数控制了SEAttention
模块中通道压缩的程度,决定了压缩后的通道数。- 它通过减少中间层的通道数来降低计算复杂度,同时通过通道压缩和恢复的过程增强特征提取能力。
- 默认值为
16
,是一个常用的折中选择。
其中个别函数方法讲解:
nn.AdaptiveAvgPool2d(output_size)
:二维自适应平均池化层。- 官网链接:AdaptiveAvgPool2d — PyTorch 2.6 documentation。
- 参数:
output_size
可为元组(h_out,w_out)
,也可为标量h_and_w
。 - 会根据输入特征图的大小,自动计算池化窗口的大小和步幅,使得输出特征图符合指定尺寸。当输入特征图有多个通道时,每个通道会单独进行池化操作(计算该通道的全局平均值),不会混合不同通道的信息。
y.expand_as(x)
:张量形状扩展方法。- 使用此方法可以确保
y
被扩展到与x
完全相同的形状,这样就能避免错误广播带来的问题。
- 使用此方法可以确保
示例:
import torch
from torch import nn
class SEAttention(nn.Module):
def __init__(self, channel, reduction=16):
super(SEAttention, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Sequential(
nn.Linear(channel, channel // reduction),
nn.ReLU(inplace=True),
nn.Linear(channel // reduction, channel),
nn.Sigmoid()
)
def forward(self, x):
b, c, _, _ = x.size() # x shape: (b, c, h, w)
y = self.avg_pool(x).view(b, c) # 全局空间压缩, b,c,h,w -> b,c,1,1 -> b,c
y = self.fc(y).view(b, c, 1, 1) # 通道重校准, b,c -> b,c//reduction -> b,c -> b,c,1,1
return x * y.expand_as(x) # 特征加权
input1 = torch.randn([1, 4, 2, 2])
model = SEAttention(input1.shape[1], 2) # 获取输入特征层的第二维数, 即通道数
print(model)
output1 = model(input1)
print(output1)
#--------------------------------------------------------------------------------------------------------------------------
# 输出结果为:
SEAttention(
(avg_pool): AdaptiveAvgPool2d(output_size=1)
(fc): Sequential(
(0): Linear(in_features=4, out_features=2, bias=True)
(1): ReLU(inplace=True)
(2): Linear(in_features=2, out_features=4, bias=True)
(3): Sigmoid()
)
)
tensor([[[[-0.4923, -0.3609],
[ 0.2345, -0.4860]],
[[-0.1664, -0.5443],
[-0.2697, 0.2866]],
[[-0.0565, 0.0348],
[ 0.0265, -0.1163]],
[[ 0.1896, -0.0806],
[-0.1932, -0.5070]]]], grad_fn=<MulBackward0>)
③空间注意力机制(SPAttention)
该算法的作用是:获得输入特征层的每一个像素点的权值,以此找到最需要关注的像素点(或区域)。
~~ 压缩成一张饼
结构图:
适用场景:
- 图像领域:想要提取出图像的空间维(
h×w
维)重要信息,就用此算法(就是常说的一张图片里哪些信息更重要,比如鸟、花)。 - 序列领域:可将样本制作成图像一样的立体块…
做法:
- 对输入特征层进行通道维取平均,即计算每个像素点在所有通道的平均值(形成一个通道平均层);
- 对输入特征层进行通道维取最大,即计算每个像素点在所有通道的最大值(形成一个通道最大层);
- 将上述两结果进行通道维拼接;
- 对拼接结果进行无尺寸损失的卷积,输入
2
通道,输出1
通道, - 将上述结果通过
sigmoid
函数形成0~1
的权重层; - 将原来的输入特征层与权重层相乘,进行空间维加权。
代码:
class SpatialAttention(nn.Module):
def __init__(self, kernel_size=7):
super().__init__()
assert kernel_size in (3,7), "kernel size must be 3 or 7"
padding = 3 if kernel_size == 7 else 1 # 7//2=3, 3//2=1
self.conv = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
# x shape: (b, c, h, w)
avg_out = torch.mean(x, dim=1, keepdim=True) # 通道维, 取平均
max_out, _ = torch.max(x, dim=1, keepdim=True) # 通道维, 取最大
combined = torch.cat([avg_out, max_out], dim=1) # 通道维, 拼接
attention = self.conv(combined) # 空间卷积
attention = self.sigmoid(attention) # 形成权重层
return x * attention # 空间重校准
上述代码参数详解:
kernel_size
:卷积层的卷积核尺寸,强制使用3×3
或7×7
卷积核;3×3
擅长捕获局部特征,适合在深层网络中使用。7×7
卷积核擅长捕获全局特征,适合在网络的早期层中使用。- 其他尺寸的卷积核也行,不过经验告诉我们,大部分情况下这两种效果较好。
padding
:池化层的池化核尺寸,当kernel_size=7
时padding=3
;当kernel_size=3
时padding=1
。dim=1
:指向第二维,在源代码中第二维指的是通道维。keepdim
:是否保持输出的维度与输入一致,True
则保持一致,False
则去掉被缩减的维度。- 最后返回值中的
*
是逐元素相乘(python
会自动进行广播,使得x
和attention
的形状相同)
示例:
import torch
from torch import nn
class SpatialAttention(nn.Module):
def __init__(self, kernel_size=7):
super().__init__()
assert kernel_size in (3, 7), "kernel size must be 3 or 7"
padding = 3 if kernel_size == 7 else 1
self.conv = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
# x shape: (b, c, h, w)
avg_out = torch.mean(x, dim=1, keepdim=True) # 通道维, 取平均
max_out, _ = torch.max(x, dim=1, keepdim=True) # 通道维, 取最大
combined = torch.cat([avg_out, max_out], dim=1) # 通道维, 拼接
attention = self.conv(combined) # 空间卷积
attention = self.sigmoid(attention) # 形成权重层
return x * attention # 空间重校准
model = SpatialAttention()
print(model)
input1 = torch.randn([1, 2, 32, 32])
output1 = model(input1)
print(output1, output1.shape)
#--------------------------------------------------------------------------------------------------------------------------
# 输出结果为:
SpatialAttention(
(conv): Conv2d(2, 1, kernel_size=(7, 7), stride=(1, 1), padding=(3, 3), bias=False)
(sigmoid): Sigmoid()
)
tensor([[[[ 0.3043, -0.1115, 0.8072, ..., -0.3164, -0.9592, 0.1138],
[ 0.2794, 0.5417, -0.4712, ..., -0.0697, 0.1992, -0.2221],
[ 0.7240, 0.7404, -0.4684, ..., -0.7113, -0.3191, -0.2365],
...,
[ 0.1644, -0.0619, -0.2743, ..., 0.2799, -0.6323, -0.2177],
[-0.6202, -0.4627, 0.0088, ..., 0.5412, 0.4545, 0.4991],
[ 0.1950, -0.6817, -0.1495, ..., 0.2904, 0.4762, 0.1911]],
[[ 0.7864, -1.0805, 1.0262, ..., 0.0502, 0.3625, 0.1452],
[ 0.4181, -0.8384, 0.3122, ..., -0.9477, -0.6478, -0.7619],
[ 0.0280, 0.1082, 0.4468, ..., -0.5216, 0.4628, 0.3429],
...,
[ 1.0672, 0.2410, 0.7409, ..., -0.3077, -0.6315, -0.0043],
[ 0.1524, -0.5792, 1.3052, ..., -0.3562, 0.1129, -0.4460],
[ 0.0162, 0.1711, -0.3322, ..., 0.6097, -0.3655, 0.2307]]]],
grad_fn=<MulBackward0>) torch.Size([1, 2, 32, 32])
④ CBAM 模块
全称为:Convolutional Block Attention Module,联合通道和空间注意力
后续改进版通道注意力机制 ECA 也可以替代此处的原通道注意力机制。
结构图:
这里的结构图就只给出网上流传最广的,并非自己画的了:
做法:
该模块先使用通道注意力机制,后使用空间注意力机制。
不过其中的通道注意力机制里的池化层也变成了两个,分别是全局自适应平均池化层、全局自适应最大池化层,并且他们共享同一组全连接层,最后会将两者的结果相加,通过 sigmoid
函数生成权重层。
其他的和 SEAttention
、SPAttention
没有区别。
代码:
import torch
import torch.nn as nn
class ChannelAttention(nn.Module):
"""通道注意力模块"""
def __init__(self, in_channels, reduction=16):
super().__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1) # 全局平均池化
self.max_pool = nn.AdaptiveMaxPool2d(1) # 全局最大池化
# 共享的全连接层
self.fc = nn.Sequential(
nn.Linear(channel, channel //reduction, False)
nn.ReLU(),
nn.Linear(channel //reduction, channel, False)
)
self.sigmoid = nn.sigmoid()
def forward(self, x):
avg_out = self.avg_pool(x).view([b, c]) # 平均池化
max_out = self.max_pool(x).view([b, c]) # 最大池化
avg_fc_out = self.fc(avg_out) # 两次全连接
max_fc_out = self.fc(max_out) # 两次全连接
channel_weights = self.sigmoid(avg_fc_out + max_fc_out).view([b, c, 1, 1]) # 融合双路径
return x * channel_weights # 通道加权
class SpatialAttention(nn.Module):
"""空间注意力模块"""
def __init__(self, kernel_size=7):
super(SpatialAttention, self).__init__()
assert kernel_size in (3, 7), "kernel_size应为3或7"
padding = 3 if kernel_size == 7 else 1 # 保持空间维度不变
self.conv = 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) # 通道最大
combined = torch.cat([avg_out, max_out], dim=1) # 通道拼接
spatial_weights = self.sigmoid(self.conv(combined)) # 生成空间权重
return x * spatial_weights # 空间加权
class CBAM(nn.Module):
"""CBAM模块(通道+空间注意力级联)"""
def __init__(self, in_channels, reduction=16, kernel_size=7):
super().__init__()
self.channel_att = ChannelAttention(in_channels, reduction)
self.spatial_att = SpatialAttention(kernel_size)
def forward(self, x):
x = self.channel_att(x) # 先应用通道注意力
x = self.spatial_att(x) # 再应用空间注意力
return x
示例:
import torch
import torch.nn as nn
class ChannelAttention(nn.Module):
"""通道注意力模块"""
def __init__(self, channel, reduction=16):
super().__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1) # 全局平均池化
self.max_pool = nn.AdaptiveMaxPool2d(1) # 全局最大池化
# 共享的全连接层
self.fc = nn.Sequential(
nn.Linear(channel, channel // reduction, False),
nn.ReLU(),
nn.Linear(channel // reduction, channel, False)
)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
b, c, _, _ = x.size() # x shape: (b, c, h, w)
avg_out = self.avg_pool(x).view([b, c]) # 平均池化路径
max_out = self.max_pool(x).view([b, c]) # 最大池化路径
avg_fc_out = self.fc(avg_out) # 平均池化路径
max_fc_out = self.fc(max_out) # 最大池化路径
channel_weights = self.sigmoid(avg_fc_out + max_fc_out).view([b, c, 1, 1]) # 融合双路径
return x * channel_weights # 通道加权
class SpatialAttention(nn.Module):
"""空间注意力模块"""
def __init__(self, kernel_size=7):
super(SpatialAttention, self).__init__()
assert kernel_size in (3, 7), "kernel_size应为3或7"
padding = 3 if kernel_size == 7 else 1 # 保持空间维度不变
self.conv = 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) # 通道最大
combined = torch.cat([avg_out, max_out], dim=1) # 拼接特征图
spatial_weights = self.sigmoid(self.conv(combined)) # 生成空间权重
return x * spatial_weights # 空间加权
class CBAM(nn.Module):
"""CBAM模块(通道+空间注意力级联)"""
def __init__(self, in_channels, reduction=16, kernel_size=7):
super().__init__()
self.channel_att = ChannelAttention(in_channels, reduction)
self.spatial_att = SpatialAttention(kernel_size)
def forward(self, x):
x = self.channel_att(x) # 先应用通道注意力
x = self.spatial_att(x) # 再应用空间注意力
return x
if __name__ == '__main__':
input1 = torch.randn([1, 4, 32, 32])
model = CBAM(input1.shape[1], 2)
print(model)
output1 = model(input1)
print(output1, output1.shape)
#--------------------------------------------------------------------------------------------------------------------------
# 输出结果为:
CBAM(
(channel_att): ChannelAttention(
(avg_pool): AdaptiveAvgPool2d(output_size=1)
(max_pool): AdaptiveMaxPool2d(output_size=1)
(fc): Sequential(
(0): Linear(in_features=4, out_features=2, bias=False)
(1): ReLU()
(2): Linear(in_features=2, out_features=4, bias=False)
)
(sigmoid): Sigmoid()
)
(spatial_att): SpatialAttention(
(conv): Conv2d(2, 1, kernel_size=(7, 7), stride=(1, 1), padding=(3, 3), bias=False)
(sigmoid): Sigmoid()
)
)
......
⑤ ECA 模块
全称:Efficient Channel Attention(高效通道注意力机制)
原通道注意力机制(SEAttention)的改良版
将原来的两个全连接层换成了 1D
无损失卷积,其余的大差不差。
换成一维卷积的原因是:卷积能更好的捕获通道之间的局部相关性,而全连接层其实破坏了这种相关性的获取。并且全连接层的参数比卷积层大得多。
结构图:
做法:
- 先在输入特征层的高和宽上做平均池化,将单个输入数据池化成
1×c
的特征长条序列; - 接着使用一维无损失卷积,将特征长条序列进行特征提取,得到新的长条序列。
- 之后再取一次
Sigmoid
将值固定到0-1
之间,此时我们获得了输入特征层每一个通道的权值(0-1
之间), - 在获得这个权值后,我们将这个权值乘上原输入特征层即可。
其中卷积层的卷积核是 “基于通道数的自适应卷积核” ,其核大小会随通道数动态调整。
原论文通过实验发现计算公式为:
k
≈
l
o
g
2
C
+
b
γ
k≈\frac{log_2C+b}{γ}
k≈γlog2C+b
- C C C:通道数。
- γ γ γ(默认2):缩放因子,控制核大小的增长速度。 b b b(默认1):偏移项,防止通道数过小时核大小降为0。
- 在某些数据集中,当 γ = 2 , b = 1 γ=2,b=1 γ=2,b=1 时,模型在分类、检测等任务上达到最优精度。
通道数越大,卷积核越大;通道数越小,卷积核越小。
代码:
class ECAttention(nn.Module):
def __init__(self, channel, gamma=2, b=1):
super(ECAttention, self).__init__()
kernel_size = int(abs((math.log(channel, 2) + b) / gamma))
kernel_size = kernel_size if kernel_size % 2 else kernel_size + 1
padding = kernel_size // 2
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.conv = nn.Conv1d(1, 1, kernel_size, padding=padding, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
b, c, h, w = x.size()
avg = self.avg_pool(x).view([b, 1, c])
out = self.conv(avg)
out = self.sigmoid(out).view([b, c, 1, 1])
return out * x
其中个别函数方法讲解:
nn.Conv1d
:一维卷积,其输入输出相当于序列。- 输入尺寸: ( N , C i n , L i n ) (N,C_{in},L_{in}) (N,Cin,Lin) , C i n C_{in} Cin 表示序列的通道数,可以理解为条数; L i n L_{in} Lin 表示序列长度。
- 输出尺寸: ( N , C o u t , L o u t ) (N,C_{out},L_{out}) (N,Cout,Lout) ,其中 L o u t L_{out} Lout 是要靠卷积核尺寸、池化核尺寸等参数计算出来的(也可尤其反求池化核尺寸等参数,就看谁先给出了)。
- 详情请参考官网链接:Conv1d — PyTorch 2.6 documentation。
示例:
import torch
from torch import nn
import math
class ECAttention(nn.Module):
def __init__(self, channel, gamma=2, b=1):
super(ECAttention, self).__init__()
kernel_size = int(abs((math.log(channel, 2) + b) / gamma))
kernel_size = kernel_size if kernel_size % 2 else kernel_size + 1
padding = kernel_size // 2
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.conv = nn.Conv1d(1, 1, kernel_size, padding=padding, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
b, c, h, w = x.size()
avg = self.avg_pool(x).view([b, 1, c])
out = self.conv(avg)
out = self.sigmoid(out).view([b, c, 1, 1])
return out * x
if __name__ == '__main__':
input1 = torch.randn([1, 4, 32, 32])
model = ECAttention(input1.shape[1], 2)
print(model)
output1 = model(input1)
print(output1, output1.shape)
#--------------------------------------------------------------------------------------------------------------------------
# 输出结果为:
ECAttention(
(avg_pool): AdaptiveAvgPool2d(output_size=1)
(conv): Conv1d(1, 1, kernel_size=(1,), stride=(1,), bias=False)
(sigmoid): Sigmoid()
)
......
二、相关问题:
待更新…
-
和 L S T M LSTM LSTM 的信息筛选有什么区别?
L S T M LSTM LSTM 的门控机制是:时序信息的局部过滤器(此时刻对上一时刻信息的筛选能力,距离远的就不太能保留下来),而注意力机制是:全局信息的动态选择器(和距离没多大关系)。 L S T M LSTM LSTM 的目标是解决 R N N RNN RNN 的梯度消失问题,在时间维度上捕捉长期依赖关系,但本质上仍是对序列的局部处理。而注意力机制不受时间顺序限制,能够跨步长、跨模态捕捉全局依赖。
另外在 N L P NLP NLP 中,注意力机制得到的新的词向量具有句法特征和语义特征(表征更完善,不仅具有上下文关联信息,而且句子更符合语法)。
(另一个区别是: L S T M LSTM LSTM 是串行结构,而 A t t e n t i o n Attention Attention 是并行结构)
维度 LSTM 注意力机制 关注范围 时间序列的局部依赖(相邻时间步) 全局或跨步长的关联(如长距离依赖) 计算方式 基于门控的线性筛选 基于相似度计算的动态权重分配 可解释性 门控权重反映时序过滤逻辑 注意力权重直接显示关键信息位置 参数化程度 固定门控结构(参数与时间步绑定) 完全参数化(可学习Query/Key/Value) -
A t t e n t i o n Attention Attention 的缺点是什么?
计算量非常大。