【引】收到图灵寄来的两本书《大模型应用开发极简入门》和《从零构建大模型》,重新点燃了自己深入理解大模型内部机制的热情,不能只知其然而不知其所以然,于是重温大模型核心的transformer架构, 尝试用25个公式进行解读,遂成此文。 ![]() Transformer是一种用于处理序列数据的深度学习模型,特别适用于自然语言处理任务。其独特之处在于它们的自我注意力机制。这种机制允许模型确定不同序列部件的重要性,关注输入序列中最相关部分的能力导致了各种自然语言处理任务的显著进步。 1.Transformer的编解码器框架Transformer建立在一个编解码器框架的基础上,该框架旨在有效地处理序列数据,并通过一种称为自注意力机制来捕获长期依赖关系。编码器和解码器是Transformer的两个主要部件。可以将编码器想象成一个团队,处理所有来宾信息并将其组织成有意义的类别。它获取输入序列并创建编码,突出显示输入中有哪些部分彼此相关。另一方面,解码器就像是负责生成最终事件计划的团队,使用来自编码器的信息,并利用先前做出的决定,逐步生成输出序列。 ![]() 每个编码器和解码器由多个相同的层组成,通常为十二层。每个编码器层有两个主要的子层: 一个多头自注意力机制和一个全连接的前馈网络。每个子层都有剩余的连接和层标准化,这有助于稳定和加快训练过程。解码器层是相似的,但有一个额外的多头注意力子层,允许参与编码器的输出。 1.1 编码器编码器的核心是多头注意力机制。把它想象成一组专家,每个专家都关注输入序列的不同方面。多头注意力不再是单一的专家,而是将输入分成多个较小的注意头。每个头独立地将输入投影到查询、键和值向量中,计算注意力分数,并组合结果。然后将所有头的输出连接起来并进行转换以产生最终结果。这个过程允许模型捕获输入序列中的各种关系。 编码器遵循多头注意力机制,包括位置全连通的前馈神经网络。这个部分就像一个分析师,他接受专家提供的信息并对其进行改进。它由两个线性变换组成,中间有一个 ReLU 激活函数,对序列中的每个位置独立操作。这有助于将与会者信息转换为更丰富的表示形式,向模型添加非线性,并允许它学习复杂的模式。 编码器中的每个子层,包括多头注意力和前馈网络,随后是剩余连接和层归一化。剩余连接,也称为跳跃连接,通过允许梯度直接通过网络,有助于缓解渐变问题。这是通过将子层的输入添加到其输出来完成的。然后,层规范化通过规范化子层的输出来稳定并加快训练过程,从而确保值的分布保持一致。 1.2 解码器解码器与编码器有一些相似之处,但也引入了一些关键的差异,以促进其在产生输出序列中的作用。就像编码器,解码器有多个相同的层,每一层都包含一个子层,其中包括多头注意力和一个前馈神经网络。编码器和解码器都使用剩余连接和层归一化来增强学习和保持稳定的梯度。 然而,在编码器和解码器之间存在着关键的区别。主要的区别是在每个解码器层中加入第三个子层,称为掩蔽多头注意力机制。这个额外的子层允许解码器专注于先前生成的token,同时保持序列生成的自回归特性。此外,解码器的多头注意力机制被设计用于处理两个信息源: 输入序列(来自编码器)和部分生成的输出序列。 掩蔽多头注意力机制是解码器所特有的。它的工作方式类似于编码器中使用的标准多头注意力,但包括一个额外的掩蔽步骤。这种屏蔽确保了解码器在训练期间不能在序列中查看未来的令牌,从而保留了模型的自回归特性。在注意力计算过程中,对输入序列应用一个掩模,将未来标记的注意力分数设置为负无穷大。这有效地防止了模型在生成当前令牌时考虑未来令牌,确保每个令牌仅基于过去令牌和编码的输入序列生成。 解码器的多头注意力子层,包括掩蔽的多头注意力和标准的多头注意力(注意编码器的输出) ,共同生成输出序列。掩蔽多头注意力允许模型一次产生一个token,而标准的多头注意力集成了来自编码器的信息,使得解码器能够产生连贯和上下文准确的输出。 1.3 注意力机制注意力机制是Transformer高效运行的核心创新。它允许模型动态地关注输入序列的不同部分,使它能够捕获依赖关系,而不管依赖关系在序列中的距离如何。Transformer的注意力机制主要有两种: 自我注意力和交叉注意力。 ![]() 自我注意,也称为内注意,用于编码器和解码器层。在自我注意中,序列中的每个元素都关注所有其他元素,包括它自己。这意味着,例如,一个句子中的每个单词都可以考虑其他每个单词,以构建更丰富的上下文表示。 交叉注意在解码器处理编码器输出时使用。这种机制允许解码器集中于输入序列的相关部分,同时生成输出序列中的每个令牌。在交叉注意力中,解码器序列的每个元素都注意到编码器输出的元素,整合来自输入序列的信息以产生最终输出。 自我注意和交叉注意的核心计算是缩放的点积。首先,将每个输入元素投影到三个向量中: Query (Q)、 Key (K)和 Value (V)。然后,在Query 向量上点积key向量,以计算注意力得分,这表明要在输入的不同部分放置多少注意力。这些点积按key向量维数的平方根进行缩放,以稳定训练过程中的梯度。按比例缩放的分数然后通过一个softmax函数将它们转换成概率,强调输入中最相关的部分。最后,value向量根据这些注意力得分加权,并相加得出每个输入元素的最终输出。 想象一下,你正在试图理解一个有多个人物和事件同时发生的故事。自注意力可以帮助你考虑每一个角色和每一个其他角色和事件,让你对故事有一个全面的理解。另一方面,交叉注意力专注于故事的具体细节,以确保随着你的进步,能将最相关的信息融入到你的理解中。 2. 注意力机制的数学公式注意力机制决定了一个词在一个序列中应该给每个其他词多少注意力。这从注意力分数的计算开始。对于输入中的每个单词,我们需要确定它应该对其他每个单词给予多少关注。这是使用三个矩阵完成的: Query (Q)、 Key (K)和 Value (V)。 输入序列中的每个单词首先使用 W_Q、 W_K 和 W_V 的学习权重矩阵投影到这三个矩阵中: ![]() 这里,X 是输入序列矩阵,其中每一行对应一个单词嵌入。例如,如果你有一个包含10个单词的句子,并且每个单词都由一个512维向量表示,那么 X 就是一个10x512的矩阵。将输入序列 X 乘以这些权矩阵,将嵌入的每个单词投影到三个新的空间(Q、K和V)中,为注意力机制设置了阶段。 接下来,我们用查询向量 Q 和键向量 K 的点乘来计算注意力得分。这衡量了查询和关键向量之间的相似性: ![]() 这个操作生成一个原始注意力得分矩阵,其中每个元素代表一对单词之间的注意力得分。
因此,上述公式的结果是注意力得分矩阵。此矩阵中的每个元素表示输入序列中一对单词之间的注意力得分,指示一个单词应该给另一个单词多少关注。 然而,随着向量维数的增加,点积的值可能会变得非常大,导致在softmax操作期间出现较小的梯度。为了抵消这个影响,可以用键向量 d_k 的平方根来衡量注意力得分: ![]() 因此,如果每个键向量有64个维度,那么关键向量维度的平方根就是 sqrt {64} = 8。这个比例因子有助于稳定梯度,使softmax更加有效。softmax激活函数将得分转换为概率,确保得分总和为1,这突出了单词之间最重要的关系: ![]() softmax操作使缩放后的分数归一化了。Softmax对每个缩放得分进行指数化,并将其除以指数化分数之和,将得分转换为概率。这强调了最相关的单词,同时确保所有概率的总和为1。 ![]() 注意力机制的最后一步是使用这些注意力权重来计算值向量 V 的加权和: ![]() 在这里,注意力权重是从softmax激活函数中获得的注意力权重矩阵。每个元素代表注意到一个特定单词的概率。V 是从 XW_V 得到的值矩阵。V 中的每一行表示序列中每个单词的值向量。将注意力权重乘以 V 得到值向量的加权和。这个输出是每个单词的新表示形式,包含了整个输入序列的上下文。 Transformer不使用单一的注意力分数,而是使用多头注意力分数。这意味着输入序列被分成多个较小的部分,每个部分通过上述注意力机制独立处理, 每一部分都叫做头。对于每个头,我们有不同的权矩阵 W_ Q^i,W_K^i 和 W_V^i。在处理每个头之后,我们将它们的输出连接起来,并使用另一组权重 W_O 将它们投影回单个向量空间: ![]() 这种多头注意力允许模型同时关注序列的不同部分,捕获广泛的依赖关系。 3. FFN 的数学原理在多头注意力之后,每个词的表示数据通过位置逐元素前馈网络进一步处理。这个网络独立操作每个单词的表示,并对每个单词应用相同的转换。前馈网络由两个线性变换组成,中间有一个ReLU激活函数: ![]() 这里 x 是注意力机制的输入,W _ 1和 W _ 2是权矩阵,b _ 1和 b _ 2是偏差。这种激活函数引入了非线性,使网络能够学习更复杂的模式。实际上,ReLU 就像一个看门人,不变地传递正值,但阻止负值,把它们变成零。 3.1 层归一化和残差连接为了稳定和加速训练,编码器中的每个子层(多头注意力和前馈网络)后面都有一个残差连接和层归一化。 残差连接有助于缓解逐渐消失的梯度问题,并允许对更深的网络进行训练。基本思想是将子层的输入添加到其输出中。在数学上,它可以表示为: ![]() 这里:
通过将输入 x 添加到子层的输出中,即使子层转换引入了重大更改,模型也可以确保原始信息得到保留。 将层归一化应用于剩余连接的组合输出,以标准化值。这个过程通过确保每一层的输入具有一致的值分布来帮助稳定训练。层归一化的公式是: ![]() 把条件分解一下:
归一化输出确保每个子层接收平均值为0和方差为1的输入,这有助于保持稳定的梯度,提高了训练过程的效率和有效性。 3.2 位置编码Transformer同时处理序列中的所有令牌,而不是像递归模型(RNN)那样逐个处理。虽然这种并行处理是有效的,但这意味着模型本身并不理解令牌的顺序。为了给模型一个位置感,我们使用位置编码。此编码将有关序列中每个标记的位置的信息添加到其嵌入中。 位置编码是在编码器和解码器栈处理它们之前加入到输入嵌入中的数学表示。最常用的方法是使用不同频率的正弦和余弦函数来生成这些编码。位置编码的公式如下: ![]()
这些正弦和余弦函数确保序列中的每个位置都有唯一的编码。此外,使用不同的频率允许模型区分不同的位置,以一种既平滑又连续的方式。这对于模型有效地了解令牌的相对位置非常重要。 使用正弦函数和余弦函数有几个好处:
4. 编码器的数学原理编码器将输入序列转换为一组经过编码的表示形式。首先,输入序列 X 嵌入到一个高维空间中。这种嵌入捕获了序列中每个单词的意思。为了帮助模型理解序列中每个单词的位置,我们向这些嵌入添加位置编码,结果是 X_pos: ![]() ![]() 然后,我们将原始输入 X _ pos 添加回该输出(称为残差连接的过程) ,并应用层标准化来稳定训练。这样我们就得到了 Z _ add _ norm _ 1: ![]()
![]() 我们再次将输入 Z _ add _ norm _ 1添加回这个输出,并应用层标准化来得到 Z _ encoder _ output: ![]() 这个最终输出 Z _ encoder _ output 是输入序列的经过编码的表示形式,准备由解码器处理。 5. 解码器的数学原理解码器通过使用已编码的输入和先前生成的令牌生成输出序列。首先,输入序列 Y (右移)嵌入到一个高维空间。右移是指在开头插入一个特殊的开始标记(通常是' < SOS >’表示“启动顺序”) ,然后删除最后一个标记。然后在训练期间将这个移位序列用作解码器的输入。这样做的目的是确保解码器只能使用已经生成(或预测)的令牌,而不能使用任何未来的令牌,从而保持模型的自回归属性。 接下来,我们将位置编码添加到这些嵌入中,形成 Y _ pos: ![]() 该序列通过掩蔽的多头注意力机制传递,该机制阻止模型查看序列中的未来标记输出是 Z_masked_attn: 然后,我们将原始输入 Y _ pos 添加回这个输出,并应用层标准化得到 Z _ add _ norm _ 2: 接下来,该输出通过交叉注意力机制传递,该机制允许解码器将注意力集中在来自编码器的编码输入序列的相关部分。结果是 Z _ cross _ attn: ![]() 我们将输入 Z _ add _ norm _ 2添加回这个输出并应用层标准化,结果是 Z _ add _ norm _ 3: ![]() 这个输出通过另一个位置前馈网络传递,产生 Z _ ffn _ decder: ![]() 最后,我们将输入 Z _ add _ norm _ 3添加回这个输出,并应用层标准化来获得最终的解码器输出 Z _ decder _ output: 这个最终的输出,Z _ decder _ output,然后用来通过一个线性层和一个Softmax激活函数生成最终的输出序列。 6.一句话小结本文从 Transformer 的整体结构开始,对每个组件的计算方式以数学公式表达, 一共25个公式,了解了这些数学原理, 无论是看开源代码还是手写Transformer都大有裨益。 ps. 当然,深入理解大模型的运行机制仍然是未来更好的开发基于大模型的应用,如果希望快速入门的话,《大模型应用开发极简入门》(第2版)或许是个不错的选择。 |
|