1 为什么使用 Transformer
RNN 像人类"逐字阅读"一样处理文本,这带来了以下问题:
- 梯度消失: 处理长文本时,模型会"忘记"较早的信息。例如"我昨天在图书馆借了一本关于量子物理的书"中,读到"书"时早已忘记"我"是主语,长距离依赖极难捕捉。
- 无法并行: RNN 必须按顺序处理每一个词,无法利用 GPU 的并行计算能力,训练超长文本时速度极慢。
Transformer的解法极其暴力:抛弃递归,全部用“注意力(Attention)”。

2 词向量 & 位置编码
你可以把Transformer模型想象成一个大型会议室,而词向量和位置编码就是每位参会者(单词)的身份名牌。
1. 词向量(Word Embedding)
词向量是把一个单词(如“苹果”)转换成一串固定长度的数字列表(比如512维)。
- 核心作用:将人类语言(文字)翻译成机器能计算的数学语言(向量空间中的坐标)。
- 关键特性:它不仅仅是“编号”,而是语义的载体。在向量空间中,意思相近的词,其向量距离更近。经典的例子是:
国王-男人+女人≈女王。
在Transformer中:
输入的句子(如“我 爱 中国”)中,每个词都会先查一个词嵌入表(Embedding Table),把自己映射成一个初始的词向量。
2. 位置编码(Positional Encoding)
既然词向量没有位置信息,而“狗咬人”和“人咬狗”的词向量完全一样,模型就无法区分。为了解决这个问题,Transformer引入了位置编码。
- 核心作用:给每个单词的向量叠加一个代表其绝对位置或相对位置的“位置信号”,告诉模型这个单词在句子中的顺序。
Transformer原始论文(《Attention Is All You Need》)中用了独特的三角函数 (正弦和余弦)来计算位置编码:
- 为什么用三角函数?
- 数值有界:正弦/余弦值在[-1, 1]之间,不会造成数值不稳定。
- 相对位置表达能力:数学上可以证明,对于任何固定的偏移量
k,位置pos+k的编码可以表示为位置pos编码的线性函数。这意味着模型能够轻松捕捉到“前后相距k个位置”这种相对关系。 - 可外推:即使句子比训练时见过的都长,三角函数也能计算出有效的位置编码,不会“越界”。
- (另一种选择)可学习位置编码:
现在很多大模型(如BERT、GPT系列)也采用“可学习的位置编码”。即把位置编码当作一堆可训练的参数,让模型在训练中自己“悟”出位置规律。这种方法更灵活,但缺点是位置序号不能超过训练时的最大长度。
3. 最终输入 = 词向量 + 位置编码(关键一步)
这是最容易被误解的地方。它们不是拼接(Concat),而是直接相加(Add)。
为什么能直接相加?
因为两者的维度完全相同。这相当于在原有的语义空间(内容)上,加上了一个位置偏移量(坐标)。模型拿到的最终向量同时包含了“这个词是什么意思”和“这个词在第几位”两种信息。
3 自注意力
假设你在一个巨大的 知识库(V) 里找信息:
- Q(Query,查询):你的“需求”。是当前这个词在问:“我现在最需要关注什么?”
- K(Key,键):信息的“索引/标签”。是句子中每个词贴出的名片:“我包含哪方面的信息?”
- V(Value,值):信息的“真实内容”。是句子中每个词真正携带的语义。
工作流程(极其形象):
你(Q)拿着一份需求清单,去对比数据库里所有文件的标题(K),算出每个标题和你的需求有多匹配(点积),得出一个“相关性分数”(Attention Score)。最后,你根据这个分数,去把对应文件的内容(V)提取出来,加权求和,形成你最终读到的信息。
自注意力的第一步,就是让 $ X $ 分别乘以三个不同的权重矩阵 W Q , W K , W V W^Q, W^K, W^V WQ,WK,WV。
- W Q , W K , W V W^Q, W^K, W^V WQ,WK,WV 是训练中需要学习的参数矩阵。
- 为了简化(标准Transformer里通常降维),假设它们的维度都是 d m o d e l × d k d_{model} \times d_k dmodel×dk。
数学公式极其简单:
Q
=
X
W
Q
,
K
=
X
W
K
,
V
=
X
W
V
Q = X W^Q,\quad K = X W^K,\quad V = X W^V
Q=XWQ,K=XWK,V=XWV
线性代数视角:
这相当于对 $ X $ 进行三次不同的线性变换(旋转/缩放)。输入矩阵 $ X $ 的每一行(每个词),被映射到了三个不同的向量空间:
- $ Q $ 空间(查询空间)
- $ K $ 空间(键空间)
- $ V $ 空间(值空间)
此时,$ Q, K, V $ 的形状都是 $ L \times d_k $。
计算注意力分数是最核心的一步。我们需要计算每个词的 $ Q $ 和所有词的 $ K $ 的点积。
在线性代数中,点积就是矩阵乘法。
Scores = Q ⋅ K T \text{Scores} = Q \cdot K^T Scores=Q⋅KT
- $ Q $ 的形状:$ L \times d_k $
- K T K^T KT(K的转置)的形状: d k × L d_k \times L dk×L
- 相乘后,Scores 的形状是:$ L \times L $。
这个 $ L \times L $ 方阵极其重要!
- 第 $ i $ 行,第 $ j $ 列 的数字,代表第 $ i $ 个词对第 $ j $ 个词的原始关注度分数。
- 比如第2行第5列,就是第2个词“看”第5个词一眼得到的“亲密度”数值。
为了防止分数过大导致梯度消失,除以 $ \sqrt{d_k} $(缩放),然后对每一行做 Softmax(让每一行的和等于1,变成概率)。
Attention Weights = softmax ( Q ⋅ K T d k ) \text{Attention Weights} = \text{softmax}\left( \frac{Q \cdot K^T}{\sqrt{d_k}} \right) Attention Weights=softmax(dkQ⋅KT)
- 形状依然是 $ L \times L $。
- 此时,第 $ i $ 行变成了:第 $ i $ 个词对全句所有词的注意力权重分布(总和为1)。
最后,用刚刚算出的注意力权重,去“提纯” $ V $(值)。
Output = Attention Weights ⋅ V \text{Output} = \text{Attention Weights} \cdot V Output=Attention Weights⋅V
- Attention Weights 形状:$ L \times L $
- $ V $ 形状:$ L \times d_k $
- 相乘后,Output 的形状:$ L \times d_k $。
线性代数解释:
这个乘法相当于对 $ V $ 的行向量做线性组合。
对于第 $ i $ 行的输出,就是把 $ V $ 中所有行的向量,按照第 $ i $ 行的注意力权重比例加权求和,得到一个新的向量。
把所有步骤合并,自注意力机制的完整线性代数公式就是:
Attention ( X ) = softmax ( ( X W Q ) ( X W K ) T d k ) ( X W V ) \text{Attention}(X) = \text{softmax}\left( \frac{(XW^Q)(XW^K)^T}{\sqrt{d_k}} \right) (XW^V) Attention(X)=softmax(dk(XWQ)(XWK)T)(XWV)
4 多头自注意力
在单头自注意力中,整个模型只能学到一种“关注模式”。
- 比如,它可能只学会了关注“主谓宾”这种语法结构。
- 但现实中的语言非常复杂,同一个词在同一个句子里,可能同时需要关注语法、指代、语义、时态等多种信息。
单头注意力的 $ Q、K、V $ 权重矩阵只产生一套注意力分布,很难同时捕获所有这些关系。
多头注意力的做法是:并行运行 $ h $ 个(比如 8 个或 12 个)独立的单头注意力,每个头有自己的 $ W^Q, W^K, W^V $ 矩阵(参数不共享)。这样每个头可以“专攻”一种关系。
在原始 Transformer 论文中,作者并没有真的去训练 8 个完全独立的大型矩阵(那样参数太多),而是采用了一种偷懒但高效的数学技巧:
- 假设词向量维度 $ d_{model} = 512 $。
- 我们设定头数 $ h = 8 $。
- 那么每个头分到的维度就是 $ d_k = d_{model} / h = 64 $。
具体步骤:
- 线性变换:输入 X X X( L × 512 L \times 512 L×512)乘以 W Q W^Q WQ( 512 × 512 512 \times 512 512×512),得到 Q Q Q( L × 512 L \times 512 L×512)。
- 拆分成多头:把 $ Q $ 在特征维度上切分成 8 块,每块维度 64。这样我们就得到了 8 个独立的 $ Q_1, Q_2, …, Q_8 $,每个形状是 $ L \times 64 $。
- 并行计算:这 8 个头同时做自注意力计算(在 GPU 上就是 8 个不同的矩阵流),每个头都算出自己的输出 $ head_i $(形状 $ L \times 64 $)。
- 拼接(Concat):把这 8 个头的输出在特征维度上拼起来,重新变回 $ L \times 512 $。
- 最终投影:把拼接后的结果再乘以一个权重矩阵 W O W^O WO( 512 × 512 512 \times 512 512×512),做一次线性变换,得到最终输出。
对于第 $ i $ 个头:
h e a d i = Attention ( X W i Q , X W i K , X W i V ) head_i = \text{Attention}\left( XW_i^Q,\ XW_i^K,\ XW_i^V \right) headi=Attention(XWiQ, XWiK, XWiV)
其中 $ W_i^Q, W_i^K, W_i^V $ 的维度都是 $ d_{model} \times d_k $(即 $ 512 \times 64 $)。
最终输出:
MultiHead ( X ) = Concat ( h e a d 1 , h e a d 2 , . . . , h e a d h ) W O \text{MultiHead}(X) = \text{Concat}(head_1, head_2, ..., head_h) W^O MultiHead(X)=Concat(head1,head2,...,headh)WO
其中 $ W^O $ 的维度是 $ (h \cdot d_k) \times d_{model} = 512 \times 512 $。
5 标准化
在深度网络中,经过多层矩阵乘法($ XW $)和加法后,数据的数值分布会变得极其不稳定:
- 有的维度数值可能变成 $ +100 $,有的变成 $ -0.001 $。
- 这种分布极其不均匀的数据(分布偏移)丢进激活函数(如ReLU或Softmax)时,会导致梯度要么极大(爆炸)、要么极小(消失),模型很难收敛。
标准化的核心目标:把数据强行拉回 “均值为0、方差为1” 的标准正态分布附近。这样,梯度就能保持在比较健康的区间,模型就能用较大的学习率快速学习。
对于输入向量 $ x $(假设它是某个样本的所有特征),LayerNorm 就做了两件事:
第一步:计算均值和方差
μ
=
1
d
∑
i
=
1
d
x
i
(均值)
\mu = \frac{1}{d} \sum_{i=1}^{d} x_i \quad \text{(均值)}
μ=d1i=1∑dxi(均值)
σ 2 = 1 d ∑ i = 1 d ( x i − μ ) 2 (方差) \sigma^2 = \frac{1}{d} \sum_{i=1}^{d} (x_i - \mu)^2 \quad \text{(方差)} σ2=d1i=1∑d(xi−μ)2(方差)
第二步:标准化(减去均值,除以标准差)
x
^
i
=
x
i
−
μ
σ
2
+
ϵ
\hat{x}_i = \frac{x_i - \mu}{\sqrt{\sigma^2 + \epsilon}}
x^i=σ2+ϵxi−μ
(其中 $ \epsilon $ 是一个极小的小数,比如 $ 1e-8 $,防止除以0)
第三步:缩放和平移(引入可学习参数)
为了防止网络被“强行标准化”后丢失了原本的表示能力,LayerNorm 引入了两个可训练的参数:
输出
i
=
γ
i
⋅
x
^
i
+
β
i
\text{输出}_i = \gamma_i \cdot \hat{x}_i + \beta_i
输出i=γi⋅x^i+βi
- $ \gamma $(Gamma):缩放参数(初始为1),负责恢复数据的“方差”。
- $ \beta $(Beta):平移参数(初始为0),负责恢复数据的“均值”。
6 残差连接
在传统的神经网络(没有残差连接)中,数据是这样的:
输入 $ x $ → 经过一堆复杂的层(比如多头注意力)→ 直接输出 $ F(x) $。
这要求网络层自己学会把 $ x $ 完美地映射成我们想要的目标。如果网络很深(比如几十上百层),梯度在反向传播时连乘很多次,数值会越来越小(梯度消失)或越来越大(梯度爆炸),导致底层参数几乎无法更新。
残差连接的数学表达式极其简单:
输出 = F ( x ) + x \text{输出} = F(x) + x 输出=F(x)+x
- $ F(x) $:是经过多头注意力或前馈网络等变换后的结果(称为残差)。
- $ x $:是未经任何处理的原始输入。
也就是把原始的输入 $ x $ 原封不动地跨层传递,直接加到变换后的输出上
假设有一个残差块,你希望它的输出等于输入(即什么也不做,恒等映射)。
- 没有残差:网络必须把 $ F(x) $ 这堆复杂的矩阵乘法的所有权重全部精确调整到0,这在数学上几乎不可能完美做到。
- 有残差:网络只需要把 $ F(x) $ 的权重全部置为0,输出就是 $ 0 + x = x $。把权重变成0,比精确调整到某个特定值要简单得多。
7 前馈神经网络 FNN
FFN在Transformer的每个编码器(Encoder)和解码器(Decoder)层中,紧跟在多头注意力后面。
它的数学形式极其简单,就是两个线性变换夹着一个激活函数:
FFN
(
x
)
=
ReLU
(
x
W
1
+
b
1
)
W
2
+
b
2
\text{FFN}(x) = \text{ReLU}(xW_1 + b_1)W_2 + b_2
FFN(x)=ReLU(xW1+b1)W2+b2
或者更现代一点的变体(如GPT用的SwiGLU):
FFN
(
x
)
=
SwiGLU
(
x
W
1
,
x
W
3
)
W
2
\text{FFN}(x) = \text{SwiGLU}(xW_1, xW_3)W_2
FFN(x)=SwiGLU(xW1,xW3)W2
关键特性:这个操作是逐位置(Position-wise)的。也就是说,对于句子中的每个词,FFN独立地、一模一样地处理它,词与词之间不互相通信。
- 如果输入是 $ L \times d_{model} $(L个词,每个词512维),FFN就是把这个矩阵的每一行单独拿出来,过一遍同样的两层网络。
- 和自注意力的区别:自注意力是词与词之间横向通信;FFN是每个词纵向思考。
为什么使用前馈神经网络
① 增加模型的“非线性”表达能力
多头注意力虽然强大,但它本质上就是一堆加权求和(线性操作),即使堆叠多层,线性的组合还是线性的,无法拟合复杂模式。
- FFN中的激活函数(如ReLU)引入了非线性。
- 这使得模型能够学习到数据中复杂的、非线性的决策边界。
通俗理解:自注意力像在画“关系图”(线性连接),而FFN像在“拍桌子做决定”(非线性的“是/否”判断)。
② 特征空间变换(扩维和降维)
FFN通常设计成先扩维、再降维的“瓶颈结构”:
- 第一层:把维度从 $ d_{model} $(如512)扩展到 $ d_{ff} $(如2048,通常是4倍)。这叫扩维,给模型提供了更大的“思考空间”,让模型可以在高维空间中把特征拆分得更细。
- 激活函数(ReLU):把小于0的部分直接置0,相当于特征筛选——只保留最“强烈”的信号,丢掉噪音。
- 第二层:把维度从2048压回512。这叫降维,把筛选后的精华信息压缩回标准维度,传递给下一层。
形象比喻:
你有一份512个关键字的概要(输入)。FFN先把它扩展成2048个细节的脑暴草稿(扩维),然后划掉所有不重要的想法(ReLU筛选),最后提炼成一份全新的512字精华总结(输出)。
8 编码器执行流程
在Transformer架构中,编码器通常堆叠 N 层(原论文用了 6 层,现代大模型如BERT用了 12 层,GPT用了 96 层)。
- 输入:原始句子的词向量 + 位置编码(比如“我 爱 中国”)。
- 输出:一组维度相同的、但融入了全句上下文信息的高维向量(每个词的新表示)。
- 核心特点:编码器是 双向(Bi-directional) 的,也就是在处理某个词时,它可以同时“看见”左边和右边的所有词(这在后面的掩码解码器中是严格禁止的)。
一个标准的编码器层由 两个子层(Sub-layer) 组成,每个子层都套用了 “残差连接 + 层归一化” 的模板。
数据进入后的完整流程(从下往上):
第一步:子层1 —— 多头自注意力(全局信息交互)
- 进来的是:包含位置信息的词向量矩阵 X。
- 做的是:让句子中的每个词,通过 Q、K、V 机制,互相“打量”对方。计算全句的注意力权重,完成词与词之间的横向通信。
- 得到的是:每个词都“耳听八方”后的上下文感知向量。
- 关键操作:紧接着做 残差连接 和 层归一化。保底原始信息不丢,同时把数值拉回标准区间。
第二步:子层2 —— 前馈神经网络(个体深度思考)
- 进来的是:经过自注意力更新后的向量。
- 做的是:每个词独立地进入一个双层全连接网络(先扩维4倍,激活函数筛选,再降维回来)。词与词之间完全不通信,各自“独立思考”并做非线性变换。
- 得到的是:经过深层特征提取的新向量。
- 关键操作:再次做 残差连接 和 层归一化。
输出向量 (富含上下文语义)
↑
┌───────┴───────┐
│ 层归一化 │ ← 第2次标准化
└───────┬───────┘
↑
┌───────┴───────┐
│ 加法 (残差) │ ← 第2次残差连接
└───────┬───────┘
↑
┌──────────────────┴──────────────────┐
│ 前馈神经网络 (FFN) │ ← 子层2:逐位置非线性变换
│ (扩维→ReLU激活→降维) │
└──────────────────┬──────────────────┘
↑
┌───────┴───────┐
│ 层归一化 │ ← 第1次标准化
└───────┬───────┘
↑
┌───────┴───────┐
│ 加法 (残差) │ ← 第1次残差连接
└───────┬───────┘
↑
┌──────────────────┴──────────────────┐
│ 多头自注意力 (Multi-Head) │ ← 子层1:全局上下文交互
└──────────────────┬──────────────────┘
↑
输入 X (词向量 + 位置编码)
9 解码器执行流程
解码器通常也堆叠 N 层(原论文是 6 层,和编码器对称)。
- 输入:目标语言(比如英文)的已生成部分(在训练时是完整的目标句子,但在推理时是动态生成的)。
- 输出:下一个词的概率分布(最终通过Softmax变成具体的一个词)。
- 核心特点:解码器是自回归(Auto-regressive)的——即用已经生成的词,去预测下一个词。
解码器的每个层比编码器多了一个子层,总共有 3 个子层。数据流从下往上依次是:
子层 0:掩码自注意力(Masked Self-Attention)
这是解码器的第一道关卡。
- 进来的是:目标语言句子的已生成部分(比如“I love”)。
- 做的是:和编码器的自注意力一样,计算词与词之间的注意力。但多了一个掩码(Mask)——把未来位置的权重全部屏蔽掉。
- 为什么要屏蔽? 因为在预测第 $ t $ 个词时,解码器不能看到第 $ t+1 $ 个词(那相当于考试偷看答案)。
- 怎么屏蔽的? 在计算注意力分数( Q K T QK^T QKT)后,把矩阵的右上三角(未来位置)全部设置为 − ∞ -\infty −∞,Softmax后这些位置的权重变成0。
- 操作后:加上残差连接,过层归一化。
子层 1:交叉注意力(Cross-Attention / Encoder-Decoder Attention)
这是解码器的核心创新点,也是它和编码器最大的不同。
- 进来的是:两个来源。
- Q(查询):来自解码器上一个子层(掩码自注意力)的输出。
- K(键)和V(值):来自编码器的最终输出(那个“语义蓝图”)。
- 做的是:解码器的每个词,拿着自己的 Q,去和编码器输出的所有词的 K 计算注意力分数。相当于问编码器:“在我生成当前这个词时,输入句子的哪个部分最重要?”
- 形象比喻:这就像在做英译中时,你写英文词写到一半,需要回头看看中文原文对应的那句话,确认一下意思。
- 操作后:加上残差连接,过层归一化。
子层 2:前馈神经网络(FFN)
- 和编码器完全一样:逐位置、独立地对每个词做两次线性变换+激活函数。
- 操作后:加上残差连接,过层归一化。
输出向量 (去预测下一个词)
↑
┌───────┴───────┐
│ 层归一化 │
└───────┬───────┘
↑
┌───────┴───────┐
│ 加法 (残差) │
└───────┬───────┘
↑
┌──────────────────┴──────────────────┐
│ 前馈神经网络 (FFN) │ ← 子层2:逐位置非线性变换
└──────────────────┬──────────────────┘
↑
┌───────┴───────┐
│ 层归一化 │
└───────┬───────┘
↑
┌───────┴───────┐
│ 加法 (残差) │
└───────┬───────┘
↑
┌──────────────────┴──────────────────┐
│ 交叉注意力 (Cross-Attention) │ ← 子层1:Q来自解码器,K/V来自编码器
└──────────────────┬──────────────────┘
↑
(Q来自下方) ┌─────┴─────┐ (K/V来自编码器)
↑
┌───────┴───────┐
│ 层归一化 │
└───────┬───────┘
↑
┌───────┴───────┐
│ 加法 (残差) │
└───────┬───────┘
↑
┌──────────────────┴──────────────────┐
│ 掩码自注意力 (Masked Self-Attention)│ ← 子层0:只能看到左边的词
└──────────────────┬──────────────────┘
↑
输入 (目标语言已生成的部分)

1179

被折叠的 条评论
为什么被折叠?



