静态网站制作wordpress模版,东营新闻综合频道在线直播,WordPress标题别名自动翻译插件,网站建设费会计参考资料#xff1a;https://github.com/datawhalechina/happy-llm
2.1.1 什么是注意力机制
注意力机制最初源于计算机视觉领域#xff0c;其核心思想是通过集中关注重点部分来高效处理信息。在自然语言处理中#xff0c;注意力机制通过聚焦于关键的 token#xff08;如…参考资料https://github.com/datawhalechina/happy-llm
2.1.1 什么是注意力机制
注意力机制最初源于计算机视觉领域其核心思想是通过集中关注重点部分来高效处理信息。在自然语言处理中注意力机制通过聚焦于关键的 token如单词或短语可以实现更高效和高质量的计算。其三个核心变量为Query查询值、Key键值和 Value真值。例如在查找新闻报道中的时间时Query 可以是“时间”或“日期”等向量Key 和 Value 是整个文本。通过计算 Query 和 Key 的相关性得到权重再将权重与 Value 结合最终得到对文本的注意力加权结果。注意力机制通过这种方式拟合序列中每个词与其他词的相关关系。
2.1.2 深入理解注意力机制
注意力机制的核心变量是 Query查询值、Key键值 和 Value真值。通过类比字典查询的过程可以理解注意力机制的计算逻辑 字典查询类比 字典的 键Key 和 值Value 对应于注意力机制中的 Key 和 Value。 查询Query通过与 Key 的匹配来获取对应的 Value。 当 Query 匹配多个 Key 时可以通过为每个 Key 分配权重注意力分数来组合多个 Value。 注意力分数的计算 使用 点积 计算 Query 和 Key 的相似度 。 通过 softmax 函数 将点积结果归一化为权重。 权重反映了 Query 和每个 Key 的相似程度且权重之和为 1。 注意力机制的公式 基本公式。 为了处理高维数据并保持梯度稳定引入放缩因子。
最终注意力机制通过计算 Query 和 Key 的相似度为每个 Key 分配权重并结合 Value 得到加权结果。
2.1.3 注意力机制的实现
注意力计算函数
def attention(query, key, value, dropoutNone):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)# Softmaxp_attn scores.softmax(dim-1)if dropout is not None:p_attn dropout(p_attn)# 采样# 根据计算结果对value进行加权求和return torch.matmul(p_attn, value), p_attn
2.1.4 自注意力 定义 自注意力是注意力机制的变种用于计算同一序列中每个元素对其他所有元素的注意力分布。 计算过程 Q、K、V 都由同一个输入通过不同的参数矩阵 Wq、Wk、Wv 计算得到。 通过自注意力机制可以建模文本中每个 token 与其他所有 token 的依赖关系。 应用场景 在 Transformer 的 Encoder 中输入通过参数矩阵 Wq、Wk、Wv 分别得到 Q、K、V从而拟合输入语句中每个 token 对其他所有 token 的关系。 代码实现 在代码中自注意力机制通过将 Q、K、V 的输入设置为同一个参数来实现。
# attention 为上文定义的注意力计算函数
attention(x, x, x)
2.1.5 掩码自注意力
掩码自注意力Mask Self-Attention 是一种在自注意力机制中引入掩码的技术用于遮蔽特定位置的 token使模型在学习过程中只能使用历史信息进行预测而不能看到未来信息。这种方法的核心动机是实现并行计算提高 Transformer 模型的效率。 生成掩码矩阵 使用上三角矩阵作为掩码其中上三角部分的值为 −∞其余部分为 0。 掩码矩阵的维度通常为 (1, \text{seq_len}, \text{seq_len})通过广播机制应用于整个输入序列。 掩码的应用 在计算注意力分数时将掩码矩阵与注意力分数相加。 通过 Softmax 操作将上三角部分的 −∞ 转换为 0从而忽略这些位置的注意力分数。
示例
假设待学习的文本序列为 【BOS】I like you【EOS】掩码自注意力的输入如下 BOS 【MASK】【MASK】【MASK】【MASK】 BOS I 【MASK】 【MASK】【MASK】 BOS I like 【MASK】【MASK】 BOS I like you 【MASK】 BOS I like you /EOS 每个输入样本只看到前面的 token预测下一个 token。 通过并行处理模型可以同时处理整个序列而不是逐个步骤串行处理。
代码实现
# 创建一个上三角矩阵用于遮蔽未来信息
mask torch.full((1, args.max_seq_len, args.max_seq_len), float(-inf))
mask torch.triu(mask, diagonal1)# 在注意力计算时将掩码与注意力分数相加
scores scores mask[:, :seqlen, :seqlen]
scores F.softmax(scores.float(), dim-1).type_as(xq) 掩码矩阵上三角部分为 −∞其余部分为 0。 Softmax 操作将 −∞ 转换为 0忽略上三角区域的注意力分数。
2.1.6 多头注意力
多头注意力机制Multi-Head Attention 是 Transformer 模型的核心组件用于更全面地拟合语句序列中的相关关系。它通过同时进行多次注意力计算每次拟合不同的关系然后将结果拼接并线性变换从而更深入地建模语言信息。 核心动机 单一注意力的局限性一次注意力计算只能拟合一种相关关系难以全面捕捉语句中的复杂依赖。 多头注意力的优势通过多个注意力头同时计算每个头可以捕捉不同的信息从而更全面地拟合语句关系。
多头注意力机制的工作原理 公式表示 其中 Q,K,V输入的查询、键和值矩阵。 每个头的参数矩阵。 输出权重矩阵用于将拼接后的结果投影回原始维度。 多头注意力的实现 将输入序列通过不同的参数矩阵 分别计算得到 Q,K,V。 将 Q,K,V 分成多个头。 对每个头分别进行注意力计算然后将结果拼接。 最后通过一个线性层 WO 将拼接后的结果投影回原始维度。
代码实现 import torch.nn as nn
import torch多头自注意力计算模块
class MultiHeadAttention(nn.Module):def __init__(self, args: ModelArgs, is_causalFalse):# 构造函数# args: 配置对象super().__init__()# 隐藏层维度必须是头数的整数倍因为后面我们会将输入拆成头数个矩阵assert args.n_embd % args.n_heads 0# 模型并行处理大小默认为1。model_parallel_size 1# 本地计算头数等于总头数除以模型并行处理大小。self.n_local_heads args.n_heads // model_parallel_size# 每个头的维度等于模型维度除以头的总数。self.head_dim args.dim // args.n_heads# Wq, Wk, Wv 参数矩阵每个参数矩阵为 n_embd x n_embd# 这里通过三个组合矩阵来代替了n个参数矩阵的组合其逻辑在于矩阵内积再拼接其实等同于拼接矩阵再内积# 不理解的读者可以自行模拟一下每一个线性层其实相当于n个参数矩阵的拼接self.wq nn.Linear(args.dim, args.n_heads * self.head_dim, biasFalse)self.wk nn.Linear(args.dim, args.n_heads * self.head_dim, biasFalse)self.wv nn.Linear(args.dim, args.n_heads * self.head_dim, biasFalse)# 输出权重矩阵维度为 n_embd x n_embdhead_dim n_embeds / n_headsself.wo nn.Linear(args.n_heads * self.head_dim, args.dim, biasFalse)# 注意力的 dropoutself.attn_dropout nn.Dropout(args.dropout)# 残差连接的 dropoutself.resid_dropout nn.Dropout(args.dropout)# 创建一个上三角矩阵用于遮蔽未来信息# 注意因为是多头注意力Mask 矩阵比之前我们定义的多一个维度if is_causal:mask torch.full((1, 1, args.max_seq_len, args.max_seq_len), float(-inf))mask torch.triu(mask, diagonal1)# 注册为模型的缓冲区self.register_buffer(mask, mask)def forward(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor):# 获取批次大小和序列长度[batch_size, seq_len, dim]bsz, seqlen, _ q.shape# 计算查询Q、键K、值V,输入通过参数矩阵层维度为 (B, T, n_embed) x (n_embed, n_embed) - (B, T, n_embed)xq, xk, xv self.wq(q), self.wk(k), self.wv(v)# 将 Q、K、V 拆分成多头维度为 (B, T, n_head, C // n_head)然后交换维度变成 (B, n_head, T, C // n_head)# 因为在注意力计算中我们是取了后两个维度参与计算# 为什么要先按B*T*n_head*C//n_head展开再互换1、2维度而不是直接按注意力输入展开是因为view的展开方式是直接把输入全部排开# 然后按要求构造可以发现只有上述操作能够实现我们将每个头对应部分取出来的目标xq xq.view(bsz, seqlen, self.n_local_heads, self.head_dim)xk xk.view(bsz, seqlen, self.n_local_heads, self.head_dim)xv xv.view(bsz, seqlen, self.n_local_heads, self.head_dim)xq xq.transpose(1, 2)xk xk.transpose(1, 2)xv xv.transpose(1, 2)# 注意力计算# 计算 QK^T / sqrt(d_k)维度为 (B, nh, T, hs) x (B, nh, hs, T) - (B, nh, T, T)scores torch.matmul(xq, xk.transpose(2, 3)) / math.sqrt(self.head_dim)# 掩码自注意力必须有注意力掩码if self.is_causal:assert hasattr(self, mask)# 这里截取到序列长度因为有些序列可能比 max_seq_len 短scores scores self.mask[:, :, :seqlen, :seqlen]# 计算 softmax维度为 (B, nh, T, T)scores F.softmax(scores.float(), dim-1).type_as(xq)# 做 Dropoutscores self.attn_dropout(scores)# V * Score维度为(B, nh, T, T) x (B, nh, T, hs) - (B, nh, T, hs)output torch.matmul(scores, xv)# 恢复时间维度并合并头。# 将多头的结果拼接起来, 先交换维度为 (B, T, n_head, C // n_head)再拼接成 (B, T, n_head * C // n_head)# contiguous 函数用于重新开辟一块新内存存储因为Pytorch设置先transpose再view会报错# 因为view直接基于底层存储得到然而transpose并不会改变底层存储因此需要额外存储output output.transpose(1, 2).contiguous().view(bsz, seqlen, -1)# 最终投影回残差流。output self.wo(output)output self.resid_dropout(output)return output