目录

注意力机制让AI拥有黄金七秒记忆的魔法-自注意力

注意力机制:让AI拥有黄金七秒记忆的魔法–(自注意力)

注意力机制:让AI拥有"黄金七秒记忆"的魔法–(自注意力)

⾃注意⼒就是⾃⼰对⾃⼰的注意,它允许模型在同⼀序列中的不同位置之间建⽴依赖关系。⽤我们刚才讲过的最简单的注意⼒来理解,如果我们把x2替换为x1⾃身,那么我们其实就实现了x1每⼀个位置对⾃身其他序列的所有位置的加权和。

如下:

import torch
import torch.nn.functional as F
# 一个形状为 (batch_size, seq_len, feature_dim) 的张量 x
x = torch.randn(2, 3, 4)
# 计算原始权重,形状为 (batch_size, seq_len, seq_len)
raw_weights = torch.bmm(x, x.transpose(1, 2))
# 对原始权重进行 softmax 归一化,形状为 (batch_size, seq_len, seq_len)
attn_weights = F.softmax(raw_weights, dim=2)
# 计算加权和,形状为 (batch_size, seq_len, feature_dim) 
attn_outputs = torch.bmm(attn_weights, x)

知道了这个,那么下面继续展示一下如何对输入序列进行不同的线性变换,得到Q,K和V向量,然后应用放缩点积注意力即可:

# 一个形状为 (batch_size, seq_len, feature_dim) 的张量 x
x = torch.randn(2, 3, 4) # 形状 (batch_size, seq_len, feature_dim)
# 定义线性层用于将 x 转换为 Q, K, V 向量
linear_q = torch.nn.Linear(4, 4)
linear_k = torch.nn.Linear(4, 4)
linear_v = torch.nn.Linear(4, 4)
# 通过线性层计算 Q, K, V
Q = linear_q(x) # 形状 (batch_size, seq_len, feature_dim)
K = linear_k(x) # 形状 (batch_size, seq_len, feature_dim)
V = linear_v(x) # 形状 (batch_size, seq_len, feature_dim)
# 计算 Q 和 K 的点积,作为相似度分数 , 也就是自注意力原始权重
raw_weights = torch.bmm(Q, K.transpose(1, 2)) # 形状 (batch_size, seq_len, seq_len)
# 将自注意力原始权重进行缩放
scale_factor = K.size(-1) ** 0.5  # 这里是 4 ** 0.5
scaled_weights = raw_weights / scale_factor # 形状 (batch_size, seq_len, seq_len)
# 对缩放后的权重进行 softmax 归一化,得到注意力权重
attn_weights = F.softmax(scaled_weights, dim=2) # 形状 (batch_size, seq_len, seq_len)
# 将注意力权重应用于 V 向量,计算加权和,得到加权信息
attn_outputs = torch.bmm(attn_weights, V) # 形状 (batch_size, seq_len, feature_dim)
print(" 加权信息 :", attn_outputs)
 加权信息 : tensor([[[ 0.5676, -0.0132, -0.8214, -0.0548],

         [ 0.5352, -0.1170, -0.5392, -0.0256],

         [ 0.6141, -0.1343, -0.5587, -0.0331]],



        [[ 0.5973, -0.2426, -0.3217, -0.0335],

         [ 0.5996, -0.1914, -0.2840,  0.0152],

         [ 0.6117, -0.2507, -0.3363, -0.0404]]], grad_fn=<BmmBackward0>)

1.多头自注意力

多头⾃注意⼒(Multi-head Attention)机制是注意⼒机制的⼀种扩展,它可以帮助模型从不同的表示⼦空间捕获输⼊数据的多种特征。具体⽽⾔,多头⾃注意⼒在计算注意⼒权重和输出时,会对 QKV 向量分别进⾏多次线性变换,从⽽获得不同的头(Head),并进⾏并⾏计算

如下图所示:

https://i-blog.csdnimg.cn/img_convert/12a929408de1da6244e43ce361e7cfd3.png

以下是多头⾃注意⼒的计算过程。

(1)初始化:设定多头⾃注意⼒的头数。每个头将处理输⼊数据的⼀个⼦空间。

(2)线性变换:对 QKV 向量进⾏数次线性变换,每次变换使⽤不同的权重矩阵。这样,我们可以获得多组不同的 QKV 向量。

(3)缩放点积注意⼒:将每组 QKV 向量输⼊缩放点积注意⼒中进⾏计算,每个头将⽣成⼀个加权输出。

(4)合并:将所有头的加权输出拼接起来,并进⾏⼀次线性变换,得到多头⾃注意⼒的最终输出。

  • 假设我们有一个输入序列的表示矩阵 X(例如编码器的输出或者词嵌入),

  • 我们通过三个不同的线性层(也就是不同的权重矩阵)分别计算 Query、Key 和 Value:

    • q

      X W q q=XW_q

      q

      =

      X

      W

      q

    • k

      X W k k=XW_k

      k

      =

      X

      W

      k

    • v

      X W v v= XW_v

      v

      =

      X

      W

      v

  • 这里,

    W q W_q

    W

    q

    W k W_k

    W

    k

    W v W_v

    W

    v

    是模型在训练过程中学习到的参数矩阵。

多头⾃注意⼒机制的优势在于, 通过同时学习多个⼦空间的特征 ,可以 提⾼模型捕捉⻓距离依赖和不同语义层次的能⼒ 。

如下列代码:

