Transformer 模型架构(Transformer Architecture) #
注意力机制(Attention Mechanism) #
⁉️ 什么是注意力机制(Attention Mechanism)?其中的Q,K,V都代表什么?
什么是注意力机制(Attention Mechanism)?其中的Q,K,V都代表什么?
注意力机制(Attention Mechanism) 的核心思想是将 输入看作键-值对的数据库,并 基于查询计算注意力权重 (attention weights),可以动态地选择哪些输入部分(例如词语或特征)最为重要。注意力机制定义如下:
\[ \textrm{Attention}(\mathbf{q}, \mathcal{D}) \stackrel{\textrm{def}}{=} \sum_{i=1}^m \alpha(\mathbf{q}, \mathbf{k}_i) \mathbf{v}_i, \]- 这里的 q,查询(Query) 是当前模型试图“寻找”或“关注”的目标。它是一个向量,代表了你想匹配的信息。
- 公式中的 k,键(key) 是所有潜在匹配目标的特征表示。每个键对应于输入中的一个元素,表示这个元素的特性或身份。
- 其中的 v,值(value) 是和键一起存储的信息,也是最终被提取的信息。注意力机制的目标是通过查询和键找到最相关的值。
注意力机制的 一般步骤 为:
- 对查询和每个键计算相似度。(⚠️注意:Transformer在比较相似性时不是像 Embedding 后一样通过手动定义相似性方法(如余弦相似度,kernel)的比较 vector,而是使用自注意力(Self-Attention),它不是直接定义相似度,而是 让神经网络自己学习“什么是相似”。在提升模型表现,调整 weight 的过程中,模型利用自注意力(Self-Attention)最终学会了:哪些 Query 和哪些 Key 需要匹配。)
- 对这些相似度进行归一化(通常使用 Softmax 函数)。归一化后的结果称为注意力权重(Attention Weights)。
- 将注意力权重与对应的值相乘,得到一个加权求和结果。这个结果就是当前查询的输出。
在 transformer 中,Query (Q)、Key (K) 和 Value (V) 都是从输入嵌入(embedding)中线性变换得到的。它们的计算方式如下:
\[ Q = X W_Q, \quad K = X W_K, \quad V = X W_V \]得到 Q,K,V 的过程 相当于经历了一次线性变换。Attention不直接使用 X 而是使用经过矩阵乘法生成的这三个矩阵,因为使用三个可训练的参数矩阵,可增强模型的拟合能力。
Note: 这里的
$$ X ∈ R^{B \times L \times d_{model}} $$
其中
B: Batch_size
,L: Context_Window_size
,d_{model}: Embedding size
。通过和$$ W_Q ∈ R^{d_{model} \times d_q}, W_K ∈ R^{d_{model} \times d_k}, W_V ∈ R^{d_{model} \times d_v} $$
三个 weight matrix,将原本的 embedded 信息变换到新的
$$ Q ∈ R^{B \times L \times d_q}, K ∈ R^{B \times L \times d_k}, V ∈ R^{B \times L \times d_v} $$ 中。在标准实现中,通常令
d_q = d_k = d_v = d_{model} / h
,其中h
是 head 数(multi-head attention)。
⁉️ 关于点积的理解?
关于点积的理解?
在 Self-Attention 机制中,相似性本质上是由 点积(Dot Product) 计算得出的,它用于衡量词向量(embedding)之间的关系。
$$ \mathbf{x} \cdot \mathbf{y} = x_0 y_0 + x_1 y_1 + \dots + x_n y_n $$
$$ 如 \mathbf{a} \cdot \mathbf{b} = (1)(4) + (3)(2) = 4 + 6 = 10 $$
Note: 点乘的几何意义是:
x
在y
方向上的投影再与y
相乘,反映了两个向量的相似度。点乘结果越大,表示两个向量越相似。
一个矩阵 X
由 n
行向量组成。比如,我们可以将某一行向量 V_i
理解成一个词的词向量,共有 n
个行向量组成 n×n
的方形矩阵:
$$ V_0 = \begin{bmatrix} v_{00}, v_{01}, \dots, v_{0d_{model}} \end{bmatrix} $$
\[ V = \begin{bmatrix} V_0 \\ V_1 \\ \vdots \\ V_n \end{bmatrix}, V^\top = \begin{bmatrix} V_0^\top & V_1^\top & \dots & V_n^\top \end{bmatrix} \]矩阵相乘计算如下:
\[ VV^\top = \begin{bmatrix} V_0 \cdot V_0 & V_0 \cdot V_1 & \dots & V_0 \cdot V_n \\ V_1 \cdot V_0 & V_1 \cdot V_1 & \dots & V_1 \cdot V_n \\ \vdots & \vdots & \ddots & \vdots \\ V_n \cdot V_0 & V_n \cdot V_1 & \dots & V_n \cdot V_n \end{bmatrix} \]以 VV^T
中的第一行第一列元素为例,其实是向量 V_0
与 V_0
自身做点乘,其实就是 V_0
自身与自身的相似度,那第一行第二列元素就是 V_0
与 V_1
之间的相似度。
⁉️ 什么是缩放点积注意力(Scaled Dot-Product Attention)?其中的过程是什么?
什么是缩放点积注意力(Scaled Dot-Product Attention)?其中的过程是什么?
缩放点积注意力(Scaled Dot-Product Attention) 是 Transformer 结构中的核心机制之一,它用于计算查询(Query)、键(Key)和值(Value)之间的注意力分数,以捕捉序列中不同位置的关联性。其数学公式为:
$$ \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V $$
- 在计算过程中,首先对查询矩阵 Q 和键矩阵 K 进行点积(Dot Product),得到注意力得分(Attention Scores)。这个点积运算的本质是衡量 查询向量(Query) 和 键向量(Key) 之间的相似度。
$$ S = QK^T \in \mathbb{R}^{L \times L} $$
Note: 查询矩阵 Q 和键矩阵 K 进行点积的结果 - LxL 的矩阵,可以理解为在 Context Window 为 L 的文本中,每一个位置的 token i,去“问”整句话里的每个 token(包括它自己),打分每个 token 的重要性。
在 Softmax 后就变成每行是一个 关注分布。
- 之后,Softmax 作用于Q,K计算出的相似度得分,以将其转换为概率分布,使其满足:
- 归一化(Normalization):确保所有注意力权重总和为 1,便于解释。
- 放大差异(Sharpening):通过指数运算增强高相关性词的权重,抑制低相关性词。
$$ A = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) $$
- 最后将 Softmax 的结果(矩阵 A)和矩阵 V 加权求和,每一行输出是当前 token 从整句话中“拉取”的语义信息。
- Q 与 K 的相似度 → 决定应该「从谁那获取信息」
- V 储存的是语义信息(比如词性、上下文、含义等)
- 最后加权融合 → 得到的是上下文感知的语义表示
这一步是 Transformer 的“信息流动”核心—— 你不是只看自己,而是看整句话对你有意义的部分,然后合成一个更丰富的表示。
$$ \text{Output} = A \cdot V \in \mathbb{R}^{L \times d_v} $$
L
表示输入序列的 token 数量,即 每个 token 都有一个上下文增强的表示。d_v
是值向量的维度,表示每个 token 的信息表示的维度。
⁉️ 为什么缩放点积注意力(Scaled Dot-Product Attention)要除以 √d?
为什么缩放点积注意力(Scaled Dot-Product Attention)要除以 √d?
缩放点积注意力的计算过程如下:
$$ \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V $$
设想 Query 和 Key 是随机向量,维度为 d_k
。假设它们的每个元素都是 0 均值,单位方差的正态分布。那么:
$$ Q \cdot K = \sum_{i=1}^{d_k} q_i k_i $$
这个点积的期望为 0,但方差是:
$$ \text{Var}(Q \cdot K) = d_k $$
也就是说,当维度越大,点积的结果可能会随着 d_k(Key 维度的大小)增加而变大。将点积作为输入传递给 Softmax 函数时,Softmax 对大数值特别敏感,因为它会根据输入的相对大小来计算概率值。如果点积值变得非常大,Softmax 会让其中一些值的输出接近 1,而其他值接近 0,这会 导致计算不稳定或梯度消失等问题。
因此,在应用 Softmax 之前,需要对注意力得分进行缩放,即除以 √d_k,这样可以防止梯度消失或梯度爆炸问题,提高训练稳定性。除以 √d_k 相当于把方差从 d_k 降为 1,使得输入的数值规模保持稳定:
$$ \text{Var}\left( \frac{Q \cdot K}{\sqrt{d_k}} \right) = 1 $$
这个缩放的主要目的是 将点积的方差控制在一个合理的范围,使其不随向量维度的增加而变得过大。
⁉️ 什么是自注意力(Self-Attention)机制?它与传统注意力有何区别?
什么是自注意力(Self-Attention)机制?它与传统注意力有何区别?
Self-Attention(自注意力) 和 一般 Attention(注意力机制) 的 核心计算原理是相同的,都是通过 Query(Q) 和 Key(K) 计算相似度分数,再对 Value(V) 进行加权求和。但它们的区别在于作用目标不同:
Self-Attention(自注意力)
- Q、K、V 都来自同一个输入序列 X ,即 自身内部计算注意力,挖掘序列中不同位置之间的关系。例如,在 Transformer 的 Encoder 里,每个单词都和句子中的所有单词计算注意力。
General Attention(通用注意力,通常用于 Seq2Seq 结构)
- Q 和 K、V 来自不同的地方,通常是 Q 来自 Decoder,而 K、V 来自 Encoder,用于建立 Encoder 和 Decoder 之间的联系。例如,在机器翻译中,Decoder 生成当前词时,会对 Encoder 编码的所有词计算注意力,从而获取最相关的信息。
⁉️ 上下文窗口(Context Window)的限制如何影响模型能力?
上下文窗口(Context Window)的限制如何影响模型能力?
上下文窗口(Context Window) 的限制,直接决定了模型能“看见”多少输入信息,对它的理解能力、记忆能力、推理能力都有深刻影响。Transformer 中的上下文窗口大小(context window size,通常是 n)指 模型一次性处理的Token序列的最大长度。
模型在训练和推理时,只能基于这个窗口内的文本进行计算注意力,超出长度的内容根本看不到,也无法建模。
上下文超长时,模型会直接截断输入,无法访问超出的历史信息。
- 改进方法:让模型知道历史信息:引入外部存储,避免简单截断。
人类阅读可以把小说第一章和最后一章关联,但标准Transformer只能关注窗口内的Token。
- 改进方法:用相对位置信息和稀疏注意力缩短信息路径。
上下文窗口越大,计算量指数级暴涨,训练成本和推理时间都会大幅上升。
- 改进方法:稀疏、低秩近似、滑窗等方法降低计算复杂度。
⁉️ 计算自注意力机制的时间和空间复杂度,分析其瓶颈。
计算自注意力机制的时间和空间复杂度,分析其瓶颈。
自注意力机制(Self-Attention Mechanism)的时间复杂度(Time Complexity)和空间复杂度(Space Complexity)主要受输入序列长度 n 影响。在标准的 Transformer 结构中,每个 Self-Attention Layer 计算 注意力权重(Attention Weights) 需要进行矩阵乘法,计算 Query Q 和 Key K 之间的点积并进行 Softmax 归一化。
其中, Q 和 K 的维度均为 (n x d_k) ,计算 QK^T 需要 O(n^2 d_k) 次乘法运算,而应用 Softmax 需要 O(n^2) 的额外计算,因此 整体时间复杂度为:
\[ O(n^2 d_k) \]Self-Attention 计算过程中,需要存储 注意力权重矩阵( n x n ),此外还需要存储 中间结果(如 Softmax 输出、梯度),使得 空间复杂度达到:
\[ O(n^2 + n d_k) \]瓶颈分析(Bottleneck Analysis)
- 计算瓶颈(Computational Bottleneck):由于 Self-Attention 需要 O(n^2 d_k) 的计算量,因此在超长文本(如 10K 以上 Token)上,计算成本极高,推理速度变慢。
- 内存瓶颈(Memory Bottleneck):存储 O(n^2) 的注意力权重矩阵会 占用大量显存(VRAM),限制了可处理的最大序列长度。
- 长序列扩展性差(Scalability for Long Sequences):当 n 增大时,Transformer 计算复杂度随 n^2 级增长,难以应用于长文本建模。
⁉️ 为什么需要多头注意力(Multi-Head Attention)?多头设计如何提升模型表达能力?
为什么需要多头注意力(Multi-Head Attention)?多头设计如何提升模型表达能力?
多头注意力(Multi-Head Attention)是 Transformer 结构中的关键组件,它通过多个独立的注意力头来提升模型的表达能力。其核心思想是 让模型在不同的子空间(Subspaces)中独立学习不同的特征表示,而不是仅依赖单一注意力机制。例如可以关注不同类型的关系(如语法关系、语义关系、长距离依赖等)。
在计算过程中,输入序列的特征矩阵首先经过线性变换,生成查询(Query, Q)、键(Key, K)、和值(Value, V)。然后,每个注意力头都会独立地对 Q、K、V 进行投影,将其拆分成多个低维子空间,即:
\[ \mathbf{h}_i = f(\mathbf W_i^{(q)}\mathbf q, \mathbf W_i^{(k)}\mathbf k,\mathbf W_i^{(v)}\mathbf v) \in \mathbb R^{p_v}, \]其中 W_i^q, W_i^k, W_i^v 是可训练的投影矩阵,每个头都对应一组独立的参数。随后,每个头分别执行 Scaled Dot-Product Attention(缩放点积注意力)。计算完成后,各个头的注意力输出会被拼接(Concatenation),然后通过一个最终的线性变换矩阵 W^o 进行映射:
\[ \begin{split}\mathbf W_o \begin{bmatrix}\mathbf h_1\\\vdots\\\mathbf h_h\end{bmatrix} \in \mathbb{R}^{p_o}.\end{split} \]这样,多头注意力的最终输出仍然保持与输入相同的维度,同时融合了来自多个注意力头的信息,提高了模型对不同层次语义的建模能力。
位置编码(Positional Encoding) #
⁉️ 为什么 Transformer 需要位置编码?纯 Self-Attention 为何无法感知位置信息?
为什么 Transformer 需要位置编码?纯 Self-Attention 为何无法感知位置信息?
在 Transformer 模型中,位置编码(Position Encoding)是用于注入位置信息的关键机制,因为模型本身的 Self-Attention 机制无法感知输入序列中元素的顺序或位置。Transformer 通过 Self-Attention 计算序列中各元素之间的关系,每个元素的表示(representation)由其与其他所有元素的相互作用决定。然而, Self-Attention 本身是位置无关的(position-independent),即它并不考虑元素在序列中的相对或绝对位置。因此,如果不显式地引入位置编码,模型就无法了解输入序列的顺序信息。
Note: 我们在使用 PE 时关注的是词语之间的 相对关系,而不是绝对位置。因为Transformer 结构没有像 RNN 那样的 顺序处理能力,所以我们必须显式告诉它词语的位置信息。
⁉️ 绝对位置编码(Absolute PE)和相对位置编码(Relative PE)的核心区别是什么?
绝对位置编码(Absolute PE)和相对位置编码(Relative PE)的核心区别是什么?
绝对位置编码(Absolute Position Encoding, Absolute PE)和相对位置编码(Relative Position Encoding, Relative PE)的核心区别在于它们对序列中单词位置的表示方式。绝对位置编码是基于序列中单词的固定位置来定义每个单词的位置编码,这些编码是通过对每个位置进行显式编码(例如使用正弦和余弦函数)来获得的。这意味着 每个位置的编码是固定的,与其他词汇之间的相对关系无关。简单来说,绝对位置编码的设计是通过为每个位置分配唯一的标识符来捕捉顺序信息。绝对位置编码被广泛用于 Transformer 模型中,如原始的 Transformer 和 BERT,这些模型通过对输入的词汇序列和其位置编码的加和来保留词汇的顺序信息。
相对位置编码则是通过 考虑单词之间的相对位置来计算每个单词的编码,而不是单纯地依赖于其绝对位置。在这种方法中,位置编码的更新基于词语之间的相对距离,因此它能捕捉到不同词之间的相对关系,而不仅仅是它们在序列中的固定位置。相对位置编码的一个例子是 Transformer-XL 模型,它通过引入相对位置编码来克服标准 Transformer 在处理长序列时存在的记忆限制问题,从而提升了对长距离依赖的建模能力。
尽管在某些情况下,相对位置编码可以通过绝对位置得到(例如,简单地计算位置差),但这种方法仍然有限。相对位置编码有以下优势:
- 灵活性和泛化性:相对位置编码使得模型能够处理不同长度的输入,而绝对位置编码依赖于固定的输入长度。这意味着在不同任务或不同数据集上,使用相对位置编码的模型能够更好地进行泛化,尤其是在处理较长序列时。
- 更好的长距离依赖建模:相对位置编码能够更有效地捕捉长距离的依赖关系,因为它直接反映了词汇间的相对关系,而绝对位置编码则对远距离的依赖建模较弱,尤其是在长序列的上下文中。
- 减少位置编码的冗余:在传统的绝对位置编码中,序列中的每个位置都有唯一的编码,且这些编码是全局固定的,而相对位置编码只关心词汇间的相对位置,从而避免了位置编码的冗余,尤其是在处理非常长的序列时。
特征 | 绝对位置编码(Absolute PE) | 相对位置编码(Relative PE) |
---|---|---|
📍 输入内容 | 每个 token 被赋予一个对应的位置向量 PE(pos)。 | 直接建模 token 与 token 之间的相对位移 i - j。 |
⚙️ 融合方式 | 通常是词向量 + 位置向量相加后一起输入注意力层。 | 相对位置信息通常进入注意力权重的计算公式中,不参与Embedding相加。 |
🧠 模型的工作负担 | 需要模型通过学习注意力权重,自行感知相对距离。 | 相对距离信息直接注入权重计算,模型能明确知道谁离谁近。 |
🧾 编码效果 | 提供的是全局位置信息,比如第5个词、第10个词。 | 提供的是距离关系,比如“距离我+1”,“距离我-3”。 |
💡 经典用途 | 原版Transformer(如BERT、GPT)默认用Sinusoidal或Learned方式。 | 改进型结构如Transformer-XL、T5、DeBERTa偏向用相对位置编码。 |
⁉️ 为什么不直接用位置索引(如idx=1,2,3,...)作为位置编码?
为什么不直接用位置索引(如idx=1,2,3,...)作为位置编码?
假设用位置索引直接作为编码,例如:
位置1 → 编码为[1]
位置2 → 编码为[2]
位置3 → 编码为[3]
这种方式存在以下问题:
- 数值范围不受控
- 索引值会随着序列长度的增加无限增长(例如,序列长度100时位置编码为100),导致数值过大。
- 深度学习模型(尤其是基于梯度的优化)对输入的范围敏感,过大的值可能破坏训练的稳定性。
- 无法泛化到未见过的序列长度
- 如果模型在训练时只见过长度为512的序列,而测试时遇到长度为1024的序列,直接用idx会导致位置编码超出训练时的范围,模型无法处理。
- 无法表示相对位置
- 绝对位置索引(如1和2)无法直接表达相对距离(如“相邻”或“间隔3”)。
- 例如,位置
2-1=1
和位置100-99=1
的差值相同,但它们的语义关联可能完全不同。
Note: 位置编码的本质作用,就是在 点积(dot-product)这个步骤里,将位置信息“掺和进”模型的注意力计算,并不是直接告诉模型“这是第5个词”,而是通过数学结构,引导模型在点积阶段感知相对位置的关系。如果直接用位置索引,在点积后会丢失位置信息。
⁉️ Sinusoidal 位置编码的公式是什么?为什么选择正弦/余弦函数组合?
Sinusoidal 位置编码的公式是什么?为什么选择正弦/余弦函数组合?
Sinusoidal 位置编码(Sinusoidal Positional Encoding) 是 Transformer 模型中用于捕捉序列中单词位置的一种方法,是常见的绝对位置编码(Absolute Position Encoding)方法。Sinusoidal 位置编码通过正弦和余弦函数的组合来生成每个位置的唯一向量,这些向量与输入的词嵌入(Word Embedding)相加,从而使模型能够学习到每个单词在序列中的位置。Sinusoidal 位置编码使用相同形状的位置嵌入矩阵 P 输出 X+P,其元素按以下公式生成:
\[ \begin{split}\begin{aligned} p_{pos, 2j} &= \sin\left(\frac{pos}{10000^{2j/d}}\right),\\p_{pos, 2j+1} &= \cos\left(\frac{pos}{10000^{2j/d}}\right).\end{aligned}\end{split} \]Sinusoidal 位置编码输出的矩阵形状是:
$$ PE \in \mathbb{R}^{seq_len \times d_{model}} $$
矩阵内的每一行对应着不同词的位置信息 Vector,长度为 d_model
方便与词嵌入(Word Embedding)相加。
- 为什么选择正弦/余弦函数组合?
不同频率的周期性:正弦和余弦函数有不同的频率,使得每个位置的编码在不同维度上具有不同的周期。这种周期性使得模型可以通过不同频率的变化来学习相对位置关系。通过正弦和余弦函数的组合,位置编码能够覆盖较长序列的不同范围,模型可以捕捉到全局和局部的位置信息。无论文本有多长,编码的幅度不会发散,始终在
[-1,1]
范围。无重复的唯一表示:正弦和余弦函数的组合能够确保每个位置有一个独特的编码,这些编码在向量空间中是可区分的,能够提供丰富的位置信息。而且由于这两种函数的周期性和无穷制性质,不同位置的编码不会重复。
容易计算和扩展:正弦和余弦函数的计算非常简单且高效。它们无需额外的学习参数,且可以通过简单的公式根据位置直接计算得出。这样的位置编码方式能够在大规模数据中有效应用,同时支持较长序列的处理。
支持相对位置关系:这种编码方法能够通过比较不同位置的编码来推测它们之间的相对距离和顺序,尤其是在模型学习到的位置编码与实际任务(如机器翻译、文本生成)相关时,正弦/余弦函数的变化有助于保持序列的结构和信息流动。
假设两个位置
pos
和pos + Δ
,它们的编码满足一个很好的数学特性:$$ PE_{pos+\Delta} = f(PE_{pos}, \Delta) $$
具体来说,正弦余弦的加法公式:
$$ \sin(a + b) = \sin(a) \cos(b) + \cos(a) \sin(b) $$ $$ \cos(a + b) = \cos(a) \cos(b) - \sin(a) \sin(b) $$
会让模型在做 点积 时,天然地包含相对距离信息,无需模型后续显式推断
Note: 假设第
pos
个 token 的向量是:$$ PE_{pos} = [\sin(\frac{pos}{f_1}), \cos(\frac{pos}{f_1}), \sin(\frac{pos}{f_2}), \cos(\frac{pos}{f_2}), \dots ] $$
当两个位置
pos_i
和pos_j
做点积时:$$ PE_{pos_i} \cdot PE_{pos_j} = \sum_{k} \sin(\frac{pos_i}{f_k}) \sin(\frac{pos_j}{f_k}) + \cos(\frac{pos_i}{f_k}) \cos(\frac{pos_j}{f_k}) $$
这里每个 k 都是同频率的 sin/cos 配对相乘。而根据三角恒等式:
$$ \sin(a)\sin(b) + \cos(a)\cos(b) = \cos(a - b) $$
最终点积简化为:
$$ \sum_k \cos\left(\frac{pos_i - pos_j}{f_k}\right) $$
⁉️ 为什么位置编码可以直接与词向量逐元素相加?位置编码会破坏词向量的语义空间吗?
为什么位置编码可以直接与word embedding逐元素相加?位置编码会破坏词向量的语义空间吗?
Word embedding 主要表示单词的语义信息。Positional Encoding 提供额外的位置信息,以弥补 Transformer 结构中缺少序列顺序感的问题。相加的效果 是让同一个词(如 “apple”)在不同的位置有略微不同的表示,但仍保留其主要的语义信息。
虽然位置嵌入矩阵 P 与词向量 X 直接相加,但在 transformer 获得 Query (Q)、Key (K) 和 Value (V)的线形变化过程中(i.e. Q = XW_q),在学习 Weight 的过程中会将语义和位置信息分别投射在不同的维度上。Positional Encoding 并不需要通过训练来学习,它是固定的、基于位置的函数,因此不干扰原本的语义信息。
例如输入 token 的向量是:
$$ X_i = \text{WordEmbedding}(i) + \text{PositionalEncoding}(i) $$
Note: 位置编码与词向量的相加(在这个点,词向量和位置编码已经不可分辨地融合在了一起,对于模型来说,这就是一个“带位置信息的词语表示”),本质上就是把“位置信息”混入词的表达空间,形成一个新的、更丰富的特征空间。模型并不需要“知道”两者的来源,而是会 自动学习如何从这个混合后的向量中解读出信息。
Transformer的注意力机制和FeedForward层,本质上是线性变换 + 非线性激活的堆叠。只要输入的特征是连续的、可学习的,模型可以自己从统计规律中拆解出“这是位置的贡献”还是“这是语义的贡献”。很多论文都验证过这个现象:
- 如果用不同方式编码位置(比如加、拼接、乘法),最终模型的表现差别很小。
- 因为Transformer可以通过权重学习,把位置特征和词义特征解耦,或者做融合。
e.g. 好比听音乐,左声道和右声道的声音混在一起,你不用知道左右声道分别是什么,依然能听出音乐的立体感。模型也是一样,输入的
X_i = E_i + PE_i
就像混音,学习的过程就是在拆解、重构信号。
其中 PositionalEncoding
对应 sinusoidal
编码:
$$ PE_{pos, 2i} = \sin \left( \frac{pos}{10000^{2i/d}} \right), PE_{pos, 2i+1} = \cos \left( \frac{pos}{10000^{2i/d}} \right) $$
到注意力打分阶段,每个 token 被映射为:
$$ Q_i = W_Q X_i, K_j = W_K X_j $$
它们的点积:
$$ Q_i \cdot K_j = (W_Q (E_i + PE_i)) \cdot (W_K (E_j + PE_j)) $$
展开:
$$ = (W_Q E_i) \cdot (W_K E_j) + (W_Q E_i) \cdot (W_K PE_j) + (W_Q PE_i) \cdot (W_K E_j) + (W_Q PE_i) \cdot (W_K PE_j) $$
项目 | 含义 |
---|---|
$$(W_Q E_i) \cdot (W_K E_j)$$ | 💡词义相似度项:纯靠词向量计算的语义相关性。如果 E_i 和 E_j 意义接近,这一项会大。 |
$$(W_Q E_i) \cdot (W_K PE_j)$$ | ⚡词对位置敏感项:查询是词,键是位置。代表“词 i ”是否偏好关注某种位置的 j 。 |
$$(W_Q PE_i) \cdot (W_K E_j)$$ | ⚡位置对词的敏感项:查询是位置,键是词。代表“我在这个位置”是否想关注语义为 E_j 的词。 |
$$(W_Q PE_i) \cdot (W_K PE_j)$$ | 📏相对位置信息项:纯位置信息,捕捉 token 之间的空间关系,决定远近感知。 |
位置编码部分:
PE_i
和PE_j
会直接进入点积结果。- 因为
PE_i
本身带有顺序的波动特征,两个位置的PE
相似度决定了最终的 QK 值。
也就是说:
- 如果两个 token 相邻,
PE_i
和PE_j
非常接近。 - 这样它们的点积值更高,
Softmax
权重更大。 - 反之,距离越远,点积下降,权重越小。
⁉️ 什么是可学习的位置编码(Learned PE)?
什么是可学习的位置编码(Learned PE)?
可学习的位置编码(Learned Positional Encoding, Learned PE)是一种在模型训练过程中通过优化学习得到的位置编码方法。与传统的 Sinusoidal Positional Encoding(正弦波位置编码)不同,Learned PE 不使用固定的数学公式来表示位置,而是通过神经网络的训练自动学习每个位置的编码表示。通常,这些位置编码是通过与输入的 词嵌入(Word Embedding) 相加来为模型提供位置信息,从而使模型能够捕捉到输入序列中各个元素的顺序。具体来说,Learned Positional Encoding 是 通过一个嵌入层来生成的。这个过程如下:
- 位置嵌入(Position Embedding):每个位置(序列中的每个元素)都会被映射到一个可学习的向量。对于输入序列中的每个位置 i,我们为其分配一个嵌入向量 P_i ,这个向量是通过一个嵌入层学习得到的。
- 添加到词嵌入(Word Embedding):这些位置嵌入向量会与对应的词嵌入(Word Embedding)向量相加。假设某个词 w_i 在序列中的位置为 i,那么该词的最终输入向量为:
由于每个位置的编码表示是一个可训练的向量,它会在训练过程中和词嵌入(Word Embedding)一起作为输入传递到模型中。然后,模型通过反向传播算法更新这些可训练的参数,以便它们能够更好地捕捉到任务相关的位置信息。
Note: Learned PE 直接定义一个 可训练的位置Embedding矩阵,和词向量类似。它的 本质仍然是绝对位置编码,因为查表依赖的是位置索引 pos,不考虑“相对关系”。如果要模型能自动知道“相邻”还是“间隔三格”,还得靠 模型自己从训练中摸索。
可学习的位置编码的优点主要体现在 灵活性,由于位置编码是通过训练学习的,因此它可以在不同的任务和数据集上找到最优的表示,而不依赖于固定的模式(如正弦波的频率和相位)。这种灵活性使得它能够更好地适应各种复杂的数据模式和任务需求。
但它 需要更多的参数,Learned PE 需要为每个位置学习一个独立的参数,这使得模型的参数量增加,尤其是在处理长序列时,这可能会导致显著的计算和存储开销。同时也会有 过拟合风险:由于 Learnable PE 是基于数据学习的,它可能会过度拟合训练数据中的位置模式,尤其是在数据量较少的情况下,从而影响模型的泛化能力。
⁉️ 长度外推(Length Extrapolation)问题的本质是什么?哪些位置编码方法能缓解该问题?
长度外推(Length Extrapolation)问题的本质是什么?哪些位置编码方法能缓解该问题?
长度外推(Length Extrapolation)指的是:模型在训练时见过的序列长度为 L_train,但在推理时,需要处理一个远长于训练长度的序列 L_test > L_train,模型能否泛化处理这种“超出训练分布”的长度。
其本质问题是,Transformer 的自注意力机制理论上能接受任意长度输入,但位置编码部分通常是长度敏感的 —— 如果训练时只看过 pos=0~512
,测试时突然 pos=1000
,模型的空间感知会崩溃。尤其是绝对位置编码:
- 绝对位置编码将每个 pos 编码为独立特征,超出训练范围时模型完全没见过,无法泛化。
- 注意力矩阵的结构假设(比如位置偏移感知)未必能自动适应新的跨度。
而相对位置编码,不依赖绝对位置,天然支持超长文本。理论上可以任意外推,且直接嵌入相对位移。
⁉️ 写出 RoPE(Rotary Position Embedding)的数学公式,并解释其如何融合相对位置信息。
写出 RoPE(Rotary Position Embedding)的数学公式,并解释其如何融合相对位置信息。
RoPE(Rotary Positional Embedding)的核心思想:将位置信息通过旋转矩阵的方式,直接编码进Query和Key向量,位置不再是单纯的加法,而是一个“旋转操作”。 这种方法能确保:
- 点积时,天然包含相对位置的信息;
- 不需要改变Embedding维度;
- 保持空间的平滑性和周期性。
RoPE 的核心思想:将位置编码从“向量加法” → 变成“空间旋转”操作。
对于一个 token 的原始向量:
$$ x = [x_1, x_2, x_3, x_4, …, x_d] \quad \in \mathbb{R}^{d_{model}} $$
RoPE 会将每一个位置词嵌入后的 x
拆成偶数维组合,看成二维向量对:
$$ x_{i}^{(k)} = \begin{pmatrix} x_{2k-1} \ x_{2k} \end{pmatrix} $$
$$ x_i = [x_{i}^{(1)}, x_{i}^{(2)}, …, x_{i}^{(d/2)}] = \left[ \begin{pmatrix} x_1 \ x_2 \end{pmatrix}, \begin{pmatrix} x_3 \ x_4 \end{pmatrix}, \dots, \begin{pmatrix} x_{d-1} \ x_d \end{pmatrix} \right] $$
然后每对维度都做旋转矩阵变换:
\[ \text{RoPE}(x_{i}^{(k)}, pos) = \begin{bmatrix} \cos(\theta_i^{(k)} pos) & -\sin(\theta_i^{(k)} pos) \\ \sin(\theta_i^{(k)} pos) & \cos(\theta_i^{(k)} pos) \\ \end{bmatrix} \cdot x_{i}^{(k)} \]其中 $$\theta_i^{(k)} = 10000^{-\frac{2k-1}{d_{model}}}$$ (和Sinusoidal频率一样),pos
是 token
在序列中的绝对位置编号。
不像传统PE那样直接 WordEmbedding + PositionalEmbedding
,RoPE 在Attention时这样替换:
$$ Q^{\prime} = RoPE(Q, pos), K^{\prime} = RoPE(K, pos) $$
Note: RoPE 通过对 每两个相邻的维度进行旋转 来引入相对位置信息,相当于单独把
\[ \text{RoPE}(Q, \text{pos}) = \begin{bmatrix} \cos(\text{pos} \cdot \theta_i) & -\sin(\text{pos} \cdot \theta_i) \\ \sin(\text{pos} \cdot \theta_i) & \cos(\text{pos} \cdot \theta_i) \end{bmatrix} \begin{bmatrix} Q_{2i} \\ Q_{2i+1} \end{bmatrix} \]Q
(K
) 内的元素两两一组拿出来旋转再放回,这个过程并没有改变原始向量的维度。
让 Query 和 Key 都“自带位置感知”,再去做点积:
$$ \text{Attention}(Q^{\prime}, K^{\prime}) = \text{softmax} \left( \frac{Q^{\prime} {K^{\prime}}^T}{\sqrt{d}} \right) V $$
RoPE 设计上,利用旋转矩阵的性质,点积之后自动解出相对位移。假设有两个token i
和 j
,经过 RoPE 后点积:
$$ Q_i^{\prime} \cdot K_j^{\prime} = Q_i^T R(\theta, i-j) K_j $$
Note: RoPE旋转后,注意力的点积变成:
$$ Q_i^\top K_j = \sum_{k=1}^{d/2} \langle q^{(k)}, k^{(k)} \rangle \cdot \cos(\theta_i^{(k)} - \theta_j^{(k)}) $$
q^{(k)}、k^{(k)}
是第k
组的词向量子空间部分;θ_i^{(k)}、θ_j^{(k)}
是第k
组频率下的位置相位;- 整个点积由各组的相对角度共同决定。
这里 R(θ, i-j)
本质上就是 原向量的点积值乘上一个只依赖相对位置 i-j 的旋转因子。
Note: 虽然绝对位置编码像 Sinusoidal PE 点积的结果捕捉了相对位置差
(i - j)
,但是 相对位置信息是通过隐式学习得到的,他依赖模型训练来学习这种关系。而 RoPE 的相对位置
(j - i)
直接显示包含体现在点积里(点积后严格反应相对位置),而不依赖模型训练来学习这种关系。这种性质是几何上严格保持的,无论多长输入,旋转只受位置差影响。相比Sinusoidal,RoPE的点积结果就是一个经过旋转偏移的内积,不需要靠模型自己推测。
⁉️ 为什么相对位置编码在长文本任务(如文本生成)中表现更好?
为什么相对位置编码在长文本任务(如文本生成)中表现更好?
传统的绝对位置编码(Absolute PE),无论是Sinusoidal还是Learned,都是:
\[ PE(pos) \quad \text{where} \quad pos \in [0, \text{max\_len}] \]每个 pos 都被看作一个固定编号,模型学到的是:
- 第 1 个 token 通常是 BOS,
- 第 20 个 token 通常是句尾,
- 第 100 个 token 应该换段落。”
但问题是:
- 如果训练时
pos ∈ [0,512]
,推理时pos = 1024
,模型根本没见过; - 学到的 只是位置的编号感,无法迁移。
而 相对位置编码(Relative PE) 关注的是:
\[ \text{pos\_diff} = i - j \]无论输入长度是 128、512、1024,i - j 的相对差距不会失效。这是更符合语言本质的,因为语言的语义往往依赖于:
- 临近 token 的相对关系;
- 局部上下文的配对和依赖。
Note: 长文本任务,尤其是生成任务,文本会出现以下特点:
- 长序列超出训练长度;
- 长距离依赖特征极强;
- 序列位置编号不可预测。
相对位置编码天生解决这个问题,因为它不会关注token的绝对编号,而是 关注:“我在离你x步的地方。”
方法 | 含义 | 特性 | 类比 |
---|---|---|---|
加法 | 向词向量中添加一个固定的 pos_embedding 向量 | 只能告诉模型:这个 token 在第 pos 位 | 告诉模型你在哪儿 |
旋转 | 整个向量在空间上旋转,角度由 pos 决定 | 通过点积自然编码相对位置差 | 告诉模型你离谁有多远 |
Transformer 模型架构细节和其他组件 #
⁉️ 解释Transformer 模型架构细节?
解释Transformer 模型架构细节?
Transformer模型是一种 Encoder-Decoder 架构。Transformer的输入(源序列)和输出(目标序列)在送入编码器和解码器之前,会与 位置编码(positional encoding)相加。这种结构的编码器和解码器都基于 自注意力机制(self-attention),并通过堆叠多个模块来实现。
具体来说,Transformer 的编码器(Encoder)由多个相同的层堆叠而成,每一层包含两个子层:第一个是 多头自注意力(multi-head self-attention),第二个是 逐位置的前馈网络(positionwise feed-forward network)。在编码器的自注意力机制中,查询(queries)、键(keys)和值(values)都来自前一层的输出。每个子层都使用 残差连接(residual connection) 设计,并在其后进行 层归一化(layer normalization),确保模型的训练更稳定。最终,编码器为输入序列的每个位置输出一个 d-维向量表示。
Transformer的解码器与编码器类似,也是由多个相同的层组成,包含残差连接和层归一化。除了与编码器相同的两个子层外,解码器还加入了一个额外的子层,称为 编码器-解码器注意力(encoder-decoder attention)。在这个子层中,查询来自解码器自注意力子层的输出,而键和值来自编码器的输出。解码器中的自注意力机制中,查询、键和值都来自前一层的输出,但每个位置只能关注解码器中当前位置之前的所有位置,从而保留了自回归(autoregressive)特性,确保 预测仅依赖于已生成的输出标记。
⁉️ Transformer 中 Encoder 的理解?为什么 堆叠多个 TransformerEncoderBlock?
Transformer 中 Encoder 的理解?为什么 堆叠多个 TransformerEncoderBlock?
Encoder 部分更关注于语言本身,Attention 找出词与词之间的全局关系(Q,K,V 均来自于自身),Positional FFN 深化词本身的含义,最终把所以词的理解综合到一个 matrix 当中。
堆叠多个 TransformerEncoderBlock 主要原因有:
每个 TransformerEncoderBlock 都可以看作是一个特征提取器。通过堆叠多个 Block,模型能够从输入数据中提取出多层次的特征。随着层数的增加,模型能够捕捉更复杂的语义关系和全局依赖:
- 浅层特征:语法、局部依赖。
- 中层特征:句法结构、短距离语义。
- 深层特征:全局语义、长距离依赖、抽象概念。
每个 TransformerEncoderBlock 都包含一个自注意力机制和一个前馈神经网络(FFN),这些模块引入了非线性变换。通过堆叠多个 Block,模型可以逐步组合这些非线性变换,从而学习到更复杂的函数映射。深度模型(更多层)通常具有更强的表达能力,能够拟合更复杂的模式。
虽然自注意力机制理论上可以捕捉任意距离的依赖关系,但在实际中,单层的注意力机制可能仍然有限。通过堆叠多个 Block,模型可以在不同层次上反复处理信息,从而更好地捕捉长距离依赖。
⁉️ 如何理解 Decoder 中三个子层(sublayers)的作用?
如何理解 Decoder 中三个子层(sublayers)的作用?
Transformer解码器由多个相同的层(layers)组成,每一层包括三个子层(sublayers):解码器自注意力(decoder self-attention)、编码器-解码器注意力(encoder-decoder attention) 和 逐位置前馈网络(positionwise feed-forward network)。每个子层都使用残差连接(residual connection)并紧接着进行层归一化(layer normalization)。
Masked Multi-Head Self-Attention Layer(解码器自注意力):在这一步过程中,Q、K、V 全部来自目标序列的嵌入表示(即 Decoder 自身的输入),与 Encoder 的输出无关。这一层的目的是让解码器 捕捉目标序列内部的依赖关系(例如语法结构、语义一致性),类似于 Encoder 的自注意力层捕捉输入序列的依赖关系。同时也确保 自回归特性(Autoregressive Property):在生成目标序列时,解码器是自回归的,即每个 token 的生成依赖于之前已经生成的 token。解码器自注意力通过掩码(mask) 机制,确保在生成第 t 个 token 时,只能关注到第 1 到第 t−1 个 token,而不能“偷看”未来的 token。
Multi-Head Attention Layer (Encoder-Decoder Attention)(编码器-解码器注意力):该子层将解码器的输出与编码器的输出结合起来。通过这种方式,解码器能够 获取来自编码器的上下文信息,从而使得解码器能够 生成更相关的输出。在此过程中,解码器利用 编码器的输出(经过自注意力处理后的表示)来调整自己对目标序列的预测。
- 编码器-解码器注意力中,Q(Query):来自 解码器的当前状态(目标序列的嵌入表示)即“我需要关注什么”。K(Key) 和 V(Value):来自 编码器的输出(即源序列的编码表示)。Key 表示源序列的特征,用于与 Query 计算相似度。Value 表示源序列的实际内容,用于加权求和。
- Feed-Forward Neural Network Layer(前馈神经网络层):该子层负责对每个位置的表示进行非线性变换和进一步的处理。它通常由两个全连接层(Fully Connected Layers)组成,其中一个是激活函数(通常是 ReLU/GELU)处理的隐藏层,另一个是输出层。前馈层提供了模型的表达能力,使得模型可以学习更复杂的特征。
⁉️ LayerNorm 和 BatchNorm 的核心区别是什么?为什么 Transformer 选择 LayerNorm?
LayerNorm 和 BatchNorm 的核心区别是什么?为什么 Transformer 选择 LayerNorm?
Batch Normalization(BN) 在 batch 维度 上归一化,每个特征维度 独立计算均值和方差,统计 batch 内的样本均值 和方差。LayerNorm 在 特征维度(对每个样本的所有特征) 进行归一化,即在单个样本内部计算均值和方差,因此它不受 batch size 影响。
Transformer 选择 LayerNorm 而非 BatchNorm 的主要原因是 Transformer 需要处理变长序列并进行自回归推理(Autoregressive Inference),BatchNorm 在这种情况下无法正确归一化,而 LayerNorm 在每个时间步独立计算归一化统计量,避免了 batch 之间的依赖。
- Transformer 处理的是变长文本,例如 短句(“Hello”)和长句(“The weather is nice today”)可能共存于同一个 batch,但它们的 token 数不同。BatchNorm 无法直接在这些变长数据上计算 batch 统计量,因为不同长度的序列无法对齐进行批量归一化。 即使使用填充(Padding),这些填充值可能会影响均值和方差计算,导致不稳定的归一化效果。
- BatchNorm 依赖于 整个 mini-batch 统计量 进行归一化,而在推理阶段(Inference),模型通常只能接收到一个 token 或一个小段文本,并无法获取完整 batch 进行归一化。因此,BatchNorm 统计量在 训练时计算的是 整个 batch 的均值和方差,但在推理时,batch size 可能是 1,导致统计量发生偏移,影响预测质量。
此外,LayerNorm 在梯度流动上比 BatchNorm 更稳定,特别是对于深层 Transformer 模型,能够有效减少梯度消失(Gradient Vanishing)和梯度爆炸(Gradient Explosion)问题。
Note: 假设:
batch_size = 2 seq_len = 5 embedding_dim = 4
输入一个batch的Token Embedding,形状是:
X.shape = [2, 5, 4]
LayerNorm 的归一化维度是,对每一个 token 的 embedding 向量,单独在 embedding_dim 维度上做标准化。 也就是说,计算均值和方差的范围是:
X[i, j, :] # 固定batch和token位置,只在embedding维度上归一化。
BatchNorm 先把
X
看作是一个batch_size × seq_len
的“伪样本集合”:总共有 2 × 5 = 10 个 token, 每个 token 有 4 维特征。
比如
X[:,:,0]
(第0维)会拿这10个数计算:X[:, :, 0].reshape(-1) -> shape: [10] μ = mean(X[:,:,0].flatten()) σ² = var(X[:,:,0].flatten())
然后标准化:
X_norm[:,:,0] = (X[:,:,0] - μ) / sqrt(σ² + ε)
对于第1、2、3维同理,都是用
batch_size × seq_len
个值计算。
⁉️ Pre-LN 和 Post-LN 的区别是什么?为什么要在残差连接之后进行归一化(Post-LN)?
Pre-LN 和 Post-LN 的区别是什么?为什么要在残差连接之后进行归一化(Post-LN)?
Pre-Layer Normalization(Pre-LN)和 Post-Layer Normalization(Post-LN)是 Transformer 结构中两种不同的 Layer Normalization(LN,层归一化)策略。Pre-LN 在 Multi-Head Self-Attention(MHSA,多头自注意力) 和 Feed-Forward Network(FFN,前馈神经网络) 之前进行归一化,而 Post-LN 则在 残差连接(Residual Connection)之后进行归一化。

