步骤 1 – 定义数据集
为了演示目的,我这里的数据集仅包含三个英文句子,使用非常小的数据集来直观地执行数值计算。现实应用中,会使用更多更大的数据集来训练神经网络模型,例如我们所熟知的ChatGPT,用来训练他的数据达到 570 GB。
我们的整个数据集仅包含三个句子
这里由于数据集较小,因此数据清洗的工作量也是相对较小。而对于570GB的数据的清洗而言,将是非常麻烦的事情。
第 2 步 – 确定词汇量
词汇量决定了数据集中唯一单词的总数。可以使用以下公式计算,其中N是数据集中的单词总数。
vocab_size
公式,其中 N 是总单词数
需要将数据集分解成单个单词,然后求的单词数 N。
计算变量N
得到N之后,去除重复项,然后统计唯一单词的数量,就可以确定词汇量。
计算词汇量
因此,词汇量为23 ,因为我们的数据集中有23 个唯一单词。
步骤 3 – 编码
现在,我们需要为每个唯一的单词分配一个唯一的编号。
唯一词语编码
由于我们将单个标记视为单个单词并为其分配一个数字,目前一些大模型使用以下公式将单词的一部分视为单个标记:1 Token = 0.75 Word
在对整个数据集进行编码之后,就该选择我们的输入并开始使用transformer架构了。
步骤 4 – 计算嵌入
我们从语料库中选择一个句子,用 transformer 架构进行处理该句子。
将句子输入transformer
我们已经选定了输入,接下来需要为它计算一个嵌入向量。原始论文为每个输入词使用一个512 维的嵌入向量。
原始论文采用 512 维向量
这里,为了方便演示,我们使用较小维度的嵌入向量来可视化计算的进行方式。这里选用6
嵌入向量的维度。
输入的嵌入向量
嵌入向量的这些值介于 0 和 1 之间,并且在开始时是随机填充的(随机初始化)。随着transformer开始理解单词之间的含义,嵌入向量也会随之更新。
第 5 步 – 计算位置嵌入
现在我们需要为输入计算位置嵌入。位置嵌入有两个公式,具体取决于每个单词的嵌入向量第 i 个值的位置。
位置嵌入公式
我们的输入句子是**“when you play the game of thrones”,起始词是“when”**,起始索引(POS)值为0
,维度(d)为6
,因此i
是从0
到5
。那么我们计算输入句子第一个单词的位置嵌入。
单词的位置嵌入:when
类似地,我们可以计算输入句子中所有单词的位置嵌入。
计算输入的位置嵌入(计算值已四舍五入)
第 6 步 – 连接位置和词嵌入
在计算出位置嵌入之后,我们需要将词嵌入和位置嵌入相加。
两个嵌入连接
由两个矩阵(词嵌入矩阵和位置嵌入矩阵)组合得到的矩阵将被视为编码器部分的输入。
第 7 步 – 多头注意力
多头注意力由许多单头注意力组成,每个单头注意力负责不同的信息提取和关注点。多头注意力技术将输入的序列分别映射到一组虚拟的查询、键和值向量上。这些映射得到的向量将被用于计算注意力权重并生成上下文表示。每个头都会学习不同的映射,从而能够关注不同的特征。
我们需要组合多少个单头注意力取决于我们自己。例如,Meta 的 LLaMA LLM 在编码器架构中使用了 32 个单头注意力。下面是单头注意力的示意图。
输入信号 --->| Q |-----\
输入信号 --->| K |----MultiHead Attention---> 输出 |
输入信号 --->| V |-----/
Transformer 中的注意力机制
有三个输入:查询(query)、键(key)和值(value)。****每个矩阵都是通过将我们之前通过添加词嵌入和位置嵌入矩阵计算出的同一矩阵的转置与不同的权重矩阵集相乘而获得的。
可以通过以下步骤进行计算:
- 获取查询矩阵(query)和键(key)矩阵的转置。
- 将查询矩阵(query)与键(key)矩阵的转置相乘,得到注意力分数矩阵。
- 将注意力分数矩阵与值(value)矩阵相乘,得到最终输出矩阵。
在实际实现中,你需要使用矩阵乘法的算法来执行这些步骤。你也需要考虑权重矩阵的初始化和学习过程,以及在实际应用中如何处理随机值的更新。
假设,为了计算查询矩阵,权重矩阵的行数必须与转置矩阵的列数相同,而权重矩阵的列数可以是任意的;例如,我们假设权重矩阵中的列4
。权重矩阵中的值是随机的 0
和 1
,当我们的转换器开始学习这些单词的含义时,这些值稍后会更新。
计算查询矩阵
类似地,我们可以使用相同的程序计算键和值矩阵,但权重矩阵中的值必须不同。
计算键和值矩阵
因此,矩阵相乘后,得到了结果**查询(query)**、**键(key)和值(value)**:
查询、键、值矩阵
现在有了所有三个矩阵,开始逐步计算单头注意力。
计算公式:
Query 和 Key 之间的矩阵乘法
为了缩放结果矩阵,我们必须重用嵌入向量的维度,即6
。
将结果矩阵缩放至5维
问题来了:这里为啥要除以:
这个问题在《Attention is All Your Need》的原始论文中是给出了一个粗略的答案的。
While for small values of the two mechanisms perform similarly, additive attention outperforms dot product attention without scaling for larger values of . We suspect that for large values of , the dot products grow large in magnitude, pushing the softmax function into regions where it has extremely small gradients . To counteract this effect, we scale the dot products by .
作者说,当的值变大的时候,softmax 函数会造成梯度消失问题,所以设置了一个 softmax 的 temperature 来缓解这个问题。这里 temperature 被设置为了,也就是乘上。
这个回答当然没什么问题,但是接下来就会再问两个问题:
- 为什么会导致梯度消失?
- 为什么是 , 有更好的值么?
第一个问题。
- 如果 变大,方差会变大。
- 方差变大会导致向量之间元素的差值变大。
- 元素的差值变大会导致 softmax 退化为 argmax, 也就是最大值 softmax 后的值为 1, 其他值则为0。
- softmax 只有一个值为 1 的元素,其他都为 0 的话,反向传播的梯度会变为 0, 也就是所谓的梯度消失。
第二个问题。
scale 的值为其实是把归一化成了一个 均值为 0, 方差为 1 的向量。
至于是不是最好呢?不好说,因为参数的分布我们不太清楚。苏神曾经试图求解了一些常用分布的最佳 scale 值,感兴趣的可以看下:https://spaces.ac.cn/archives/9812
下一步是Mask,这是可选的,我们不会对其进行计算。使用Mask就像告诉模型只关注在特定时间点之前发生的情况,而不是在确定句子中不同单词的重要性时窥视未来。这将有助于模型逐步理解事物,而不会通过预知未来来进行作弊。
因此,我们现在将在缩放的结果矩阵上应用softmax操作。
softmax操作通常用于多分类问题,它可以将输入的向量映射为概率分布,使得向量的每个元素都在0到1之间,并且所有元素的和为1。这有助于模型输出各类别的概率。
对结果矩阵应用 softmax
执行最后的乘法步骤以获得单头注意力的结果矩阵。
计算单头注意力的最终矩阵
多头注意力由多个单头注意力组成,可视化如下所示:
Transformer 中的多头注意力机制
每个单头注意力机制有三个输入:查询、键和值,每三个都有一组不同的权重。一旦所有单头注意力机制输出其结果矩阵,它们将全部连接起来,最终的连接矩阵将再次通过将其与一组用随机值初始化的权重矩阵相乘来进行线性变换,这些权重矩阵稍后将在 Transformer 开始训练时进行更新。
多头注意力计算得到的结果矩阵是通过对输入的原始矩阵进行多个注意力权重的计算得到的,每个注意力权重都对应一个不同的“头”,最终会得到多个结果矩阵。将这些结果矩阵与原始矩阵相加的目的是为了将多个注意力头的信息整合起来,以更好地捕捉输入数据的复杂关系和特征。这种加和操作能够增强模型对于不同输入之间的关联性和复杂性的建模能力,从而提高模型的性能和鲁棒性。
因为在我们的例子中,使用多头注意力如下图所示。
单头注意力机制 vs 多头注意力机制
无论是单头注意力还是多头注意力,结果矩阵都需要通过乘以一组权重矩阵再次进行线性变换。
归一化单头注意力矩阵
确保线性权重矩阵的列数必须等于我们之前计算的矩阵(词嵌入+位置嵌入)矩阵的列数,因为下一步,我们将把结果的规范化矩阵与(词嵌入+位置嵌入)矩阵相加。
多头注意力的输出矩阵
第 8 步 – 残差连接和归一化
多头注意力计算得到的结果矩阵,将其与原始矩阵相加。
多头注意力计算得到的结果矩阵与原始矩阵相加的目的是为了增强模型对于关键信息的学习和理解能力。在多头注意力机制中,对输入进行多组不同的注意力权重计算,可以使模型更好地捕捉输入数据中的关联信息和上下文,提高模型的表达能力。
通过将注意力计算得到的结果矩阵与原始矩阵相加,可以将原始矩阵中的信息与注意力得到的重要信息结合起来,从而使模型更好地利用重要的信息进行学习和预测,提高模型的性能和泛化能力。这种操作可以增加模型对输入数据中相关性和重要性的感知,从而提高模型对输入数据的处理效果。
注意力结果与原始矩阵相加
接下来将相加后的矩阵进行归一化操作,需要逐行计算每一行的平均值和标准差。
使用Layer Normalization来对每个注意力机制层的输出进行归一化处理,以帮助模型更快地收敛并减小梯度消失或爆炸的问题。
计算平均值和标准差
我们用矩阵的每个值减去相应的行平均值,然后除以相应的标准差。
对结果矩阵进行归一化
在transformer模型的自注意力机制中,为了避免分母为零并且不至于使整个项无穷大,通常会添加一个小的误差值(例如1e-6)到分母中进行归一化。这个小的误差值可以确保数值稳定性并且避免因为分母为零导致的数值计算错误。
第 9 步 – 前馈网络
矩阵归一化后,将通过前馈网络进行处理。我们将使用一个非常基本的网络,该网络仅包含一个线性层和一个 ReLU 激活函数层。
前馈网络
首先,我们需要通过将最后计算的矩阵与一组随机初始化后的权重矩阵相乘来计算线性层,该权重矩阵将在 Transformer 开始学习时更新,然后将结果矩阵添加到同样包含随机值的偏置矩阵中。
计算线性层
计算完线性层之后,我们需要将其穿过ReLU层,并利用其公式。
计算 ReLU 层
第 10 步 – 再次残差连接和归一化
一旦我们从前馈网络获得结果矩阵,就需要将其添加到从前面的残差连接和归一化步骤获得的矩阵中,然后使用行平均值和标准差对其进行规范化。
前馈网络后Add&Norm
该残差连接和归一化步骤的输出矩阵将作为解码器部分中存在的多头注意力机制之一中的Query和Key矩阵,可以通过从残差连接和归一化向外追踪到解码器部分来轻松理解它。
步骤 11 – 解码器部分
到目前为止,我们已经计算了编码器部分的所有步骤,从编码数据集到将矩阵传递给前馈网络。
编码器负责将输入序列转换为隐藏表示。Transformer每个编码器本质上是由多个编码器层组成的堆叠结构,每个编码器层由两个子层组成:多头自注意力机制和全连接前馈神经网络。
- 多头自注意力机制:用于捕捉输入序列中不同位置之间的相关性。这个层能够同时计算输入序列中每个元素之间的注意力权重,以便对每个位置的信息进行编码。它允许模型在同时考虑输入序列中的所有位置,并学习不同位置之间的依赖关系。在自注意力机制中,输入序列的每个元素都会与所有其他元素进行注意力计算,从而得到每个元素的表示。
- 全连接前馈神经网络:用于每个位置的表示学习更丰富的特征表示。这个层对每个位置的隐藏表示进行独立的转换,再进行残差连接和层归一化。这一子层对编码器层的每个位置的表示应用两层线性变换和激活函数,将输入转换为更抽象的表示。
编码器的功能是将输入序列转换为一系列隐藏表示,其中包含了输入序列的语义和上下文信息。这些隐藏表示能够被后续的解码器使用,以生成目标序列或执行其他任务,如语言建模、机器翻译等。
Transformer 的解码器部分都将涉及类似的矩阵乘法。
看一下我们的transformer架构。到目前为止我们已经涵盖了什么以及我们还需要涵盖什么:
编码器和解码器
我们不会计算整个解码器,因为其大部分部分包含与我们在编码器中已经完成的计算类似的计算。详细计算解码器只会因重复步骤而使本我冗长。相反,我们只需要关注解码器的输入和输出的计算。
训练时,解码器有两个输入。一个来自编码器,其中最后一个残差连接和归一化的输出矩阵作为Query和Key用于解码器部分的第二个多头注意层。以下是它的可视化效果(来自batool haider):
而Value矩阵则来自解码器经过第一次残差连接和归一化步骤之后的结果。
解码器的第二个输入是预测文本。如果你还记得的话,我们对编码器的输入是,when you play game of thrones
所以解码器的输入是预测文本,在我们的例子中是you win or you die
。
但是预测的输入文本需要遵循标准的标记,以使转换器知道从哪里开始和在哪里结束。
编码器和解码器的输入比较
其中<start>
和<end>
是引入的两个新token。此外,解码器每次只接受一个token作为输入。这意味着<start>
将作为输入,并且you
必须是它的预测文本。
解码器输入词
我们已经知道,这些嵌入填充了随机值,这些随机值稍后将在训练过程中更新。
按照我们之前在编码器部分计算的相同方式计算其余块。
计算解码器
在深入探讨更多细节之前,我们需要先用一个简单的数学例子来了解什么是Mask多头注意力。
第 12 步 --了解 Mask 多头注意力机制
在 Transformer 中,Mask多头注意力就像一个聚光灯,模型用它来聚焦句子的不同部分。它很特别,因为它不会让模型通过查看句子后面的单词来作弊。这有助于模型逐步理解和生成句子,这在交谈或将单词翻译成另一种语言等任务中非常重要。
假设我们有以下输入矩阵,其中每行代表序列中的一个位置,每列代表一个特征:
用于屏蔽多头注意力的输入矩阵
现在,了解一下具有两个头的掩蔽多头注意力组件:
- **线性投影(查询、键、值):**假设每个头的线性投影为:头1:_Wq_1、_Wk_1、_Wv_1 和 头2:Wq2、_Wk_2、_Wv_2
- **计算注意力得分:**对于每个头,使用查询和键的点积计算注意力得分,并应用掩码以防止关注未来的位置。
- **应用Softmax:**应用softmax函数来获得注意力权重。
- **加权总和(值):**将注意力权重乘以值以获得每个头的加权和。
- **连接和线性变换:**连接两个头的输出并应用线性变换。
我们来进行一个简单的计算:
假设有两个条件
- _Wq_1 = _Wk_1 = _Wv_1 = _Wq_2 = _Wk_2 = _Wv_2 = I,即单位矩阵。
- Q = K = V = 输入矩阵
Mask多头注意力机制(双头)
连接步骤将两个注意力头的输出组合成一组信息。假设你有两个朋友,他们各自就一个问题给你建议。连接他们的建议意味着将两条建议放在一起,这样你就能更全面地了解他们所建议的内容。在 Transformer 模型的上下文中,此步骤有助于从多个角度捕获输入数据的不同方面,从而为模型提供更丰富的表示,以供进一步处理。
步骤 13 – 计算预测单词
解码器的最后一个加法和范数块的输出矩阵必须包含与输入矩阵相同的行数,而列数可以是任意的。这里我们使用6
。
解码器的残差连接和归一化输出
解码器的最后一个残差连接和归一化块结果矩阵必须被平坦化,以便将其与线性层匹配,以找到我们的数据集(语料库)中每个唯一单词的预测概率。
展平最后一个残差连接和归一化块矩阵
该扁平层将通过线性层来计算数据集中每个唯一单词的logit (分数)。
计算 Logits
一旦我们获得了logits,我们就可以使用softmax函数对它们进行归一化,并找到包含最高概率的单词。
查找预测单词
因此根据我们的计算,解码器预测的单词是you
。
解码器的最终输出
这个预测的词you
将被视为解码器的输入词,这个过程一直持续到<end>
预测出 token 为止。
重点
- 上述示例简单易懂,不涉及特定时期或其他编程语言要求,可通过Python等实现可视化。
- 该示例展示了训练过程,并指出通过矩阵方法难以直观地进行评估或测试。
- 利用掩蔽的多头注意力可以防止transformer观察未来,有助于避免过拟合模型。
写在最后
在本文中,我演示了使用矩阵方法进行基本数学运算的方法。除了介绍了位置编码、softmax和前馈网络,最关键的是多头注意力。