
我们先从理论角度来解释QKV机制,QKV机制是注意力机制的核心,尤其在Transformer模型中,注意力机制源于人类感知世界的方式:在处理信息时,我们会选择性地关注一部分信息,而忽略其他信息。在机器学习中,注意力机制允许模型在处理序列数据时,对不同的部分赋予不同的权重,从而更有效地利用信息。

在注意力机制中,每个输入元素会生成三个向量:Query(查询)、Key(键)和Value(值)。这些向量是通过对输入进行线性变换得到的。
结合图书馆来理解,好比我们去图示馆借阅书籍:
借书过程就是QKV机制:
给定输入序列X ∈ R^(n×d) ,其中n是序列长度,d是特征维度。我们通过线性变换得到Q、K、V,然后计算注意力权重,公式偏数学理论,先了解大概,记住公式内容,在示例中对比逐步领悟。
X = [[x₁₁, x₁₂, ..., x₁d], # 第1个token的d维向量 [x₂₁, x₂₂, ..., x₂d], # 第2个token的d维向量 ... [xₙ₁, xₙ₂, ..., xₙd]] # 第n个token的d维向量 # 假设句子:"I love AI" 有3个token,每个token用4维向量表示 X = [[0.1, 0.2, 0.3, 0.4], # "I"的向量表示 [0.5, 0.6, 0.7, 0.8], # "love"的向量表示 [0.9, 0.1, 0.2, 0.3]] # "AI"的向量表示 # 这里 n=3, d=4
每个权重矩阵的作用:
实际计算示例:
# 假设 d=4, d_k=3, d_v=3 W_Q = [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.7, 0.8, 0.9], [0.2, 0.3, 0.4]] # 4×3 # 计算第一个token的Query向量 token1 = [0.1, 0.2, 0.3, 0.4] Q1 = [0.1×0.1 + 0.2×0.4 + 0.3×0.7 + 0.4×0.2, 0.1×0.2 + 0.2×0.5 + 0.3×0.8 + 0.4×0.3, 0.1×0.3 + 0.2×0.6 + 0.3×0.9 + 0.4×0.4] = [0.01 + 0.08 + 0.21 + 0.08, 0.02 + 0.10 + 0.24 + 0.12, 0.03 + 0.12 + 0.27 + 0.16] = [0.38, 0.48, 0.58]
数学含义:
点积相似度:
缩放因子 1/√d_k:
实际意义:
# 示例:3个token的关联矩阵 S = [[0.9, 0.3, 0.1], # token1: 很关注自己,不太关注其他 [0.2, 0.8, 0.6], # token2: 关注自己和token3 [0.1, 0.5, 0.7]] # token3: 比较关注token2
数学含义:
Softmax操作:
概率解释:
实际效果:
# Softmax前 S = [[1.0, 0.5, 0.1], [0.2, 2.0, 0.3], [0.1, 0.4, 0.8]] # Softmax后(每行和为1) A = [[0.57, 0.29, 0.14], # 主要关注自己 [0.12, 0.73, 0.15], # 强烈关注自己 [0.18, 0.27, 0.55]] # 主要关注自己,也关注其他
实际意义:
上下文感知的表示:
# 示例:代词消解 句子:"The cat ate the fish because it was hungry" # 计算"it"的输出时: Z_it = 0.02 × V_The + 0.01 × V_cat + 0.01 × V_ate + 0.02 × V_the + 0.01 × V_fish + 0.90 × V_cat + # 主要关注"cat" 0.03 × V_was + 0.00 × V_hungry
假设我们有3个词的句子:"猫 吃 鱼",每个词用4维向量表示:
计算得出输入矩阵 X = [[0.8, 0.2, 0.4, 0.1], [0.1, 0.9, 0.3, 0.7], [0.3, 0.1, 0.9, 0.2]]
模型有三个可学习的权重矩阵(开始时是随机值,通过训练学习):
计算过程:
让我们手动计算"猫"这个词的Q向量:
猫的Q = [0.8, 0.2, 0.4, 0.1] × [[0.5, 0.2], [0.1, 0.3], [0.4, 0.6], [0.2, 0.1]] = [0.8×0.5 + 0.2×0.1 + 0.4×0.4 + 0.1×0.2, 0.8×0.2 + 0.2×0.3 + 0.4×0.6 + 0.1×0.1] = [0.4 + 0.02 + 0.16 + 0.02, 0.16 + 0.06 + 0.24 + 0.01] = [0.6, 0.47]
计算最终得到:
Q = [[0.60, 0.47], # 猫的Query [0.58, 0.90], # 吃的Query [0.72, 0.65]] # 鱼的Query
K = [[0.32, 0.56], # 猫的Key [0.50, 0.71], # 吃的Key [0.68, 0.42]] # 鱼的Key
V = [[0.35, 0.42], # 猫的Value [0.62, 0.28], # 吃的Value [0.54, 0.63]] # 鱼的Value
现在计算每个Query与所有Key的匹配度:
计算过程:
得到注意力分数矩阵:
注意力分数 = [[0.455, 0.634, 0.605], # 猫对猫/吃/鱼的关注度 [0.572, 0.869, 0.753], # 吃对猫/吃/鱼的关注度 [0.533, 0.767, 0.763]] # 鱼对猫/吃/鱼的关注度
对每一行进行Softmax,使得每行之和为1:
最终注意力权重:
注意力权重 = [[0.298, 0.356, 0.346], # 猫: 35.6%关注"吃", 34.6%关注"鱼" [0.246, 0.422, 0.332], # 吃: 最关注自己(42.2%) [0.276, 0.354, 0.370]] # 鱼: 37.0%关注自己, 35.4%关注"吃"
最后用注意力权重对Value进行加权求和:
对于"猫"这个词的输出:
输出[0] = 0.298×[0.35,0.42] + 0.356×[0.62,0.28] + 0.346×[0.54,0.63] = [0.104,0.125] + [0.221,0.100] + [0.187,0.218] = [0.512, 0.443]
最终输出:
输出 = [[0.512, 0.443], # 猫的上下文感知表示 [0.527, 0.422], # 吃的上下文感知表示 [0.519, 0.468]] # 鱼的上下文感知表示
整个流程包括输入、线性变换(生成Q、K、V)、计算注意力分数、Softmax归一化、加权求和得到输出。

流程详细说明:
示例1:代词消解
示例2:多义词理解
实际中大模型使用多头注意力,让模型同时从多个角度关注信息:

比如在分析"我去银行取钱"时:
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
class SimpleSelfAttention(nn.Module):
"""简单的自注意力机制实现"""
def __init__(self, d_model=4, d_k=2, d_v=2):
super(SimpleSelfAttention, self).__init__()
self.d_model = d_model # 输入维度
self.d_k = d_k # Query和Key的维度
self.d_v = d_v # Value的维度
# 定义Q、K、V的线性变换层
# 注意:nn.Linear的权重形状是 (out_features, in_features)
self.W_Q = nn.Linear(d_model, d_k, bias=False)
self.W_K = nn.Linear(d_model, d_k, bias=False)
self.W_V = nn.Linear(d_model, d_v, bias=False)
# 初始化权重,便于理解计算过程
self._initialize_weights()
def _initialize_weights(self):
"""手动初始化权重,便于跟踪计算过程"""
# 预设的权重值,注意形状要与nn.Linear匹配
# nn.Linear权重形状是 (out_features, in_features) = (d_k, d_model)
preset_W_Q = torch.tensor([[0.5, 0.1, 0.4, 0.2], # 第一行对应第一个输出维度
[0.2, 0.3, 0.6, 0.1]], dtype=torch.float32) # 第二行对应第二个输出维度
preset_W_K = torch.tensor([[0.1, 0.3, 0.6, 0.2],
[0.5, 0.2, 0.4, 0.3]], dtype=torch.float32)
preset_W_V = torch.tensor([[0.2, 0.5, 0.3, 0.1],
[0.4, 0.1, 0.6, 0.2]], dtype=torch.float32)
self.W_Q.weight.data = preset_W_Q
self.W_K.weight.data = preset_W_K
self.W_V.weight.data = preset_W_V
def forward(self, X, verbose=False):
"""
前向传播
Args:
X: 输入张量 [seq_len, d_model]
verbose: 是否打印详细计算过程
"""
if verbose:
print("=" * 60)
print("QKV自注意力机制详细计算过程")
print("=" * 60)
print(f"输入 X (shape: {X.shape}):")
print(X)
print()
# 步骤1: 生成Q, K, V
Q = self.W_Q(X) # [seq_len, d_k]
K = self.W_K(X) # [seq_len, d_k]
V = self.W_V(X) # [seq_len, d_v]
if verbose:
print("步骤1: 生成Q, K, V")
print(f"Q (shape: {Q.shape}):")
print(Q)
print(f"K (shape: {K.shape}):")
print(K)
print(f"V (shape: {V.shape}):")
print(V)
print()
# 步骤2: 计算注意力分数 Q × K^T
attention_scores = torch.matmul(Q, K.transpose(0, 1)) # [seq_len, seq_len]
if verbose:
print("步骤2: 计算注意力分数 Q × K^T")
print(f"注意力分数矩阵 (shape: {attention_scores.shape}):")
print(attention_scores)
print()
# 打印详细的计算过程
print("详细点积计算:")
for i in range(Q.shape[0]):
for j in range(K.shape[0]):
dot_product = torch.dot(Q[i], K[j])
print(f"Q[{i}] · K[{j}] = {Q[i].detach().numpy()} · {K[j].detach().numpy()} = {dot_product:.3f}")
print()
# 步骤3: 缩放
scaled_scores = attention_scores / np.sqrt(self.d_k)
if verbose:
print("步骤3: 缩放")
print(f"缩放因子: 1/sqrt(d_k) = 1/sqrt({self.d_k}) = {1/np.sqrt(self.d_k):.3f}")
print(f"缩放后的分数矩阵:")
print(scaled_scores)
print()
# 步骤4: Softmax归一化
attention_weights = F.softmax(scaled_scores, dim=-1) # [seq_len, seq_len]
if verbose:
print("步骤4: Softmax归一化")
print("对每一行进行Softmax:")
for i in range(scaled_scores.shape[0]):
row = scaled_scores[i]
exp_row = torch.exp(row)
sum_exp = torch.sum(exp_row)
softmax_row = exp_row / sum_exp
print(f"第{i}行: {row.detach().numpy()} -> exp: {exp_row.detach().numpy()} -> "
f"softmax: {softmax_row.detach().numpy()} (sum: {sum_exp:.3f})")
print()
print(f"注意力权重矩阵 (每行和为1):")
print(attention_weights)
print()
# 步骤5: 加权求和得到输出
output = torch.matmul(attention_weights, V) # [seq_len, d_v]
if verbose:
print("步骤5: 加权求和得到输出")
print("每个位置的输出计算:")
for i in range(attention_weights.shape[0]):
weights = attention_weights[i]
weighted_sum = torch.zeros_like(V[0])
for j in range(V.shape[0]):
contribution = weights[j] * V[j]
weighted_sum += contribution
print(f" 位置 {i}: {weights[j]:.3f} × V[{j}] {V[j].detach().numpy()} = {contribution.detach().numpy()}")
print(f" → 输出[{i}] = {weighted_sum.detach().numpy()}")
print()
print(f"最终输出 (shape: {output.shape}):")
print(output)
print("=" * 60)
return output, attention_weights
def demonstrate_attention():
"""演示自注意力机制的工作过程"""
# 创建模型
attention = SimpleSelfAttention(d_model=4, d_k=2, d_v=2)
# 示例输入:3个词的序列,每个词用4维向量表示
# "猫" [0.8, 0.2, 0.4, 0.1]
# "吃" [0.1, 0.9, 0.3, 0.7]
# "鱼" [0.3, 0.1, 0.9, 0.2]
X = torch.tensor([
[0.8, 0.2, 0.4, 0.1], # 猫
[0.1, 0.9, 0.3, 0.7], # 吃
[0.3, 0.1, 0.9, 0.2] # 鱼
], dtype=torch.float32)
print("输入句子: ['猫', '吃', '鱼']")
print("每个词的向量表示:")
words = ["猫", "吃", "鱼"]
for i, word in enumerate(words):
print(f" '{word}': {X[i].numpy()}")
print()
# 前向传播,显示详细计算过程
output, attention_weights = attention(X, verbose=True)
# 可视化注意力权重
print("\n注意力权重热力图解释:")
print("行: 当前词(Query), 列: 被关注的词(Key)")
print(" 猫 吃 鱼")
for i, word in enumerate(words):
weights = attention_weights[i].detach().numpy()
print(f"{word} {weights}")
return output, attention_weights
def demonstrate_multihead_attention():
"""演示多头注意力的概念"""
print("\n" + "=" * 70)
print("多头注意力机制概念演示")
print("=" * 70)
# 使用PyTorch内置的多头注意力
multihead_attn = nn.MultiheadAttention(embed_dim=4, num_heads=2, batch_first=True)
# 同样的输入,但增加batch维度
X = torch.tensor([[
[0.8, 0.2, 0.4, 0.1], # 猫
[0.1, 0.9, 0.3, 0.7], # 吃
[0.3, 0.1, 0.9, 0.2] # 鱼
]], dtype=torch.float32) # [batch_size=1, seq_len=3, embed_dim=4]
print("输入形状:", X.shape)
# 前向传播
output, attention_weights = multihead_attn(X, X, X)
print("输出形状:", output.shape)
print("注意力权重形状:", attention_weights.shape)
print("\n多头注意力的优势:")
print("- 每个头可以关注不同类型的信息")
print("- 头1可能关注: 语法关系(主谓宾)")
print("- 头2可能关注: 语义关系(猫→吃→鱼)")
print("- 最终融合所有头的信息,获得更丰富的表示")
if __name__ == "__main__":
# 演示基础自注意力
output, weights = demonstrate_attention()
# 演示多头注意力
demonstrate_multihead_attention()输出结果:
输入句子: ['猫', '吃', '鱼'] 每个词的向量表示: '猫': [0.8 0.2 0.4 0.1] '吃': [0.1 0.9 0.3 0.7] '鱼': [0.3 0.1 0.9 0.2] ============================================================ QKV自注意力机制详细计算过程 ============================================================ 输入 X (shape: torch.Size([3, 4])): tensor([[0.8000, 0.2000, 0.4000, 0.1000], [0.1000, 0.9000, 0.3000, 0.7000], [0.3000, 0.1000, 0.9000, 0.2000]]) 步骤1: 生成Q, K, V Q (shape: torch.Size([3, 2])): tensor([[0.6000, 0.4700], [0.4000, 0.5400], [0.5600, 0.6500]], grad_fn=<MmBackward0>) K (shape: torch.Size([3, 2])): tensor([[0.4000, 0.6300], [0.6000, 0.5600], [0.6400, 0.5900]], grad_fn=<MmBackward0>) V (shape: torch.Size([3, 2])): tensor([[0.3900, 0.6000], [0.6300, 0.4500], [0.4000, 0.7100]], grad_fn=<MmBackward0>) 步骤2: 计算注意力分数 Q × K^T 注意力分数矩阵 (shape: torch.Size([3, 3])): tensor([[0.5361, 0.6232, 0.6613], [0.5002, 0.5424, 0.5746], [0.6335, 0.7000, 0.7419]], grad_fn=<MmBackward0>) 详细点积计算: Q[0] · K[0] = [0.6 0.47000003] · [0.40000004 0.63 ] = 0.536 Q[0] · K[1] = [0.6 0.47000003] · [0.6 0.56] = 0.623 Q[0] · K[2] = [0.6 0.47000003] · [0.64000005 0.59 ] = 0.661 Q[1] · K[0] = [0.39999998 0.54 ] · [0.40000004 0.63 ] = 0.500 Q[1] · K[1] = [0.39999998 0.54 ] · [0.6 0.56] = 0.542 Q[1] · K[2] = [0.39999998 0.54 ] · [0.64000005 0.59 ] = 0.575 Q[2] · K[0] = [0.56 0.65000004] · [0.40000004 0.63 ] = 0.634 Q[2] · K[1] = [0.56 0.65000004] · [0.6 0.56] = 0.700 Q[2] · K[2] = [0.56 0.65000004] · [0.64000005 0.59 ] = 0.742 步骤3: 缩放 缩放因子: 1/sqrt(d_k) = 1/sqrt(2) = 0.707 缩放后的分数矩阵: tensor([[0.3791, 0.4407, 0.4676], [0.3537, 0.3835, 0.4063], [0.4480, 0.4950, 0.5246]], grad_fn=<DivBackward0>) 步骤4: Softmax归一化 对每一行进行Softmax: 第0行: [0.37907997 0.440669 0.46760976] -> exp: [1.4609399 1.5537463 1.5961744] -> softmax: [0.31684756 0.33697537 0.34617713] (sum: 4.611) 第1行: [0.35369486 0.38353473 0.40630355] -> exp: [1.4243205 1.4674625 1.5012583] -> softmax: [0.32422197 0.33404252 0.34173554] (sum: 4.393) 第2行: [0.44795218 0.4949748 0.52460253] -> exp: [1.5651039 1.6404569 1.689787 ] -> softmax: [0.31971252 0.3351053 0.34518224] (sum: 4.895) 注意力权重矩阵 (每行和为1): tensor([[0.3168, 0.3370, 0.3462], [0.3242, 0.3340, 0.3417], [0.3197, 0.3351, 0.3452]], grad_fn=<SoftmaxBackward0>) 步骤5: 加权求和得到输出 每个位置的输出计算: 位置 0: 0.317 × V[0] [0.39000005 0.6000001 ] = [0.12357055 0.19010855] 位置 0: 0.337 × V[1] [0.63 0.45] = [0.21229446 0.1516389 ] 位置 0: 0.346 × V[2] [0.4 0.71000004] = [0.13847084 0.24578576] → 输出[0] = [0.47433585 0.58753324] 位置 1: 0.324 × V[0] [0.39000005 0.6000001 ] = [0.12644659 0.19453323] 位置 1: 0.334 × V[1] [0.63 0.45] = [0.21044679 0.15031913] 位置 1: 0.342 × V[2] [0.4 0.71000004] = [0.13669422 0.24263225] → 输出[1] = [0.4735876 0.5874846] 位置 2: 0.320 × V[0] [0.39000005 0.6000001 ] = [0.12468789 0.19182752] 位置 2: 0.335 × V[1] [0.63 0.45] = [0.21111631 0.15079737] 位置 2: 0.345 × V[2] [0.4 0.71000004] = [0.1380729 0.2450794] → 输出[2] = [0.47387707 0.5877043 ] 最终输出 (shape: torch.Size([3, 2])): tensor([[0.4743, 0.5875], [0.4736, 0.5875], [0.4739, 0.5877]], grad_fn=<MmBackward0>) ============================================================ 注意力权重热力图解释: 行: 当前词(Query), 列: 被关注的词(Key) 猫 吃 鱼 猫 [0.31684753 0.33697534 0.3461771 ] 吃 [0.324222 0.33404252 0.34173554] 鱼 [0.3197125 0.33510527 0.34518224] ===================================================================== 多头注意力机制概念演示 ===================================================================== 输入形状: torch.Size([1, 3, 4]) 输出形状: torch.Size([1, 3, 4]) 注意力权重形状: torch.Size([1, 3, 3]) 多头注意力的优势: - 每个头可以关注不同类型的信息 - 头1可能关注: 语法关系(主谓宾) - 头2可能关注: 语义关系(猫→吃→鱼) - 最终融合所有头的信息,获得更丰富的表示
QKV注意力机制是现代Transformer架构的核心突破,其本质是一个高效的信息检索与融合系统。该机制通过线性变换将输入序列转化为三组向量:
核心计算流程首先通过Query与所有Key的点积相似度建立关联矩阵,再经Softmax归一化为概率分布,最终对Value进行加权融合。QKV机制赋予模型动态上下文感知能力,每个位置的输出都融合了全局相关信息,而非固定窗口内的局部特征,这使模型能有效处理代词指代、一词多义等复杂语言现象。相比传统循环神经网络的序列处理,注意力机制能直接捕获任意距离的依赖关系,且完全并行化计算。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。