Mechanism of Transformer
1. Introduction of Transformer
1.1 Goals of Transformer
主要致力于在序列建模中提升并行性与长距离依赖建模能力,摆脱对循环与卷积的依赖。
时序建模是一个很重要的输入特性, 比如我们打一段话,
后面的输入和前面的输入都有联系.
并行性, 是针对RNN的提升, RNN, LSTM
的特点是前面的迭代进后面的, 是一种串行计算, 效率低下.
长距离依赖建模能力, 也是针对传统RNN的提升,
早期的RNN类算法中, 后续时间序列可能会遗忘很前面的输入,
导致结果不准确.
1.2 Structure of Transformer
Transformer 使用了 Encoder + Decoder 的结构.
Transformer 拥有三种注意力机制(本质上都是多头注意力机制):
- 编码器多头自注意力
- 交叉注意力
- 解码器多头自注意力
Transformer 采用了位置编码 的机制,
赋予了词向量序列信息.
Transformer 采用了 残差连接 + 层归一化 +
前馈网络的形式, 形成标准块, 稳定深层训练.
Transformer 整体的架构如下:

1.3 Gross Workflow of Transformer
以最经典的机器翻译任务为例: 输入英文符串I love you,
进行翻译任务, transformer的粗略工作流程如下:
首先输入的英文符串I love you ,
需要通过输入嵌入的过程, 将这些输入转化为向量形式,
也就是输入向量(输出是逆过程):

然后, 为了体现输入向量的时序信息, 要给这个输入向量加上一个位置编码, 这样就得到了我们完整的输入向量:

然后是Encoder模块, 开始使用神经网络对我们的输入向量做处理:

里面经过了:多头注意力机制, 残差连接, 层归一化… 先不管内部实现, 我们可以认为, Encoder的输出是输入向量的一些有用信息, 这些信息被传入Decoder中进行处理
这个时候, 注意到, Decoder中还有一个输入: Outputs shifted right. 你可能会疑惑, 为什么还有一个输入? Decoder不是处理刚刚Encoder传过来的信息就行了吗.
从结果来说, 我们的输入是I love you,
输出应该是我爱你.
实际上, 我们送进transformer的I love you是并行送进去的,
但是输出是迭代串行的, 并不是一次性输出我爱你的.
流程简述:
Encoder信息提取完毕后
Decoder Inputs Decoder Outputs “S”, 表示开始 “我”, 传回 Decoder Inputs “S”+“我” “我”+“爱”, 传回 Decoder Inputs “S”+“我”+“爱” “我”+“爱”+“你”, 传回 Decoder Inputs “S”+“我”+“爱”+“你” “我”+“爱”+“你”+“end”, 传回 Decoder Inputs 收到”end”, 结束
这种不断迭代生成输出的过程, 叫: Auto-Regressive Generate