主要区别 在于梯度传播的方式:在 Pre-LN 结构中,归一化操作使得梯度 在深层网络中更加稳定,缓解了梯度消失(Gradient Vanishing)和梯度爆炸(Gradient Explosion)问题,因此 更适合深层 Transformer 训练。而 Post-LN 由于归一化在残差连接之后,会导致前期训练时梯度信号衰减,使得深度网络难以优化,尤其在 Transformer 层数较深时,梯度消失的问题更为严重。
然而,Post-LN 具有更好的优化表现,因为 它保留了每一层的特征分布,使得模型学习到的信息在归一化前不会被直接拉回到零均值单位方差的分布。因此,在某些场景下,如 小规模 Transformer 或浅层 Transformer,Post-LN 可能具有更好的收敛效果。
⁉️ 残差连接(Residual Connection)如何与 LayerNorm 协作缓解梯度消失?
残差连接(Residual Connection)如何与 LayerNorm 协作缓解梯度消失?
残差连接的核心思想是通过 跳跃连接(Skip Connection) 让信息能够绕过多个变换层,直接传递到更深的层,使梯度在反向传播(Backpropagation)过程中能够更稳定地传播。数学上,它通过加法操作,使输入 x 直接与变换后的输出 F(x) 相加,即
$$ y = H(x) = x + F(x) $$
从而保留原始信息并防止梯度过小。
- 一个标准的网络层尝试学习一个复杂的映射 H(x)。
- Residual Block 则将目标分解为 F(x) + x,其中:
- F(x) = H(x) - x:残差,即网络学习的部分。
Note: 直接学习 H(x)(输入到输出的完整映射)可能是一个高度复杂的问题,而学习残差 F(x) = H(x) - x 相对简单得多。在许多实际任务中,输入 x 与目标 H(x) 通常是接近的(例如图像分类任务中,特征提取后的信息不会发生剧烈变化)。通过学习残差 F(x),网络只需关注输入与输出之间的细微差异,而不必重新建模整个映射。
另一方面,LayerNorm 作用于每个时间步或通道上的神经元,对其均值和方差进行归一化,从而减小内部协变量偏移(Internal Covariate Shift),使梯度分布更加稳定,避免梯度消失或梯度爆炸(Gradient Explosion)。
⁉️ FFN 的结构是什么?为什么通常包含两层线性层和一个激活函数?
FFN 的结构是什么?为什么通常包含两层线性层和一个激活函数?
前馈神经网络(Feed-Forward Network, FFN)在深度学习模型(如 Transformer)中的结构通常包含两层线性变换(Linear Transformation)和一个非线性激活函数(Activation Function)组成:
\[ \text{FFN}(x) = \max(0, x W_1 + b_1) W_2 + b_2 \]具体来说,对于
$$ x \in \mathbb{R}^{[batch, seq_len, d_{model}]} $$
FFN 的第一层是一个线性变换 W_1x + b_1 ,将 输入投影到一个更高维度的隐藏层,随后通过非线性激活函数增加模型的表示能力
$$ x_{\text{activated}} = \text{ReLU}(x W_1 + b_1) $$
- 输入 shape:
[batch, seq_len, d_model]
- 输出 shape:
[batch, seq_len, higher_dim]
- 每一个 token(
[d_model]
)用同一个 W1 投影到higher_dim
维。
这里和 X 相乘的 W_1 的维度大小为:
$$ W_1 \in [d_{model}, d_{higher_dim}] $$
最后通过第二个线性变换 W_2h + b_2 将高维特征映射回原始维度。这种结构的主要作用是增强模型的非线性表达能力,使其能够学习到更复杂的特征关系。两层线性变换的设计可以视为一种低维到高维再回归低维的映射,这样能够增加模型的容量,同时控制计算成本。
$$ \text{Output} = x_{\text{activated}} W_2 + b_2 $$
- 输入 shape:
[batch, seq_len, higher_dim]
- 输出 shape:
[batch, seq_len, d_model]
Note: 每个 token 经过 Self-Attention 计算后,得到的输出向量会被 独立地 传入 FFN,这个过程 不会跨 token 共享计算,即 每个位置的 token 独立通过相同的前馈网络 进行转换。可以理解为所有的 token(x)一起通过一个完全一样的 MLP,所以位置共用 MLP 中的一个 weight。
在这个过程中会对每一个位置自身的特征信息进行加工,增加局部特征的表达力。而 Self-Attention 则负责关注全局的相互关系。
⁉️ 激活函数在 FFN 中的作用是什么?为什么常用 GELU 而非 ReLU?
激活函数在 FFN 中的作用是什么?为什么常用 GELU 而非 ReLU?
在前馈神经网络(Feedforward Neural Network, FFN)中,激活函数(Activation Function) 的主要作用是 引入非线性(Non-linearity),使网络能够学习复杂的特征表示,而不仅仅是线性变换。
Note: 在标准的 Transformer 架构中,激活函数只在 FFN 这一步存在。
在标准 Transformer 架构中,Positional FFN 使用的激活函数是 RELU,数学表达式为:
\[ \text{RELU}(x) = \max(0, x) \]而在大规模预训练语言模型(Large Language Models, LLMs)中,高斯误差线性单元(Gaussian Error Linear Unit, GELU) 常被用作 FFN 的激活函数,而不是传统的 修正线性单元(Rectified Linear Unit, ReLU),主要是因为 GELU 能够提供更平滑的非线性变换,从而提升梯度流的稳定性。GELU 的数学表达式为:
\[ \text{GELU}(x) = x \cdot \Phi(x) = x \cdot \frac{1}{2} \left( 1 + \text{erf} \left( \frac{x}{\sqrt{2}} \right) \right) \]
与 ReLU 相比,GELU 在接近 0 的输入处具有平滑的 S 形曲线,而 ReLU 在 0 处存在不连续性(即当 x < 0 时,输出恒为 0)。这种平滑性使 GELU 在训练过程中能够更自然地保留和传播梯度,而不会像 ReLU 那样导致某些神经元完全死亡(Dead Neurons)的问题。此外,由于 GELU 允许输入以概率方式通过,而不是简单地进行硬阈值裁剪(如 ReLU 的 max(0, x) ),它在自注意力(Self-Attention)结构中表现更优,有助于 LLMs 更有效地捕捉复杂的语义关系。
Note: 在 自然语言处理(NLP)任务中,数据通常具有更复杂的特征表示,负数不一定意味着无用信息。(NLP 中的负值包含语义信息,特别是涉及到情感分析、语法结构等复杂任务时。负值可能代表对立的意思(如否定、反向情感等),因此 保留负值 变得尤为重要。)
ReLU 直接裁剪负数部分,意味着所有小于 0 的值都被映射为 0,相当于丢弃了部分信息。GELU 不是一个硬阈值,而是 基于概率平滑地裁剪输入,这意味着接近 0 的小负值仍有一定概率被保留,而不是完全消失。e.g. 假设某层神经元计算出的输出是 -0.1,在 ReLU 下,它会变成 0,而在 GELU 下,它可能仍然保持 -0.05 或其他较小值。这有助于模型保留更多信息,避免信息过早丢失。
ReLU 存在“神经元死亡(Dead Neurons)”问题:如果一个神经元的输入总是负数,那么它的输出始终是 0,对应的梯度也会一直是 0,这样该神经元可能永远无法被更新,从而降低模型的表达能力。GELU 由于其平滑的 S 形曲线,即使在负数区域仍然保持一定梯度,这样梯度可以更稳定地传播,减少“死神经元”问题。数学上看,GELU 的导数:
\[ \frac{d}{dx} GELU(x) = \Phi(x) + x \cdot \phi(x) \]Phi(x) 是标准正态分布的 CDF,表示激活的概率。phi(x) 是标准正态分布的 PDF,表示数据在某个点的概率密度。相比于 ReLU 的二值导数(1 或 0),GELU 的梯度在整个数轴上连续变化,更利于优化。