注意力机制:让AI拥有"黄金七秒记忆"的魔法–(点积注意力)
注意⼒机制对于初学者来说有点难理解,我们⼀点⼀点地讲。现在先暂时忘记编码器、解码器、隐藏层和序列到序列这些概念。想象我们有两个张量x1和x2,我们希望⽤注意⼒机制把它俩给衔接起来,让x1看⼀看,x2有哪些特别值得关注的地⽅。
具体来说,要得到x1对x2的点积注意⼒,我们可以按照以下步骤进⾏操作。
(1)创建两个形状分别为(batch_size
, seq_len1
, feature_dim
)和(batch_size
, seq_len2
, feature_dim
)的张量x1和x2。
(2)将x1中的每个元素和x2中的每个元素进⾏点积,得到形状为 (batch_size
, seq_len1
, seq_len2
)的原始权重raw_weights
。
(3)⽤softmax
函数对原始权重进⾏归⼀化,得到归⼀化后的注意⼒权重attn_weights
(注意⼒权重的值在0和1之间,且每⼀⾏的和为1),形状仍为 (batch_size
, seq_len1
, seq_len2
)。
(4)⽤注意⼒权重attn_weights
对x2中的元素进⾏加权求和(与x2相乘),得到输出张量y,形状为 (batch_size
, seq_len1
, feature_dim
)。这就是x1对x2的点积注意⼒。
程序结构如下:
一、点积注意机制
import torch # 导入 torch
import torch.nn.functional as F # 导入 nn.functional
# 1. 创建两个张量 x1 和 x2
x1 = torch.randn(2, 3, 4) # 形状 (batch_size, seq_len1, feature_dim)
x2 = torch.randn(2, 5, 4) # 形状 (batch_size, seq_len2, feature_dim)
# 2. 计算原始权重
raw_weights = torch.bmm(x1, x2.transpose(1, 2)) # 形状 (batch_size, seq_len1, seq_len2)
# 3. 用 softmax 函数对原始权重进行归一化
attn_weights = F.softmax(raw_weights, dim=2) # 形状 (batch_size, seq_len1, seq_len2)
# 4. 将注意力权重与 x2 相乘,计算加权和
attn_output = torch.bmm(attn_weights, x2) # 形状 (batch_size, seq_len1, feature_dim)
1.1 创建两个张量x1和x2
# 创建两个张量 x1 和 x2
x1 = torch.randn(2, 3, 4) # 形状 (batch_size, seq_len1, feature_dim)
x2 = torch.randn(2, 5, 4) # 形状 (batch_size, seq_len2, feature_dim)
print("x1:", x1)
print("x2:", x2)
1.2 计算张量点积,得到原始权重
# 计算点积,得到原始权重,形状为 (batch_size, seq_len1, seq_len2)
raw_weights = torch.bmm(x1, x2.transpose(1, 2))
print(" 原始权重:", raw_weights)
因为是对x1和x2的两个特征维度进行点积后归一化,所以要对x2数组进行转置。
⽐如,输出结果的第⼀⾏[ 1.2474, -0.6254, 1.4849, 2.9333, -0.1787]就代表着本批次第⼀个x1序列中第⼀个元素(每个x1序列有3个元素,所以第⼀批次共3⾏)与x2中第⼀批次5个元素的每⼀个元素的相似度得分(不难看出,x1中第⼀个元素与x2中第4个元素最相似,原始注意⼒分值为2.9333)。
相似度的计算是注意⼒机制最核⼼的思想。
因为点积其实可以一定程度上反应向量方向的相关性,所以通过将x1的元素与x2的各个元素进行点积就可以求出权重(原始得分)其中在x2中权重较大的(得分高的)对应的词即为与x1中相关度最高的,故x2可以根据这个原始的分来判断应该输出那些对应x1相关性最高的内容。
x1起到编译器,x2起到译码器的作用
在某些⽂献或代码中,有时会将相似度得分称为原始权重。这是因为它们实际上是在计算注意⼒权重之前的中间结果。严格来说,相似度得分表示输⼊序列中不同元素之间的关联性或相似度,⽽权重则是在应⽤某些操作(如缩放、掩码和归⼀化)后得到的归⼀化值。为了避免混淆,可以将这两个术语彻底区分开。
通常,将未处理的值称为得分,并在经过处理后将它们称为权重。这有助于更清晰地理解注意⼒机制的⼯作原理及其不同组件。
举一个栗子:
让我们⽤下⾯的图示来对向量点积和相似度得分进⾏相对直观的理解。在下图的例⼦中,有两个向量——电影的特征(M)和⽤户的兴趣(U)
向量U中可能蕴含⽤户是否喜欢爱情⽚、喜欢动作⽚等信息;⽽向量M中则包含电影含有动作、浪漫等特征的程度。
通过计算U和M的点积或相似度得分,我们可以得到⼀个衡量U对M兴趣程度的分数。例如,如果向量U中喜欢爱情⽚、喜欢动作⽚的权重较⾼,⽽向量M中的动作和浪漫特征的权重也较⾼,那么计算得到的点积或相似度得分就会⽐较⾼,表示U对M的兴趣较⼤,系统有可能推荐这部电影给⽤户。
1.3 对原始权重进行归一化
import torch.nn.functional as F # 导入 torch.nn.functional
# 应用 softmax 函数,使权重的值在 0 和 1 之间,且每一行的和为 1
attn_weights = F.softmax(raw_weights, dim=-1) # 归一化
print(" 归一化后的注意力权重:", attn_weights)
所谓的归⼀化,其实理解起来很简单。得到每⼀个x1序列中的元素与其所对应的5个x2序列元素的相似度得分后,使⽤softmax函数进⾏缩放,让这5个数加起来等于1。
归一化后的注意力权重: tensor([[[0.3154, 0.2383, 0.2145, 0.1589, 0.0729],[0.0015, 0.9234, 0.0090, 0.0015, 0.0645],[0.0533, 0.0576, 0.5788, 0.0858, 0.2245]],[[0.4959, 0.0374, 0.1558, 0.0349, 0.2760],[0.0034, 0.0470, 0.0424, 0.8826, 0.0246],[0.2597, 0.0678, 0.0840, 0.1356, 0.4530]]])
归⼀化后,attn_weights
(权重)和raw_weights
(得分)形状相同,但是值变了,第⼀⾏的5个数字加起来刚好是1。第4个数字是0.6697,这就表明:在本批次的第⼀⾏数据中,x2序列中的第4个元素和x1序列的第1个元素特别相关,应该加以注意。
1.4 求出注意力机制的加权和
注意⼒权重与x2相乘,就得到注意⼒分布的加权和。
换句话说,我们将x2中的每个位置向量乘以它们在x1中对应位置的注意⼒权重,然后将这些加权向量求和——这是点积注意⼒计算的最后⼀个环节。这⼀步的⽬的是根据注意⼒权重计算x2的加权和。这个加权和才是x1对x2的注意⼒输出。
加权只是对应着一个关系表,并不代表输出。
相当于在一个函数中,已经求得了对应关系,现在需要给一个输入,才能得出一个输出值。
# 与 x2 相乘,得到注意力分布的加权和,形状为 (batch_size, seq_len1, feature_dim)
attn_output = torch.bmm(attn_weights, x2)
print(" 注意力输出 :", attn_output)
en1, feature_dim)
attn_output = torch.bmm(attn_weights, x2)
print(" 注意力输出 :", attn_output)
[外链图片转存中...(img-ujffJwJa-1742036282108)]