【深度学习】实验 — 动手实现 GPT【二】:注意力机制、注意力掩码、多头注意力机制

【深度学习】实验 — 动手实现 GPT【二】:注意力机制、多头注意力机制

  • 注意力机制
    • 简单示例:单个元素的情况
    • 简单示例:计算所有输入词元的注意力权重
      • 推广到所有输入序列词元:
  • 注意力掩码
  • 代码实现多头注意力
  • 测试

注意力机制

简单示例:单个元素的情况

  • 假设我们有以下输入句子,已按照第 3 章中的描述嵌入为 3 维向量(此处使用非常小的嵌入维度,仅用于说明,方便在页面上显示而不换行):
inputs = torch.tensor([[0.43, 0.15, 0.89], # Your     (x^1)[0.55, 0.87, 0.66], # journey  (x^2)[0.57, 0.85, 0.64], # starts   (x^3)[0.22, 0.58, 0.33], # with     (x^4)[0.77, 0.25, 0.10], # one      (x^5)[0.05, 0.80, 0.55]] # step     (x^6)
)
  • (在本书中,我们遵循机器学习和深度学习的常见惯例,即训练样本表示为行,特征值表示为列;在上面的张量中,每一行表示一个词,每一列表示一个嵌入维度。)

  • 本节的主要目的是演示如何使用第二个输入序列 x ( 2 ) x^{(2)} x(2) 作为查询,计算上下文向量 z ( 2 ) z^{(2)} z(2)

  • 图示展示了该过程的初始步骤,其中通过点积操作计算 x ( 2 ) x^{(2)} x(2) 与所有其他输入元素之间的注意力分数 ω。

请添加图片描述

  • 我们使用输入序列中的元素 2,即 x ( 2 ) x^{(2)} x(2),作为示例来计算上下文向量 z ( 2 ) z^{(2)} z(2);在本节稍后,我们将推广此方法来计算所有的上下文向量。
  • 第一步是通过计算查询 x ( 2 ) x^{(2)} x(2) 与所有其他输入词元之间的点积,得到未归一化的注意力分数:
query = inputs[1]  # 2nd input token is the queryattn_scores_2 = torch.empty(inputs.shape[0])
for i, x_i in enumerate(inputs):attn_scores_2[i] = torch.dot(x_i, query) # dot product (transpose not necessary here since they are 1-dim vectors)print(attn_scores_2)

输出

tensor([0.9544, 1.4950, 1.4754, 0.8434, 0.7070, 1.0865])
  • 步骤 2: 将未归一化的注意力分数(“omegas”, ω \omega ω)归一化,使其总和为 1。
  • 以下是一种简单的归一化方法,使未归一化的注意力分数总和为 1(这种方式是约定俗成的,有助于解释,并对训练稳定性非常重要):

请添加图片描述

attn_weights_2_tmp = attn_scores_2 / attn_scores_2.sum()print("Attention weights:", attn_weights_2_tmp)
print("Sum:", attn_weights_2_tmp.sum())

输出

Attention weights: tensor([0.1455, 0.2278, 0.2249, 0.1285, 0.1077, 0.1656])
Sum: tensor(1.0000)
  • 然而,在实际操作中,通常推荐使用 softmax 函数进行归一化,因为它在处理极端值方面更有效,并且在训练过程中具有更理想的梯度特性。
attn_weights_2 = torch.softmax(attn_scores_2, dim=0)print("Attention weights:", attn_weights_2)
print("Sum:", attn_weights_2.sum())

输出

Attention weights: tensor([0.1385, 0.2379, 0.2333, 0.1240, 0.1082, 0.1581])
Sum: tensor(1.)
  • 步骤 3:通过将嵌入的输入词元 x ( i ) x^{(i)} x(i) 与注意力权重相乘,并将所得向量求和,计算上下文向量 z ( 2 ) z^{(2)} z(2)请添加图片描述
query = inputs[1] # 2nd input token is the querycontext_vec_2 = torch.zeros(query.shape)
for i,x_i in enumerate(inputs):context_vec_2 += attn_weights_2[i]*x_iprint(context_vec_2)

输出

tensor([0.4419, 0.6515, 0.5683])

简单示例:计算所有输入词元的注意力权重

推广到所有输入序列词元:

  • 上面我们计算了输入 2 的注意力权重和上下文向量。

  • 接下来,我们将推广该计算,以求得所有的注意力权重和上下文向量。
    请添加图片描述

  • (请注意,此图中的数字已截取至小数点后两位,以减少视觉杂乱;每行的值应相加为 1.0 或 100%;同样,其他图中的数字也被截取。)

  • 在自注意力机制中,首先计算注意力分数,随后对其进行归一化以得出总和为 1 的注意力权重。

  • 然后,这些注意力权重被用于通过输入的加权求和生成上下文向量。

请添加图片描述

  • 将之前的步骤 1应用于所有成对元素,以计算未归一化的注意力分数矩阵:
attn_scores = torch.empty(6, 6)for i, x_i in enumerate(inputs):for j, x_j in enumerate(inputs):attn_scores[i, j] = torch.dot(x_i, x_j)print(attn_scores)

输出

tensor([[0.9995, 0.9544, 0.9422, 0.4753, 0.4576, 0.6310],[0.9544, 1.4950, 1.4754, 0.8434, 0.7070, 1.0865],[0.9422, 1.4754, 1.4570, 0.8296, 0.7154, 1.0605],[0.4753, 0.8434, 0.8296, 0.4937, 0.3474, 0.6565],[0.4576, 0.7070, 0.7154, 0.3474, 0.6654, 0.2935],[0.6310, 1.0865, 1.0605, 0.6565, 0.2935, 0.9450]])
  • 我们可以通过矩阵乘法更高效地实现上述计算:
attn_scores = inputs @ inputs.T
print(attn_scores)

输出

tensor([[0.9995, 0.9544, 0.9422, 0.4753, 0.4576, 0.6310],[0.9544, 1.4950, 1.4754, 0.8434, 0.7070, 1.0865],[0.9422, 1.4754, 1.4570, 0.8296, 0.7154, 1.0605],[0.4753, 0.8434, 0.8296, 0.4937, 0.3474, 0.6565],[0.4576, 0.7070, 0.7154, 0.3474, 0.6654, 0.2935],[0.6310, 1.0865, 1.0605, 0.6565, 0.2935, 0.9450]])
  • 与之前的步骤 2类似,我们对每一行进行归一化,使每一行的值相加为 1:
attn_weights = torch.softmax(attn_scores, dim=-1)
print(attn_weights)

输出

tensor([[0.2098, 0.2006, 0.1981, 0.1242, 0.1220, 0.1452],[0.1385, 0.2379, 0.2333, 0.1240, 0.1082, 0.1581],[0.1390, 0.2369, 0.2326, 0.1242, 0.1108, 0.1565],[0.1435, 0.2074, 0.2046, 0.1462, 0.1263, 0.1720],[0.1526, 0.1958, 0.1975, 0.1367, 0.1879, 0.1295],[0.1385, 0.2184, 0.2128, 0.1420, 0.0988, 0.1896]])
  • 应用之前的步骤 3来计算所有上下文向量:
all_context_vecs = attn_weights @ inputs
print(all_context_vecs)

输出

tensor([[0.4421, 0.5931, 0.5790],[0.4419, 0.6515, 0.5683],[0.4431, 0.6496, 0.5671],[0.4304, 0.6298, 0.5510],[0.4671, 0.5910, 0.5266],[0.4177, 0.6503, 0.5645]])

注意力掩码

  • 模型在序列中某一位置的预测仅依赖于之前位置的已知输出,而不依赖未来位置的输出。
  • 简单来说,这确保了每个下一个词的预测仅依赖于前面的词。
  • 为了实现这一点,对于每个给定词元,我们将未来的词元(即在当前词元之后的词元)进行掩码处理:
    请添加图片描述
attn_weights

输出

tensor([[0.2098, 0.2006, 0.1981, 0.1242, 0.1220, 0.1452],[0.1385, 0.2379, 0.2333, 0.1240, 0.1082, 0.1581],[0.1390, 0.2369, 0.2326, 0.1242, 0.1108, 0.1565],[0.1435, 0.2074, 0.2046, 0.1462, 0.1263, 0.1720],[0.1526, 0.1958, 0.1975, 0.1367, 0.1879, 0.1295],[0.1385, 0.2184, 0.2128, 0.1420, 0.0988, 0.1896]])
  • 最简单的方式是通过 PyTorch 的 tril 函数创建一个掩码,将主对角线下方的元素(包括主对角线)设置为 1,主对角线上方的元素设置为 0,以掩盖未来的注意力权重:
context_length = attn_scores.shape[0]
mask_simple = torch.tril(torch.ones(context_length, context_length))
print(mask_simple)
tensor([[1., 0., 0., 0., 0., 0.],[1., 1., 0., 0., 0., 0.],[1., 1., 1., 0., 0., 0.],[1., 1., 1., 1., 0., 0.],[1., 1., 1., 1., 1., 0.],[1., 1., 1., 1., 1., 1.]])
  • 然后,我们可以将注意力权重与此掩码相乘,以将对角线上方的注意力分数置为零:
masked_simple = attn_weights*mask_simple
print(masked_simple)
tensor([[0.2098, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],[0.1385, 0.2379, 0.0000, 0.0000, 0.0000, 0.0000],[0.1390, 0.2369, 0.2326, 0.0000, 0.0000, 0.0000],[0.1435, 0.2074, 0.2046, 0.1462, 0.0000, 0.0000],[0.1526, 0.1958, 0.1975, 0.1367, 0.1879, 0.0000],[0.1385, 0.2184, 0.2128, 0.1420, 0.0988, 0.1896]])
  • 然而,如果在 softmax 之后应用掩码(如上所述),会破坏 softmax 创建的概率分布。
  • Softmax 确保所有输出值的总和为 1。
  • 在 softmax 之后进行掩码处理则需要重新归一化输出以再次使其总和为 1,这会使过程复杂化,并可能导致意想不到的效果。
  • 为确保每行的总和为 1,我们可以按如下方式归一化注意力权重:
row_sums = masked_simple.sum(dim=-1, keepdim=True)
masked_simple_norm = masked_simple / row_sums
print(masked_simple_norm)
  • 让我们简单了解一种更高效的方法来实现上述目标。
  • 因此,与其将对角线上方的注意力权重置零并重新归一化结果,我们可以在未归一化的注意力分数进入 softmax 函数之前,将对角线上方的分数掩码为负无穷大。请添加图片描述
mask = torch.triu(torch.ones(context_length, context_length), diagonal=1)
masked = attn_scores.masked_fill(mask.bool(), -torch.inf)
print(masked)

输出

tensor([[0.9995,   -inf,   -inf,   -inf,   -inf,   -inf],[0.9544, 1.4950,   -inf,   -inf,   -inf,   -inf],[0.9422, 1.4754, 1.4570,   -inf,   -inf,   -inf],[0.4753, 0.8434, 0.8296, 0.4937,   -inf,   -inf],[0.4576, 0.7070, 0.7154, 0.3474, 0.6654,   -inf],[0.6310, 1.0865, 1.0605, 0.6565, 0.2935, 0.9450]])
  • 如下所示,现在每行的注意力权重再次正确地总和为 1:
attn_weights = torch.softmax(masked, dim=-1)
print(attn_weights)

输出

tensor([[1.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],[0.3680, 0.6320, 0.0000, 0.0000, 0.0000, 0.0000],[0.2284, 0.3893, 0.3822, 0.0000, 0.0000, 0.0000],[0.2046, 0.2956, 0.2915, 0.2084, 0.0000, 0.0000],[0.1753, 0.2250, 0.2269, 0.1570, 0.2158, 0.0000],[0.1385, 0.2184, 0.2128, 0.1420, 0.0988, 0.1896]])

代码实现多头注意力

class MultiHeadAttention(nn.Module):def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):super().__init__()assert (d_out % num_heads == 0), \"d_out must be divisible by num_heads"self.d_out = d_outself.num_heads = num_headsself.head_dim = d_out // num_heads # Reduce the projection dim to match desired output dimself.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)self.out_proj = nn.Linear(d_out, d_out)  # Linear layer to combine head outputsself.dropout = nn.Dropout(dropout)self.register_buffer("mask",torch.triu(torch.ones(context_length, context_length),diagonal=1))def forward(self, x):b, num_tokens, d_in = x.shapekeys = self.W_key(x) # Shape: (b, num_tokens, d_out)queries = self.W_query(x)values = self.W_value(x)# We implicitly split the matrix by adding a `num_heads` dimension# Unroll last dim: (b, num_tokens, d_out) -> (b, num_tokens, num_heads, head_dim)keys = keys.view(b, num_tokens, self.num_heads, self.head_dim)values = values.view(b, num_tokens, self.num_heads, self.head_dim)queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)# Transpose: (b, num_tokens, num_heads, head_dim) -> (b, num_heads, num_tokens, head_dim)keys = keys.transpose(1, 2)queries = queries.transpose(1, 2)values = values.transpose(1, 2)# Compute scaled dot-product attention (aka self-attention) with a causal maskattn_scores = queries @ keys.transpose(2, 3)  # Dot product for each head# Original mask truncated to the number of tokens and converted to booleanmask_bool = self.mask.bool()[:num_tokens, :num_tokens]# Use the mask to fill attention scoresattn_scores.masked_fill_(mask_bool, -torch.inf)attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)attn_weights = self.dropout(attn_weights)# Shape: (b, num_tokens, num_heads, head_dim)context_vec = (attn_weights @ values).transpose(1, 2)# Combine heads, where self.d_out = self.num_heads * self.head_dimcontext_vec = context_vec.contiguous().view(b, num_tokens, self.d_out)context_vec = self.out_proj(context_vec) # optional projectionreturn context_vec

