type
status
date
slug
summary
tags
category
icon
password
Transformer 模型自 2017 年横空出世以来,已成为自然语言处理(NLP)乃至整个人工智能领域的基石。从 BERT 到 GPT,几乎所有现代大语言模型都构建在其强大的架构之上。
本文我们将首先探讨数据预处理的第一步——Tokenizer(分词器),然后深入其核心——自注意力机制,并最终构建出完整的 Encoder-Decoder 架构。
一、Tokenizer:文本数据的预处理基石
在将文本喂给模型之前,我们必须先将其转换成模型能理解的数字形式。这个过程的核心就是 Tokenizer(分词器或切词器)。
1. Tokenizer 的作用
Tokenizer 的主要任务是将文本字符串拆分为一系列有意义的标记(tokens),并将这些 token 转换为唯一的数字 ID。
不同的模型会使用不同的 Tokenizer,导致对同一句话的切分和编号结果也不同。

2. Tokenizer 的三种粒度
根据切分单位的大小,Tokenizer 可分为三类:
- 基于词 (Word-based)
- 原理:以完整的单词作为最小单位。
- 示例:
- 英文:I like dog ——> {”I”, “like”, “dog”}
- 中文:我喜欢小狗 ——> {”我”, “喜欢”, “小狗”}
- 优点:符合人类直觉。
- 缺点:
- 词汇表庞大:需要包含语言中所有词汇。
- 未登录词 (OOV):遇到词汇表之外的词(如新词、错别字)时,只能用
[UNK]
代替,导致信息丢失。
- 基于字符 (Character-based)
- 原理:以单个字符作为最小单位。
- 示例:
- 英文:love ——> {”l”, “o”, “v”, “e”}
- 中文:喜欢 ——> {”喜”, “欢”}
- 优点:词汇表极小,天然不存在 OOV 问题。
- 缺点:
- 序列过长:一个单词会被拆成多个字符,导致输入序列长度剧增,计算开销大。
- 语义丢失:破坏了单词作为基本语义单元的结构。
- 基于子词 (Subword-based)
- 原理:介于词和字符之间,是当前的主流方法。它能将常见词保留为整体,将罕见词拆分为有意义的子词单元(如词根、前后缀)。
- 示例:
- 英文:unhappy ——> {”un”, “happy”}
- 优点:完美平衡了词汇表大小和语义表达,有效解决了 OOV 问题。
- 常见算法:BPE, WordPiece, Unigram 等。
3. 主流 Subword 算法详解
3.1 BPE (Byte-Pair Encoding)
BPE 通过迭代合并最高频的相邻符号对来构建词汇表。
- 核心步骤:
- 初始化:将词汇表初始化为所有单个字符。
- 统计频率:统计语料库中所有相邻 token 对的出现频率。
- 合并:将出现频率最高的 token 对合并成一个新的 token,并加入词汇表。
- 迭代:重复步骤 2 和 3,直到达到预设的词汇表大小或迭代次数。
- 示例:
- 语料:
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
- 初始切分:
('h' 'u' 'g', 10), ('p' 'u' 'g', 5), ('p' 'u' 'n', 12), ('b' 'u' 'n', 4), ('h' 'u' 'g' 's', 5)
- 第一轮合并:
ug
出现频率最高(10+5+5=20),合并ug
。词汇表新增ug
。语料变为('h' 'ug', 10), ('p' 'ug', 5), ('p' 'u' 'n', 12), ('b' 'u' 'n', 4), ('h' 'ug' 's', 5)
- 第二轮合并:
un
出现频率最高,合并un
。词汇表新增un
。 - ...依此类推。
- 拓展:
- BBPE原理和BPE一致,只是使用字节(byte)作为初始token,适用于任何文本。
- 示例:
- 语料:
深度学习需要一定的学习深度
- 初始切分:
230 183 177 | 229 186 166 | 229 173 166 | 228 185 176 | 233 156 128 | 232 166 129 | 228 184 128 | 229 174 154 | 231 154 132 | 229 173 166 | 228 185 176 | 230 183 177 | 229 186 166
- 第一轮合并:
230 183 ——> 230_183(分配新ID:256)
,词汇表更新,分词结果更新:256 177 | 229 186 166 | 229 173 166 | 228 185 176 | 233 156 128 | 232 166 129 | 228 184 128 | 229 174 154 | 231 154 132 | 229 173 166 | 228 185 176 | 256 177 | 229 186 166
- 第二轮合并:
256 177 ——>256_177(分配新ID:257)
- ...以此类推
3.2 WordPiece (BERT 使用)
WordPiece 与 BPE 类似,但合并策略不同。它优先合并那些能最大化语言模型概率的 token 对。
- 合并标准:pair 得分=token1 出现的次数×token2 出现的次数pair 出现的次数
这个公式倾向于合并那些单个 token 出现频率不高,但组合在一起后频率显著提升的
pair
。- 特殊符号:WordPiece 会在非词首的子词前加上
##
来标记。例如,unhappiness
->["un", "##happ", "##iness"]
。
3.3 Unigram
Unigram 是一种 基于概率的子词分割方法,常用于 SentencePiece。与 BPE、WordPiece 不同,它不是逐步合并 token,而是从一个较大的词表开始,通过 逐步删除低概率子词 来得到更优的词表。
- 核心步骤:
- 初始化:构建一个较大的候选词表(来自字符、BPE 或高频词组合)。
- 估计概率:用 EM 算法估计每个子词的出现概率。
- 计算损失:基于所有可能的切分,计算 Unigram Loss。
- 模拟删除:逐个尝试删除子词,记录对 Loss 的影响。
- 剪枝:删除对整体影响最小的子词(如后 40%)。
- 迭代收敛:在新的词表上重新运行 EM,直到达到目标词表大小。
- 示例:
- 语料:
("hug", 10), ("pug", 12), ("lug", 5), ("bug", 4), ("dug", 5)
- 初始词表:
['h','u','g','l','p','b','d','ug','pu','hu','lu','du','bu']
- 词频概率(归一化):
- 分词概率示例("hug"):
- Loss 计算:
- 模拟删除 "ug":
- 删除后 "hug" 只能分为
hu g
,Loss 不变。 - 损失增量 ΔL = 0。
- 剪枝策略:
- 删除 ΔL 最小的子词(如 "ug", "pu", "hu"...)。
- 得到更小、更稳定的词表:
→ 最大概率路径为
hu g
或 h ug
3.4 SentencePiece
SentencePiece 将句子视为一个整体,直接在原始文本上进行操作,无需预先按空格分词。这使得它对多种语言(尤其是中文、日文这类无空格语言)具有很好的通用性。
- 核心思想:
- 将空格也视为一种普通字符(用 ▁ 表示)。
- 可以直接应用 BPE 或 Unigram 算法进行端到端的分词。
对比表格:
方法 | 核心思想 | 词表构建方式 | 优点 | 缺点 | 典型应用 |
BPE (Byte-Pair Encoding) | 基于合并频率:从字符开始,迭代合并出现频率最高的相邻 token 对 | 迭代合并 → 得到新 token,直到目标大小 | 简单高效,易实现 | 过度偏向高频模式,对低频长词处理差 | GPT-2,早期 NLP 模型 |
BBPE (Byte-level BPE) | 字节级 BPE:直接在字节空间做 BPE,而非字符级 | 所有输入先转为字节,再做 BPE | 不依赖语言/字符集,适配任意文本 | 分词结果难解读,可读性差 | GPT-2(OpenAI 原版),LLaMA 等 |
WordPiece | 基于似然提升:选择能最大化语料概率提升的 token 加入词表 | 从字符开始,逐步添加“提升最大”的新 token | 平衡频率和概率,效果更稳 | 训练较慢,需概率计算 | BERT, RoBERTa |
Unigram | 基于概率删减:从大词表开始,迭代删除低概率子词 | 初始化大词表 → EM 算法估计概率 → 剪枝 | 保留多切分可能性,词表更精炼 | 实现复杂,需 EM & 动态规划 | SentencePiece (Unigram 模式) |
SentencePiece | 统一分词框架:支持 BPE / Unigram,直接作用于原始文本或字节 | 输入不需预分词(不依赖空格),直接训练 | 无需人工预处理,支持多语言,灵活 | 训练耗时更长 | T5, ALBERT, mBERT, XLM-R |
二、Transformer 模型概览
理解了 Tokenizer 后,我们来看看 Transformer 的整体结构。它是一个典型的 Encoder-Decoder(编码器-解码器) 架构。

