LLM-Transformer架构-注意力机制
2.1 注意力机制
2.1.1 什么是注意力机制
从 计算机视觉(Computer Vision,CV)为起源发展起来的神经网络,其核心架构有三种:
- 前馈神经网络(Feedforward Neural Network,FNN),即每一层的神经元都和上下两层的每一个神经元完全连接
2. 卷积神经网络(Convolutional Neural Network,CNN),即训练参数量远小于前馈神经网络的卷积层来进行特征提取和学习
3. 循环神经网络(Recurrent Neural Network,RNN),能够使用历史信息作为输入、包含环和自重复的网络
优点:捕捉时序信息、适合序列生成
缺点: 1.限制了计算机并行计算的能力 2. RNN 难以捕捉长序列的相关关系
注意力机制:
通过将重点注意力集中在一个或几个 token,从而取得更高效高质的计算效果
有三个核心变量:Query(查询值)、Key(键值)和 Value(真值)
【通过对 Query 和 Key 进行运算我们可以得到一个权重,这个权重其实反映了从 Query 出发,对文本每一个 token 应该分布的注意力相对大小。通过把权重和 Value 进行运算,得到的最后结果就是从 Query 出发计算整个文本注意力得到的结果】
特点是通过计算 Query 与Key的相关性为真值加权求和,从而拟合序列中每个词同其他词的相关关系
2.1.2 深入理解注意力机制
给不同 Key 所赋予的不同权重,就是我们所说的注意力分数
Key 与 Query 相关性越高,则其所应该赋予的注意力权重就越大
计算出正确的注意力分数的方法:
(这个理解即可,所以直接贴图)
2.1.3 注意力机制的实现
(不知道手敲记忆会不会好一点,不管了,敲一遍)
'''注意力计算函数'''
def attention(query, key, value, dropout=None):
'''
args:
query: 查询值矩阵
key: 键值矩阵
value: 真值矩阵
'''
# 获取键向量的维度,键向量的维度和值向量的维度相同
d_k = query.size(-1)
# 计算Q与K的内积并除以根号dk
# transpose——相当于转置
scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)
# Softmax
p_attn = scores.softmax(dim=-1)
if dropout is not None:
p_attn = dropout(p_attn)
# 采样
# 根据计算结果对value进行加权求和
return torch.matmul(p_attn, value), p_attn
d_k = query.size(-1)
query 是一个多维张量(Tensor) 例:(batch_size, sequence_length, embed_dim)
.size() 或 .shape 方法返回张量的维度大小
-1 表示张量的最后一个维度
scores = torch.matmul(query, key.tranpose(-2,-1)) / math.sqrt(d_k)
计算的是"注意力分数",如下:
1. 张量乘法 torch.matmul():执行矩阵乘法或批量矩阵乘法
# 对于3D张量 (batch, seq_len, features)
# 实际执行的是批量矩阵乘法
torch.matmul(A, B) # 形状: (batch, seq_len_A, seq_len_B)
2. key.transpose(-2, -1)
转置操作:交换最后两个维度
原始key形状: (batch, seq_len, d_k) --> 转置后形状: (batch, d_k, seq_len)
这样query @ key.T才能得到(batch, seq_len_Q, seq_len_K)
使用-2,-1而非具体数字:使代码更通用,适应不同维度的张量
3. 矩阵乘法结果的意义
query @ key.T 计算的是查询(Query)和键(Key)的点积相似度:
每个分数表示一个query向量和一个key向量的匹配程度
结果矩阵形状:(batch, seq_len_Q, seq_len_K)
4. 缩放因子 1/math.sqrt(d_k)
点积结果会随d_k增大而变大,过大的值会导致softmax梯度消失(某些位置概率接近1,其余接近0)-->缩放
2.1.4 自注意力
自注意力,即是计算本身序列中每个元素对其他元素的注意力分布,即在计算过程中,Q、K、V 都由同一个输入通过不同的参数矩阵计算得到。
通过自注意力机制,我们可以找到一段文本中每一个 token 与其他所有 token 的相关关系大小,从而建模文本之间的依赖关系。
在代码中的实现,self-attention 机制其实是通过给 Q、K、V 的输入传入同一个参数实现的:
# attention 为上文定义的注意力计算函数
attention(x, x, x)
区别:
注意力机制是一个序列对另一个序列进行加权聚合;
自注意力机制是一个序列对自身内部元素进行加权聚合。
2.1.5 掩码自注意力
Mask Self-Attention,是指使用注意力掩码的自注意力机制。掩码的作用是遮蔽一些特定位置的token,模型在学习的过程中,会忽略掉被遮蔽的token
核心动机是让模型只能使用历史信息进行预测而不能看到未来信息
使用注意力机制的 Transformer 模型:串行
是通过类似于 n-gram 的语言模型任务来学习的,也就是对一个文本序列,不断根据之前的 token 来预测下一个 token,直到将整个文本序列补全。
--->因此引入基于掩码自注意力机制的方法:并行
模型只需要每一个样本根据未被遮蔽的 token 来预测下一个 token 即可
实现:其实则是一个和文本序列等长的上三角矩阵。我们可以简单地通过创建一个和输入同等长度的上三角矩阵作为注意力掩码,再使用掩码来遮蔽掉输入即可。
代码见pycharm
mask = torch.full((1,args.ax_seq_len, args.max_seq_len),float("-inf"))
创建一个注意力掩码矩阵,用于在Transformer等模型中屏蔽无效位置(如未来信息或填充位置)
- 参数分解:
- 形状:(1, max_seq_len, max_seq_len)
第一个维度1:便于广播到批量维度;后两个维度:序列中每个位置对其他位置的掩码
- 填充值:float("-inf")(负无穷)
经过softmax后,exp(-inf) = 0,完全屏蔽该位置
2. torch.full
用于创建填充特定值的张量的函数
torch.full(
size, # 张量的形状(元组形式)(必须)
fill_value, # 填充的值(必须)
*, # 后面的参数必须用关键字传递
dtype=None, # 数据类型(如torch.float32)
layout=torch.strided, # 内存布局
device=None, # 设备(CPU/GPU)
requires_grad=False # 是否需要梯度
)
mask = torch.triu(mask, diagonal = 1)
torch.tril(tensor, diagonal=0),返回矩阵的下三角部分
diagonal=0:主对角线;diagonal=1:主对角线以上1条对角线;diagonal=-1:主对角线以下1条对角线
torch.triu与tril相似 ,但保留上三角部分
2.1.6 多头注意力
其实就是将原始的输入序列进行多组的自注意力处理;然后再将每一组得到的自注意力结果拼接起来,再通过一个线性层进行处理,得到最终的输出,可更全面深入地拟合语言信息。
- 把输入向量分别映射为多个不同的子空间(head)
- 每个 head 分别做一次注意力计算
- 最后将所有 head 的输出合并并线性变换回原维度
通过矩阵运算实现并行的多头计算:核心逻辑在于使用三个组合矩阵来代替了n个参数矩阵的组合,也就是矩阵内积再拼接其实等同于拼接矩阵再内积。
代码见pycharm
def __init__(self,args:ModelArgs, is_causal=False):
is_causal=False表示:模型使用全注意力(full attention),每个位置可以关注序列中的所有位置(包括过去和未来),适用于非自回归任务,如编码器(BERT)、文本分类等
is_causal=True表示:模型使用因果注意力(causal/masked attention),每个位置只能关注该位置之前的位置(包括当前位置),适用于自回归生成任务,如语言模型(LM)、文本生成等
assert args.dim % args.n_heads == 0
assert 是 Python 中的一个关键字,用于进行断言检查,它是调试程序的重要工具。如果 condition 为 True,程序继续正常执行;如果 condition 为 False,则抛出 AssertionError 异常
self.wq = nn.Linear(args.dim, args.n_heads * self.head_dim, bias=False)
- args参数解析
这是一个参数容器(通常为argparse.Namespace或类似对象),类似字典/类,存储模型的所有超参数。
例:(transformer中)
args.dim = 512 # 模型的总嵌入维度(即d_model)
args.n_heads = 8 # 注意力头的数量
- dim
模型维度(model dimension)或嵌入维度(embedding dimension),表示输入向量的特征维度(记为d_model),也是每个token的编码维度
- n_heads * head_dim
注意力头的数量*每个头的维度
- 注意力头
注意力头是多头注意力机制(Multi-Head Attention)中的基本计算单元,头数↑:并行能力↑但计算开销↑
- Linear层
nn.Linear(in_features, out_features, bias=True)
输入特征维度(每个输入向量有多少个元素),输出同理
Out = x*W+bias 输出=输入×权重^T+偏置,即做的就是一个线性变换(不含激活)
输入 x 的 shape 是 [batch_size, in_features]
W 的 shape 是 [out_features, in_features],(W是权重矩阵)
结果 shape 是 [batch_size, out_features]
例:linear = nn.Linear(in_features=4, out_features=2)
每个输入样本有 4 个特征,每个输出样本要变成两个特征
- x = torch.randn(3, 4)
表示有 3 个样本(batch size = 3),4 表示每个样本有 4 个特征(input feature = 4)
randn 是 "random normal" 的缩写,生成服从标准正态分布(均值为 0,标准差为 1)的随机数。
(3, 4) 指定输出张量的形状为 3 行 4 列,即形状为 (3, 4) 的二维张量(矩阵)3 x = [
[x11, x12, x13, x14], # 第一个样本,4个特征
[x21, x22, x23, x24], # 第二个样本,4个特征
[x31, x32, x33, x34] # 第三个样本,4个特征
]
self.attn_dropout = nn.Dropout(args.dropout) 讲解
创建了一个PyTorch的Dropout层,并将其赋值给模型的attn_dropout属性
nn.Dropout是PyTorch提供的正则化层,用于防止神经网络过拟合,args.dropout是从模型参数中获取的dropout概率值p
Dropout:
一种用于神经网络的正则化技术,它的核心思想是在训练过程中随机"丢弃"(即暂时移除)网络中的一部分神经元。
工作原理:
- 训练阶段:对每个训练样本,随机选择一部分神经元以概率 p 暂时"关闭"(输出设为 0),剩余神经元的输出会被放大 1/(1-p) 倍(保持总期望值不变)
- 测试阶段:使用所有神经元;不需要进行缩放
As:dropout = nn.Dropout(p=0.5)
隐藏层:
1.概念:神经网络中介于输入层和输出层之间的中间层,直接与外部输入或最终输出相连,它们的值在训练数据中不可直接观测。
2. 作用:解决非线性问题;特征抽象:深层网络通过多个隐藏层可以逐层提取更高层次的特征
3. 构成要素:
- 神经元:神经元数量是超参数(需要调优)
- 权重:连接前一层和当前层的参数
- 激活函数:引入非线性(如Relu、Sigmoid)
4. 常见隐藏层类型
a. 全连接层(Dense/Fully Connected Layer):
每个神经元与前一层的所有神经元相连,最基础的隐藏层类型
b. 卷积层(Convolutional Layer):
通过卷积核提取局部特征,主要用于图像处理
c. 循环层(Recurrent Layer):
具有记忆功能,处理序列数据,如LSTM、GRU等
实例:# 一个简单的3层神经网络(1个隐藏层)
model = Sequential([
Dense(64, activation='relu', input_shape=(100,)), # 隐藏层
Dense(10, activation='softmax') # 输出层
])
self.register_buffer("mask",mask)
用于注册持久性缓冲区,register_buffer() 用于将张量注册为模块的一部分,自动转移到正确的设备(CPU/GPU)
xq = xq.view(bsz, seqlen, self.n_local_heads, self.head_dim)
用于改变张量形状(shape)的重要操作
view() 方法在不改变底层数据的情况下,重新解释张量的形状(shape)。它执行的是零拷贝的形状变换,只改变张量的元数据(metadata),不移动或复制实际数据。
[bsz, seqlen, d_model]--->>xq.view(bsz, seqlen, self.n_local_heads, self.head_dim)
前提:新形状的元素总数必须与原形状保持一致(即各维度乘积相等)
xq.transpose(1, 2)
用于交换张量两个维度的函数,这里的 xq.transpose(1, 2) 表示交换张量的第1和第2维度(从0开始计数)
目的:为并行注意力计算做准备。将注意力头维度前移,使每个头的查询向量可以独立处理、批量计算
[batch_size, seq_len, n_heads, head_dim]--->[batch_size, n_heads, seq_len, head_dim]
