本质思想就是【从大量信息中】【有选择的筛选出】【少量重要信息】并【聚焦到这些重要信息上】,【忽略/较少关注大多不重要的信息】。聚焦的过程体现在【权重系数】的计算上,权重越大越聚焦于其对应的value值上。即权重代表了信息的重要性,而value是其对应的信息。
引入Attention机制原因
类似RNN无法捕捉长序列的道理,没有引入Attention机制在输入句子较短时影响不大,但是如果输入句子比较长,此时所有语义通过一个中间语义向量表示,单词自身的信息避免不了会消失,也就是会丢失很多细节信息,这也是为何引入Attention机制的原因。
由固定的中间语义表示C换成了根据当前输出单词来调整成加入注意力模型的变化的Ci
Attention思想
一个典型的Attention思想包括三部分:Q-query 查询、K-key 索引、V-value 内容。
-
Q是query,是输入的信息;key和value成组出现,通常是原始文本等已有的信息;
-
通过计算Q与K之间的相关性a,得出不同的K对输出的重要程度;
-
再与对应的v进行相乘求和,就得到了Q的输出;
Self-Attention
只关注输入序列元素之间的关系,即每个输入元素都有它自己的Q、K、V,比如在一般任务的Encoder-Decoder框架中,输入Source和输出Target内容是不一样的,比如对于英-中机器翻译来说,Source是英文句子,Target是对应的翻译出的中文句子,Attention机制发生在Target的元素Query和Source中的所有元素之间。而Self Attention指的不是Target和Source之间的Attention机制,而是Source内部元素之间或者Target内部元素之间发生的Attention机制,也可以理解为Target=Source这种特殊情况下的注意力计算机制。】
公式
时间复杂度
self-attention注意力机制中关于时间复杂度的计算_自注意力机制的时间复杂度-CSDN博客
O(T^2d)
代码(无pose)
import torch
import torch.nn as nn
class SelfAttention(nn.Module):
def __init__(self, embed_dim):
super(SelfAttention, self).__init__()
self.embed_dim = embed_dim
# 线性层,用于计算query、key和value
self.query = nn.Linear(embed_dim, embed_dim)
self.key = nn.Linear(embed_dim, embed_dim)
self.value = nn.Linear(embed_dim, embed_dim)
# 用于计算attention的权重
self.attn = nn.Linear(embed_dim, 1)
def forward(self, x):
# 假设x的形状为[batch_size, seq_len, embed_dim]
# 计算query、key和value
query = self.query(x)
key = self.key(x)
value = self.value(x)
# 计算attention权重
score = torch.matmul(query, key.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.embed_dim, dtype=torch.float32))
attn = torch.softmax(score, dim=-1)
# 计算attended value
context = torch.matmul(attn, value)
return context
# 示例
batch_size = 10
seq_len = 5
embed_dim = 10
# 创建一个自注意力模块
attention = SelfAttention(embed_dim)
# 创建一个示例张量
x = torch.randn(batch_size, seq_len, embed_dim)
# 调用自注意力模块
output = attention(x)
print(output.shape) # 输出: torch.Size([10, 5, 10])
Multi-Head Attention
多种变换生成的Q、K、V进行计算,再将它们对相关性的结论综合起来,进一步增强自注意力的效果。
代码
import torch
import torch.nn as nn
# 位置编码
class PositionalEncoding(nn.Module):
def __init__(self, d_model, dropout=0.1, max_len=5000):
super(PositionalEncoding, self).__init__()
self.dropout = nn.Dropout(p=dropout)
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-torch.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0).transpose(0, 1)
self.register_buffer('pe', pe)
def forward(self, x):
x = x + self.pe[:x.size(0), :]
return self.dropout(x)
# 多头注意力
class MultiHeadAttention(nn.Module):
def __init__(self, embed_dim, num_heads, dropout=0.1):
super(MultiHeadAttention, self).__init__()
self.embed_dim = embed_dim
self.num_heads = num_heads
self.dropout = dropout
self.q_linear = nn.Linear(embed_dim, embed_dim)
self.k_linear = nn.Linear(embed_dim, embed_dim)
self.v_linear = nn.Linear(embed_dim, embed_dim)
self.out_linear = nn.Linear(embed_dim, embed_dim)
def forward(self, q, k, v, mask):
batch_size = q.size(0)
# 线性变换
q = self.q_linear(q)
k = self.k_linear(k)
v = self.v_linear(v)
# 分割多头
q = q.view(batch_size, -1, self.num_heads, self.embed_dim //self.num_heads).transpose(1, 2)
k = k.view(batch_size, -1, self.num_heads, self.embed_dim // self.num_heads).transpose(1, 2)
v = v.view(batch_size, -1, self.num_heads, self.embed_dim // self.num_heads).transpose(1, 2)
# 计算注意力
attn = torch.matmul(q, k.transpose(-2, -1)) / (self.embed_dim // self.num_heads)
if mask is not None:
attn = attn.masked_fill(mask == 0, -float('inf'))
attn = torch.softmax(attn, dim=-1)
attn = self.dropout(attn)
# 计算输出
out = torch.matmul(attn, v).transpose(1, 2).contiguous().view(batch_size, -1, self.embed_dim)
out = self.out_linear(out)
return out
# 示例
input_dim = 100
output_dim = 100
embed_dim = 512
num_heads = 8
dropout = 0.1
# 创建位置编码
positional_encoding = PositionalEncoding(embed_dim, dropout)
# 创建多头注意力模块
multi_head_attn = MultiHeadAttention(embed_dim, num_heads, dropout)
常见问题
-
缩放点积中为什么要除以根号dk
-
在两个向量维度非常大的时候,点乘结果的方差也会很大,即结果中的元素差距很大,在点乘的值非常大的时候,softmax的梯度会趋近于0,也就是梯度消失。在原文中有提到,假设q和k的元素是相互独立维度为dk的随机变量,它们的均值是0,方差为1,那么q和k的点乘的平均值为0,方差为dk,如果将点乘的结果进行缩放操作,也就是除以dk,就可以有效控制方差从dk回到1,也就是有效控制梯度消失问题。
-
-
为什么比较大的输入会使得softmax的梯度变得很小?
-
非常接近于one-hot,梯度消失为0
-
-
维度与点积大小的关系是怎么样的,为什么使用维度的根号来放缩?
-
DX=DY=1,EX=EY=0,E(XY)=0,D(XY)=DXDY-[EXEXY]^2=1,独立分量,求和变成0和dk了
-
方差大表示各个分量的差距较大
-