目录
写在前面
本文将聚焦于 Transformer 这一机器学习中的重要架构的基础知识和统摄理解展开行文。
本文将在重要组件详解这一部分详细讲解 Transformer 的重要组件原理;在模型架构这一部分讲解重要组件是如何有机组合起来起作用的;在数据模拟这一部分,通过一个简单的例子来展现一个完整 Transformer 模型在机器翻译任务上的运行过程。
这是作者本人在 CSDN 上第一篇博客,来记录自己的学习与成长,受到作者的知识有限,内容可能会出现偏差,请大家海涵,在评论区中友好交流。
谢谢大家!
研究背景
在 Transformer 问世之前,神经网络早已被引入 NLP 任务中,大致有以下三种思路:
- 循环神经网络(RNN):能够处理序列数据,但容易遭遇梯度消失或爆炸的问题
- 长短时记忆网络(LSTM)和门控循环单元(GRU):改进了 RNN 的结构,能够更好地捕捉长距离依赖关系,广泛应用于机器翻译、文本生成、情感分析等任务。
- 序列到序列(Encoder-Decoder)模型及注意力机制:引入注意力机制后,可以在编码器与解码器之间动态关注输入序列的不同部分,进一步提升了模型性能。
这些传统方法虽然在当时为 NLP 任务提供了重要的技术支持,但它们会受到自身模型结构的桎梏,面临着并行计算能力不足、长距离依赖难以捕捉等问题。Transformer 的出现,通过自注意力机制解决了这些难题,实现了高效并行处理和更优的序列建模。
贡献
Transformer 最大的贡献在于提出了完全基于自注意力机制(self-attention)的全新架构,彻底打破了传统循环神经网络(RNN)和卷积神经网络(CNN)在处理长距离依赖和并行计算方面的限制。具体来说:
-
高效捕捉长距离依赖:通过自注意力机制,Transformer 能够直接计算序列中任意两个位置之间的关系,不再依赖于序列的逐步传递,显著改善了长序列建模的问题。
-
完全并行化的训练:不像 RNN 那样需要按顺序处理序列数据,Transformer 的自注意力和前馈网络可以在序列所有位置上同时计算,大大提高了训练速度和资源利用效率。
-
推动预训练大规模模型发展:这种架构奠定了 BERT、GPT 等预训练语言模型的基础,推动了自然语言处理乃至计算机视觉等领域的革命性进步。
Transformer 的核心贡献在于其利用自注意力机制实现了高效并行、灵活捕捉全局信息的序列建模方式,从而极大地提升了模型的性能和扩展性。
重要组件详解
首先请看这一张原论文中给出的 Transformer 架构图,接下来我将一一详解这其中的重要组件的原理,然后在下一板块模型架构中叙述不同组件之间是如何交互起作用的。
输入嵌入
对于模型而言,它并不能够像人类一样学习文字,而是只能学习数字之间的复杂逻辑规律。
那么,对于NLP任务而言,首要的任务一定是将离散的字符转化成连续的可被模型学习的向量数据,这一步即图中的 Input Embedding
针对输入嵌入的流程,模型首先通过一定的分词技术将原文本进行词划分(这里词划分也有不同的技术方式实现),得到一个个离散的词,然后将其转化为唯一的序列数字编号,接着将这个数字编号转化为可学习的高维向量(原论文设计为512维)
举个简单而实际的例子:
在文本 "I'm an undergraduate student at the UESTC" 的输入嵌入处理中,会依次经历以下步骤操作:
-
文本标记化:将句子拆分为更小的单元(如单词或子词)。例如,使用空格分词器(当然也有其他的分词策略),句子会被拆分为以下标记:
["I'm", "an", "undergraduate", "student", "at", "the", "UESTC"]
-
将标记转换为唯一的数字编号:每个标记都会映射到词汇表中的唯一索引。例如,假设词汇表如下:
{"I'm": 101, "an": 102, "undergraduate": 103, "student": 104, "at": 105, "the": 106, "UESTC": 107}
那么,句子将被转换为以下数字序列:
[101, 102, 103, 104, 105, 106, 107]
-
词嵌入:使用一个可学习的嵌入矩阵将每个标记的数字编号转换为高维向量表示。嵌入矩阵是一个大小为 (词汇表大小 × 512) 的矩阵,每个标记的数字编号对应矩阵中的一行。
这里的嵌入矩阵最开始是随机初始化的,模型会为词汇表中的每个词分配一个随机生成的高维向量作为其初始表示。这些向量在训练过程中会通过反向传播进行更新,以逐步学习到更准确的词语表示。在训练过程中,通过反向传播,模型调整嵌入矩阵中的向量,使得相似的词语在高维空间中更接近,从而捕捉词语之间的语义关系。
例如,标记 "I'm" 的编号是 101,对应的嵌入向量可能是:
[0.25, -0.13, 0.45, ..., 0.07] # 共512维
对于整个句子,经过嵌入后得到一个形状为 (7, 512) 的矩阵,每一行对应一个标记的嵌入向量。
通过上述步骤,句子 "I'm an undergraduate student at the UESTC" 被转换为一个形状为 (7, 512) 的矩阵,和接下来要介绍的位置编码叠加之后的结果作为 Transformer 模型的输入。
位置编码
在上面的注意力机制和多头自注意力机制的讲解中,不难发现其最大的特点在于,它使得模型在训练过程中可以高度并行化,从而加快训练速度。而在并行化的过程中,就很自然地丧失掉了原文本所具有的词与词之间的相对位置关系,也就是说,"I'm an undergraduate student at the UESTC" 和 "I'm at an UESTC the student undergraduate" 这两句在实际的模型计算中是等价的,这很明显的丧失掉了句子本身所具有的逻辑。
为了解决这一问题,所以引入位置编码技术。
- 位置编码(Positional Encoding):由于 Transformer 模型缺乏内在的位置信息,需要添加位置编码以提供序列顺序信息。在标准的 Transformer 中,位置编码使用固定的正弦和余弦函数生成(后续会有一系列的改良技术方案,如可学习位置编码、相对位置编码和旋转位置编码等等)。对于第 pos 个标记和第 i 维度,位置编码的计算公式为:
-
当 i 为奇数时:
-
当 i 为偶数时:
-
其中, 是嵌入维度(例如,512)。例如,对于第一个标记(位置 0),位置编码可能是
[0.0, 1.0, 0.0, 1.0, ..., 0.0, 1.0] # 共512维
对于整个句子,生成一个形状为 (7, 512) 的位置编码矩阵。
2. 将词嵌入与位置编码相加:将每个标记的嵌入向量与对应的位置信息相加,得到最终的输入嵌入矩阵。例如,对于第一个标记,其最终嵌入向量为:
[0.25 + 0.0, -0.13 + 1.0, 0.45 + 0.0, ..., 0.07 + 1.0]
这个过程确保了模型在处理输入时,既包含了词汇的语义信息,也包含了位置信息。
在将词嵌入和位置编码相加之后,得到的 (7, 512) 大小的张量,即 "I'm an undergraduate student at the UESTC" 转化之后可供模型学习的数学表达。
在类似的操作下,得到一个 batch 的数据 (注意这里对于较短的句子序列,需要使用特殊的填充标记( <PAD>
)填充到同一个 Batch 句子最长的大小长度,并且操作在处理填充后的序列时,需要使用掩码机制来忽略填充部分,防止模型在计算注意力权重时关注这些无意义的填充标记),最终得到形状为(Batch_size, seq_len, d_model)的张量,进入编码器/解码器进行下一步的操作
注意力机制
注意力机制的直观思想类似于人类在阅读或观察时会“聚焦”于某些关键信息,而忽略其他不那么重要的信息。
在深度学习中,注意力机制让模型能够根据输入数据中各部分的重要性,动态地分配不同的权重,从而更高效地提取关键信息。例如,在机器翻译任务中,当模型翻译一个单词时,它可以“关注”原句中与该单词关系密切的部分,而不必处理整个句子的所有细节。
原理
注意力机制通常采用 “查询(Query)键(Key)值(Value)” 的结构来实现。其基本流程可以分为以下几个步骤:
-
向量化输入
每个输入元素(如文本中的单词)经过嵌入层后会变成一个高维向量。通过三个不同的线性变换,我们分别获得查询向量 Q、键向量 K 和值向量 V:其中,
是可学习的权重矩阵。
-
计算相似度
对于每个查询向量 q,我们通过计算其与所有键向量 k 的点积来衡量它们的相似度(实际中这一过程可以使用矩阵乘法来实现并行化),即:这些得分反映了查询与各个键之间的相关性,得分越大代表相关性越大,在后续转化的权重越大。
-
缩放与归一化
为了防止在高维空间中点积数值过大(数值过大在 Softmax 计算之后会进入平缓区,梯度较小,训练效果不佳),通常将得分除以一个缩放因子(
是键向量的维度),再通过 softmax 激活函数将这些得分转化为概率分布:
softmax 的作用是归一化得分,使得所有注意力权重的和为 1。
在这里使用而不是其他值,原因是:如果将 Q、K 看作符合正态分布的数据,那么他们的方差都是1,则他们计算点积之后的结果的方差为
,除以
之后又将方差投射到 1 的维度,实现既不过度,也不过小,合理缩放的目的。
-
加权求和
最后,利用得到的权重对值向量 V 进行加权求和(实际操作中使用矩阵乘法实现),得到最终的输出向量,这个向量包含了输入中所有元素的信息,但各部分的信息贡献是按相关性动态加权的。
这一整个过程称为“缩放点积注意力(Scaled Dot-Product Attention)”
效果
注意力机制的引入带来了几个重要优势:
-
捕捉长距离依赖
传统的循环神经网络(RNN)在处理长序列时容易遇到梯度消失或爆炸的问题,而注意力机制允许模型直接建立序列中任意两个位置之间的联系,从而更好地捕捉长距离依赖关系。 -
并行化计算
由于注意力机制是基于矩阵运算的,不依赖于前一个时间步的计算结果,因此可以充分利用现代 GPU 的并行计算能力,大幅提升训练和推理速度。 -
灵活性与可解释性
模型通过计算注意力权重,可以直观地展示出每个输出如何依赖输入中的哪些部分,这为模型的可解释性提供了便利(例如在 BERT、Transformer 等模型中,我们可以通过可视化注意力权重来观察模型关注的重点)。
多头注意力层
正如上文的注意力机制所叙述的优点,Transformer 中创新地单纯使用注意力机制可以使得模型在理解每一个 Token 的时候保证能够联系上下文的信息。
那么也不难看出,在每一次注意力层进行计算的时候,模型对于全局信息只有一次加权获取信息的操作。然而再联系生活实际想一想,我们人类在处理文本理解的时候,对于一个字,一个词往往会从多个角度进行理解其意义(比如词义、时态、语态等等等等),那么假如能让模型类似的通过多个角度对一个 token 进行上下文理解,最后将多个角度的信息进行汇总,那么得到的信息一定比单一方面进行理解获得的更加丰富立体。这也就是多头注意力的“多头”的思想。
在实际操作中,模型会将上文处理好的,张量大小形状为(Batch_size, seq_len, d_model)的数据分成 num_head 等份(在 d_model 维度上进行均分,所以还需要保证 d_model 能够整除num_head),得到 num_head 份张量大小形状为(Batch_size, seq_len, d_k)的数据 (d_k = d_model / num_head)。
分别将这 num_head 份子注意力头独立地、并行地进行注意力机制计算,得到 num_head 份数据后,进行“整合信息”,将其处理回(Batch_size, seq_len, d_model)大小的张量信息,这里的张量即对于每一个 token 通过不同角度融合上下文信息的结果。
掩码
在模型的设计中会遇到两个比较棘手的问题:1. 在一个批次中序列的长度不一,使用 <PAD> 填充过后引入 “<PAD>” 这一无关字符扰乱训练。2. 在解码器训练的时候,输入的是完整的目标语言,输出是预测的语言,模型会有 “看着答案算答案” 的 “作弊” 现象。
为了解决这两个问题,使用 掩码 即可很好的解决,针对性地,有以下两种掩码策略:
- Padding Mask 的原理
当批次中不同样本的序列长度不同时,为了将它们填充到统一长度,会在较短的序列后面填入特殊的填充标记(例如 <PAD>
)。Padding mask 的作用就是在计算注意力时,屏蔽这些填充标记,使其不参与注意力分数的计算,防止它们对最终输出产生干扰。
在计算自注意力时,我们先计算 Q 和 K 的点积,再除以 以稳定梯度,最后经过 softmax 得到注意力权重。如果不做处理,填充位置(通常为 0)经过
的 softmax 转换,会为这些无意义的标记分配非零的权重,从而引入噪声。解决方案是:
在填充标记的位置加上一个非常大的负数(如 -1e9),使得经过 softmax 后,这些位置的权重几乎为 0,从而真正“忽略”它们。
- Look-ahead Mask 的原理
在解码器中,为了确保生成过程是自回归的(即每个位置只能看到当前位置及之前的信息,而不能看到未来的信息),会使用 look-ahead mask。它通常构造为一个上三角矩阵,遮蔽掉当前 token 之后的位置,保证模型不会“偷看”未来的词,从而维护因果关系。
为了实现这一点,look-ahead mask 通过构造一个上三角矩阵来工作:
对于一个长度为 T 的序列,构造一个 的矩阵,其中矩阵中若
(即未来的词)则设为
。
在 softmax 操作前,将这个掩码矩阵加到计算得到的注意力分数上,使得未来位置对应的得分极低,从而在 softmax 后得到的权重为 0,确保当前输出只依赖于前面的信息。
这两种掩码分别解决了两个不同的问题:
-
Padding mask 保证了并行计算时填充部分不干扰结果;
-
Look-ahead mask 保证了解码过程中因果关系的正确性,防止模型在训练时“作弊”。
交叉注意力层
在经典 Transformer 所聚焦的机器翻译任务中,Encoder 处理原语言,Decoder 处理目标语言,那么如何将二者进行交互,进行相互综合信息呢?
这也就是架构图中左右两板块中间的连接部分,即“交叉注意力”
其实交叉注意力层和普通的自注意力层本质还是同一个东西,都是为了“联系上下文”获得丰富的语义信息。但是差异在于交叉注意力层的 K、V 矩阵来自于编码器,Q 矩阵来自于解码器。打一个比方,如果说 Encoder 的自注意力层做的工作是不断地优化自己的参数为最优,把这一个过程看作“编词典”,那么交叉注意力层的任务就是“查词典写作文”
层归一化
说到归一化技术,最常见的应该是批归一化(BN),它通过对每一个 mini-batch 中的特征,在 batch 维度 上进行归一化处理,使得加快模型训练收敛速度,减少过拟合情况。
然而批归一化由于其自身的特性,虽然非常适合 CNN 模型(几乎是标配),但是在NLP任务中表现不佳,以下是原因解释:
在 Transformer 中,处理的是变长的序列信息,不同样本的序列长度可能不同,但是批归一化适合的是像图像这种固定形状的输入,这就会导致 BN 无法准确地对有效的 token 进行归一化,填充部分会干扰均值和方差的计算,从而影响模型性能。
并且 BN 依赖较大的 batch size 来准确估计均值和方差,当 Batch Size 太小时,BN 表现不稳定。但 Transformer 模型参数大、显存占用高,训练时往往用 小 batch size,这使得 BN 的估计不稳定,进而影响模型性能。
那么如果能有一种归一化方式,满足:
-
不依赖 batch size
-
不受序列长度影响
那么就能很好的满足任务的需求。这也就是 层归一化(LN) 所具有的特性 。
和批归一化相比,层归一化其实就是改变了计算均值方差以及归一化的维度,使得归一化效果和任务特性相匹配。
为了简单清楚,下面使用一个非常小的张量来简单解释,尺寸为:(Batch_size=2, seq_len=2, d_model=3),
也就是说我们的张量是一个形状为 [2, 2, 3]
的 3D 张量。
X = [ [ [1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
],
[ [7.0, 8.0, 9.0],
[10.0, 11.0, 12.0],
] ]
那么在批归一化中,会选择一个 batch 中的所有元素的每个特征维度进行归一化,具体计算也就是求 [1.0, 4.0, 7.0, 10.0] 的均值方差进行归一化、求 [2.0, 5.0, 8.0, 11.0] 的均值方差进行归一化、求 [3.0, 6.0, 9.0, 12.0] 的均值方差进行归一化.
在层归一化中,就不依赖 batch 的大小,而是选择在 [1.0, 2.0, 3.0]、 [4.0, 5.0, 6.0]、 [7.0, 8.0, 9.0]、 [10.0, 11.0, 12.0] 上进行归一化。
很显然,使用层归一化在保留归一化所带来的作用的同时,也很好的做到了和 NLP 任务 可变长序列的特性
残差连接
最早在 ResNet 中,首次提出了残差连接的思想,后来残差连接也在深度神经网络中广泛使用。
和普通的残差连接的好处类似,在 Transformer 中添加残差连接好处有以下几点:
1. 缓解深层网络中的梯度消失问题
当网络变得非常深时(比如 Transformer 通常会堆叠多个 Encoder/Decoder 层),梯度在反向传播过程中可能会变得非常小,导致训练困难。
残差连接通过添加一个直接通路,允许梯度从后层直接传到前层,从而缓解梯度消失。
具体形式:
在每个子层(如 Multi-Head Attention、Feed Forward)后面,Transformer 都会使用:
然后再接 LayerNorm。
2. 保持原始信息流
残差连接让模型在学习新表示时,不必抛弃原始输入的信息,而是可以在保留原始输入的同时添加新特征。
这就类似于说:“我可以在已有的信息基础上,微调并添加一些改进。”
所以模型会在每一个子层的后方加入残差连接。
Teacher Forcing
在 Transformer 模型中,Teacher Forcing 是一种训练策略,主要应用于解码器端。在训练时,解码器不依赖自己先前生成的预测结果,而是直接使用真实目标序列(右移一位后的结果,第一位使用<SOS>填充,也就是架构图右下角的“Shift Right”)作为输入。这意味着每一步的预测,都由真实的前一个词作为上下文,而不是由模型自己的输出决定。
这种做法有以下几个主要优点:
-
加速收敛:由于每一步都使用了正确的上下文信息,模型可以更快地学习目标序列的分布,训练过程更加稳定。
-
避免错误累积:如果模型在早期预测出错,而这些错误又被反馈到后续时间步,会导致连锁错误。教师强制通过始终使用真实的历史信息来缓解这种累积效应。
-
支持并行训练:在 Transformer 的训练阶段,教师强制允许我们一次性将整个目标序列输入解码器(通过加上掩码确保只关注前面的信息),从而充分利用并行计算的优势。
不过,教师强制也存在一个明显的缺陷,即暴露偏差(Exposure Bias)。在训练期间,模型总是依赖真实的前一步输入,但在推理时,必须使用自己生成的输出作为下一步的输入。这种训练和推理之间的不一致可能会导致模型在实际应用时表现下降。为了解决这一问题,研究者们提出了例如计划采样(Scheduled Sampling)等方法,逐步让模型适应使用自己生成的上下文,这里聚焦于 Transformer 基础,就不详细解说。
这里的 “右移一位”操作 在序列生成任务中的核心作用是构造解码器的输入序列,使得模型在训练时,每一步都只“看到”它应该预测的前一个正确单词,而不会提前看到当前时刻应生成的目标。这个操作也叫做“shift right”,具体来说,就是对目标序列进行如下处理:
假设原始的目标序列为
我们需要构造解码器的输入序列,让模型在预测第 t 个词时,只利用前 t−1 个正确的单词。为此,我们在序列前端插入一个特殊的开始标记(例如 ⟨BOS⟩ ),并删除原始序列的最后一个单词,从而得到新的输入序列:
这样,在训练时:
-
当 t=1 时,模型的输入为
,目标输出应为
;
-
当 t=2 时,模型的输入为
,
,目标输出为
;
-
……
-
当 t=T 时,模型的输入为
,目标输出为
。
这种“右移一位”的操作确保了模型在生成每个时刻的预测时,仅依赖于之前的正确上下文,而不是依赖于自己可能会出错的预测,从而帮助模型更快、更稳定地收敛。
模型架构
原论文中的 Transformer 架构设计如上图。
Transformer 使用 Encoder-Decoder 设计架构,在原论文聚焦的机器翻译任务中分别处理源语言和目标语言以及之间的交互。
Encoder
原论文中,使用了6个编码器进行堆叠,使得模型能够充分地学习语言的复杂模式。
对于每一个编码器而言,拥有两个子层,分别是 多头注意力层 和 前馈全连接神经网络层
- 多头自注意力子层
这一层负责在当前输入序列内部捕捉各个标记之间的依赖关系。每个标记通过与所有其他标记进行“对话”,生成一个新的表示,使得模型能在全局范围内动态调整各标记的重要性。
- 前馈全连接神经网络层
该子层对每个标记独立地进行非线性转换,进一步提取并扩展特征。通常包括两个线性变换,中间夹一个激活函数(如 ReLU)
在这两个子层后,都紧跟着残差连接与层归一化的操作。这不仅缓解了深层网络的梯度消失问题,还保证了原始信息在经过多次变换后依然能够被传递下来,形成稳定的特征表示。
Decoder
Decoder 部分的结构与 Encoder 类似,同样由多个堆叠层组成,但它包含三种子层,确保生成的每个输出都能充分利用上下文信息:
- Masked 自注意力子层
与 Encoder 的自注意力不同,Decoder 首先使用带掩码的自注意力,确保在生成序列时,每个位置只能“看到”当前及之前的标记,防止信息泄露。
- 交叉注意力子层
此子层的 Q 来自于 Decoder 上一层的输出,而 K 和 V 则来自 Encoder 的输出。这一机制实现了目标语言在生成时对源语言信息的有效“查阅”,类似于在编写作文时参考字典中查找合适的词汇和表达。
- 前馈全连接神经网络子层
与 Encoder 部分相似,对每个位置独立地进行非线性转换,增强特征表达。
同样,每个子层后也都配有残差连接与层归一化,保证了信息流畅传递及训练稳定性。
数据模拟
现在我希望通过举一个简单的例子,来向大家展示完整的数据是如何处理的
假设现在使用 Transformer 执行中文到英文的机器翻译的任务
现处理一个Batch的数据,Batch_size = 2,d_model = 6, Vocab_size = 15, num_head = 2
数据:
"我喜欢机器学习" -> "I like machine learning."
"我是一名电子科技大学的本科生" -> "I'm an undergraduate student at the UESTC"
首先随机初始化词汇表(由于中英文差异较大,所以不适用共享词汇表):
vocab_Chinese = {
"[PAD]": 0,
"[UNK]": 1,
"我": 2,
"喜欢": 3,
"机器": 4,
"学习": 5,
"是": 6,
"一名": 7,
"电子科技大学": 8,
"的": 9,
"本科生": 10,
"四川大学": 11,
"讨厌": 12,
"硕士生": 13,
"草原": 14
}
vocab_English = {
"[PAD]": 0,
"[UNK]": 1,
"[SOS]": 2,
"[EOS]": 3,
"I": 4,
"like": 5,
"machine": 6,
"learning": 7,
"I'm": 8,
"an": 9,
"UESTC": 10,
"undergraduate": 11,
"student": 12,
"at": 13,
"the": 14,
}
然后构建随机初始化的嵌入矩阵(参数后续会优化调整)(由于中英文差异较大,所以不适用共享嵌入矩阵)
汉语:
[[0.60969109 0.66144237 0.95469669 0.8661779 0.42067745 0.46902473]
[0.55152981 0.03562364 0.75701237 0.52458132 0.00953526 0.0968531 ]
[0.99735047 0.24782892 0.64884505 0.64324083 0.51382229 0.55098801]
[0.17138975 0.01392306 0.7482102 0.37062883 0.05168452 0.0945155 ]
[0.94025028 0.75658386 0.02120639 0.45104344 0.90712907 0.17764339]
[0.2225371 0.05441133 0.50433318 0.74432543 0.74420664 0.79835145]
[0.29910663 0.6768008 0.4785086 0.75016066 0.17687238 0.32796164]
[0.3496367 0.41564432 0.1706538 0.36573874 0.15207125 0.88387478]
[0.76933408 0.4114227 0.87303334 0.41442398 0.99773206 0.50119766]
[0.04059963 0.37217193 0.73171821 0.16384966 0.7798295 0.91364179]
[0.09845206 0.78494201 0.8090734 0.64638396 0.63553722 0.59977089]
[0.76980124 0.7025784 0.96197042 0.56526596 0.21969655 0.13148545]
[0.92493887 0.37555814 0.67816315 0.03588925 0.14203735 0.39989097]
[0.76874384 0.05395508 0.77424028 0.36004009 0.82923285 0.2378779 ]
[0.09689787 0.72925344 0.50059452 0.58863639 0.96835475 0.06123725]]
英语:
[[0.05935148 0.60969721 0.64518412 0.79961343 0.26955856 0.79763372]
[0.66651339 0.34376842 0.52530351 0.38645669 0.41401049 0.82052145]
[0.3824251 0.39264094 0.33853952 0.01831893 0.14481918 0.62334353]
[0.35543438 0.49374263 0.7356146 0.65732495 0.10955399 0.8329374 ]
[0.43781011 0.10348441 0.44717634 0.64052957 0.06668698 0.57068417]
[0.87336878 0.80762196 0.30752814 0.21077048 0.1738149 0.11670956]
[0.21948149 0.23338354 0.94993239 0.45547546 0.93930203 0.01921823]
[0.59786433 0.32205363 0.39111074 0.7820174 0.05023058 0.48325301]
[0.67334915 0.52512439 0.6566014 0.84933365 0.37021648 0.38698461]
[0.67113165 0.19827029 0.82608323 0.57237125 0.40330122 0.44581752]
[0.05273788 0.22492352 0.88135035 0.15367129 0.77094433 0.08547193]
[0.27435948 0.16344306 0.41514788 0.52217104 0.06742183 0.69476845]
[0.74628605 0.12919476 0.82228931 0.06264966 0.2887987 0.41818602]
[0.93766781 0.32654548 0.44065106 0.33102341 0.54775743 0.07140557]
[0.34094951 0.19931962 0.28121795 0.90961758 0.66109558 0.17152011]]
然后对这两个句子对使用分词技术,对照着词汇表转化为
[2, 3, 4, 5, 0, 0] , [2, 4, 5, 6, 7, 0, 0, 0]
[2, 6, 7, 8, 9, 10] , [2, 8, 9, 11, 12, 13, 14, 10]
(目标语言需要在最开始添加<SOS>标记(Teacher Forcing机制))
然后对照着嵌入矩阵,找到每个编号对应的一行,即为映射的高维向量
比如对于 “我” ,会经历以下变化 “我”->"2"-> "[0.99735047 0.24782892 0.64884505 0.64324083 0.51382229 0.55098801]",得到所对应的高维向量表示
在将每一个token作类似处理之后,会得到 (2, 6, 6) 的数据输入到 Encoder 中,(2, 8, 6) 的数据输入到 Decoder 中(张量形状格式为(Batch_size, seq_len, d_model))
Encoder:
- 首先需要将 (2, 6, 6) 分成 num_head 等份,实现多头机制,即得到两个 (2, 6, 3) 的张量(对应 (Batch_size, seq_len, d_k))。
- 随后经过
计算得到 Q、K、V矩阵。其中,
是可学习的权重矩阵,形状为(d_k, d_k)。得到形状为 (Batch_size, seq_len, d_k)的Q、K、V的三个矩阵,进入注意力计算。
- 通过公式
计算注意力得分以及最终的加权求和结果,得到的结果张量形状为 (Batch_size, seq_len, d_k)
- 将 num_head 个 (Batch_size, seq_len, d_k)子注意力头计算结果 Concat 起来,再通过一个形状为(d_model, d_model)的
矩阵充分融合信息,得到形状为 (Batch_size, seq_len, d_model)的多头注意力计算结果
- 将多头注意力计算结果和多头注意力计算之前的数据进行相加操作,得到残差连接之后的产物
- 进行层归一化,具体而言,对于每个 (batch, token) 对,都独立地计算其 d_model 维度上的均值和方差,然后用这个均值和方差对该 token 的向量进行归一化。
- 进入前馈全连接神经网络,首先第一个层是(d_model, 4 * d_model)大小的全连接层,得到(Batch_size, seq_len, 4 * d_model)大小的张量,经过ReLU激活函数处理,再通过(4 * d_model, d_model)大小的全连接层,得到通过 前馈全连接神经网络 之后的结果,形状仍然是(Batch_size, seq_len, d_model)
- 经过类似的残差连接和层归一化
- 得到通过单个 Encoder 的结果,形状为(Batch_size, seq_len, d_model)
- 通过多个上述的流程,得到多个 Encoder 处理之后的结果,形状为(Batch_size, seq_len, d_model)
Decoder:
大体和Encoder类似,重复的部分不再赘述,仅展示不同的部分
- 将 (2, 8, 6) 形状的张量均分为 2 个 (2, 8, 3) 的张量
- 进入带掩码的多头注意力层,注意这里在计算点积注意力的时候会加上掩码矩阵。具体操作而言是在计算出
(形状为(seq_len, seq_len))之后加上一个掩码矩阵,这样可以保证未来的信息不被泄露,其余操作和 Encoder 类似
- 进行残差连接和层归一化
- 进入交叉注意力层,注意这里的Q矩阵来自解码器,K、V来自编码器,这样可以充分的满足了编码器和解码器之间的交互
- 进行残差连接和层归一化
- 进入前馈全连接神经网络层
- 进行残差连接和层归一化
- 得到最终形状为 (2, 8, 6) 的张量,出解码器
训练:
- 将解码器得到的形状为(2, 8, 6) 的张量通过一个全连接层(形状为(d_model, Vocab_size))再经过一个Softmax计算,得到(2, 8, 15)的张量数据
- 最后一维(15)对应的是输出的词语概率,计算和真实数据的独热编码的交叉熵损失,反向传播,优化参数
- 理论上对于 "<SOS> I like machine learning." 最优的预测结果应该是 "I like machine learning <EOS>"
推理:
- 在推理阶段,由于序列变成自回归任务,所以不使用 Teacher Forcing 机制,也无法进行并行计算
- 对于输入的汉语文本,Decoder 首先以 <SOS> 作为起始输入,进行逐步的推理预测,直到预测结果为 <EOS> ,代表推理结束,翻译任务完成