测试

batch = torch.stack((inputs, inputs), dim=0)
batch_size, context_length, d_in = batch.shape
d_out = 2
mha = MultiHeadAttention(d_in, d_out, context_length, 0.0, num_heads=2)context_vecs = mha(batch)print(context_vecs)
print("context_vecs.shape:", context_vecs.shape)

输出

tensor([[[-0.6033, -0.2785],[-0.5409, -0.2509],[-0.5241, -0.2439],[-0.4974, -0.2357],[-0.5224, -0.2520],[-0.4887, -0.2361]],[[-0.6033, -0.2785],[-0.5409, -0.2509],[-0.5241, -0.2439],[-0.4974, -0.2357],[-0.5224, -0.2520],[-0.4887, -0.2361]]], grad_fn=<ViewBackward0>)
context_vecs.shape: torch.Size([2, 6, 2])
  • 另外请注意,我们在上面的 MultiHeadAttention 类中添加了一个线性投影层 (self.out_proj)。这只是一个不会改变维度的线性变换。在大型语言模型的实现中,使用这样的投影层是一个标准惯例,但并非绝对必要(最近的研究表明,移除该层不会影响模型性能);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/884036.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库(31)——事务

事务 数据库事务&#xff08;Database Transaction&#xff09;是数据库管理系统&#xff08;DBMS&#xff09;中执行的一组逻辑操作单元&#xff0c;这些操作要么全部成功执行&#xff0c;要么全部不执行&#xff0c;以保持数据的一致性和完整性。事务是确保数据可靠性的重要机…

