transformer面试经验2
详细描述一下transformer⭐⭐⭐⭐⭐
Transformer 是 2017 年由 Google 团队在论文《Attention Is All You Need》中提出的深度学习模型,彻底摆脱了 RNN、CNN 等依赖序列或局部感受野的结构,完全基于自注意力机制(Self-Attention) 实现,在并行计算效率和长序列依赖捕捉上具有显著优势,成为 NLP 领域(如机器翻译、文本生成)乃至多模态任务(如图文理解)的基础架构。
其核心结构分为编码器(Encoder) 和解码器(Decoder) 两大部分,整体结构如下:
一、整体框架概览
Transformer 的输入是源序列(如源语言句子)和目标序列(如目标语言句子),输出是目标序列的预测结果(如翻译后的句子)。
- 编码器接收源序列,输出编码特征(Encoder Output),用于捕捉源序列的全局语义;
- 解码器接收目标序列和编码器输出,通过关注源序列的相关信息,生成目标序列的预测结果。
两者均由 N 个相同的层堆叠而成(论文中 N=6),且每个层内部包含多个子层,配合残差连接和层归一化稳定训练。
二、输入处理:嵌入与位置编码
Transformer 没有循环结构,无法像 RNN 那样自然捕捉序列顺序,因此输入需经过词嵌入和位置编码处理,将离散的词转化为包含语义和位置信息的连续向量。
1. 词嵌入(Embedding)
- 作用:将每个词(如“apple”)映射到一个固定维度的向量(如 512 维),捕捉词的语义信息。
- 实现:通过可学习的嵌入矩阵(类似 Word2Vec),将词的索引转化为向量。
- 维度:设嵌入维度为 $ d_{\text{model}} $(论文中为 512),与后续所有子层的输出维度一致。
2. 位置编码(Positional Encoding)
- 作用:为每个位置附加位置信息,确保模型能区分“我吃苹果”和“苹果吃我”这类语序差异。
- 实现:采用正弦余弦函数生成固定编码(非可学习),公式如下:
其中,$ pos $ 是词在序列中的位置(如第 0 个、第 1 个词),$ i $ 是向量的维度索引(如 0 到 511)。
- 特点:
- 不同位置的编码不同,且位置差固定的两个词,编码差也固定(便于模型学习相对位置);
- 编码维度与词嵌入相同(均为 $ d_{\text{model}} $),通过加法与词嵌入结合(即
Input = Embedding + Positional Encoding
)。
三、编码器(Encoder)结构
编码器由 N 个相同的编码器层 堆叠而成,每个编码器层包含两个子层,且每个子层后均有残差连接和层归一化。
1. 编码器层的子层结构
每个编码器层的流程为:
其中 $ x $ 是子层的输入,$ \text{Sublayer}(x) $ 是子层的输出,残差连接($ x + \text{Sublayer}(x) $)缓解梯度消失,层归一化(Layer Norm)稳定训练时的参数分布。
子层具体包括:
(1)多头自注意力(Multi-Head Self-Attention)
-
作用:让每个词同时关注序列中所有其他词(包括自身),捕捉全局依赖关系(如“他”指代“小明”)。
-
原理:将输入拆分为多个“头”(Head)并行计算注意力,再拼接结果,以捕捉不同子空间的特征(类似 CNN 的多滤波器)。
步骤如下:
- 线性投影:将输入 $ x $(维度 $ d_{\text{model}} $)通过 3 个线性层,生成查询(Query, Q)、键(Key, K)、值(Value, V),维度均为 $ d_k $(论文中 $ d_k = d_{\text{model}} / h = 64 h=8 $ 头)。
- 拆分多头:将 Q、K、V 按头拆分,每个头的 Q、K、V 维度为 $ d_k $,共 $ h $ 组。
- 缩放点积注意力(Scaled Dot-Product Attention):每个头独立计算注意力:
- $ QK^T $:计算每个词与其他词的相似度(得分矩阵,维度 $ n \times n n $ 为序列长度);
- 缩放($ 1/\sqrt{d_k} $):避免 $ d_k $ 过大导致内积值过大,softmax 后梯度消失;
- softmax:将得分归一化为概率,再与 V 加权求和,得到每个词的注意力输出。
- 拼接多头:将 $ h $ 个头的输出拼接,通过线性层映射回 $ d_{\text{model}} $ 维度,得到多头注意力的最终输出。
(2)前馈神经网络(Feed Forward Network, FFN)
- 作用:对每个词的注意力输出进行独立的非线性变换(与其他词无关),增强模型的非线性表达能力。
- 结构:
其中,$ W_1 $ 维度为 $ d_{\text{model}} \times d_{\text{ff}} W_2 $ 维度为 $ d_{\text{ff}} \times d_{\text{model}} $(论文中 $ d_{\text{ff}} = 2048 $),ReLU 激活函数引入非线性。
四、解码器(Decoder)结构
解码器同样由 N 个相同的层堆叠而成,每个层包含 3 个子层(比编码器多一个),同样使用残差连接和层归一化。
解码器层的子层结构
流程同样为:
子层具体包括:
(1)掩码多头自注意力(Masked Multi-Head Self-Attention)
- 作用:让目标序列中的每个词只能关注到自身及之前的词(避免“偷看”未来信息,如翻译时预测第 3 个词不能用第 4 个词的信息)。
- 实现:在计算 $ QK^T $ 后,对得分矩阵的上三角部分(未来位置)填充负无穷,softmax 后这些位置的权重为 0。
(2)编码器-解码器注意力(Encoder-Decoder Attention)
- 作用:让解码器关注源序列中与当前目标词相关的部分(如翻译时“猫”对应源语言的“cat”)。
- 原理:与多头自注意力类似,但 Q 来自解码器的前一个子层输出,K 和 V 来自编码器的最终输出(Encoder Output),即:
(3)前馈神经网络(Feed Forward Network)
- 与编码器的前馈神经网络完全相同,对每个位置进行独立的非线性变换。
五、输出层(Output Layer)
解码器的最终输出经过以下处理得到预测结果:
- 线性层:将解码器输出(维度 $ d_{\text{model}} V $)的维度,得到每个词的未归一化得分。
- Softmax:将得分转化为概率分布,取概率最高的词作为当前位置的预测结果。
六、总结:Transformer 结构核心特点
部分 | 核心组件 | 关键作用 |
---|---|---|
编码器 | 多头自注意力 + 前馈网络 | 捕捉源序列全局依赖,输出编码特征 |
解码器 | 掩码自注意力 + 编码器-解码器注意力 + 前馈网络 | 生成目标序列,关注源序列相关信息,避免偷看未来 |
输入处理 | 词嵌入 + 位置编码 | 转化离散词为含语义和位置的向量 |
训练稳定机制 | 残差连接 + 层归一化 | 缓解梯度消失,稳定深层网络训练 |
Transformer 的并行性(无循环依赖)使其能高效利用 GPU 训练,而自注意力机制使其能直接建模长距离依赖,为后续 BERT(仅用编码器)、GPT(仅用解码器)等预训练模型奠定了基础。
为什么要除以 而不是 $ d_k $
在 Transformer 的自注意力机制中,查询向量($ Q K $)的点积会除以 而不是 $ d_k $,这是为了防止随着向量维度增大,点积的结果值过大,导致softmax值过于极端。
1. 点积和向量维度的关系
当我们对查询和键向量做点积时,计算结果的量级和向量的维度($ d_k $)密切相关。具体来说:
- 如果 $ Q $ 和 $ K $ 的每个元素都服从均值为零、方差为 1 的分布,那么 $ Q \cdot K $ 的期望值大约为 $ d_k $。
- 随着 $ d_k $ 增大,点积的结果值可能会增大到一个较高的量级。
2. 避免 softmax 饱和
在点积计算得到较大数值后,进入 softmax 函数前,如果不进行缩放处理,计算得到的数值会偏离零点过多,使得 softmax 输出趋向于“饱和”:
- 当输入值较大时,softmax 会使输出更加极端,接近 0 或 1,这种极端化会导致梯度消失的问题,使得模型难以学习到有效的注意力分布。
- 这种现象会导致模型的注意力分布趋于“硬注意力”,即仅对少数位置有高权重,对其余位置的权重几乎为零。
3. 为什么是 而不是 $ d_k $
通过除以 而不是 $ d_k $,可以使点积的结果维持在一个合理的范围内,同时不会导致缩放过度。将点积缩放到方差为 1 的范围,而不是更小的值,可以确保 softmax 的输入适度地集中,输出的注意力权重分布更为平滑,使模型能够更好地学习不同位置之间的相对关系。
总结
将点积除以 是一种缩放策略,可以有效缓解随着维度增大导致的值过大问题,从而保持 softmax 的平滑分布,避免模型学习陷入梯度消失问题。这种处理在实践中被证明能显著提高模型性能和收敛稳定性。
transformer计算自注意力时为什么需要对K转置
在Transformer中,计算自注意力(self-attention)时需要对键(key)矩阵 $ K $ 进行转置,以实现每个查询(query)与所有键之间的点积计算。
具体原因如下:
-
点积计算:
自注意力的核心在于计算每个查询 $ Q $ 与键 $ K $ 的相似性。这种相似性通常使用点积来度量,即每个查询向量和每个键向量的点积,点积值越大,表示向量相似度越高。对于输入的查询矩阵 $ Q \in \mathbb{R}^{n \times d} $ 和键矩阵 $ K \in \mathbb{R}^{n \times d} $,点积相似性可通过矩阵乘法实现,计算公式为:其中,$ Q $ 的每一行表示一个查询向量,$ K^T $ 的每一列表示一个键向量。
-
获得查询与所有键的相似性矩阵:
通过 $ Q K^T $ 的操作,可以得到一个相似性矩阵 $ S \in \mathbb{R}^{n \times n} $,其中 $ S_{i,j} $ 表示第 $ i $ 个查询与第 $ j $ 个键的点积结果。这一矩阵正是计算注意力权重(attention weights)的基础。转置后,查询能够一次性计算出与所有键的相似度,而不需要逐一计算。 -
并行化计算:
通过对 $ K $ 进行转置,点积计算可以转换成矩阵乘法操作,从而使得整个自注意力计算可以并行化。这种矩阵形式的操作对于GPU加速特别友好,有利于提高计算效率。
权重矩阵$ W_q W_k W_v $是怎么来的
Transformer的权重矩阵 $ W_q $ 是在自注意力(self-attention)机制中用来生成查询向量(query vector)的矩阵。它是通过训练学习到的参数,用于将输入的特征映射到查询空间。
1. 作用与位置
在 Transformer 的自注意力机制中,每一个输入向量(通常是词嵌入向量或编码后的向量)通过三组权重矩阵 $ W_q W_k W_v $ 分别映射成查询向量(query)、键向量(key)、值向量(value):
其中:
- $ Q $ 是查询矩阵,代表对其他输入信息的查询。
- $ K $ 是键矩阵,用来存储不同输入的信息内容。
- $ V $ 是值矩阵,存储需要传播的信息。
- $ X $ 是输入向量,通常是词的嵌入表示或者前一层的输出表示。
2. 权重矩阵 $ W_q $ 的来源
$ W_q $ 是一个随机初始化的权重矩阵,并在模型训练过程中不断调整。通常是一个线性变换矩阵,其维度是:
- 输入特征维度 $ d_{\text{model}} $(如输入嵌入维度)到注意力空间的维度 $ d_k $ 的映射(例如,假设 $ d_k $ 通常为 64 或 128 等)。
在训练过程中,模型会根据损失函数(如交叉熵损失)来优化 $ W_q $ 的参数,使得查询向量能够更好地捕捉重要特征,从而提高模型对输入信息的关注能力。
3. 自注意力的计算过程
在自注意力机制中,查询、键和值之间的关系计算如下:
- 计算查询和键的点积,得到注意力分数:
- 将注意力分数通过 softmax 进行归一化:
- 将归一化的注意力权重与值矩阵 $ V $ 相乘,得到最终的输出。
4. 总结
$ W_q $ 的作用是学习将输入向量转化为查询向量,使模型能够计算不同输入之间的相关性。
各个矩阵的维度都代表什么
Transformer模型中的各个矩阵维度主要与输入序列、嵌入维度、注意力头、隐藏层和输出层相关,理解这些维度可以帮助解释模型的工作原理。以下是Transformer中常见矩阵的维度及其含义:
- 输入嵌入矩阵 (Input Embeddings)
- 维度:
- N: 批量大小(batch size)
- L: 序列长度(输入句子的单词或token个数)
- : 每个token的嵌入向量维度
- 解释: 每个输入的token被嵌入到一个维的向量空间,形成一个大小为 的输入嵌入矩阵。
- 位置编码矩阵 (Positional Encoding)
- 维度:
- L: 序列长度
- : 嵌入维度
- 解释: 为了让模型感知序列中token的位置,位置编码与输入嵌入相加,保持与输入嵌入相同的维度。
- 查询、键和值矩阵 (Query, Key, Value Matrices)
在自注意力机制中,输入的嵌入会被投影成查询(Q)、键(K)和值(V)矩阵。
- 维度:
- 查询矩阵 :
- 键矩阵 :
- 值矩阵 :
- 解释:
- 分别从输入嵌入通过线性变换得到,其中 是查询和键的维度, 是值的维度。
- 注意力得分通过 计算,结果用于加权值矩阵 。
- 注意力矩阵 (Attention Matrix)
- 维度:
- N: 批量大小
- H: 注意力头数(number of attention heads)
- L: 序列长度
- 解释: 每个注意力头会为序列中的每个token计算其与其他token的相关性(注意力得分),生成的矩阵捕捉了token间的依赖关系。
- 多头注意力输出矩阵 (Multi-Head Attention Output)
- 维度:
- 解释: 多头注意力机制将来自不同头的注意力结果拼接并投影回 维度。
- 前馈网络输出矩阵 (Feedforward Network Output)
- 维度:
- 解释: Transformer中的前馈神经网络通常包含两个线性层,隐藏层维度通常大于嵌入维度。中间维度为 ,通常是 的4倍。
- 输出矩阵 (Output Matrix)
- 维度:
- 解释: 最终经过多层Transformer层的处理后,输出依然保持和输入相同的形状,即每个token的输出仍然是 维的向量。
- Softmax 输出矩阵 (Softmax Output)
- 维度:
- N: 批量大小
- L: 序列长度
- V: 词汇表大小(vocabulary size)
- 解释: 在语言模型中,最终的输出通常通过Softmax层转换为对词汇表中每个词的概率分布。
每个矩阵的维度设计都与序列的长度、模型的嵌入维度和注意力头的数量等超参数息息相关。理解这些维度能帮助你掌握Transformer模型的核心计算和数据流。
Self-Attention公式
Self-attention的公式为:
attention公式中除以的原因
原因有两点:
- 首先要除以一个数,防止输入softmax的值过大,导致偏导数趋近于0。
- 选择根号是因为可以使得的结果满足期望为0,方差为1的分布,类似于归一化。
公式分析
首先假设和都是服从期望为0,方差为1的独立的随机变量。
- 期望计算:
假设 ,,那么:
- 方差计算:
- 的计算:
需要注意的是:
权重矩阵$ W_Q W_K $ 和 $ W_V $ 的行列维度是一样的吗
在 Transformer 中,计算 Query (Q)、Key (K) 和 Value (V) 的权重矩阵 $ W_Q W_K $ 和 $ W_V $ 的行列维度一般不完全相同,具体情况取决于实现和模型的配置。
让我们仔细看看 Transformer 中计算 Q、K、V 时,权重矩阵 $ W_Q W_K $ 和 $ W_V $ 的维度。
1. 输入表示与维度
通常,Transformer 的输入是一个长度为 $ L $ 的序列,每个词或 token 都被表示为一个维度为 $ d_{model} $ 的向量。
- 序列长度 L:输入序列中有多少个词。
- 模型维度 :每个输入 token 被嵌入到一个 $ d_{model} $-维的向量空间中(embedding)。
因此,输入的矩阵维度是 $ (L, d_{model}) $,即序列中每个词被表示为一个 $ d_{model} $ 维度的向量。
2. Q、K、V 的计算
为了计算 Query (Q)、Key (K) 和 Value (V),我们需要将输入序列的每个 token 映射到新的向量空间,这个映射通过线性变换实现。对于每个输入 token,其 Q、K、V 是通过与各自的权重矩阵 $ W_Q W_K $ 和 $ W_V $ 相乘得到的:
其中:
- $ X $ 是输入矩阵,维度是 $ (L, d_{model}) $。
- $ W_Q W_K W_V $ 是相应的权重矩阵。
3. 权重矩阵 $ W_Q W_K W_V $ 的维度
- $ W_Q $ 的维度:$ (d_{model}, d_{qkv}) $
- $ W_K $ 的维度:$ (d_{model}, d_{qkv}) $
- $ W_V $ 的维度:$ (d_{model}, d_{qkv}) $
其中,$ d_{qkv} $ 通常等于 $ d_{model} / h h $ 是注意力头(head)的数量。
- $ d_{qkv} $:Query、Key、Value 的向量维度。它通常等于 $ d_{model} $ 除以注意力头的数量 $ h $,这是因为在多头注意力机制中,会将每个词的表示拆分为多个头,每个头会有独立的 Q、K、V。
- $ d_{model} $:输入 token 表示的维度。
4. 为什么维度不同?
- 输入维度:$ d_{model} $ 表示输入 token 的维度,通常是一个较大的数值,比如 512 或 768。
- 输出维度:每个 Query、Key、Value 向量的维度 $ d_{qkv} $ 通常比 $ d_{model} $ 小。在多头注意力机制中,$ d_{qkv} $ 通常是 $ d_{model} / h $,这样可以在每个注意力头上处理较小的向量。
例如,对于一个 $ d_{model} = 512 h = 8 $ 的 Transformer 模型:
- $ d_{qkv} = d_{model} / h = 512 / 8 = 64 $
- $ W_Q W_K W_V $ 的维度就是 $ (512, 64) $。
5. 多头注意力中的维度变化
在多头注意力中,整个过程如下:
- 线性变换:输入 $ X $ 通过线性层分别得到 Q、K、V 向量。
- 分头处理:Q、K、V 向量被拆分成多个注意力头,每个头都有独立的 Q、K、V。比如,$ d_{qkv} $ 为 64 的情况下,8 个头会分别计算维度为 64 的 Q、K、V。
- 并行计算:每个头的 Q、K、V 进行并行计算,得到注意力分数和加权后的 Value。
- 合并结果:所有头的结果拼接在一起,经过一个线性层还原回原始维度 $ d_{model} $。
总结
权重矩阵 $ W_Q W_K W_V $ 的行维度是 $ d_{model} $,列维度是 $ d_{qkv} $。它们的维度不完全相同,因为 $ d_{qkv} $ 是注意力头的大小,通常比 $ d_{model} $ 小。
transformer过程简介
Transformer 模型是生成语言输出的经典模型,尤其是自回归模型如 GPT 系列。Transformer 通过词嵌入、自注意力机制和解码器架构的组合来逐步生成序列。以下是 Transformer 生成语言输出的详细过程:
1. 输入预处理和嵌入层
-
输入序列:模型首先接收一个输入序列,通常是一些前缀文本,或者在完全生成时为空。
-
词嵌入 (Word Embedding):输入序列中的每个词会被映射到一个高维嵌入向量。这些嵌入向量表示了词的语义信息。例如,“dog” 和 “cat” 可能有相近的嵌入向量。
-
位置编码 (Positional Encoding):因为 Transformer 不像 RNN 那样拥有序列的顺序信息,它通过位置编码为每个词加上其位置相关的信息。位置编码通常是正弦和余弦函数,帮助模型识别词在句子中的顺序。
2. 自注意力机制 (Self-Attention Mechanism)
在嵌入之后,输入被传递到自注意力层。自注意力机制是 Transformer 的核心组件,能够让模型在生成某个词时同时关注输入序列的所有其他词。
-
自注意力步骤:
- 每个词的嵌入向量会被映射成
Query
、Key
和Value
向量。 - 每个词的
Query
会与其他所有词的Key
做点积,计算注意力得分,衡量当前词和其他词之间的相关性。 - 这些得分会经过 softmax 操作归一化,表示不同词对当前词的影响权重。
- 最后,将每个词的
Value
根据注意力权重加权,生成新的上下文表示。
- 每个词的嵌入向量会被映射成
-
Masked Attention:在生成任务中,解码器使用 Masked Self-Attention,确保当前步骤只能看到之前已经生成的词,不能看到未来词。这样,模型在预测下一个词时只依赖先前生成的内容。
3. 前馈神经网络 (Feed-Forward Network, FFN)
每个注意力层之后,都有一个前馈神经网络(FFN)。FFN是对每个位置独立的两层全连接网络,用来进一步处理和提取特征,提升模型的表达能力。
4. 多层堆叠 (Multi-layer Stacking)
Transformer 的编码器和解码器由多个相同的层堆叠而成。每一层都包含:
- 自注意力机制
- 前馈网络
- 残差连接和层归一化
这些层的堆叠让模型能够逐步提取更复杂的特征,从而更好地理解和生成自然语言。
5. 解码器生成过程
解码器是用于生成语言输出的关键部分。在自回归生成任务中,解码器逐步生成每个词,直到达到终止条件(如生成结束符 <EOS>
)。
解码过程分为以下几步:
-
初始化状态:解码器首先接收输入的部分序列(或为空),以及编码器的输出(如果是翻译或其他条件生成任务)。
-
逐步生成词:
- 在第
t
步,解码器根据已经生成的前t-1
个词,通过自注意力机制和前馈网络,生成第t
个词的概率分布。 - 对于自回归生成模型如 GPT,前
t-1
个词来自已已经生成的序列。
- 在第
-
词的选择:每一步生成一个新的词。词的选择过程有两种常见方法:
- 贪婪搜索:直接选择概率分布中概率最大的词。
- 采样策略:如 Top-k 或 Top-p (nucleus) sampling,在生成时从高概率的候选集中随机采样一个词,增加多样性。
-
重复步骤:将生成的词加入序列,继续解码直到达到结束条件,如生成结束符
<EOS>
或达到最大长度。
6. 后处理与输出
生成的序列通过解码过程不断增长,当模型生成了 <EOS>
标记或达到最大长度限制时,生成过程终止。最终输出是一个由词嵌入转回词汇表索引的序列,即生成的句子。
transformer生成案例
Transformer 模型生成语言的过程确实非常复杂,我们可以通过更详细的分步描述来讲解。假设我们的输入是 "The cat is"
,目标是生成句子 "The cat is sleeping on the couch"
。我们将详细讲述每个步骤的执行机制和背后的原理。
第一步:输入处理和嵌入
1.1. 输入序列的表示
输入序列 "The cat is"
被转化为词汇表中的索引。例如:
1 | "The" -> 1001 |
所以输入可以表示为 [1001, 453, 87]
。
1.2. 词嵌入 (Word Embedding)
每个词索引通过嵌入层被转换为词嵌入向量。嵌入向量是一个高维向量,捕捉词的语义信息。假设嵌入维度是 512,那么对于 The
、cat
和 is
的嵌入可能类似于:
1 | "The" -> [0.3, -0.1, 0.5, ..., 0.7] |
1.3. 位置编码 (Positional Encoding)
因为 Transformer 本身没有顺序信息,需要通过位置编码来为每个词加上位置信息。位置编码是一组正弦和余弦函数,通过词的顺序生成唯一的编码。例如,假设序列中的位置是 [0, 1, 2]
,那么位置编码可以是:
1 | Pos_0 -> [0.0, 1.0, 0.0, ..., 1.0] |
将词嵌入与位置编码相加,得到了带有位置信息的嵌入:
1 | The_embedding + Pos_0 |
第二步:自注意力机制 (Self-Attention)
2.1. Query、Key 和 Value 计算
Transformer 中的自注意力机制通过计算每个词与其他词的相关性。为了实现这一点,每个词的嵌入会被投影成三个向量:Query
、Key
和 Value
。
假设有一个线性变换矩阵将嵌入向量投影为 Query
、Key
和 Value
:
1 | Q_The = W_Q * The_embedding |
对于 "cat"
和 "is"
也是类似的:
1 | Q_cat = W_Q * cat_embedding |
2.2. 计算注意力得分 (Attention Scores)
对于每个词,使用其 Query
与其他所有词的 Key
进行点积,计算注意力得分。这些得分表示该词和其他词的相关性。计算如下:
1 | score_The_cat = dot(Q_The, K_cat) |
对每个词进行相同的计算,形成一个注意力得分矩阵:
1 | [ |
然后通过 softmax 将这些得分归一化,以确保权重和为 1,形成注意力权重。
2.3. 计算上下文表示 (Context Representation)
将每个词的 Value
向量按照注意力权重加权求和,得到该词的上下文表示。比如,The
的上下文表示会结合它对 cat
和 is
的关注程度:
1 | context_The = weight_The_cat * V_cat + weight_The_is * V_is |
这个过程会对每个词执行一遍,得到新的上下文表示:
1 | context_The, context_cat, context_is |
第三步:前馈神经网络 (Feed-Forward Network)
每个词的上下文表示会被输入到一个前馈神经网络中。前馈网络由两层全连接层构成,能够进一步提取和增强特征。
假设对于 context_The
,前馈神经网络如下:
1 | hidden_The = ReLU(W1 * context_The + b1) |
同样的过程也会对 cat
和 is
进行。这个步骤后,每个词的表示变得更加丰富,模型能够更好地捕捉词之间的复杂关系。
第四步:解码器生成新词
4.1. 开始生成新词
当输入序列 "The cat is"
被处理完毕,解码器将基于这个输入预测下一个词。解码器通过多层自注意力和前馈神经网络计算得到下一个词的概率分布。
假设在这一阶段,模型计算出以下概率分布:
1 | {"sleeping": 0.4, "running": 0.3, "sitting": 0.2, ...} |
模型选择概率最高的词 "sleeping"
作为下一个词。
4.2. 输入更新,继续生成
模型现在将 "The cat is sleeping"
作为新的输入。输入再次被嵌入、位置编码、自注意力机制处理,解码器生成下一个词。
假设此时模型生成的概率分布为:
1 | {"on": 0.5, "under": 0.3, "next": 0.1, ...} |
模型选择 "on"
。
4.3. 重复生成过程
接下来,输入更新为 "The cat is sleeping on"
,解码器继续生成新词,如 "the"
和 "couch"
。这一生成过程会持续,直到模型生成结束符 <EOS>
或达到最大长度。
第五步:完成生成
生成句子的完整流程如下:
- 初始输入
"The cat is"
。 - 模型生成下一个词
"sleeping"
,输入变为"The cat is sleeping"
。 - 模型生成
"on"
,输入变为"The cat is sleeping on"
。 - 模型生成
"the"
,输入变为"The cat is sleeping on the"
。 - 模型生成
"couch"
,输入变为"The cat is sleeping on the couch"
。
当生成结束符 <EOS>
后,生成过程完成,最终的输出是:
1 | "The cat is sleeping on the couch" |
下一个词的概率分布是怎么得来的?
为了更详细地解释模型如何生成概率分布 {"sleeping": 0.6, "running": 0.2, "walking": 0.1, ...}
,我们需要深入到每个步骤的核心,特别是在 Transformer 中,如何从输入的句子生成每个词的概率分布。这个概率分布的产生是通过多个复杂的步骤实现的,涉及了自注意力机制、词嵌入、线性变换以及 softmax 函数。让我们按步骤剖析:
1. 输入处理
假设输入是 "The cat is"
,模型接收这个输入进行处理。由于我们使用的是 Transformer,它将输入的词转化为词嵌入,每个词的嵌入是一个向量,包含了词的语义信息。对于句子 "The cat is"
,输入部分的处理可以表示如下:
"The"
→ 向量1"cat"
→ 向量2"is"
→ 向量3
这些词嵌入向量被编码器处理,生成相应的上下文表示(即考虑了句子整体结构的向量)。
2. 自注意力机制
在生成下一个词之前,解码器需要通过自注意力机制结合句子上下文和已经生成的词。例如,在预测 "is"
之后的词时,解码器会处理前面生成的 "The cat is"
,并且通过自注意力机制关注这些词之间的关系。
自注意力机制会让模型理解词与词之间的依赖关系。例如,它可能发现 "cat"
是名词,而 "is"
是动词,这样模型在预测下一个词时,就会倾向于选择一个合理的补充词,例如动词的分词形式(如 "sleeping"
)。
3. 线性变换与 softmax
接下来,模型会将当前的上下文表示(即 "The cat is"
)输入到一个线性层(全连接层),这个线性层的作用是将上下文表示转化为与词汇表中每个词相关的得分。
-
线性层的输出:假设词汇表中有 10,000 个词,那么线性层的输出就是一个长度为 10,000 的向量。这个向量的每个元素代表某个词作为下一个词的分数(可以理解为“可能性”)。
-
softmax 函数:线性层输出的得分通过 softmax 函数转换为概率。softmax 将每个词的得分转换成一个 0 到 1 之间的值,表示该词作为下一个词的可能性。这些概率的总和为 1。
4. 概率分布生成过程
通过 softmax 函数,模型为每个词生成一个概率。例如,假设以下词汇表中的部分词的概率分布是:
1 | { |
这些概率是基于当前上下文 "The cat is"
,以及自注意力机制生成的语义信息得出的。模型计算出,"sleeping"
最符合当前上下文,概率最大,因此它的概率是 0.6。"running"
和 "walking"
也符合语法,但不如 "sleeping"
合适,因此它们的概率较低。
5. 为什么是这些概率?
这些概率的生成是基于以下几点:
-
语法合理性:模型会优先选择那些符合语法的词。因为
"The cat is"
是一个常见的语法结构,接下来的词很可能是一个动词的分词形式(例如"sleeping"
或"running"
)。所以模型给这些词较高的概率。 -
上下文语义:自注意力机制让模型能够根据前面的词
"cat"
和"is"
推测下一个词可能是描述“猫”在做什么(例如"sleeping"
)。根据模型学习到的知识,"sleeping"
是更合理的选择,因此它的概率是 0.6。 -
词频与共现:模型在训练时接触过大量的句子,知道在
"The cat is"
这样的上下文中,"sleeping"
是常见的补充词。因此它会在 softmax 输出中为"sleeping"
分配更高的概率。
6. 最终选择下一个词
根据生成的概率分布,模型会选择概率最高的词,作为下一个输出词。在这个例子中,"sleeping"
的概率是 0.6,远高于其他候选词,因此模型会选择 "sleeping"
作为下一个词。
当然,除了贪心策略,模型也可以使用采样等策略随机选择词,但通常贪心策略效果较好。
总结
- 概率分布
{"sleeping": 0.6, "running": 0.2, "walking": 0.1, ...}
是通过解码器处理输入上下文("The cat is"
)并结合线性层与 softmax 函数得到的。 - 模型使用自注意力机制理解上下文,并为词汇表中的每个词计算出它作为下一个词的可能性。
- 概率最高的词通常是最符合上下文和语法的,因此模型最终选择概率最高的词进行输出。
生成第一个新词的过程
通常发生在模型接收到初始输入(如用户给定的一段话)并进行推理时。具体过程如下:
-
输入句子的预处理:
用户输入的句子(例如 “How are you?”)会被分词器(Tokenizer)转换为一系列词元(tokens)。比如,“How are you?” 可能会被分成 [How, are, you, ?],每个词元对应一个词表中的编号。 -
词元嵌入(Embedding):
这些词元会通过嵌入层(Embedding Layer)转换为向量表示。假设模型的嵌入维度是512,那么每个词元会被转换成一个512维的向量,最终形成一个形状为4×512
的输入矩阵。 -
经过自注意力模块:
这组向量(即输入矩阵)会被输入到自注意力模块中,模型通过自注意力机制生成key、value和query。这些向量捕捉了句子中的各个词之间的关系。 -
生成logits:
经过模型的多层处理(包括注意力机制和前馈神经网络),输出会是一组logits向量。这些logits是模型对词表中所有词的预测值,表示生成每个词的可能性。 -
通过softmax生成概率分布:
对logits进行softmax操作,将其转换为概率分布。每个词对应的概率表示模型生成该词的可能性。模型会根据这些概率从词表中选择最有可能的词作为下一个生成的词。 -
第一个新词的选择:
通常,生成的第一个新词是通过两种常见方法之一来选择的:- 贪婪解码(Greedy Decoding):直接选择概率最高的词。
- 采样(Sampling):根据概率分布随机采样一个词,可能会加入一些控制参数(如温度系数)来调整随机性的强度。
-
更新输入:
第一个生成的词元被添加到已经生成的序列中,作为下一步生成的上下文输入。模型会重复这个过程,继续生成接下来的词元。
通过这些步骤,模型会基于输入的上下文生成第一个新词,然后依次生成后续词元,直到完成整个句子的生成。
面试问题
简单描述一下wordpiece model 和 byte pair encoding,有实际应用过吗?
- WordPiece Model 和 Byte Pair Encoding (BPE) 都是自然语言处理中常用的分词算法,用于将单词分解为更小的子词单元,从而减少词表大小,同时处理未登录词问题。它们主要应用在语言模型训练过程中,如BERT、GPT等大规模预训练模型。
- WordPiece Model
- 原理:WordPiece将词分解为子词单元,根据频率来选择合适的子词组合。初始的词汇表包含单个字符,然后通过计算子词的合并频率,逐步构建词汇表。
- 优点:它能通过子词表示未知词汇,提高模型的泛化能力,并且在减少词汇量的同时,保留语义信息。
- 应用:BERT模型使用了WordPiece来处理输入的文本。
- Byte Pair Encoding (BPE)
- 原理:BPE也是一种基于频率的分词方法。它通过反复合并出现频率最高的字符对或子词对,逐步构建子词词汇表。初始词汇表也是由单个字符组成,合并规则则由训练数据决定。
- 优点:它简单高效,能够捕捉常见的子词模式,处理未登录词的能力也非常强。
- 应用:GPT模型和Transformer架构经常使用BPE进行分词。
FlashAttention
v1、v2 - 公式推导 && 算法讲解
- 为了提高大模型中 Attention 层的计算速度,Tri Dao 在 2022 年 5 月提出了 FlashAttention 算法(即 V1),计算速度相比于标准实现提高了 2 - 4 倍(不同的 sequence length 会不一样)。这个算法主要针对的是训练场景。
- 讲解文章 https://zhuanlan.zhihu.com/p/680091531
Transfomer相对于RNN有什么优势
- 并行计算能力
RNN:RNN 是一种序列模型,必须按时间步依次处理输入数据。也就是说,第 t 时刻的输出依赖于第 t-1 时刻的计算结果,因此不能并行化处理序列中的元素,这导致训练时间较长。
Transformer:Transformer 利用自注意力机制(Self-Attention),使得每个输入位置的处理可以与其他位置的元素并行进行。这意味着 Transformer 能够在同一时间处理整个输入序列,从而大大加快了训练和推理速度。这种并行化特性使 Transformer 非常适合在 GPU 上运行,尤其对于长序列,训练速度相较 RNN 显著提升。 - 捕捉长距离依赖关系的能力
RNN:RNN 的主要缺点之一是难以有效捕捉长距离依赖关系。由于其每个时间步的状态依赖于上一个时间步的状态,随着序列的增长,长距离的依赖可能被遗忘(梯度消失问题)。虽然 LSTM 和 GRU 在一定程度上缓解了这个问题,但仍然存在限制。
Transformer:通过自注意力机制,Transformer 可以直接建模序列中任意两个位置之间的依赖关系,无论这两个位置相隔多远。这意味着 Transformer 在处理长序列时,能够高效且准确地捕捉长距离的依赖关系,而不会像 RNN 那样受到序列长度的限制。 - 消除递归结构,提升效率
RNN:RNN 的递归结构需要依次处理序列中的每个元素,这种顺序性导致模型无法并行执行,尤其在长序列任务中,RNN 的训练和推理效率较低。
Transformer:Transformer 通过完全消除递归结构,取而代之的是基于全局自注意力机制的并行计算,这大大提升了计算效率。每个位置的输出只需通过矩阵运算来计算,无需逐个时间步依赖前一个输出,极大地提升了效率。 - 更好的记忆和上下文捕捉
RNN:RNN 的记忆是通过隐状态(hidden state)来传递的,每一步的状态依赖于前一步的信息传递。这种方式在处理长序列时会因为过多的信息传递而产生信息损失,导致模型难以保留远距离的上下文信息。
Transformer:Transformer 使用自注意力机制,使得每个词或元素能够直接与其他所有元素建立联系。这种全局视角能够让模型在同一层次上捕捉到整个输入序列的全局信息,从而更好地保留上下文信息。 - 避免梯度消失问题
RNN:RNN 依赖于逐步传递的隐状态,训练时容易遇到梯度消失或梯度爆炸的问题,尤其是在处理长序列时,梯度的传播会变得非常困难。
Transformer:Transformer 没有依赖于序列传递的隐状态,而是通过自注意力机制和前馈神经网络来计算输出。这样就避免了在长序列上梯度消失或爆炸的问题,训练更为稳定,尤其在长文本或长序列任务中表现尤为显著。 - 更强的表示能力
RNN:RNN 只能通过顺序计算来处理序列信息,这种线性的计算方式在处理复杂序列依赖时可能有局限。
Transformer:通过多头自注意力机制(Multi-Head Attention),Transformer 可以从多个子空间同时处理信息,每个注意力头关注不同的信息模式,增强了模型的表示能力。多头自注意力机制允许模型同时关注输入序列中的不同部分,获取不同层次的特征。 - 适应性更强的输入表示
RNN:RNN 的输入表示是通过序列顺序传递的,缺乏对输入元素之间关系的灵活表示。
Transformer:Transformer 通过位置编码(Positional Encoding)来为序列中的每个位置引入位置信息。这种设计使得 Transformer 在处理文本等有序数据时,不会像 RNN 一样依赖于输入的顺序,还可以灵活适应各种输入长度和结构。 - 更容易并行扩展
RNN:RNN 的递归结构天然不适合并行处理,尤其在训练非常深层的 RNN 模型时,计算资源的利用效率较低。
Transformer:Transformer 的并行性和层级结构使其更容易扩展到大规模数据和模型,特别适合深层模型的并行计算。因为没有递归关系,Transformer 可以利用 GPU 进行大规模的并行计算,大幅提升训练效率和处理能力。 - 更适合预训练和迁移学习
RNN:尽管 RNN 可以在特定任务上进行预训练,但其能力有限,尤其在处理大规模数据和迁移学习时,效果不如 Transformer。
Transformer:Transformer 的架构非常适合大规模预训练模型,如 BERT、GPT 系列。这些模型通过在大规模数据上进行无监督预训练,能够很好地迁移到各种下游任务中,并且预训练的表示可以用于不同领域的任务,大大提高了模型的泛化能力和迁移学习能力。
为什么缓存KV,而不是Q呢?
在Transformer模型里,注意力机制对键(Key,K)和值(Value,V)进行缓存,却不缓存查询(Query,Q),这主要和模型的推理过程以及计算效率有关。下面来详细分析:
1. KV缓存的用途
KV缓存主要在自回归生成的场景下发挥作用,像文本生成这种一个词接一个词生成的过程。具体作用如下:
- 避免重复计算:在处理输入序列时,键(K)和值(V)矩阵是由编码器或者之前的解码器层计算得出的,而且在生成长输出序列时,它们不会发生改变。要是把这些结果缓存起来,就不用在生成每个新token的时候都重新计算,能让推理速度提升好几倍。
- KV维度固定:K和V的维度取决于输入序列的长度(记为 $ N $),在整个生成过程中,这个维度是固定不变的。
2. Q值不缓存的原因
- 动态生成:查询(Q)矩阵是根据输入的token和权重矩阵动态计算出来的。每生成一个新token,Q矩阵都会发生变化,所以没办法提前进行缓存。
- Q是用于查询当前token与所有已生成token的关系,所以只有当前token的Q是新的,之前的Q并不需要被再次使用。(后续步骤不需要历史Q),因此不将Q做缓存。
- 总结:Q 不缓存的原因:Q 是当前步骤的专属向量,后续步骤无需使用,缓存既无收益,又会浪费内存。
3. 数学层面的解释
假设输入序列为 $ X $,已生成的输出序列为 $ Y_{1:t} $。
- KV计算:$ K = \text{Linear}_k(X) V = \text{Linear}_v(X) $,这里的 $ K $ 和 $ V $ 只和输入 $ X $ 有关,所以可以缓存。
- Q计算:$ Q_t = \text{Linear}q(\text{DecoderState}(Y{1:t})) $,其中 $ Q_t $ 依赖于当前的解码器状态,也就是已生成的输出 $ Y_{1:t} $,所以每次都要重新计算。
4. 实际应用中的性能对比
在实际应用中,使用KV缓存能让推理速度提升3 - 10倍。以GPT - 2为例:
- 无缓存情况:生成1000个token可能需要10秒。
- 有KV缓存时:生成同样数量的token可能只需要2秒。
综上所述,对KV进行缓存是为了在自回归生成过程中复用固定的计算结果,而Q值由于其动态特性,必须在每一步都重新计算,所以不进行缓存。
prefill阶段的 Q 矩阵会一直变化吗
在Transformer模型的生成过程中,prefill阶段(也称为并行解码阶段)和后续的自回归生成阶段在Q矩阵的计算方式上存在本质区别。具体来说:
1. Prefill阶段的Q矩阵特性
在prefill阶段,模型会并行处理整个输入序列(例如Prompt中的所有token),此时Q矩阵的生成规则如下:
- Q矩阵维度固定:Q的维度由输入序列长度 ( N ) 决定,即 ( Q \in \mathbb{R}^{N \times d_q} ),其中 ( d_q ) 是query向量的维度。
- 一次性计算所有Q:所有位置的query向量会根据输入序列一次性计算完成,并且在prefill阶段不会动态变化。
- 与KV维度匹配:在prefill阶段,Q、K、V的序列长度均为 ( N ),因此可以并行计算注意力权重矩阵 ( A = \text{softmax}(QK^T/\sqrt{d}) )。
2. Prefill与自回归阶段的关键区别
阶段 | Q矩阵计算方式 | KV缓存状态 | 是否支持并行 |
---|---|---|---|
Prefill | 并行计算所有位置的Q(固定维度 ( N )) | 初始化KV缓存 | 是 |
自回归生成 | 逐token计算Q(维度随生成步数 ( t ) 增长) | 复用缓存的KV,动态扩展Q | 否 |
3. 为什么Prefill阶段不需要缓存Q?
虽然Prefill阶段的Q矩阵在计算过程中不变化,但通常不会对其进行缓存,主要原因是:
- 计算成本低:Q矩阵可以通过简单的线性变换(如 ( Q = XW_q ))从输入嵌入 ( X ) 直接计算得到,计算开销远低于KV。
- 仅需计算一次:Prefill阶段本身就是一次性操作,无需像KV那样在多个生成步骤中复用。
- 内存效率优先:缓存Q会增加额外的内存占用(尤其在长序列下),而其复用价值有限。
4. 总结
- Prefill阶段的Q矩阵在单次计算中不变化,但由于其计算成本低且无需跨步骤复用,因此通常不进行缓存。
- Q矩阵的动态变化特性仅出现在自回归生成阶段,此时每个时间步 ( t ) 只计算当前位置的query向量,并且需要依赖之前的生成结果。
这种设计使得Transformer在生成过程中能够平衡计算效率和内存使用,尤其在处理长文本时表现出显著优势。