import torch
import torch.nn.functional as F
# 一个形状为 (batch_size, seq_len, feature_dim) 的张量 x
x = torch.randn(2, 3, 4)  # 形状 (batch_size, seq_len, feature_dim) 
# 定义头数和每个头的维度
num_heads = 2
head_dim = 2
# feature_dim 必须是 num_heads * head_dim 的整数倍
assert x.size(-1) == num_heads * head_dim
# 定义线性层用于将 x 转换为 Q, K, V 向量
linear_q = torch.nn.Linear(4, 4)
linear_k = torch.nn.Linear(4, 4)
linear_v = torch.nn.Linear(4, 4)
# 通过线性层计算 Q, K, V
Q = linear_q(x)  # 形状 (batch_size, seq_len, feature_dim) 
K = linear_k(x)  # 形状 (batch_size, seq_len, feature_dim) 
V = linear_v(x)  # 形状 (batch_size, seq_len, feature_dim) 
# 将 Q, K, V 分割成 num_heads 个头
def split_heads(tensor, num_heads):
    batch_size, seq_len, feature_dim = tensor.size()
    head_dim = feature_dim // num_heads
    output = tensor.view(batch_size, seq_len, num_heads, head_dim).transpose(1, 2)
    return  output # 形状 (batch_size, num_heads, seq_len, feature_dim)
Q = split_heads(Q, num_heads)  # 形状 (batch_size, num_heads, seq_len, head_dim)
K = split_heads(K, num_heads)  # 形状 (batch_size, num_heads, seq_len, head_dim)
V = split_heads(V, num_heads)  # 形状 (batch_size, num_heads, seq_len, head_dim)
# 计算 Q 和 K 的点积,作为相似度分数 , 也就是自注意力原始权重
raw_weights = torch.matmul(Q, K.transpose(-2, -1))  # 形状 (batch_size, num_heads, seq_len, seq_len)
# 对自注意力原始权重进行缩放
scale_factor = K.size(-1) ** 0.5
scaled_weights = raw_weights / scale_factor  # 形状 (batch_size, num_heads, seq_len, seq_len)
# 对缩放后的权重进行 softmax 归一化,得到注意力权重
attn_weights = F.softmax(scaled_weights, dim=-1)  # 形状 (batch_size, num_heads, seq_len, seq_len)
# 将注意力权重应用于 V 向量,计算加权和,得到加权信息
attn_outputs = torch.matmul(attn_weights, V)  # 形状 (batch_size, num_heads, seq_len, head_dim)
# 将所有头的结果拼接起来
def combine_heads(tensor, num_heads):
    batch_size, num_heads, seq_len, head_dim = tensor.size()
    feature_dim = num_heads * head_dim
    output = tensor.transpose(1, 2).contiguous().view(batch_size, seq_len, feature_dim)
    return output# 形状 : (batch_size, seq_len, feature_dim)
attn_outputs = combine_heads(attn_outputs, num_heads)  # 形状 (batch_size, seq_len, feature_dim) 
# 对拼接后的结果进行线性变换
linear_out = torch.nn.Linear(4, 4)
attn_outputs = linear_out(attn_outputs)  # 形状 (batch_size, seq_len, feature_dim) 
print(" 加权信息 :", attn_outputs)

多头自注意力机制就是将输⼊向量投影到多个向量空间,在每个向量空间中执⾏点积注意⼒计算,然后连接各头的结果。

在实际应⽤中,多头⾃注意⼒通常作为更复杂模型(如Transformer)的⼀个组成部分。这些复杂的模型通常包含其他组件,例如前馈神经⽹络(Feed-Forward Neural Network)和层归⼀化(Layer Normalization),以提⾼模型的表达能⼒和稳定性。

2.注意力掩码

注意⼒中的掩码机制,不同于BERT训练过程中的那种对训练⽂本的“掩码”。注意⼒掩码的作⽤是避免模型在计算注意⼒分数时,将不相关的单词考虑进来。掩码操作可以防⽌模型学习到不必要的信息。

要直观地解释掩码,我们先回忆⼀下填充(Padding)的概念。在NLP任务中,我们经常需要将不同⻓度的⽂本输⼊模型。为了能够批量处理这些⽂本,我们需要将它们填充⾄相同的⻓度。

以这段有关损失函数的代码为例。

criterion = nn.CrossEntropyLoss(ignore_index=word2idx_en['']) # 损失函数

这段代码中的ignore_index=word2idx_en[‘’],就是为了告诉模型,是附加的冗余信息,模型在反向传播更新参数的时候没有必要关注它,因此也没有什么单词会被翻译成。

填充掩码(Padding Mask)的作⽤和上⾯损失函数中的ignore_index参数有点类似,都是避免在计算注意⼒分数时,将填充位置的单词考虑进来(⻅右图)。因为填充位置的单词对于实际任务来说是⽆意义的,⽽且可能会引⼊噪声,影响模型的性能。

https://i-blog.csdnimg.cn/img_convert/5d87ec4b56cbb0b7dc365e538a2ea4a5.png

加⼊了掩码机制之后的注意⼒如下图所示,我们会把 将注意⼒权重矩阵与⼀个注意⼒掩码矩阵相加 ,使得不需要的信息所对应的权重变得⾮常⼩(接近负⽆穷)。然后,通过应⽤softmax函数,将不需要的信息对应的权重变得接近于0,从⽽实现忽略它们的⽬的。

https://i-blog.csdnimg.cn/img_convert/0d45f7e384c5c2be2239e883ac276288.png

在Transformer中,使⽤了⾃注意⼒机制、多头⾃注意⼒机制和掩码,不仅有前⾯介绍的填充掩码,还有⼀种解码器专⽤的 后续注意⼒掩码 (Subsequent Attention Mask),简称后续掩码,也叫前瞻掩码(Look-ahead Masking),这是为了在训练时为解码器遮蔽未来的信息。