Android 获取OAID

获取OAID 老规矩&#xff0c;直接上&#xff1a; implementation com.huawei.hms:opendevice:6.11.0.300 // 要获取华为vaid 和aaid&#xff0c;还需添加opendevice 依赖implementation(name: oaid_sdk_2.5.0, ext: aar) import android.content.Context; import android.util.…

每日互动基于 Apache DolphinScheduler 从容应对ClickHouse 大数据入库瓶颈

引言 大家好&#xff0c;我叫张琦&#xff0c;来自每日互动&#xff0c;担任大数据平台架构师。今天我将分享我们团队在基于Apache DolphinScheduler实现ClickHouse零压入库过程中的实践经验。 这个实践项目涉及到两个关键组件&#xff1a;Apache DolphinScheduler和ClickHous…

[vulnhub] Brainpan1

https://www.vulnhub.com/entry/brainpan-1,51/ 主机发现端口扫描 使用nmap扫描网段类存活主机 因为靶机是我最后添加的&#xff0c;所以靶机IP是166 nmap -sP 192.168.75.0/24 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-1…

数学建模与优化算法:从基础理论到实际应用

数学建模和优化算法&#xff0c;它们不仅帮助我们理解和描述复杂系统的行为&#xff0c;还能找到系统性能最优化的解决方案。本文将从基础的数学理论出发&#xff0c;逐步深入到各种优化算法&#xff0c;并探讨它们在实际问题中的应用。 思维导图文件可获取&#xff1a;https:…

