原理
自注意力(Self-Attention)是一种强大的机制,广泛应用于自然语言处理、计算机视觉等领域,尤其是在Transformer架构中发挥了关键作用。它的核心思想是让模型能够动态地关注输入序列中不同位置之间的关系,从而更好地捕捉全局信息和长距离依赖。
在传统的序列处理模型如循环神经网络(RNN)中,信息是按时间步逐个传递的,模型在处理当前时刻的信息时,只能依赖于之前时刻的信息,这使得它在处理长序列时容易出现梯度消失或梯度爆炸的问题,难以捕捉长距离依赖关系。而自注意力机制通过计算序列中每个位置与其他所有位置的关联程度,直接在全局范围内进行信息交互,解决了这一问题。
具体来说,自注意力机制的实现过程可以分为以下几个步骤:
- 首先,输入序列会被转换为三个矩阵,分别是查询(Query)、键(Key)和值(Value)。这三个矩阵是通过输入序列与三个不同的可学习权重矩阵
相乘得到的。
- 具体公式:
- 查询矩阵用于表示当前需要关注的内容,键矩阵用于表示序列中各个位置的特征,值矩阵则包含了序列中各个位置的实际信息。这三个矩阵的维度通常是相同的,且可以根据具体任务进行调整。
- 具体公式:
- 接下来,计算注意力分数。公式为
。对于输入序列中的每个位置,都会将其查询向量与序列中所有位置的键向量进行点积运算,得到一个分数矩阵。
- 为了使这些分数具有可比性,通常会对它们进行缩放,即将每个分数除以键向量维度的平方根。这样做的目的是防止点积结果过大而导致梯度消失或梯度爆炸。
- 然后,将分数矩阵通过softmax函数进行归一化,公式为
,得到注意力权重矩阵。softmax函数的作用是将分数矩阵中的每个元素转换为一个概率值,使得所有权重的和为1。这样,每个位置的权重就表示了它在全局范围内的重要性,权重越大,说明该位置与其他位置的关联越强。
- 最后,将注意力权重矩阵与值矩阵相乘,公式为
,得到加权的值矩阵。这个加权的值矩阵就是自注意力机制的输出,它包含了输入序列中各个位置经过加权后的信息。通过这种方式,模型能够根据注意力权重动态地组合序列中的信息,从而更好地捕捉序列中的全局依赖关系。
- 总公式为:
自注意力机制的一个重要特性是并行化计算。由于它不需要像RNN那样按顺序逐个处理序列中的元素,因此可以同时计算所有位置之间的注意力关系,大大提高了计算效率。这使得自注意力机制在处理大规模数据时具有显著的优势。
此外,自注意力机制还可以通过多头注意力(Multi-Head Attention)的方式进一步增强模型的表现能力。多头注意力的核心思想是将输入序列分成多个不同的“头”,每个头都独立地进行自注意力计算,然后将所有头的输出拼接在一起,再通过一个线性变换进行整合。这样做的好处是能够让模型从不同的角度捕捉序列中的信息,从而更好地理解序列的结构和语义。
-
-
单头自注意力
实现方法1
这段代码实现了一个简化版本的单头自注意力机制(Self-Attention),并展示了如何使用它处理输入数据。
在forward方法中
- 首先分别对输入数据
x
应用三个线性变换,得到查询(query
)、键(key
)和值(value
)。它们的形状仍然是[batch_size, seq_len, hidden_dim]
。 - 计算query和key的点积
-
key.transpose(1, 2)
将key
的形状从[batch_size, seq_len, hidden_dim]
转置为[batch_size, hidden_dim, seq_len]
。 -
torch.matmul(query, key.transpose(1, 2))
计算query
和key
的点积,得到形状为[batch_size, seq_len, seq_len]
的注意力分数矩阵。 -
/ math.sqrt(self.hidden_dim)
是一个缩放操作,用于防止点积结果过大导致的梯度消失或梯度爆炸问题。缩放因子是hidden_dim
的平方根。
-
- 对注意力分数矩阵应用 Softmax 函数,将每个位置的分数转换为概率值,使得每一行的和为 1。这一步确保了注意力权重的合理性。
- 使用注意力权重矩阵
attn_weights
对值矩阵value
进行加权求和,得到最终的自注意力输出。输出的形状为[batch_size, seq_len, hidden_dim]
。
import math
import torch
import torch.nn as nn
class SelfAttn1(nn.Module): # 简化版本,单头
def __init__(self, hidden_dim=728):
super().__init__()
self.hidden_dim = hidden_dim # 隐藏层维度
self.query_proj = nn.Linear(hidden_dim, hidden_dim) # q的线性映射,映射到hidden_dim维
self.key_proj = nn.Linear(hidden_dim, hidden_dim) # k的线性映射,映射到hidden_dim维
self.value_proj = nn.Linear(hidden_dim, hidden_dim) # v的线性映射,映射到hidden_dim维
def forward(self, x):
# x: [batch_size, seq_len, hidden_dim]
query = self.query_proj(x) # 分别对输入数据 x 应用三个线性变换
key = self.key_proj(x)
value = self.value_proj(x)
attn_weights = torch.matmul(query, key.transpose(1, 2)) / math.sqrt(self.hidden_dim) # 计算注意力权重 [batch_size, seq_len, seq_len]
attn_weights = torch.softmax(attn_weights, dim=-1) # 对注意力分数矩阵应用 Softmax 函数,将每个位置的分数转换为概率值,使得每一行的和为 1。 [batch_size, seq_len, seq_len]
attn_output = torch.matmul(attn_weights, value) # 使用注意力权重矩阵 attn_weights 对值矩阵 value 进行加权求和,得到最终的自注意力输出。 [batch_size,