Loading

深度学习进阶(三)Transformer Block

上一篇我们已经完成了多头自注意力机制的内容,并知道了它是 Transformer Block 中的一个子模块。

Transformer Block 是 Transformer 模型的核心计算单元,它不仅创造并应用了多头自注意力机制,还结合了残差学习、归一化等多门技术。

先简单概括一下 Transformer Block 的整体传播如下:

\[\mathbf{X} \rightarrow \mathrm{Multi\text{-}Head\ Attention} \rightarrow \mathrm{Add\ \&\ Norm} \rightarrow \mathrm{FFN} \rightarrow \mathrm{Add\ \&\ Norm} \rightarrow \mathbf{Y} \]

image.png

接下来我们逐步拆解这个过程。

1.位置编码(Positional Encoding, PE)

在开始 Transformer Block 之前,首先要说明的是它的输入细节。
在前面的自注意力机制中,我们了解了自注意力相对 RNN 实现了信息关联间的全局建模、并行建模。

但实际上,其并非十全十美,一个关键的问题在于:自注意力本身并不能隐式建模序列的顺序信息。

回想 RNN ,它的计算是按时间步递归的:

\[\mathbf{h}_t = f(\mathbf{x}_t, \mathbf{h}_{t-1}) \]

关键点在于信息是按时间顺序一层层传递的,这意味着输入顺序不能打乱,也就是说模型本身就“知道”谁在前、谁在后,顺序是通过计算路径自然体现的。

而Transformer 的 Attention 是这样的:

\[\mathrm{Attention}(Q,K,V)=\mathrm{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V \]

所有 token 同时参与计算,没有“先后处理顺序”的结构约束。
也就是说,在原始的词嵌入逻辑中,我们虽然可以通过注意力计算得到 token 间的相关性,但这个相关性里是不包括顺序成分的。

除非我们想办法把一个序列中的顺序信息也加入每个 token 的词向量中。

这就是位置编码的由来。
于是,Transformer 原论文提出了一种位置编码机制,将位置信息显式注入到输入表示中。
其具体做法是:对每个 token 的词向量 \(\mathbf{E}_{embedding}\),加入一个与其位置相关的向量 \(\mathbf{E}_{pos}\),从而得到最终输入表示:

\[\mathbf{X}_{input} = \mathbf{E}_{embedding} + \mathbf{E}_{pos} \]

其中 \(\mathbf{E}_{pos}\) 即为位置编码。
原文中设计了一种基于正弦和余弦函数的固定位置编码方法如下:
对于位置 \(pos\) 和维度索引 \(i\),其定义如下:

\[PE(pos, 2i) = \sin\left(\frac{pos}{10000^{2i/d}}\right) \]

\[PE(pos, 2i+1) = \cos\left(\frac{pos}{10000^{2i/d}}\right) \]

其中:

  1. \(pos\) 表示 token 在序列中的位置(从 0 开始)。
  2. \(d\) 表示 embedding 的维度。
  3. \(i\) 表示维度索引。

公式看起来略显复杂,我们举个例子,假定信息如下:

  1. 序列:我 / 爱 / 你
  2. 位置:\(pos = 0, 1, 2\)
  3. 维度:\(d = 3\)

注意,实际实现中词向量维度通常为偶数,这里简化来演示,计算过程如下:

位置 pos token 维度 计算公式 数值
0 PE₀ \(\sin\left(\frac{0}{10000^{2\cdot 0/3}}\right)\) \(0\)
0 PE₁ \(\cos\left(\frac{0}{10000^{2\cdot 0/3}}\right)\) \(1\)
0 PE₂ \(\sin\left(\frac{0}{10000^{2\cdot 1/3}}\right)\) \(0\)
1 PE₀ \(\sin\left(\frac{1}{10000^{2\cdot 0/3}}\right)\) \(\sin(1) \approx 0.84\)
1 PE₁ \(\cos\left(\frac{1}{10000^{2\cdot 0/3}}\right)\) \(\cos(1) \approx 0.54\)
1 PE₂ \(\sin\left(\frac{1}{10000^{2\cdot 1/3}}\right)\) \(\approx 0.00215\)
2 PE₀ \(\sin\left(\frac{2}{10000^{2\cdot 0/3}}\right)\) \(\sin(2) \approx 0.91\)
2 PE₁ \(\cos\left(\frac{2}{10000^{2\cdot 0/3}}\right)\) \(\cos(2) \approx -0.42\)
2 PE₂ \(\sin\left(\frac{2}{10000^{2\cdot 1/3}}\right)\) \(\approx 0.00431\)

最终得到三个 token 的 PE :

\[\mathbf{E}_{pos}(0) = [0,\ 1,\ 0] \]

\[\mathbf{E}_{pos}(1) \approx [0.84,\ 0.54,\ 0.00215] \]

\[\mathbf{E}_{pos}(2) \approx [0.91,\ -0.42,\ 0.00431] \]

现在,我们再来理解一下这种设计思路,这其实涉及一些信号处理的知识:

从本质上看,正弦和余弦函数可以被视为一种周期信号。当我们用不同频率的正弦/余弦函数去对同一个位置进行编码时,相当于在多个“频率通道”上对该位置进行投影。
而不同频率就是通过指数缩放项 \(10000^{2i/d}\) 来实现的,而同时使用正弦和余弦也能避免不同的频率可能得到相同的值的情况。

而且,不同位置之间的“距离信息”,可以在这些周期函数的相位差中体现出来。 这样,位置之间的相对关系就可以被隐式表达。
image.png

通过 PE 的设计,我们就完善了对输入数据的顺序信息的建模。
在更近的研究中,对 PE 的获取方法也在不断地创新发展,一个很容易想到的方向就是可学习位置编码,我们之后再详细展开。

2. Attention 块

2.1 多头自注意力子层

对于经过嵌入层、注入 PE 后的序列信息 \(\mathbf{X}\) (向量化),它在 Transformer Block 中的第一步就是应用多头注意力机制进行信息增强。
这一部分我们已经详细展开过,其作用是:在序列内部建模任意位置之间的依赖关系,实现全局信息交互。

本次的输入与输出保持维度一致,如下:

\[\mathbf{X} \in \mathbb{R}^{n \times d} \rightarrow \mathbf{Z} \in \mathbb{R}^{n \times d} \]

这种设计方便了下面的残差学习。

2.2 残差连接

Attention 块到这里并没有结束,Transformer Block 在每个子层还增加了残差连接和归一化的步骤。
展开来说,首先对于注意力子层的输出 \(\mathbf{Z}\) ,我们直接把它和输入 \(\mathbf{X}\) 相加:

\[\mathbf{Z}+\mathbf{X} \]

这涉及到残差学习的基本原理,我们在很早之前有所介绍:残差网络
再简单概述一下:我们知道 \(\mathbf{Z}\) 是由可学习的参数决定的,而这种对输出的建模方式会让学习目标从 “直接学习从输入到输出的映射” 转变为 “学习在输入基础上的改变量”,从而改善梯度现象,支撑更深层的网络。

2.3 LayerNorm

然后,下一步处理就是归一化:

\[\mathbf{X}_{norm} = \mathrm{LayerNorm}(\mathbf{Z}+\mathbf{X}) \]

同样在前面的吴恩达深度学习内容中,我们介绍过归一化的基本内容,并在此基础上拓展了Batch Norm

而这里的 LayerNorm 和 Batch Norm 其实只是数据的选择方向不同。
公式如下:

\[\mathrm{LN}(\mathbf{x}) = \frac{\mathbf{x} - \mu}{\sigma} \cdot \gamma + \beta \]

其中:

  • \(\mu\) 是均值
  • \(\sigma\) 是标准差
  • \(\gamma, \beta\) 为可学习参数

显然,不同于 Batch Norm 对一批次样本的逐个特征做归一化,LayerNorm 是对每个样本自身的所有特征做归一化。

来简单举个例子:

假设我们有如下特征向量(设 \(d=4\)):

\[\mathbf{x} = [2,4,6, 8] \]

计算均值和标准差如下:

\[\mu = \frac{2 + 4 + 6 + 8}{4} = 5 \]

\[\sigma = \sqrt{\frac{(2-5)^2 + (4-5)^2 + (6-5)^2 + (8-5)^2}{4}} = \sqrt{5} \]

代入计算:

\[\hat{\mathbf{x}} = \left[\frac{2-5}{\sqrt{5}}, \frac{4-5}{\sqrt{5}}, \frac{6-5}{\sqrt{5}},\frac{8-5}{\sqrt{5}}\right] \]

现在,该向量均值为 \(0\),方差为 \(1\)
接下来再通过学习平移和缩放参数 \(\gamma, \beta\)

\[\mathbf{y} = \hat{\mathbf{x}} \cdot \gamma + \beta \]

这一步的作用是:在完成标准化之后,让模型再特化学习“什么样的分布是最合适的”。

可以这样理解 LayerNorm 的作用: 它可以保持特征的相对结构,同时将整体数值尺度归一到稳定范围。
这样做使得每一个 token 的表示都被拉回到一个稳定的数值范围,从而避免在深层网络中出现数值分布不断偏移的问题。

你可能还有这样一个问题:

为什么不用 Batch Norm ?

这便是针对问题的特化所在:
在这里,最重要的原因是注意力计算和词嵌入本身都会导致每个 token 的分布都不同,用 BatchNorm 强行对齐反而会破坏信息。

同样举个简单例子来说明:

假设有一句话:“猫 吃 鱼”,则三个 token 经过注意力后可能会出现这种情况:

  1. 猫关注“吃”:\(\mathbf{z}_1 = [0.1, 0.2, 0.1, 0.2]\) ,比较平缓。
  2. 吃关注“猫”和“鱼”:\(\mathbf{z}_2 = [10, 0.5, 0.3, 0.2]\) ,某一维特别大。
  3. 鱼关注“吃”:\(\mathbf{z}_3 = [-3, -2, -1, 0]\) ,整体偏负。

也就是说:每个 token 的“特征分布”完全不同。

而这就是关键点,BatchNorm 的假设是:不同样本的同一维度,应该有类似分布。但在这里不同 token 的分布本来就应该不同!
因为每个 token 表达不同语义,而注意力让它们差异更大。如果用 BatchNorm 强行把它们拉成一样 ,反而破坏语义差异。
此外,序列数据的长度不固定、BatchNorm 依赖批次大小等因素也会产生负面影响。

这便是 Attention 块 的全部逻辑,写成一个总体公式如下:

\[\mathbf{X}_{out} = \mathrm{LayerNorm}\left(\mathbf{X} + \mathrm{MultiHead}(\mathbf{X})\right) \]

image.png

3. 前馈神经网络 FFN

经过Attention 块完成信息增强后,下一步就是就是输入一个小型前馈网络进行计算,这里提供的就是非线性变换、重构特征的逻辑。

形式上,FFN 通常由两层线性变换加一个激活函数构成:

\[\mathrm{FFN}(\mathbf{X}) = \max(0, \mathbf{X}\mathbf{W}_1 + \mathbf{b}_1)\mathbf{W}_2 + \mathbf{b}_2 \]

其中的维度设计较为关键:

  1. \(\mathbf{W}_1 \in \mathbb{R}^{d \times d_{ff}}\)\(\mathbf{W}_2 \in \mathbb{R}^{d_{ff} \times d}\)
  2. \(d_{ff}\) 通常远大于 \(d\),在原文中设计为 \(4d\)

其具体传播过程如下:
image.png
在这里,你会发现 FFN 在序列间其实没有任何信息交互,其变换、建模完全局限于单个 token,因此必须与注意力机制配合使用。

同样地,在完成 FFN 传播得到输出后,也要进行残差连接和归一化。

\[\mathbf{Y} = \mathrm{LayerNorm}(\mathbf{X}_{out} + \mathrm{FFN}(\mathbf{X}_{out})) \]

至此,就完成了 Transformer Block 的全部计算,在实际模型中,这样的 Block 会被堆叠多层,从而逐步提升模型对复杂结构和长距离依赖的建模能力。

4.Post-Norm 和 Pre-Norm

在这里值得补充的一点关于 Post-Norm 和 Pre-Norm,我们先把 Transformer Block 的两个核心公式再摆一遍:

\[\mathbf{X}_{out} = \mathrm{LayerNorm}\left(\mathbf{X} + \mathrm{MultiHead}(\mathbf{X})\right) \]

\[\mathbf{Y} = \mathrm{LayerNorm}(\mathbf{X}_{out} + \mathrm{FFN}(\mathbf{X}_{out})) \]

这里,也是原论文的逻辑是:先计算,再对结果归一化。
这种结构通常被称为 Post-Norm(后归一化)结构:也就是每一个子层(Attention 或 FFN)都先完成自身的计算,再与输入进行残差相加,最后统一进行归一化处理。

然而,在后续研究实践中,发现了这种结构在层数较深时容易出现训练不稳定的问题,于是提出了另一种更常用的变体:Pre-Norm(前归一化)结构
其对应形式为:

\[\mathbf{X}_{out} = \mathbf{X} + \mathrm{MultiHead}(\mathrm{LayerNorm}(\mathbf{X})) \]

\[\mathbf{Y} = \mathbf{X}_{out} + \mathrm{FFN}(\mathrm{LayerNorm}(\mathbf{X}_{out})) \]

可以看到,其变化在于:先对输入进行归一化,再送入子层计算,最后再进行残差连接。

其更稳定的原因仍在梯度传播的逻辑上:
我们设计残差连接,形成恒等映射,让梯度可以绕过子层计算直接传播,从而保证了深层网络中的稳定性。
而在 Post-Norm 中,LayerNorm 位于残差连接后,也就是梯度传播必须先经过归一化,这导致其在多层堆叠时更容易发生缩放与扰动,从而影响训练稳定性,相比之下,Pre-Norm 避免了这一问题,保留了直连的残差路径。
因此在实际工程中,更常见的是 Pre-Norm 结构。

至此,我们已经完整构建了 Transformer 的基本计算单元,在下一篇中,就可以较丝滑地进入 Transformer 的整体结构了。

posted @ 2026-04-04 17:03  哥布林学者  阅读(3)  评论(0)    收藏  举报