2. Input Data Process
首先来到第一部分, 对输入数据进行处理.
我们的输入是自然语言, 比如翻译任务, 翻译”我是一条狗”. 我们是不可能用字符或者字符串的形式来进行训练的, 最后一定是用数字的形式, 所以我们的输入和输出一定要是数字的形式.
2.1 Collect Enough Text Data
我们要对文本进行处理, 一定要有足够多的文本量, 不然的话可能会出现识别不了的问题.
2.2 Tokenization
Transformer或者说大模型, 可以当成一个黑盒, 他的输入和输出都是不同的文本. 这些文本是由不同的基本元素组成的, 比如英文的基本组成是ASCII表里面那些字符, 中文是一个一个字. 我们输入这些变量, 经过transformer后, 再输出这些变量的其它组合.
不过这里有一个问题, 如果拿26个字母或者普通的中文字符来做, 最小变量, 效率太低了, 如果组成单词来作为 比如:
“I am a dog”, 拆解成”I” “am” “a” “dog”, 效率上一定是优于”I” “a” “m” “a” “d” “o” “g”的,
中文里: “我” “是” “一条” “狗”, 效率一定是比 “我” “是” “一” “条” “狗” 高.
这种拆解出来的最小变量单元”dog”, “一条”… 就叫token.
这些基本变量, 放在一起就是我们Vocabulary,
也就是词表.
思考, token和word(单词)的区别? 他们一样吗?
当然不一样, 不然token为什么不直接叫单词算了? 看起来我们是按单词(word)为单位, 来切割实际上并不是.
按word为单位进行token化的话, 有两个主要问题:
- 词表爆炸, 因为有很多生僻词和复杂的组合词,
同一单词的不同形式…会导致我们的词表
Vocabulary Size非常大 - 未登陆词问题(OOV, Out of Vocabulary),
如果遇到一个词典里没有的新词(比如人名 “Elon Musk”,或者新造词
“Qwen”),按“单词”处理,模型会直接懵掉,显示为
<UNK>(未知)。
所以我们要找到一个平衡, 让效率高的同时, 词表尽量小.
比如: “The unbelievable cat is running.”这个句子, 按照单词分就是5个单词, 然而按照token分, 可能会分成7个 Token:
The(完整单词)▁un(unbelievable 的前缀)believ(unbelievable 的词根)able(unbelievable 的后缀)▁cat(完整单词,▁代表前面的空格)▁is(完整单词)▁running(完整单词)
对于简单的词(如 cat),Token 就是单词;但对于长词或生僻词(如
unbelievable),Token
会被拆成单词的碎片(子词)。乍一看,
5个单词能搞定的事情, 分成了7个token, 这不是更复杂了吗? 实际上,
假如有一些新的语句, 其中有believable
unfortune
disable…这些单词, 如果按照word划分的话, 这些都是一个新的
Vocabulary 项, 但是如果按token划分, 会发现,
他们很多部分已经存储在已知的token中了, 所以语料库足够大的时候,
Vocabulary Size实际上是减小的.
2.3 Sort Token by Frequence & One Hot Encode
当我们把所有文本token化后, 可以按照出现的频次进行一个排序, 比如:
1 | [我 是 一条 狗 篮球 鸡 ...] |
排序后, 实际上每个token都有一个数字信息了, 比如:
我 = 1, 是 = 2, 一条 = 3, 狗 = 4, 篮球 = 5 , 鸡 = 6
有这个数字信息, 理论上就可以进行运算了, 比如:
“1 + 2”, 对应的是 “我是”, 然而 “3”对应的是”一条”, 这种计算是没有意义的.
如何让他们的数字计算有意义呢? 这里引入了独热编码(One Hot Encode).也就是把每一个token向量化, 且和其它token都正交:
| Token | Vector |
|---|---|
| 我 | [1, 0, 0, 0, 0, 0] |
| 是 | [0, 1, 0, 0, 0, 0] |
| 一条 | [0, 0, 1, 0, 0, 0] |
| 狗 | [0, 0, 0, 1, 0, 0] |
| 篮球 | [0, 0, 0, 0, 1, 0] |
| 鸡 | [0, 0, 0, 0, 0, 1] |
此时 “我 + 是” = [1, 1, 0, 0, 0, 0], 并不等于”一条([[0, 0, 1, 0, 0, 0]])“, 这样的组合是有一定意义的.
但是One Hot Encode有两个严重的问题:
- 独热向量值维度巨大, 假设词表大小V, V=50k 时每个词, 是 50k 维、只有 1 个非零。
- 每个词都是独立的, 他们正交, 因为当时设计的时候就是想要让他们单独表示, 然而大模型时代, 我们需要找到不同token之间的相关性, 不能让他们完全正交.
2.4 BPE
占位后面补充
2.7 Similarity
相似度”(Similarity)本质上是衡量两个向量在多维空间中“有多接近”或“方向有多一致”的数值指标。
前面提到的one-hot 编码, 向量全部是正交的, 所以表示向量的语义完全无关.
为了计算相似度,我们通常会将其转化为计算两个向量之间的 “距离” 或 “夹角”。
下面是两种常用的相似度:
余弦相似度(Cosine Similarity)
它不看两个向量的“长度”(模长),只看它们的“方向”(夹角)。
分子是点积,分母是两个向量长度的乘积:
$(, ) = () = = $
- 1:夹角为 0°,方向完全一致(语义完全相同)。
- 0:夹角为 90°,正交(语义完全无关,比如“猫”和“拖拉机”)。
- −1:夹角为 180°,方向完全相反(语义相反,比如“好”和“坏”)。
这种表示法在NLP领域中应用很广泛.
点积(Dot Product)
Dot Product(A, B) = Cosine Similarity(A, B) × (∥A∥∥B∥)
从几何上来看:
cos(θ)代表了方向一致性, |A||B|代表了信号强度, 说明这个向量在空间中的“存在感”越强,或者它代表的特征越显著。
点积衡量的是两个向量在同一个方向的重叠度, 同时它还叠加了向量自身的“信号强度(长度).
$\text{Dot Product}(\mathbf{A}, \mathbf{B}) = \mathbf{A} \cdot \mathbf{B} = \sum_{i=1}^{n} A_i B_i$
在词嵌入中,每一个维度代表一种潜在的语义特征(比如“是否为动物”、“情感是正面还是负面”)。
- 如果词 A 和词 B 在某个特征上都是高分(同为正数),它们相乘就是大正数。
- 如果它们在某个特征上都是低分/负向(同为负数),负负得正,相乘也是正数。
- 如果一个高分一个低分(一正一负),相乘就是负数(抵消了相似度)。
代数直觉:把所有维度上“同向”的特征得分乘起来并累加。累加的正数越多、越大,说明这两个词在越多的特征上“同频共振”,自然就越相似。
2.6 Word Embedding
前文说过, One-Hot Encode, 功能是将token映射为相互独立(正交)的向量, 由于v个向量要正交, 那么每个向量都应该是v维的, 所以One-Hot 矩阵是V × V的:

