一切的起点:线性变换
深度学习的世界里,有一个最基础、最频繁出现的操作: 线性变换 。说到底就是一件事——用矩阵乘法把一个向量变成另一个向量:
y = Wx
x 是输入向量,W 是一个矩阵(里面的数字是模型训练出来的参数),y 是输出向量。就这么简单。但这个简单的操作背后蕴含着极其丰富的几何意义,也是整个深度学习的基石。
为什么叫"线性"?
“线性"这个词有严格的数学定义,满足两个条件:
可加性: 对两个输入分别做变换再加起来,和先加起来再做变换,结果一样。即 f(a + b) = f(a) + f(b)。
齐次性: 先把输入放大 k 倍再变换,和先变换再放大 k 倍,结果一样。即 f(k × a) = k × f(a)。
这两个条件合在一起意味着:线性变换 保持了向量空间的基本结构 ——它不会"扭曲"空间,只会做旋转、缩放、投影、剪切这一类"均匀"的操作。原来的直线变换后还是直线,原来平行的线变换后还是平行的,原点永远不动。
相比之下,像 f(x) = x² 这样的操作就是非线性的。你可以验证:f(2+3) = 25,但 f(2) + f(3) = 4 + 9 = 13,不相等。
用 2D 的具体数字来感受
假设有一个二维向量 x = [1, 0],和一个 2×2 的矩阵 W:
W = | 2 0 |
| 0 3 |
y = Wx = | 2×1 + 0×0 | = | 2 |
| 0×1 + 3×0 | | 0 |
这个 W 把 x 方向放大了 2 倍,y 方向放大了 3 倍。这是一个 缩放变换 。
再看另一个矩阵:
W = | 0 -1 |
| 1 0 |
对 x = [1, 0]:y = | 0×1 + (-1)×0 | = | 0 |
| 1×1 + 0×0 | | 1 |
对 x = [0, 1]:y = | 0×0 + (-1)×1 | = | -1 |
| 1×0 + 0×1 | | 0 |
[1, 0] 变成了 [0, 1],[0, 1] 变成了 [-1, 0]。这是一个 逆时针 90° 旋转 。关键点在于:W 矩阵中的数字决定了空间被怎样变换,而计算过程永远是同一个机械的矩阵乘法。
维度可以改变——这一点极其重要
前面的例子都是 2D 到 2D,但线性变换完全可以改变向量的维度。一个 m×n 的矩阵可以把 n 维向量变成 m 维向量:
W 的形状:(3, 2) x 的形状:(2,) y = Wx 的形状:(3,)
W = | 1 0 |
| 0 1 | x = | 1 | y = | 1 |
| 1 1 | | 2 | | 2 |
| 3 |
2 维向量被"升"到了 3 维空间。反过来,也可以用一个 (2, 3) 的矩阵把 3 维向量"压"到 2 维空间。这种维度变换能力在深度学习中无处不在。你遇到过的每一个 nn.Linear(in_features, out_features) 本质上就是一个形状为 (out_features, in_features) 的矩阵乘法加上一个偏置向量。
理解"投影”:从影子说起
在 Transformer 的论文和教程中,“投影(projection)“是一个高频词汇。它到底是什么意思?
最直觉的理解:影子
想象阳光从正上方照下来,你手里举着一根筷子,在地面上会留下一个影子。这根筷子是三维空间中的一个物体(一个 3D 向量),而地面上的影子是它在二维平面上的 投影 。
关键观察:影子保留了筷子在水平方向上的信息,但丢失了垂直方向的信息。你无法从影子的长度判断筷子有多高,你只能知道它在水平方向上"有多长”。
这就是投影的本质:把高维信息压缩到低维空间,过程中有选择地保留某些方面的信息,同时不可避免地丢弃另一些方面的信息。
用数字走一遍
假设有一个 3D 向量 v = [3, 4, 5],要把它投影到 xy 平面(“地面”)上。这等价于直接扔掉 z 分量:
投影矩阵 W = | 1 0 0 |
| 0 1 0 |
投影结果 = W × v = | 1×3 + 0×4 + 0×5 | = | 3 |
| 0×3 + 1×4 + 0×5 | | 4 |
3D 向量 [3, 4, 5] 变成了 2D 向量 [3, 4]。z 方向的信息(5)被完全丢弃了。
但这只是最粗暴的投影方式。更有趣的情况是: 投影方向不沿着坐标轴 。
“往哪面墙上投"决定了你看到什么
想象你站在一个房间里举着那根筷子。阳光从正上方照下来,影子落在地板上——这是往地板投影。但如果光从左边照过来,影子就落在右边的墙上——这是往另一个平面投影。两个影子保留的信息不同,丢失的信息也不同。
在数学上,投影到哪个子空间,完全由投影矩阵 W 决定。不同的 W 就像不同角度的光源,把同一个向量的不同"侧面"照出来。
一个帮助深度理解的类比
想象一个人站在你面前。这个人是一个"高维对象”——有身高、体重、发型、表情、穿着、声音等等无数个维度的信息。
- 从 正面拍照 ——这是一种投影,你能看到他的表情和穿着,但看不到后脑勺的发型。
- 从 侧面拍照 ——这是另一种投影,你能看到侧脸轮廓和身高比例,但看不到正面表情。
- 只录音不拍照 ——又一种投影,只保留了声音维度的信息,丢弃了所有视觉信息。
每种投影都是对同一个高维对象的一种"简化视角”。你丢失了信息,但换来了在某个特定方面更清晰、更聚焦的表达。这就是投影在深度学习中如此重要的原因——高维空间中的信息太丰富了,你需要根据不同的任务需求,用不同的投影矩阵提取出你当前需要的那个侧面。
这跟 Transformer 的 Q、K、V 有什么关系?
当我们说:
Q = X × Wq
K = X × Wk
V = X × Wv
同一个 token embedding X,被三个不同的矩阵投影到了三个不同的子空间。就好像三面不同角度的墙,同一根筷子在每面墙上的影子形状不同,因为每面墙捕捉了筷子不同方面的信息。
- Wq 投影出来的影子强调的是"我在找什么"
- Wk 投影出来的影子强调的是"我能被什么样的查询匹配到"
- Wv 投影出来的影子强调的是"我携带的实际内容"
注意: 严格数学意义上的"投影"有一个额外条件——做两次投影和做一次投影的结果一样(P² = P)。但在深度学习语境中,大家说"投影"时通常没有这么严格,更接近日常用法:把数据从一个空间映射到另一个空间,带有"提取某个方面的信息"的含义。
线性变换在 Transformer 中的四个关键角色
在继续之前,让我们先建立全局视野——线性变换在 Transformer 中扮演了四个不同的角色:
角色一:Embedding 层。 把离散的 token ID 映射成连续的向量。本质上是一个大查找表,但也可以理解为 one-hot 向量乘以 embedding 矩阵——一个线性变换。
角色二:Q/K/V 投影。 把通用的 token 表示投影到 Attention 需要的不同语义子空间中。
角色三:Feed-Forward Network。 两层线性变换中间夹一个非线性激活,负责在每个位置上做复杂的特征变换。
角色四:最终输出层。 把最后一层的隐藏状态投影到词汇表大小的维度,每一维对应一个 token 的分数,再通过 softmax 变成概率分布。
为什么线性变换"不够"——非线性激活的必要性
如果神经网络只有线性变换,会有一个致命问题: 多个线性变换的叠加仍然是线性变换 。
y = W2 × (W1 × x) = (W2 × W1) × x = W_combined × x
两层网络和一层网络在数学上等价。堆 100 层也没用,最终都可以合并成一个矩阵。所以在每一层线性变换之后,要加一个 非线性激活函数 (比如 ReLU、GELU),让网络能够表达复杂的非线性模式。Transformer 的 Feed-Forward Network 层就是这个结构:
FFN(x) = GELU(x × W1 + b1) × W2 + b2
先线性变换(投影到更高维度,通常是 4 倍),然后 GELU 激活(引入非线性),再线性变换(投影回原始维度)。这一"扩展-激活-压缩"的过程,是在更高维空间里做复杂的特征组合和筛选,然后再压缩回来。
手算演示:一个句子在 Transformer 中的完整旅程
现在,让我们用一个极度简化但完整的例子,追踪一个句子从输入到输出的每一步。为了让数字可以手算,用极小的维度,但每一步的逻辑和真实模型完全一致。
迷你 Transformer 设定
词汇表大小:6 个词 → {I, love, cats, hate, dogs, .}
Embedding 维度:4(真实模型用 768 ~ 4096)
只有 1 个 Attention Head(真实模型用 8 ~ 128 个)
Q/K/V 维度:4(简化处理,不降维)
输入句子:“I love cats”
第一步:Tokenization — 把文字变成数字
模型不认识文字,只认识数字。Tokenizer 把每个词映射成词汇表中的 ID:
"I" → token ID: 0
"love" → token ID: 1
"cats" → token ID: 2
输入序列:[0, 1, 2]
真实模型中这一步更复杂(BPE 等子词分割),但本质一样:文字变成整数序列。
第二步:Token Embedding — 从 ID 查找向量
模型有一个 Embedding 矩阵,形状为 (vocab_size, d_model) = (6, 4)。每一行对应一个词的向量表示。这些数字是 训练出来的 ,不是人类手动设定的:
Embedding 矩阵 E (6×4):
dim0 dim1 dim2 dim3
ID 0 (I): [ 1.0, 0.2, -0.5, 0.3]
ID 1 (love): [ 0.1, 0.9, 0.8, -0.2]
ID 2 (cats): [ 0.6, -0.1, 0.4, 0.7]
ID 3 (hate): [-0.1, 0.8, -0.7, 0.2]
ID 4 (dogs): [ 0.5, -0.3, 0.3, 0.8]
ID 5 (.): [ 0.0, 0.0, 0.1, 0.0]
用 token ID 去查表:
"I" → [1.0, 0.2, -0.5, 0.3]
"love" → [0.1, 0.9, 0.8, -0.2]
"cats" → [0.6, -0.1, 0.4, 0.7]
现在每个词不再是一个干巴巴的 ID,而是一个 4 维空间中的点,包含了这个词的语义信息。
第三步:加入 Positional Encoding — 注入位置信息
为什么需要这一步?因为 Attention 机制是 permutation invariant(置换不变的) 。Q·K 的点积只看两个向量的方向关系,跟它们在序列中的位置完全无关。如果不加位置信息,“I love cats” 和 “cats love I” 在模型眼里是同一个东西。
位置 0 的编码:[0.00, 0.01, 0.00, 0.01]
位置 1 的编码:[0.84, 0.05, 0.04, 0.05]
位置 2 的编码:[0.91, -0.04, 0.08, 0.03]
直接加到 token embedding 上:
X0 ("I", pos=0) = [1.00, 0.21, -0.50, 0.31]
X1 ("love", pos=1) = [0.94, 0.95, 0.84, -0.15]
X2 ("cats", pos=2) = [1.51, -0.14, 0.48, 0.73]
现在同一个词出现在不同位置时,它的向量会不同。这就是位置感知。
第四步:线性投影生成 Q、K、V
这就是前面讲的"三面不同角度的墙"。三个权重矩阵都是 (4, 4),由训练学到:
Wq = | 0.5 0.0 0.1 0.0 | Wk = | 0.3 0.1 0.0 0.2 | Wv = | 0.1 0.0 0.5 0.0 |
| 0.0 0.6 0.0 0.1 | | 0.0 0.4 0.1 0.0 | | 0.0 0.3 0.0 0.2 |
| 0.1 0.0 0.4 0.0 | | 0.1 0.0 0.5 0.1 | | 0.2 0.0 0.4 0.1 |
| 0.0 0.1 0.0 0.5 | | 0.0 0.1 0.0 0.3 | | 0.0 0.1 0.0 0.6 |
以 “I”(X0)的 Query 为例,手算一下:
Q0 = X0 × Wq = [1.00, 0.21, -0.50, 0.31] × Wq
Q0[0] = 1.00×0.5 + 0.21×0.0 + (-0.50)×0.1 + 0.31×0.0 = 0.45
Q0[1] = 1.00×0.0 + 0.21×0.6 + (-0.50)×0.0 + 0.31×0.1 = 0.157
Q0[2] = 1.00×0.1 + 0.21×0.0 + (-0.50)×0.4 + 0.31×0.0 = -0.10
Q0[3] = 1.00×0.0 + 0.21×0.1 + (-0.50)×0.0 + 0.31×0.5 = 0.176
Q0 ≈ [0.45, 0.16, -0.10, 0.18]
同样的过程对每个 token 做三次(Wq、Wk、Wv),得到:
Q("我在找什么") K("我能被匹配什么") V("我的实际内容")
"I" : [0.45, 0.16,-0.10, 0.18] [0.36, 0.12,-0.20, 0.13] [0.05, 0.12,-0.09, 0.21]
"love" : [0.55, 0.56, 0.43,-0.01] [0.37, 0.46, 0.51, 0.05] [0.51, 0.25, 0.53,-0.02]
"cats" : [0.81,-0.02, 0.34, 0.35] [0.60, 0.02, 0.39, 0.25] [0.39, 0.11, 0.49, 0.42]
同一个 token 的 Q、K、V 是三个不同的向量——同一根筷子在三面墙上的不同影子。
第五步:计算 Attention Scores — Q 和 K 的点积
每个 token 的 Q 去和所有 token 的 K 做点积,衡量"我应该关注谁"。以 “love” 的视角(Q1)为例:
score(love→I) = Q1 · K0 = 0.55×0.36 + 0.56×0.12 + 0.43×(-0.20) + (-0.01)×0.13
= 0.198 + 0.067 - 0.086 - 0.001 = 0.178
score(love→love) = Q1 · K1 = 0.55×0.37 + 0.56×0.46 + 0.43×0.51 + (-0.01)×0.05
= 0.204 + 0.258 + 0.219 - 0.001 = 0.680
score(love→cats) = Q1 · K2 = 0.55×0.60 + 0.56×0.02 + 0.43×0.39 + (-0.01)×0.25
= 0.330 + 0.011 + 0.168 - 0.003 = 0.506
缩放(除以 √d_k = √4 = 2),防止点积值过大导致 softmax 进入梯度饱和区域:
scaled scores = [0.178/2, 0.680/2, 0.506/2] = [0.089, 0.340, 0.253]
第六步:Softmax — 变成概率分布
exp(0.089) = 1.093
exp(0.340) = 1.405
exp(0.253) = 1.288
总和 = 1.093 + 1.405 + 1.288 = 3.786
attention_weights = [1.093/3.786, 1.405/3.786, 1.288/3.786]
= [0.289, 0.371, 0.340]
这告诉我们:当 “love” 生成自己的新表示时,它会把 28.9% 的注意力分给 “I”,37.1% 分给自己,34.0% 分给 “cats”。“love” 最关注自己和 “cats”——动词和宾语之间天然有强关联。
第七步:加权求和 V — 提取信息
用注意力权重对所有 token 的 Value 向量做加权求和:
output_love = 0.289 × V_I + 0.371 × V_love + 0.340 × V_cats
= 0.289 × [0.05, 0.12, -0.09, 0.21]
+ 0.371 × [0.51, 0.25, 0.53,-0.02]
+ 0.340 × [0.39, 0.11, 0.49, 0.42]
dim0: 0.289×0.05 + 0.371×0.51 + 0.340×0.39 = 0.014 + 0.189 + 0.133 = 0.336
dim1: 0.289×0.12 + 0.371×0.25 + 0.340×0.11 = 0.035 + 0.093 + 0.037 = 0.165
dim2: 0.289×(-0.09)+0.371×0.53+ 0.340×0.49 =-0.026 + 0.197 + 0.167 = 0.338
dim3: 0.289×0.21 + 0.371×(-0.02)+0.340×0.42= 0.061 - 0.007 + 0.143 = 0.197
output_love ≈ [0.336, 0.165, 0.338, 0.197]
这个新向量不再只代表 “love” 自己,它已经 融合了整句话的上下文信息 ——里面有 “I” 的成分,有 “cats” 的成分,按照 attention weights 混合在一起。这就是 Attention 的产出:一个上下文感知的表示。
第八步:Feed-Forward Network — 深层特征变换
Attention 的输出会经过一个两层的前馈网络:
先升维: 4 维 → 16 维(乘以 W1)
非线性: GELU 激活
再降维: 16 维 → 4 维(乘以 W2)
升维是为了在更高维空间里做复杂的特征组合,GELU 引入非线性让网络能学习复杂模式,降维把信息压回原来的维度。每一步之间还有残差连接(把输入加回输出)和 LayerNorm。
在真实模型中,以上步骤(Attention + FFN)会堆叠很多层(GPT-3 有 96 层,LLaMA 有 32~80 层)。每一层都在上一层的输出上继续做 Attention 和 FFN,逐渐构建出越来越抽象的语义表示。
Hidden State:模型内部的中间产物
经过多层堆叠之后,我们需要理解一个关键概念: hidden state 。
输入 “I love cats” 三个 token,经过 embedding + positional encoding 之后,每个 token 都有一个 4 维向量。这三个向量就是 第 0 层的 hidden states 。
然后它们进入第 1 层 Attention + FFN,出来之后每个 token 的向量都变了(因为融合了上下文、做了特征变换)。这三个新的向量就是 第 1 层的 hidden states 。再进入第 2 层,又变了……
输入 embedding: [1.00, 0.21, -0.50, 0.31] ← "I" 的初始表示
│
第 1 层 Attention+FFN 之后: [0.72, 0.35, -0.12, 0.48] ← hidden state (layer 1)
│
第 2 层 Attention+FFN 之后: [0.55, 0.61, 0.20, 0.33] ← hidden state (layer 2)
│
第 3 层 Attention+FFN 之后: [0.82,-0.15, 0.63, 0.91] ← hidden state (layer 3, 最终层)
每一层的 hidden state 都是 4 维向量,维度从未改变,但 里面的数值在每一层都被更新了 。
为什么叫 “hidden”?
因为这些中间向量对外部不可见。你作为用户,只能看到最终输出的 token(比如 “.")。至于模型内部第 1 层、第 2 层的向量长什么样,你看不到——它们"藏"在模型里面。
你能看到的: 输入 "I love cats" → 输出 "."
你看不到的: 中间每一层每个 token 的向量 ← 这些就是 hidden states
这个概念其实不是 Transformer 发明的,RNN 时代就有了。RNN 每个时间步也会产生一个内部向量,传递给下一个时间步,那个也叫 hidden state。Transformer 沿用了这个术语。
最后一层的 hidden state 为什么特殊?
在自回归模型(GPT 类)中,每个位置预测的是"下一个 token 是什么”。最后一个 token “cats” 的 hidden state 经过了所有层的处理,已经充分吸收了 “I love cats” 整句话的信息,所以用它来预测下一个词:
位置 0 ("I") 的最终 hidden state → 预测 "I" 后面是什么 → "love"
位置 1 ("love") 的最终 hidden state → 预测 "love" 后面是什么 → "cats"
位置 2 ("cats") 的最终 hidden state → 预测 "cats" 后面是什么 → "."
第九步:最终输出层 — 从向量变回词
假设经过所有层之后,“cats” 位置的最终 hidden state 是:
hidden_state = [0.82, -0.15, 0.63, 0.91]
现在需要用一个 (4, 6) 的矩阵把它投影到词汇表大小(6 维),每一维对应一个词的分数:
logits = hidden_state × W_output
假设得到:
logits = [0.1, 0.3, 0.1, -0.8, 0.5, 1.2]
"I" "love" "cats" "hate" "dogs" "."
Softmax 转换成概率:
probabilities ≈ [0.08, 0.10, 0.08, 0.03, 0.12, 0.59]
"I" "love" "cats" "hate" "dogs" "."
模型预测下一个 token 最可能是 “."(概率 59%)。“I love cats” 后面跟句号是自然的。
所有矩阵都是训练出来的
你在整个流程中看到的 所有矩阵 ——Embedding 矩阵、Wq、Wk、Wv、FFN 的 W1 和 W2、最终输出层的 W_output——全都是训练出来的。这些矩阵里的数字就是所谓的 模型参数(parameters) 。当我们说"GPT-3 有 1750 亿参数”,指的就是这些矩阵里所有数字加起来的总数。
训练开始时,这些矩阵全部是随机初始化的,模型的输出基本是胡言乱语。然后训练过程做的事情本质上就是一个循环:
1. 给模型喂一段文本,比如 "I love cats"
2. 模型用当前的参数预测下一个 token,比如预测出 "dogs"(概率最高)
3. 但正确答案是 ".",所以模型错了
4. 计算"错了多少"(loss)
5. 用反向传播算出每个参数应该往哪个方向调、调多少
6. 微调所有矩阵里的数字
7. 回到第 1 步,换一段新文本,重复几十亿次
经过海量数据的反复调整,这些矩阵里的数字逐渐从随机变成了有意义的值。最终输出层的 W_output 学会了"什么样的 hidden state 应该映射到什么词",Wq 学会了"怎么提取查询信息",Embedding 矩阵学会了"语义相近的词应该有相近的向量"。
有一个有趣的细节:很多模型中,最终输出层的 W_output 和最开始的 Embedding 矩阵 E 其实是 同一个矩阵的转置 ,这个技巧叫 weight tying 。直觉上说得通——Embedding 把 token ID 映射成向量,输出层把向量映射回 token ID,两者是互逆的过程,共享参数既节省内存又能让两端的语义空间保持一致。
全流程总览
"I love cats"
│
▼
① Tokenization: [0, 1, 2] 文字 → 数字
│
▼
② Token Embedding: 3个4维向量 数字 → 向量(查表)
│
▼
③ + Positional Enc: 注入位置信息 向量 + 位置
│
▼
④ Linear → Q, K, V: 三组投影 三个不同视角
│
▼
⑤ Q · Kᵀ: 算注意力分数 谁该关注谁?
│
▼
⑥ Softmax: 变成概率分布 关注多少?
│
▼
⑦ 加权求和 V: 融合上下文 生成新表示
│
▼
⑧ FFN: 深层特征变换 ×N 层堆叠
│
▼
⑨ Output Projection: 4维 → 6维(词汇表) 向量 → 概率
│
▼
下一个 token: "." (概率最高)
写在最后
理解 Transformer 不需要从一开始就看懂所有的数学推导。更重要的是抓住几个核心直觉:
线性变换是基础操作。 它通过矩阵乘法把向量从一个空间映射到另一个空间,是整个模型中最基本的构建模块。
投影是有选择地提取信息。 不同的投影矩阵从同一个输入中提取不同方面的信息,就像从不同角度拍照。
Attention 让每个 token 能直接看到所有其他 token。 通过 Q·K 匹配找到相关的 token,然后用 V 提取它们的信息,生成融合了全局上下文的新表示。
非线性激活让网络超越线性的局限。 没有它,再多层的网络也等价于一层。
所有参数都是训练出来的。 模型通过在海量数据上反复试错,逐渐学会了每个矩阵应该长什么样。
当你理解了这些,再去看论文中更复杂的变体——Multi-Head Attention、RoPE、FlashAttention、Group Query Attention——就会发现它们只是在这个基础框架上做局部优化,核心逻辑没有变。