diff --git a/Image/chapter3/figure3.17.png b/Image/chapter3/figure3.17.png new file mode 100644 index 0000000..3e2717b Binary files /dev/null and b/Image/chapter3/figure3.17.png differ diff --git a/Image/chapter3/figure3.18.png b/Image/chapter3/figure3.18.png new file mode 100644 index 0000000..c4ef69c Binary files /dev/null and b/Image/chapter3/figure3.18.png differ diff --git a/Image/chapter3/figure3.19.png b/Image/chapter3/figure3.19.png new file mode 100644 index 0000000..18acb79 Binary files /dev/null and b/Image/chapter3/figure3.19.png differ diff --git a/Image/chapter3/figure3.20.png b/Image/chapter3/figure3.20.png new file mode 100644 index 0000000..7e00395 Binary files /dev/null and b/Image/chapter3/figure3.20.png differ diff --git a/Image/chapter3/figure3.21.png b/Image/chapter3/figure3.21.png new file mode 100644 index 0000000..9a2e081 Binary files /dev/null and b/Image/chapter3/figure3.21.png differ diff --git a/Image/chapter3/figure3.22.png b/Image/chapter3/figure3.22.png new file mode 100644 index 0000000..3c664f8 Binary files /dev/null and b/Image/chapter3/figure3.22.png differ diff --git a/Image/image3.16.png b/Image/image3.16.png deleted file mode 100644 index 8b6b316..0000000 Binary files a/Image/image3.16.png and /dev/null differ diff --git a/Image/image3.22.png b/Image/image3.22.png new file mode 100644 index 0000000..fcb8db7 Binary files /dev/null and b/Image/image3.22.png differ diff --git a/cn-Book/3.实现注意力机制.md b/cn-Book/3.实现注意力机制.md index 97e95c2..8a59078 100644 --- a/cn-Book/3.实现注意力机制.md +++ b/cn-Book/3.实现注意力机制.md @@ -3,8 +3,11 @@ 本章涵盖以下内容: + **探讨在神经网络中使用注意力机制的原因** + + **介绍一个基本的自注意力框架,并逐步深入到改进的自注意力机制** + + **实现一个因果注意力模块,使 LLM 能够一次生成一个token** + + **使用 dropout 随机掩盖部分注意力权重,以减少过拟合** @@ -211,7 +214,7 @@ tensor([0.9544, 1.4950, 1.4754, 0.8434, 0.7070, 1.0865]) > ```python > res = 0. > for idx, element in enumerate(inputs[0]): -> res += inputs[0][idx] * query[idx] +> res += inputs[0][idx] * query[idx] > print(res) > print(torch.dot(inputs[0], query)) > ``` @@ -328,7 +331,7 @@ tensor([0.4419, 0.6515, 0.5683]) -### 3.2 为所有输入的 token 计算注意力权重 +### 3.3.2 为所有输入的 token 计算注意力权重 在前一节中,我们计算了第二个输入元素的注意力权重和上下文向量,如图 3.11 中的高亮行所示。现在,我们将扩展该计算,以对所有输入计算注意力权重和上下文向量。 @@ -623,6 +626,404 @@ print(attn_weights_2) > + **Softmax函数的特性**:在计算注意力权重时,点积结果会通过Softmax函数转换为概率分布。而Softmax函数对输入值的差异非常敏感,当输入值较大时,Softmax的输出会趋近于0或1,表现得类似于阶跃函数(step function)。 > + **梯度消失问题**:当Softmax的输出接近0或1时,其梯度会非常小,接近于零(可以通过3.3.1小节中提到的Softmax公式推断)。这意味着在反向传播过程中,梯度更新幅度会很小,导致模型学习速度减慢,甚至训练停滞。 > -> 为了解决上述问题,在计算点积后,将结果除以嵌入维度的平方根(即 $` \sqrt{d\k\} `$),其中 dk 是键向量的维度。这样可以将点积结果缩放到适当的范围,避免Softmax函数进入梯度平缓区,从而保持梯度的有效性,促进模型的正常训练。$`\sqrt{\$4}`$ +> 为了解决上述问题,在计算点积后,将结果除以嵌入维度的平方根(即 $` \sqrt{dk} `$),其中 dk 是键向量的维度。这样可以将点积结果缩放到适当的范围,避免Softmax函数进入梯度平缓区,从而保持梯度的有效性,促进模型的正常训练。 + +好了,我们只剩最后一步,也就是计算上下文向量,如图3.17所示。 + + + +与第 3.3 节中我们通过输入向量的加权和来计算上下文向量相似,现在我们通过值向量的加权和来计算上下文向量。这里,注意力权重作为加权因子,用于衡量每个值向量的重要性。与第 3.3 节类似,我们可以通过矩阵乘法一步得到输出结果: + +```python +context_vec_2 = attn_weights_2 @ values +print(context_vec_2) +``` + +结果如下: + +``` +tensor([0.3061, 0.8210]) +``` + +到目前为止,我们只计算了一个上下文向量 z(2)。在下一节中,我们将完善代码,以计算输入序列中的所有上下文向量,从 z(1) 到 z(T)。 + +> [!NOTE] > -> +> **为什么使用`Q`、`K`和`V`向量?** +> +> 在注意力机制的上下文中,“键”(key)、“查询”(query)和“值”(value)这些术语来源于信息检索和数据库领域,在这些领域中也使用类似的概念来存储、搜索和检索信息 +> +> **查询**(query)类似于数据库中的搜索查询。它代表模型当前关注或试图理解的项(如句子中的某个词或 token)。通过查询,模型可以探查输入序列中的其他部分,以确定对它们应关注的程度。 +> +> **键**(key)类似于数据库中用于索引和查找的键。在注意力机制中,输入序列的每个元素(例如句子中的每个单词)都对应一个关联的‘键’。这些‘键’用于与‘查询’进行匹配。 +> +> **值**(value)类似于数据库中的键值对中的“值”。它表示输入项的实际内容或表示。当模型确定哪些键(即输入中的哪些部分)与查询(当前的关注项)最相关时,就会检索出对应的值。 + + + +### 3.4.2 实现一个简洁的自注意力机制 Python 类 + +在前面的章节中,我们逐步讲解了计算自注意力输出的多个步骤。这样做主要是为了便于分步骤展示每个环节的细节。在实际应用中,考虑到下一章将介绍的大语言模型的实现,采用如下方式将这段代码组织到一个 Python 类中会更为有利: + +```python +# Listing 3.1 A compact self-attention class +import torch.nn as nn +class SelfAttention_v1(nn.Module): + def __init__(self, d_in, d_out): + super().__init__() + self.d_out = d_out + self.W_query = nn.Parameter(torch.rand(d_in, d_out)) + self.W_key = nn.Parameter(torch.rand(d_in, d_out)) + self.W_value = nn.Parameter(torch.rand(d_in, d_out)) + + def forward(self, x): + keys = x @ self.W_key + queries = x @ self.W_query + values = x @ self.W_value + attn_scores = queries @ keys.T # omega + attn_weights = torch.softmax( + attn_scores / keys.shape[-1]**0.5, dim=-1) + context_vec = attn_weights @ values + return context_vec +``` + +在这段 PyTorch 代码中,`SelfAttention_v1` 是一个从 `nn.Module` 派生的类。`nn.Module` 是 PyTorch 模型的基础组件,提供了创建和管理模型层所需的必要功能。 + +`__init__` 方法初始化了用于计算查询(query)、键(key)和值(value)的可训练权重矩阵(`W_query`、`W_key` 和 `W_value`),每个矩阵都将输入维度 `d_in` 转换为输出维度 `d_out`。 + +前向传播过程在 forward 方法中实现,我们通过将查询(query)和键(key)相乘来计算注意力得分(attn_scores),并使用 softmax 对这些得分进行归一化。最后,我们使用这些归一化的注意力得分对值(value)加权,生成上下文向量。 + +我们可以按如下方式使用这个类: + +```python +torch.manual_seed(123) +sa_v1 = SelfAttention_v1(d_in, d_out) +print(sa_v1(inputs)) +``` + +由于输入包含六个嵌入向量,因此会生成一个用于存储这六个上下文向量的矩阵: + +``` +tensor([[0.2996, 0.8053], + [0.3061, 0.8210], + [0.3058, 0.8203], + [0.2948, 0.7939], + [0.2927, 0.7891], + [0.2990, 0.8040]], grad_fn=) +``` + +观察以上的输出,注意第二行 ([0.3061, 0.8210]) 的内容与上一节中的 `context_vec_2` 内容一致。 + +图 3.18 概述了我们刚刚实现的自注意力机制。 + + + +如图3.18所示,自注意力机制涉及可训练的权重矩阵 Wq、Wk 和 Wv。这些矩阵将输入数据转换为查询、键和值,它们是注意力机制的重要组成部分。随着训练过程中数据量的增加,模型会不断调整这些可训练的权重,在后续章节中我们会学习相关细节。 + +我们可以通过使用 PyTorch 的 `nn.Linear` 层来进一步改进 SelfAttention_v1 的实现。当禁用偏置单元时,`nn.Linear` 层可以有效地执行矩阵乘法。此外,使用 `nn.Linear` 替代手动实现的 `nn.Parameter(torch.rand(...))` 的一个显著优势在于,`nn.Linear` 具有优化的权重初始化方案,从而有助于实现更稳定和更高效的模型训练。 + +```python +# Listing 3.2 A self-attention class using PyTorch's Linear layers +class SelfAttention_v2(nn.Module): + def __init__(self, d_in, d_out, qkv_bias=False): + super().__init__() + self.d_out = d_out + self.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) + + def forward(self, x): + keys = self.W_key(x) + queries = self.W_query(x) + values = self.W_value(x) + attn_scores = queries @ keys.T + attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1) + context_vec = attn_weights @ values + return context_vec +``` + +SelfAttention_v2 的使用方法和 SelfAttention_v1 一样: + +```python +torch.manual_seed(789) +sa_v2 = SelfAttention_v2(d_in, d_out) +print(sa_v2(inputs)) +``` + +输出如下: + +``` +tensor([[-0.0739, 0.0713], + [-0.0748, 0.0703], + [-0.0749, 0.0702], + [-0.0760, 0.0685], + [-0.0763, 0.0679], + [-0.0754, 0.0693]], grad_fn=) +``` + +`SelfAttention_v1` 和` SelfAttention_v2` 的输出不同,因为它们的权重矩阵使用了不同的初始权重,这是由于 `nn.Linear` 层采用了一种更复杂的权重初始化方案。 + +> [!NOTE] +> +> **练习 3.1:比较`SelfAttention_v1`和 `SelfAttention_v2`** +> +> 请注意,`SelfAttention_v2` 中的 `nn.Linear` 层使用了一种不同的权重初始化方式,而 `SelfAttention_v1` 则使用 `nn.Parameter(torch.rand(d_in, d_out))` 进行初始化。这导致两种机制生成的结果有所不同。为了验证 `SelfAttention_v1` 和 `SelfAttention_v2` 的其他部分是否相似,我们可以将 `SelfAttention_v2` 对象中的权重矩阵转移到 `SelfAttention_v1` 中,从而使两者生成相同的结果。 +> +> 你的任务是将 `SelfAttention_v2` 实例中的权重正确分配给 `SelfAttention_v1` 实例。为此,你需要理解两个版本中权重之间的关系。(提示:`nn.Linear` 存储的是转置形式的权重矩阵。)分配完成后,你应该能观察到两个实例生成相同的输出。 + +在下一节中,我们将对自注意力机制进行增强,重点加入因果和多头机制。因果属性涉及对注意力机制的修改,防止模型访问序列中的后续信息。这在语言建模等任务中至关重要,因为在这些任务中,每个词的预测只能依赖之前的词。 + +多头组件将注意力机制分解为多个‘头’。每个头能够学习数据的不同方面,使模型能够同时关注来自不同表示子空间的不同位置的信息。这提高了模型在复杂任务中的性能。 + + + +## 3.5 使用因果注意力机制来屏蔽后续词 + +在本节中,我们将标准自注意力机制修改为因果注意力机制,这对于后续章节中开发大语言模型至关重要。 + +因果注意力(也称为掩蔽注意力)是一种特殊的自注意力形式。它限制模型在处理任何给定的 token 时,只能考虑序列中的前一个和当前输入,而不能看到后续的内容。这与标准的自注意力机制形成对比,后者允许模型同时访问整个输入序列。 + +因此,在计算注意力分数时,因果注意力机制确保模型只考虑当前 token 之前或之前的 token。 + +在 GPT 类大语言模型中,为了实现这一点,我们会对每个处理的 token 屏蔽其后续 token,即在输入文本中当前词之后的所有词,如图 3.19 所示。 + + + +如图 3.19 所示,我们对注意力权重的对角线上方部分进行了掩码操作,并对未掩码的注意力权重进行归一化,使得每一行的注意力权重之和为 1。在下一节中,我们将用代码实现这个掩码和归一化过程。 + + + +### 3.5.1 应用因果注意力掩码 + +在本节中,我们将编码实现因果注意力掩码。我们首先按照图 3.20 中总结的步骤开始。 + + + +如图3.20总结,我们可以利用上一节的注意力得分和权重来实现因果注意力机制,以获得掩码后的注意力权重。 + +在图 3.20 所示的第一步中,我们使用 softmax 函数计算注意力权重,如在前几节中所做的那样: + +```python +queries = sa_v2.W_query(inputs) #A +keys = sa_v2.W_key(inputs) +attn_scores = queries @ keys.T +attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=1) +print(attn_weights) + +#A 为了方便起见,我们复用上一节中 SelfAttention_v2 对象的query和key权重矩阵。 +``` + +这会得到以下注意力权重: + +``` +tensor([[0.1921, 0.1646, 0.1652, 0.1550, 0.1721, 0.1510], + [0.2041, 0.1659, 0.1662, 0.1496, 0.1665, 0.1477], + [0.2036, 0.1659, 0.1662, 0.1498, 0.1664, 0.1480], + [0.1869, 0.1667, 0.1668, 0.1571, 0.1661, 0.1564], + [0.1830, 0.1669, 0.1670, 0.1588, 0.1658, 0.1585], + [0.1935, 0.1663, 0.1666, 0.1542, 0.1666, 0.1529]], + grad_fn=) +``` + +我们可以使用 PyTorch 的 `tril` 函数来实现图 3.20 中的步骤 2,该函数生成一个掩码矩阵,使对角线以上的值为零: + +```python +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.]]) +``` + +现在,我们可以将这个掩码矩阵与注意力权重相乘,从而将对角线以上的值置零。 + +```python +masked_simple = attn_weights*mask_simple +print(masked_simple) +``` + +可以看到,对角线以上的元素已成功被置零: + +``` +tensor([[0.1921, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], + [0.2041, 0.1659, 0.0000, 0.0000, 0.0000, 0.0000], + [0.2036, 0.1659, 0.1662, 0.0000, 0.0000, 0.0000], + [0.1869, 0.1667, 0.1668, 0.1571, 0.0000, 0.0000], + [0.1830, 0.1669, 0.1670, 0.1588, 0.1658, 0.0000], + [0.1935, 0.1663, 0.1666, 0.1542, 0.1666, 0.1529]], + grad_fn=) +``` + +图 3.20 中的第三步是将注意力权重重新归一化,使得每一行的权重和再次等于 1。我们可以通过将每一行中的每个元素除以该行的总和来实现这一点: + +```python +row_sums = masked_simple.sum(dim=1, keepdim=True) +masked_simple_norm = masked_simple / row_sums +print(masked_simple_norm) +``` + +最终得到的注意力权重矩阵具有以下特性:主对角线以上的注意力权重被置零,每一行的权重和为 1: + +``` +tensor([[1.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], + [0.5517, 0.4483, 0.0000, 0.0000, 0.0000, 0.0000], + [0.3800, 0.3097, 0.3103, 0.0000, 0.0000, 0.0000], + [0.2758, 0.2460, 0.2462, 0.2319, 0.0000, 0.0000], + [0.2175, 0.1983, 0.1984, 0.1888, 0.1971, 0.0000], + [0.1935, 0.1663, 0.1666, 0.1542, 0.1666, 0.1529]], + grad_fn=) +``` + +> [!NOTE] +> +> **信息泄露** +> +> 当我们应用掩码并重新归一化注意力权重时,乍一看似乎未来的 token(即我们打算掩盖的部分)仍可能影响当前 token,因为它们的值仍然参与了 softmax 计算。然而,关键在于,当我们在掩码之后重新归一化注意力权重时,本质上是在一个更小的子集上重新计算 softmax(因为被掩盖的位置不会贡献到 softmax 的计算值中)。 +> +> softmax 算法的优雅之处在于,尽管最初所有位置都包含在分母中,但经过掩码处理和重新归一化后,被掩盖的位置的影响被抵消了——它们在任何实质性意义上都不会影响 softmax 得分。 +> +> 简而言之,在应用掩码和重新归一化之后,注意力权重的分布就像一开始只在未被掩码的位置上计算的一样。这确保了不会有来自未来(或其他掩码位置)的信息泄露,从而实现了我们的预期。 + +尽管通过上文的方式我们已经完成了因果注意力的实现,但我们还可以利用 softmax 函数的数学特性,更高效地计算掩码后的注意力权重,减少计算步骤,具体实现如图 3.21 所示。 + + + +Softmax 函数将输入值转换为概率分布。当一行中存在负无穷值(-∞)时,Softmax 函数会将这些值视为零概率。(从数学上讲,这是因为 e−∞ 接近于 0。) + +我们可以通过创建一个对角线以上全为 1 的掩码,然后将这些 1 替换为负无穷大(-inf)值,从而实现这种更高效的掩码技巧: + +```python +mask = torch.triu(torch.ones(context_length, context_length), diagonal=1) +masked = attn_scores.masked_fill(mask.bool(), -torch.inf) +print(masked) +``` + +由此生成以下掩码: + +``` +tensor([[0.2899, -inf, -inf, -inf, -inf, -inf], + [0.4656, 0.1723, -inf, -inf, -inf, -inf], + [0.4594, 0.1703, 0.1731, -inf, -inf, -inf], + [0.2642, 0.1024, 0.1036, 0.0186, -inf, -inf], + [0.2183, 0.0874, 0.0882, 0.0177, 0.0786, -inf], + [0.3408, 0.1270, 0.1290, 0.0198, 0.1290, 0.0078]], + grad_fn=) +``` + +现在我们只需要对这些掩码后的结果应用 softmax 函数,就可以完成了: + +```python +attn_weights = torch.softmax(masked / keys.shape[-1]**0.5, dim=1) +print(attn_weights) +``` + +如输出所示,每一行的值之和为 1,因此不再需要进一步的归一化: + +``` +tensor([[1.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], + [0.5517, 0.4483, 0.0000, 0.0000, 0.0000, 0.0000], + [0.3800, 0.3097, 0.3103, 0.0000, 0.0000, 0.0000], + [0.2758, 0.2460, 0.2462, 0.2319, 0.0000, 0.0000], + [0.2175, 0.1983, 0.1984, 0.1888, 0.1971, 0.0000], + [0.1935, 0.1663, 0.1666, 0.1542, 0.1666, 0.1529]], + grad_fn=) +``` + +现在,我们可以使用修改后的注意力权重,通过 `context_vec = attn_weights @ values` 来计算上下文向量,这在第 3.4 节中介绍过。不过,在下一节中,我们将首先介绍一个对因果注意力机制的细微调整,这一调整在训练大语言模型时有助于减少过拟合现象。 + + + +### 3.5.2 使用 dropout 遮掩额外的注意力权重 + +Dropout 在深度学习中是一种技术,即在训练过程中随机忽略一些隐藏层单元,实际上将它们“丢弃”。这种方法有助于防止过拟合,确保模型不会过于依赖任何特定的隐藏层单元组合。需要特别强调的是,Dropout 仅在训练过程中使用,训练结束后则会禁用。 + +在 Transformer 架构中(包括 GPT 等模型),注意力机制中的 Dropout 通常应用于两个特定区域:计算注意力得分之后,或将注意力权重应用于 value 向量之后。 + +在这里,我们会在计算完注意力权重之后应用 dropout 掩码(如图 3.22 所示),因为在实际应用中这是更为常见的做法。 + + + +在以下代码示例中,我们使用了50%的 dropout 率,这意味着屏蔽掉一半的注意力权重。(在后续章节中训练 GPT 模型时,我们将使用更低的 dropout 率,比如 0.1 或 0.2) + +在以下代码中,我们首先将 PyTorch 的 dropout 实现应用于一个由 1 组成的 6×6 张量以作说明: + +```python +torch.manual_seed(123) +dropout = torch.nn.Dropout(0.5) #A +example = torch.ones(6, 6) #B +print(dropout(example)) + + +#A 我们使用的dropout率为0.5 +#B 创建一个由1组成的矩阵 +``` + +如我们所见,约一半的数值被置零: + +``` +tensor([[2., 2., 0., 2., 2., 0.], + [0., 0., 0., 2., 0., 2.], + [2., 2., 2., 2., 0., 2.], + [0., 2., 2., 0., 0., 2.], + [0., 2., 0., 2., 0., 2.], + [0., 2., 2., 2., 2., 0.]]) +``` + +当对注意力权重矩阵应用 50% 的 dropout 时,矩阵中一半的元素会被随机设置为零。为了补偿有效元素的减少,矩阵中剩余元素的值会被放大 1/0.5 = 2 倍。这个缩放操作至关重要,可以在训练和推理阶段保持注意力机制的整体权重平衡,确保注意力机制在这两个阶段的平均影响保持一致。 + +> [!TIP] +> +> **个人思考:** 读到这一段时,我有些不解,Dropout相当于丢弃一定比例的注意力权重,这表明对输入中的某些token关注度降为0了(完全不关注),这样的处理方式难道对最终的预测效果没有影响么?另外如何理解Dropout之后的缩放操作是为了保持注意力在不同阶段的平衡? +> +> 经过查阅额外的资料及深度思考,我觉得可以从以下几个方面理解上述的疑问: +> +> 1. **Dropout 的目的:提高模型的泛化能力** +> +> dropout 的设计初衷是**提高模型的泛化能力**。通过随机丢弃一部分神经元或注意力权重,dropout 迫使模型在每次训练时学习略有不同的表示方式,而不是依赖某一特定的注意力模式。这种随机化的训练方式可以帮助模型在**面对新数据时更具鲁棒性**,减少过拟合的风险。 +> +> 2. **注意力机制的冗余性** +> +> 在 Transformer 的注意力机制中,模型通常会对多个 token 进行注意力计算,实际上会有一些冗余信息。也就是说,**不同 token 之间的信息通常会有部分重叠**,并且模型能够从多个来源获取类似的信息。在这种情况下,dropout 随机丢弃一部分注意力权重并不会完全破坏模型的性能,因为模型可以依赖于其他未被丢弃的注意力路径来获取所需信息。 +> +> 3. **缩放操作的作用** +> +> 在应用 dropout 时,一部分注意力权重被随机置零(假设 dropout 率为 p)。剩余的权重会被放大,其放大倍数为 $ \frac{1}{1-p} $。放大后的权重记为 z′: +> +> $$ z_{i}^{\prime}=\frac{z_{i}}{1-p} \quad \text { (对于未被置零的权重) } $$ +> +> +> +> + +现在,让我们将 dropout 应用于注意力权重矩阵本身: + +```python +torch.manual_seed(123) +print(dropout(attn_weights)) +``` + +由此生成的注意力权重矩阵中,部分元素被置零,剩余的元素重新进行了缩放: + +``` +tensor([[2.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], + [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], + [0.7599, 0.6194, 0.6206, 0.0000, 0.0000, 0.0000], + [0.0000, 0.4921, 0.4925, 0.0000, 0.0000, 0.0000], + [0.0000, 0.3966, 0.0000, 0.3775, 0.0000, 0.0000], + [0.0000, 0.3327, 0.3331, 0.3084, 0.3331, 0.0000]], + grad_fn= +``` + +请注意,由于操作系统的不同,生成的 dropout 输出可能看起来有所差异;您可以在 PyTorch 问题跟踪页面上查看更多关于此不一致性的信息,网址为:[https://github.com/pytorch/pytorch/issues/121595](https://github.com/pytorch/pytorch/issues/121595)。 + +在理解了因果注意力和 dropout 掩码的基础上,接下来的部分中我们将开发一个简洁的 Python 类,以便高效应用这两种技术。 +