- 主要特点:
- 完全基于自注意力机制:摒弃了 RNN 的循环结构和 CNN 的卷积结构。
- 并行计算能力强:由于没有循环依赖,模型可以同时处理整个序列,训练速度极快。
- 编码器-解码器:
- 编码器 (Encoder):左侧部分,负责理解输入文本,将其转换为一系列富含上下文信息的向量表示。
- 解码器 (Decoder):右侧部分,负责根据编码器的输出和已经生成的内容,预测下一个词,最终生成目标文本。
三、核心机制:自注意力 (Self-Attention)
自注意力是 Transformer 的灵魂。它让模型在处理一个词时,能够计算这个词与句子中所有其他词的关联程度,从而更好地理解上下文。
1. 为什么需要自注意力?
传统的 RNN/LSTM 在处理长序列时,存在梯度消失和难以并行计算的问题。自注意力机制通过直接计算序列内任意两个位置之间的依赖关系,解决了长距离依赖问题,并且可以完全并行化。
例如,在句子 “机器人必须遵守人类给它的命令” 中,自注意力机制可以帮助模型理解 “它” 指代的是 “机器人”。
2. Q, K, V:自注意力的三个关键角色
为了实现注意力计算,模型会为输入序列中的每一个词向量生成三个新的向量:
- Query (Q, 查询向量):代表当前词,主动去“查询”与其他词的关联。
- Key (K, 键向量):代表句子中所有词(包括自己),用来被 Q 查询,以匹配相关性。
- Value (V, 值向量):代表句子中所有词的实际信息。一旦 Q 和 K 匹配成功,V 就会被传递出去。
这三个向量是通过将原始词嵌入向量乘以三个可学习的权重矩阵 () 得到的。
3. 自注意力的计算过程
自注意力的计算可以分为四个步骤:
- 计算注意力得分 (Score):
- 将当前词的 Q 向量与所有词的 K 向量进行点积运算,得到它们之间的原始相关性得分。
- 为了防止梯度过小,将得分除以一个缩放因子 (是 K 向量的维度)。