基于Intel Gaudi AI加速器的大语言模型微调与推理优化赛题等你挑战 | CCF BDCI进行时

一年一度的行业盛事2024 CCF大数据与计算智能大赛&#xff08;简称2024 CCF BDCI&#xff09;又在激烈进行中啦&#xff01; 多个赛题等你挑战还没有报名的伙伴们抓紧时间咯&#xff0c;叫上你伙伴练起来吧&#xff01; 2024 CCF大数据与计算智能大赛 CCF大数据与计算智能大…

使用 FastGPT 工作流搭建 GitHub Issues 自动总结机器人

如今任何项目开发节奏都很快&#xff0c;及时掌握项目动态是很重要滴&#xff0c;GitHub Issues 一般都是开发者和用户反馈问题的主要渠道。 然而&#xff0c;随着 Issue 数量的增加&#xff0c;及时跟进每一个问题会变得越来越困难。 为了解决这个痛点&#xff0c;我们开发了…

Unreal Engine 5 C++(C#)开发:使用蓝图库实现插件(一)认识和了解Build.cs

目录 引言 一、创建一个C插件TextureReader插件 二、Build.cs文件 三、ModuleRules 四、TextureReader插件的构造 4.1ReadOnlyTargetRules的作用 4.2TextureReaderd的构造调用 4.3设置当前类的预编译头文件的使用模式 4.4PublicIncludePaths.AddRange与PrivateInclude…

探索C嘎嘎:初步接触STL

#1024程序员节&#xff5c;征文# 前言&#xff1a; 在前文小编讲述了模版初阶&#xff0c;其实讲述模版就是为了给讲STL提前铺垫&#xff0c;STL是C中很重要的一部分&#xff0c;各位读者朋友要知道它的份量&#xff0c;下面废话不多说&#xff0c;开始走进STL的世界。 目录&am…

指令系统 I(指令的格式、寻址)

一、指令系统 1. 指令集体系结构 指令&#xff08;机器指令&#xff09;是指示计算机执行某种操作的命令&#xff0c;是计算机运行的最小功能单位。一台计算机的所有指令的集合构成该机的指令系统&#xff0c;也称指令集。 指令系统是指令集体系结构&#xff08;ISA&#xf…

如何精准选择Yandex关键词

Hey小伙伴们&#x1f44b;&#xff0c;今天来聊聊如何精准选择Yandex关键词&#xff0c;让你的广告投放效果最大化&#xff01;&#x1f50d; 1.使用Yandex关键词工具&#xff1a;Yandex提供了关键词工具&#xff0c;如Yandex Keyword Planner和Yandex Wordstat&#xff0…

快速生成高质量提示词,Image to Prompt 更高效

抖知书老师推荐&#xff1a; 随着 AI 技术的不断发展&#xff0c;视觉信息与语言信息之间的转换变得越来越便捷。在如今的数字化生活中&#xff0c;图像与文字的交互需求愈发旺盛&#xff0c;很多人都希望能轻松将图像内容直接转化为文本描述。今天我们来推荐一款实用的 AI 工…

jmeter附件上传

可以采用录制的方式获取附件上传的http请求 普通参数 附件参数 文件名称: 方式一:如果只添项目名称,默认充jmeter的bin目录下获取 方式二:点击文件名称,再点击浏览,可以自己选择文件信息

OpenCV视觉分析之目标跟踪(7)目标跟踪器类TrackerVit的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 VIT 跟踪器由于特殊的模型结构而变得更快且极其轻量级&#xff0c;模型文件大约为 767KB。模型下载链接&#xff1a;https://github.com/opencv/…

如何用Python同时抓取多个网页:深入ThreadPoolExecutor

背景介绍 在信息化时代&#xff0c;数据的实时性和获取速度是其核心价值所在。对于体育赛事爱好者、数据分析师和投注行业而言&#xff0c;能否快速、稳定地抓取到实时比赛信息显得尤为重要。特别是在五大足球联赛中&#xff0c;能够在比赛进行时获得比分、控球率等实时数据&a…

【UBuntu20 配置usb网卡】 记录Ubuntu20配置usb网卡(特别是建立热点)

【UBuntu20 配置usb网卡】 Ubuntu20配置usb网卡&#xff08;特别是建立热点&#xff09; 一、 闲言碎语的前言 usb的外置网卡&#xff0c;相比Windows即插即用&#xff0c;Linux买回来一顿折腾&#xff0c;准备把过程梳理一下记录起来。 网卡的方案其实就那几家&#xff0c;…

前端开发模块VUE-Element UI学习笔记

前端开发模块VUE-Element UI学习笔记 文章目录 前端开发模块VUE-Element UI学习笔记 1、Element UI 简介2、Element UI 安装3、Icon 图标4、Button 按钮5、Link 超链接6、Radio 单选框7、Checkbox 多选框8、Input 输入框9、Select 下拉框10、Switch 开关 1、Element UI 简介 基…

Java面试经典 150 题.P169. 多数元素(005)

本题来自&#xff1a;力扣-面试经典 150 题 面试经典 150 题 - 学习计划 - 力扣&#xff08;LeetCode&#xff09;全球极客挚爱的技术成长平台https://leetcode.cn/studyplan/top-interview-150/ 题解&#xff1a; class Solution {public int majorityElement(int[] nums) …

如何在Python爬虫等程序中设置和调用http代理

在Python爬虫中为了更好地绕过反爬机制&#xff0c;获取网页信息&#xff0c;有时可能需要在Python中应用代理服务&#xff0c;这样做的目的就是防止自己的ip被服务器封禁&#xff0c;造成程序运行时中断连接&#xff0c;那么如何在python中设置代理呢&#xff1f; 我们通过几个…

海思MPP音视频总结

基础篇 1.常用图像格式介绍 常用图像像素格式 RGB 和 YUV。 1.1RGB RGB分类通常指的是将图像或颜色按照RGB&#xff08;红、绿、蓝&#xff09;颜色空间进行分组或分类。RGB图像格式通常包括RGB24&#xff08;RGB888&#xff09;、RGB32、RGBA、RGB565等。 RGB24是一种常用…