--- created: 2025-08-03 21:19:11 tags: - "Research" - "基础" - "Transformer" - "模型结构" - "注意力机制" --- # 0. 参考资料 \[1] [Transformers Explained Visually](https://towardsdatascience.com/transformers-explained-visually-part-1-overview-of-functionality-95a6dd460452)-- 一个不错的系列, 共4篇, 这里放出第一篇 \[2] [The Illustrated Transformer](https://jalammar.github.io/illustrated-transformer/)-- 较为简单直观地自顶向下解释 (绝大多数博客都源于这篇) \[3] [图解Transformer | The Illustrated Transformer](https://blog.csdn.net/qq_36667170/article/details/124359818)-- 对\[2]不错的中文翻译 \[4] [Attention机制详解](https://zhuanlan.zhihu.com/p/47282410) \[5] [Transformer Explainer](https://poloclub.github.io/transformer-explainer/)-- Transformer计算过程的可视化 \[6] [LLM Visualization](https://bbycroft.net/llm)-- 更加详细的可视化 \[7] [Transformers](https://nn.labml.ai/zh/transformers/index.html)--代码实现的详细注释 \[8] [一文看懂Transformer内部原理(含PyTorch实现)](https://www.cnblogs.com/guoyaohua/p/transformer.html)-- PyTorch实现 \[9] [Attention Is All You Need (Transformer) 论文精读](https://zhouyifan.net/2022/11/12/20220925-Transformer/)--论文解析 ![image.png|500](https://raw.githubusercontent.com/student-zsbd/myImageHost/master/Transformer_architecture.png) # 1. 目的 **为什么要有Transformer?** Transformer是为了解决传统序列到序列(Seq2Seq)模型中的问题而提出的。这些传统模型主要基于循环神经网络(RNN)结构,它们在处理长距离依赖问题时表现不佳,并且由于其顺序性处理效率较低。Transformer的出现使得模型能够并行处理序列中的所有单词,从而大幅提高训练效率,并有效捕捉长距离依赖关系。 **它是从什么东西进化而来的?** Transformer架构是从Seq2Seq模型和引入Attention机制的RNN模型进化而来。Attention机制最早被用来增强RNN模型在机器翻译任务中的表现,后来研究者发现可以完全依赖Attention机制来构建模型,从而摒弃RNN结构。 # 2. 效果 **它相较于它的前辈在什么方面有了什么样的改进?** - **并行处理能力**:Transformer摒弃了RNN的顺序处理方式,能够并行处理整个序列,极大提高了训练速度。 - **长距离依赖捕捉**:通过Self-Attention机制,Transformer能够有效捕捉序列中任意两个位置之间的依赖关系,无论它们相隔多远。 - **模型性能**:在特定任务上,如机器翻译,Transformer的表现超越了当时最先进的基于RNN的模型。 # 3. 推理流程 ~~实际上目的和方法在[[Self-Attention]]已经完全重复地提过了, 毕竟 "*Attention is All You Need*"~~ 下面让我们挑战一下, 从一个句子的输入到结果的输出, 全流程地梳理 Transformer 是如何工作的. ## 3.1 输入的处理 人类语言的词汇无法在机器上进行运算处理, 所以我们要将其 **向量化** , 也就是[[词嵌入 (Word Embedding)]], 通过巧妙的Embedding方法, 我们将人类语言转化成了一个 **低维稠密的向量** (这个低维稠密是相对于[[One-Hot编码]]那样冗长的向量而言). 现在我们得到了一个能够代表句子 *内容* 的一个向量, 而这个向量的长度是我们可以自己设置的, 也就是一个 **超参数** (一般来说是我们训练集中最长的那个句子的长度.) 但是这个向量目前还只是能够表示输入句子的 *内容* , 我们知道, 句子的真正内涵是由字符与字符的排列顺序组成的, 现在我们相当于用 *向量表示了字符* , 但是由于在Transformer模型中, 句子中的词语都是**同时进入网络进行处理, 顺序信息在输入网络时就已丢失**, (它不像[[CNN-卷积层, 池化层 与 全连接层|CNN]]那样经过卷积计算之后仍然保留了空间信息), 所以我们还要在 表示输入句子语义的向量 中加入每个token的 **位置信息** , 这就是[[位置编码 (Positional Encoding)]]. 位置编码也是从输入的句子中获得, 长度通常和词嵌入中获得的向量一样. 我们对原句分别进行 [[词嵌入 (Word Embedding)]] 与 [[位置编码 (Positional Encoding)]] 得到两个相等长度的向量, 将它们 **简单地相加** (对于序列中的每个单词,其词嵌入向量和对应位置的位置编码向量进行相加。这个操作是在每个维度上独立进行的,即对应维度的数值相加) 就得到了包含输入句子 *完整**语义**信息与**位置**信息的向量* , 也就得到了 **嵌入向量**. ## 3.2 进入编码器 (Encoder) ### 3.2.1 Multi-Head Attention 我们得到的 **嵌入向量** 作为 **Encoder** 的输入, 它首先进入一个[[Self-Attention]]层得到注意力结果, [[Self-Attention]]中的计算与本质在这里已经写的相对不错了. 总而言之, 相比于嵌入向量包含的位置信息和语义信息, 经过SA层计算得到的向量有了更多的信息, 其中包括有: 1. **单词自身的语义信息**:嵌入向量本身就包含了单词自身的语义信息。 2. **序列的上下文信息**:通过自注意力机制,得到的向量包含了序列中其他单词对当前单词的影响,反映了单词之间的相互关系。 3. **多头注意力的信息**:在多头自注意力中,不同的头可能会关注不同的上下文特征,合并后得到的向量包含了这些多样化的信息。 这里提到了[[Multi-head Attention]]多头注意力, 在Transformer原论文中, SA层采用了8个头的多头注意力, 相当于用自注意力做8次不同的计算, 并得到8个不同的结果. 首先他们的输入都是一样的--嵌入向量, 但是产生了8个不同的结果, **前馈神经网络**不能一下接受8个矩阵输入, 它需要的是 *单个矩阵(矩阵的每个行向量对应一个单词*) 所以要把这8个矩阵合并成一个矩阵, 采用什么方法呢? 这里就不是像合成嵌入向量一样简单的相加了, 我们先把经过多头注意力得到的8个矩阵链接起来, 再与一个形状是链接后的矩阵的转置一样的 *权重矩阵* 进行相乘, 然后就得到一个形状和单个注意力得到的矩阵一样的结果矩阵. 这就是我们要送入前馈神经网络的输入. ### 3.2.2 Add&Norm 在多头注意力之后与送入前馈神经网络之前, 中间还有一个Add&Norm层, 正如它的名字一样, 它就是将MHA层计算出来的结果向量, 使用[[残差链接 (Residual Skip Connect)]], 词嵌入向量与MHA计算结果相加, 来**缓解梯度消失**的问题, 并且**提高的模型的表达能力**. 然后又将相加完的结果进行[[LayerNorm]], 这样做稳定了训练过程, 加快了收敛, 并且提高了模型的**泛化能力** 在经过Add&Norm层之后的向量, 形状通常是不变的. 如果输入向量的形状是 $[batch_size,seq_length,hidden_size]$,那么经过Add&Norm处理后的输出向量的形状仍然是 $[batch_size,seq_length,hidden_size]$ 但是内容发生了变化: 1. **残差连接(Add)**: - **信息融合**:残差连接将输入向量直接添加到处理后的向量上,这使得原始信息得以保留,并且与新学到的特征进行融合。 - **避免信息丢失**:通过残差连接,可以避免在深层网络中信息逐渐丢失的问题,确保每个层都能对最终输出有所贡献。 2. **层归一化(Norm)**: - **分布归一化**:层归一化通过对每个样本的特征进行归一化处理,使得输出的分布更加标准化,通常具有0的均值和1的方差。 - **减少方差**:层归一化减少了不同层输出的方差,使得模型训练更加稳定。 - **提高泛化能力**:通过规范化特征,层归一化有助于提高模型对输入数据分布变化的鲁棒性,从而提高模型的泛化能力。 ### 3.2.3 FFN 前馈神经网络 [[前馈神经网络 (FFN)]]主要的作用就是: 特征提取, 提高模型表达能力, 信息整合 ~~和[[CNN-卷积层, 池化层 与 全连接层|CNN里的全连接层]]基本一样.~~, 不过在Transformer的框架中 **每个Encoder和Decoder**都有这样的模块. ### 3.2.4 编码器栈 (Encoder Stack) 上面三个提到的组件只是组成了一个Encoder, 而在Transformer中, 有 *6个* 这样一模一样的Encoder模块, 组成了 *编码器栈* . 为啥是6个? 这是可调的, 所以说 *编码器个数* 是一个 **超参数**. 同样的, 后面要提到的解码器也是可调的超参数. >[!note] >至此我们在Transformer中已经提到了两个超参数: 编/解码器个数, 嵌入向量的长度. 在Transformer/Bert架构中基本就只有这两个超参数可以调了 从编码器栈的第一个编码器开始, 它接受 *嵌入向量* 的输入, 然后输出一个形状和嵌入向量一样的向量, 作为 *下一个编码器的输入* , 依次经过六个编码器. 直到最后一个编码器, 它的输出没有下一个编码器接了, 所以它给到了 **解码器**. ## 3.3 来到解码器 (Decoder) 从架构的示意图我们也可以发现, Decoder和Encoder其实没有什么太大的差别, 主要的差别有两点: - Decoder模块的第一个MHA层变成了 ***Masked** Multi-Head Attention* - 中间多了一个 **接收Encoder输入的**MHA层. ### 3.3.1 Decoder的输入 Decoder的输入不像是Encoder, 接收一个我们输入句子的嵌入向量. 因为我们会生成一个句子, 所以在开始的开始, 会有一个开始的标记(例如``作为初始的输入, 标记目标语言序列的开始). 由于我们输出的句子也要具有意义, 所以在计算的时候也需要有 *位置信息* 和 *语义信息* , 所以也要经过 *Embedding* 来进行向量化. 输出为一个向量送往下一个模块. ### 3.3.2 Masked Multi-Head Attention Decoder这边的嵌入向量送入了下一个 [[Mask MHA|**Masked**多头注意力层]] . 其实都是MHA层, 输入和输出的向量和Encoder中的MHA层没什么两样. 那么Mask是怎么操作的呢? 就是在输入矩阵中的上三角区域, 将值设置成 *负无穷大* , 这样就算是经过计算, softmax之后的权值也是0. ### 3.3.3 Encoder-Decoder Attention 工作原理实际上和[[Self-Attention]]一摸一样, 只不过输入有所差别. $Q$是从下层计算出来的(比如解码器的输入和下层decoder组件的输出),但是其$K$和$V$是来自**编码器最后一个组件的输出**结果。 >编码器是对整个输入序列进行编码,然后将其结果转化成$K$和$V$传给解码器,这个$K$、$V$包含 *整个句子的所有信息*。 >但是解码器的输入是什么?是 *拼接之前解码器的输出的单词* 。所以解码器造出来的Q仅包含已经输出的内容。 ### 3.3.4 FFN 在前文说过了编/解码器在最后都会经过FFN, 这里不再赘述. ### 3.3.5 解码器栈 (Decoder Stack) 解码器模块同样由多个解码器组成的, 输入输也是依次经过计算的. 但是要说的一点, **解码器的数量不一定非得和编码器相等** , 这取决于任务要求. ## 3.4 最后的线性层与Softmax Decoder输出的是一个浮点型向量,如何把它变成一个词? 这就是最后一个线性层和softmax要做的事情。 线性层就是一个简单的全连接神经网络,它将解码器生成的向量映射到logits向量中。 假设我们的模型词汇表是10000个英语单词,它们是从训练数据集中学习的。那logits向量维数也是10000,每一维对应一个单词的分数。 然后,softmax层将这些分数转化为概率(全部为正值,加起来等于1.0),选择其中概率最大的位置的词汇作为当前时间步的输出。 **注意: 输出了一个单词之后, 这个单词会回到 Decoder 最开始的输入, 进行 Embedding 处理, 再进行新一轮的计算, 来输出下一个单词, 直到输出`` 这样的终止符为止**. ~~所以GPT是一个个字蹦出来的~~