假如这个矩阵维度非常高, 比如是100000 × 100000 的, 信息含量其实非常少, 我们有没有什么方法可以减少无用信息? 有的兄弟有的! 用它乘一个低维矩阵就可以降维了.
我们用一个低维矩阵乘这个One-Hot编码矩阵可以达到降维的目的, 比如, 用一个D×V的矩阵:
MV × V × MV × D = MatrixV × D
如图, 通过对一个6维的one-hot词表乘上一个6×3的低维矩阵, 我们将这个矩阵降维到了6x3, 也就是把6维的向量降到了3维.
之前的每个词向量都是线性无关的, 现在降维之后, 变得线性相关.
Note: 实际上, 有没有发现, 其实这个one-hot矩阵是一个单位矩阵, 进行完了运算之后, 实际上最后的结果和这个低维矩阵是一样的.

在大模型/transformer中, 降维采取的维度一般是512维, 降维矩阵的初始值是随机的, 通过训练, 正向传播(以提高相似度为目标), 进行训练.
代码实现:
1 | import torch |
2.6.1 Note: How does Embedding Table work?
前面说到,
V * V的one-hot矩阵去乘一个V * D的矩阵来降维
在实际工程中, 这是一个非常蠢的做法,
因为V * V的出示矩阵中, 每个元素都是单位正交的,
存储起来很大, 实际有用的元素只有那一个1, 而这个词向量, 与矩阵做乘法:
Vector1 * V * EmbeddingTableV * D
假设词向量的第i个元素为1, 其余为0, 这个运算的实质上是取出了EmbeddingMatrixV * D的第i行.
2.6.2 Recognize the Notion of Embedding Table and Token Embeddings
事实上, transformer架构中有一个全局的嵌入矩阵, 也叫权重矩阵,
(Embedding Table), 包含了所有的词向量的嵌入矩阵,
规格为V x D
而我们每次向大模型输入一段token的时候, 只会输入很少的几个token, 这个时候难道我们还要走一遍全局的词嵌入吗?
当然不会, 假如我们只输入了6个token,
那么实际操作是从全局的Embedding Table中,
抽取这6个token的词向量, 组成一个Token Embedding矩阵.
然后同时对这6个向量生成一个位置编码矩阵, 两者相加,
就是我们对整个token序列的处理了.
2.7 Positional Encoding
位置编码有什么用?
在我们输入的数据中, 比如翻译任务, 翻译一句话, 这句话的token实际上是有先后顺序的, 位置编码就是为了给我们的输入向量, 添加位置信息, 下面是公式:
$$ \begin{equation} \begin{aligned} PE_{(pos, 2i)} &= \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) \\ PE_{(pos, 2i+1)} &= \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) \end{aligned} \end{equation} $$
以”我 爱 深度 学习 模型 啊”为输入token举例:
pos代表输入token的位置, 从0开始计数
pos我 = 0, pos爱 = 1, pos深度 = 2, pos学习 = 3, pos模型 = 4, pos啊 = 5
dmodel代表前面嵌入矩阵的d, 比如常用的transformer的d是512维
i是指, 当前token序列中, token的