- 归一化 (Softmax):
- 将上一步得到的得分输入到 Softmax 函数中,将其转换为和为 1 的概率分布,即注意力权重。这个权重表示当前词应该对其他词投入多少“注意力”。
- 加权求和 (Weighted Sum):
- 将得到的注意力权重与所有词的 V 向量相乘,然后求和。这样,注意力权重越高的词,其 V 向量的贡献就越大。
- 输出:
- 最终的输出向量融合了整个句子的上下文信息,代表了当前词在当前语境下的新表示。
4. 代码实现(PyTorch 矩阵运算版)
下面的代码演示了如何使用矩阵运算一次性为整个句子计算自注意力。
四、进阶:多头注意力机制 (Multi-Head Attention)
单一的自注意力机制只允许模型在一个“表示子空间”中学习信息。为了让模型能够同时关注来自不同子空间的信息(例如,一个头关注语法结构,另一个头关注语义关系),Transformer 引入了多头注意力机制。
- 核心思想:
- 分割:将原始的 Q, K, V 向量在维度上切分成 h 个头(组)。
- 并行计算:每个头独立地执行完整的自注意力计算。
- 合并:将所有头的输出结果拼接起来。
- 线性变换:将拼接后的结果通过一个线性层进行融合,得到最终的输出。
PyTorch 内置实现
在实际开发中,我们无需手动实现多头注意力,可以直接使用 PyTorch 提供的
nn.MultiheadAttention
层。五、Transformer 的关键组件
除了注意力机制,Transformer 还有几个不可或缺的组件。
1. 位置编码 (Positional Encoding)
自注意力机制本身是“无序”的,它无法感知词语在句子中的位置。为了解决这个问题,我们需要在词嵌入中加入位置编码,明确地告诉模型每个词的位置信息。
最终输入 = 词嵌入 + 位置编码
原始论文中使用正弦和余弦函数来生成固定的位置编码,其优点是能够处理比训练时更长的序列。
2. 残差连接 (Add) 与层归一化 (Norm)
在 Transformer 的每个子层(如多头注意力和前馈网络)之后,都会进行
Add & Norm
操作。- 残差连接 (Add):将子层的输入直接加到子层的输出上。这是一种经典的深度学习技巧,可以有效防止梯度消失,让网络更容易训练。
- 层归一化 (Layer Normalization):对每个样本的所有特征进行归一化。与 BatchNorm 不同,它不依赖于批次大小,非常适合处理变长的序列数据,能稳定训练过程。
3. 前馈神经网络 (Feed Forward Network)
每个注意力子层之后都跟着一个简单的全连接前馈网络,它对每个位置的输出进行非线性的变换,增强了模型的表达能力。
六、整体架构:编码器与解码器
现在我们可以将所有组件组装起来了。
1. 编码器 (Encoder)
- 一个编码器层由多头自注意力和前馈网络两个子层构成。
- 原始 Transformer 由 6 个相同的编码器层堆叠而成(深拷贝)。
- 输入序列经过所有编码器层后,生成最终的上下文表示,这个表示将传递给解码器的每一个层。
2. 解码器 (Decoder)
- 一个解码器层比编码器层多一个注意力模块,共三个子层:
- 掩码多头自注意力 (Masked Multi-Head Attention):对解码器自身的输入进行自注意力计算。这里的“掩码”是关键,它确保在预测第
i
个词时,只能看到第i
个词之前的内容,防止“剧透”。 - 交叉注意力 (Cross-Attention):这是编码器和解码器交互的地方。它的 Q 来自于解码器前一层的输出,而 K 和 V 则来自于编码器的最终输出。这使得解码器在生成时能够“关注”输入句子的相关部分。
- 前馈网络:与编码器中的作用相同。
- 原始 Transformer 也由 6 个相同的解码器层堆叠而成(深拷贝)。
七、不可或缺的掩码机制 (Masking)
掩码是 Transformer 实现正确计算的关键。
掩码类型 | 使用场景 | 作用 |
填充掩码 (Padding Mask) | 输入序列长短不一时,短序列被填充 | 告诉注意力机制忽略填充部分( <PAD> token),避免其参与计算。 |
序列掩码 (Sequence Mask) | 解码器的自注意力层中 | 防止模型在生成当前词时“偷看”未来的词,保证自回归特性。 |

总结与扩展阅读
通过本文,我们从最基础的 Tokenizer 出发,逐步探索了 Transformer 的核心组件——自注意力、多头注意力、位置编码、层归一化,并最终理解了其经典的 Encoder-Decoder 架构。正是这些设计的精妙组合,造就了 Transformer 在处理序列数据时无与伦比的强大能力。
- 推荐阅读:
- 必读原文:Attention Is All You Need
- 实践工具:Hugging Face Transformers 库,提供了大量预训练模型和易于使用的 Tokenizer。
- 作者:sisui
- 链接:https://www.sisui.me//article/py-transformer-tokenizer-attention-deep-dive
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章