在理解 Transformer 架构时,我们经常听到 Tokenizer 和 Embedding 这两个概念。它们是什么关系?Tokenizer 到底在做什么?让我为你详细解析。


什么是 Tokenizer?

1.1 Tokenizer 的基本概念

Tokenizer(分词器) 是将原始文本转换为模型可以处理的数字表示的工具。它是 NLP 流程中的第一步,负责将人类语言转化为机器能理解的"语言"。

Tokenizer : 原始文本 → Token IDs \text{Tokenizer}: \text{原始文本} \rightarrow \text{Token IDs} Tokenizer:原始文本Token IDs

直观理解

┌─────────────────────────────────────────────────────────────┐
│                    Tokenizer 的工作流程                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  原始文本: "I love AI"                                      │
│         ↓                                                  │
│  分词 (Tokenization): ["I", "love", "AI"]                  │
│         ↓                                                  │
│  映射到 ID: [1045, 2293, 9932]                             │
│         ↓                                                  │
│  输入模型 → Embedding 层 → Transformer ...                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 为什么需要 Tokenizer

计算机无法直接理解文本,需要将文本转换为数字:

阶段 数据形式 说明
原始文本 “Hello world” 人类可读,机器不可读
Tokenization [“Hello”, “world”] 分割成最小单元
Token IDs [7592, 2088] 机器可读的数字
Embeddings [ 0.12 , − 0.34 , . . . ] [0.12, -0.34, ...] [0.12,0.34,...] 密集向量表示

Tokenizer 的三种主要类型

2.1 Word-based Tokenizer(基于词的分词)

原理:按空格和标点分割单词。

示例

"Let's learn tokenizer!" → ["Let", "'", 's', "learn", "tokenizer", "!"]

优缺点

优点 缺点
直观易懂 词汇表巨大(>50万)
实现简单 无法处理未登录词
保留完整词义 时态变体占用多个位置(“run”, “running”, “ran”)

词汇表大小估算

  • 英语约有 17 万单词
  • 加上专有名词、缩写等,轻松超过 50 万
  • 导致模型参数量爆炸: ∣ V ∣ × d m o d e l |V| \times d_{model} V×dmodel

2.2 Character-based Tokenizer(基于字符的分词)

原理:按单个字符分割。

示例

"AI" → ["A", "I"]
"hello" → ["h", "e", "l", "l", "o"]

优缺点

优点 缺点
词汇表很小(~100) 序列长度剧增
无未登录词问题 丢失词级别语义
任何语言都可处理 学习难度大

序列长度对比

  • 词级别:“hello” → 1 个 token
  • 字符级别:“hello” → 5 个 tokens
  • Transformer 计算复杂度 O ( n 2 ) O(n^2) O(n2),长度增加 5 倍意味着计算量增加 25 倍!

2.3 Subword Tokenizer(子词分词)—— 当前主流

原理:在词和字符之间取得平衡,将词拆分为更小的语义单元。

示例(BPE)

"unhappiness" → ["un", "happiness"]  # 保留语义
"tokenization" → ["token", "ization"]  # 词根+后缀
"playing" → ["play", "ing"]  # 词干+时态

核心优势

┌─────────────────────────────────────────────────────────────┐
│                   Subword 的优势                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  词汇表大小适中:30k-100k                                   │
│                                                             │
│  处理未登录词:"deeplearning" → ["deep", "learning"]        │
│                                                             │
│  共享语义单元:"run", "running", "ran" 共享 "run"           │
│                                                             │
│  平衡序列长度:比字符短,比词略长                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

主流 Subword 分词算法

3.1 BPE (Byte-Pair Encoding)

原理:从字符开始,迭代合并最频繁的字符对。

算法步骤

┌─────────────────────────────────────────────────────────────┐
│                     BPE 算法流程                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  初始词汇表:所有字符 + 特殊token                           │
│  {"h", "e", "l", "o", "w", "r", "d"}                       │
│                                                             │
│  训练语料:["hello", "world"]                               │
│                                                             │
│  Step 1: 统计字符对频率                                     │
│  ("h", "e"): 1, ("e", "l"): 1, ("l", "l"): 1, ...          │
│                                                             │
│  Step 2: 合并最频繁的对 ("l", "o") → "lo"                   │
│  {"h", "e", "l", "o", "w", "r", "d", "lo"}                 │
│                                                             │
│  Step 3: 重复直到达到目标词汇表大小                         │
│  ...                                                        │
│                                                             │
│  最终词汇表包含:{"h", "e", "l", "o", "w", "r", "d",       │
│                 "lo", "hel", "wor", "hell", "world"}       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

BPE 编码示例

训练后词汇表:["a", "b", "c", "ab", "bc", "abc"]

文本:"abc" 的分词可能:
- 最优:["abc"] (1 token)
- 次优:["ab", "c"] (2 tokens)
- 最差:["a", "b", "c"] (3 tokens)

算法会选择最长匹配的子词!

3.2 WordPiece (BERT 使用)

原理:类似 BPE,但使用似然概率而非频率决定合并。

核心公式

合并两个子词 x x x y y y 的得分:
score ( x , y ) = count ( x y ) count ( x ) × count ( y ) \text{score}(x, y) = \frac{\text{count}(xy)}{\text{count}(x) \times \text{count}(y)} score(x,y)=count(x)×count(y)count(xy)

得分越高,表示这两个子词越应该合并。

示例

统计信息:
count("un") = 1000
count("happy") = 500
count("unhappy") = 200

score("un", "happy") = 200 / (1000 × 500) = 0.0004

如果得分高于阈值,就合并为 "unhappy"

3.3 Unigram (SentencePiece 使用)

原理:假设每个子词独立出现,通过 EM 算法训练。

数学模型

假设句子 X X X 的分词概率为:
P ( X ) = ∑ x ∈ seg ( X ) ∏ i = 1 ∣ x ∣ p ( x i ) P(X) = \sum_{x \in \text{seg}(X)} \prod_{i=1}^{|x|} p(x_i) P(X)=xseg(X)i=1xp(xi)

其中 seg ( X ) \text{seg}(X) seg(X) X X X 的所有可能分词方式, p ( x i ) p(x_i) p(xi) 是子词 x i x_i xi 的概率。

Viterbi 算法用于解码

输入:"unhappiness"
词汇表概率:
p("un") = 0.1
p("happiness") = 0.05
p("ha") = 0.01
...

动态规划找到最大概率路径:
["un", "happiness"] 概率 = 0.1 × 0.05 = 0.005
["un", "ha", "ppiness"] 概率 = 0.1 × 0.01 × ... = 更小

选择概率最大的分词方式!

3.4 三种算法对比

算法 代表模型 特点 词汇表大小
BPE GPT, RoBERTa 简单高效,频率驱动 50k
WordPiece BERT, DistilBERT 概率驱动,更精细 30k
Unigram T5, ALBERT 概率模型,可采样 32k-250k

Tokenizer 的工作流程详解

4.1 完整流程

┌─────────────────────────────────────────────────────────────┐
│                    Tokenizer 完整流程                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  原始文本: "I love AI!"                                     │
│         ↓                                                  │
│  Step 1: 文本规范化 (Normalization)                         │
│  处理:统一大小写、Unicode 标准化等                         │
│  "i love ai!"                                              │
│         ↓                                                  │
│  Step 2: 预分词 (Pre-tokenization)                          │
│  按空格和标点初步分割                                       │
│  ["i", "love", "ai", "!"]                                  │
│         ↓                                                  │
│  Step 3: 子词分割 (Subword Segmentation)                    │
│  应用 BPE/WordPiece 算法                                    │
│  ["i", "love", "ai", "[UNK]"]  (如果 "!" 不在词汇表)        │
│         ↓                                                  │
│  Step 4: 编码到 ID (Encode to IDs)                          │
│  查找词汇表映射                                             │
│  [1045, 2293, 9932, 1000]                                  │
│         ↓                                                  │
│  Step 5: 添加特殊标记 (Add Special Tokens)                  │
│  添加 [CLS], [SEP], [PAD] 等                                │
│  [101, 1045, 2293, 9932, 1000, 102]                        │
│         ↓                                                  │
│  Step 6: 创建注意力掩码 (Attention Mask)                    │
│  标记哪些是真实 token,哪些是 padding                       │
│  [1, 1, 1, 1, 1, 1]                                        │
│         ↓                                                  │
│  最终输入:                                                  │
│  input_ids: [101, 1045, 2293, 9932, 1000, 102]            │
│  attention_mask: [1, 1, 1, 1, 1, 1]                       │
│  token_type_ids: [0, 0, 0, 0, 0, 0]  (单句任务)            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

4.2 代码实现 (HuggingFace Tokenizers)

from transformers import AutoTokenizer

# 加载 BERT 的 tokenizer
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')

# 原始文本
text = "I love AI!"

# Step 1-4: 直接编码
encoded = tokenizer(text)
print(encoded)
# {
#   'input_ids': [101, 1045, 2293, 9932, 999, 102],
#   'token_type_ids': [0, 0, 0, 0, 0, 0],
#   'attention_mask': [1, 1, 1, 1, 1, 1]
# }

# 分步查看
# 1. 预分词
pretokens = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)
print("Pre-tokens:", pretokens)
# [('i', (0, 1)), ('love', (2, 6)), ('ai', (7, 9)), ('!', (9, 10))]

# 2. 子词分词
tokens = tokenizer.tokenize(text)
print("Tokens:", tokens)
# ['i', 'love', 'ai', '!']

# 3. 转 ID
ids = tokenizer.convert_tokens_to_ids(tokens)
print("IDs:", ids)
# [1045, 2293, 9932, 999]

# 4. 添加特殊标记
input_ids = tokenizer.build_inputs_with_special_tokens(ids)
print("With special tokens:", input_ids)
# [101, 1045, 2293, 9932, 999, 102]

# 解码回文本
decoded = tokenizer.decode(input_ids)
print("Decoded:", decoded)
# "[CLS] i love ai! [SEP]"

Tokenizer 与 Embedding 的关系

5.1 完整数据处理流水线

┌─────────────────────────────────────────────────────────────┐
│              从文本到向量:完整流水线                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  原始文本                                                    │
│  "I love AI"                                                │
│       ↓                                                     │
│  ┌──────────────────────────────────────────────────────┐   │
│  │                    Tokenizer                          │   │
│  │  • 文本规范化                                         │   │
│  │  • 预分词                                             │   │
│  │  • 子词分割                                           │   │
│  │  • 映射到 ID                                          │   │
│  └──────────────────────────────────────────────────────┘   │
│       ↓                                                     │
│  Token IDs: [1045, 2293, 9932]                              │
│       ↓                                                     │
│  ┌──────────────────────────────────────────────────────┐   │
│  │                 Embedding Layer                       │   │
│  │  • 查找表:E ∈ R^{|V| × d}                            │   │
│  │  • 每个 ID 映射到向量                                  │   │
│  │  • 输出形状:[seq_len, d]                             │   │
│  └──────────────────────────────────────────────────────┘   │
│       ↓                                                     │
│  词嵌入矩阵:                                                │
│  [[0.12, -0.34, 0.56, ...],  ← "I" 的向量                  │
│   [0.23, 0.45, -0.67, ...],   ← "love" 的向量              │
│   [-0.11, 0.78, -0.22, ...]]  ← "AI" 的向量                │
│       ↓                                                     │
│  ┌──────────────────────────────────────────────────────┐   │
│  │              + Positional Encoding                   │   │
│  └──────────────────────────────────────────────────────┘   │
│       ↓                                                     │
│  带位置信息的嵌入:                                          │
│  [[0.12+PE₀, -0.34+PE₀, ...],                              │
│   [0.23+PE₁, 0.45+PE₁, ...],                               │
│   [-0.11+PE₂, 0.78+PE₂, ...]]                              │
│       ↓                                                     │
│  ┌──────────────────────────────────────────────────────┐   │
│  │                Transformer Layers                    │   │
│  │  • Self-Attention 融合上下文                          │   │
│  │  • FFN 非线性变换                                     │   │
│  └──────────────────────────────────────────────────────┘   │
│       ↓                                                     │
│  上下文嵌入:                                                │
│  [[0.45, -0.12, 0.89, ...],  ← "I" 在上下文中的表示        │
│   [0.67, 0.23, -0.45, ...],   ← "love" 在上下文中的表示    │
│   [0.11, -0.56, 0.78, ...]]   ← "AI" 在上下文中的表示      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

5.2 Tokenizer 和 Embedding 的分工

组件 输入 输出 作用 是否可学习
Tokenizer 原始文本 Token IDs 文本 → 数字 ❌ 不可学习
Embedding Token IDs 向量 数字 → 语义空间 ✅ 可学习

关键区别

  • Tokenizer 是规则/统计驱动的预处理步骤
  • Embedding 是神经网络参数,随训练更新

5.3 数学关系

假设:

  • 词汇表 V \mathcal{V} V,大小 ∣ V ∣ |V| V
  • 嵌入维度 d d d

Tokenizer 定义映射:
T : 文本 → N ∣ V ∣ T: \text{文本} \rightarrow \mathbb{N}^{|V|} T:文本NV

Embedding 定义映射:
E : N ∣ V ∣ → R d E: \mathbb{N}^{|V|} \rightarrow \mathbb{R}^d E:NVRd

完整流程
文本 → T Token ID → E 向量 → Transformer 上下文向量 \text{文本} \xrightarrow{T} \text{Token ID} \xrightarrow{E} \text{向量} \xrightarrow{\text{Transformer}} \text{上下文向量} 文本T Token IDE 向量Transformer 上下文向量


主流模型的 Tokenizer 配置

6.1 各模型 Tokenizer 对比

模型 Tokenizer 类型 词汇表大小 特殊标记 特点
BERT WordPiece 30,522 [CLS], [SEP], [MASK] 大小写敏感/不敏感版本
GPT-2/3 BPE 50,257 `< endoftext
RoBERTa BPE 50,265 <s>, </s>, <mask> 动态掩码
T5 SentencePiece 32,000 <extra_id_0> 无监督预训练
LLaMA BPE 32,000 <s>, </s> 字节级 BPE
Qwen BPE 152,064 `< im_start
DeepSeek BPE 128,256 <|begin▁of▁sentence|> 字节级 BPE

6.2 代码示例:不同模型的 Tokenizer

from transformers import AutoTokenizer

# BERT (WordPiece)
bert_tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
print(bert_tokenizer.tokenize("unhappiness"))
# ['un', '##happiness']  # ## 表示是子词的一部分

# GPT-2 (BPE)
gpt2_tokenizer = AutoTokenizer.from_pretrained('gpt2')
print(gpt2_tokenizer.tokenize("unhappiness"))
# ['un', 'h', 'appiness']  # 字节级 BPE

# LLaMA (BPE)
llama_tokenizer = AutoTokenizer.from_pretrained('meta-llama/Llama-2-7b-hf')
print(llama_tokenizer.tokenize("unhappiness"))
# ['un', 'h', 'appiness']  # 类似 GPT-2

# T5 (SentencePiece - Unigram)
t5_tokenizer = AutoTokenizer.from_pretrained('t5-small')
print(t5_tokenizer.tokenize("unhappiness"))
# ['▁un', 'happiness']  # ▁ 表示词开头

Tokenizer 的挑战与优化

7.1 未登录词处理

问题:遇到词汇表中没有的词怎么办?

解决方案

┌─────────────────────────────────────────────────────────────┐
│                   未登录词处理策略                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1️⃣ 退化为子词 (Subword Fallback)                          │
│  "deeplearning" → ["deep", "learning"]  ✓ 最常用           │
│                                                             │
│  2️⃣ 退化为字符 (Character Fallback)                        │
│  "deeplearning" → ["d", "e", "e", "p", ...]  ✗ 序列过长    │
│                                                             │
│  3️⃣ 使用 [UNK] 标记 (Unknown Token)                        │
│  "deeplearning" → ["[UNK]"]  ✗ 信息丢失严重                 │
│                                                             │
│  4️⃣ 字节级表示 (Byte-level BPE)                            │
│  任何 UTF-8 字符都可表示,无 [UNK]  ✓  GPT-2/LLaMA 使用    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

字节级 BPE 示例

# 字节级 BPE 可以处理任何 Unicode
text = "Hello 你好 🌟"
tokens = tokenizer.tokenize(text)
# 所有字符都能被表示,没有 [UNK]

7.2 多语言支持

模型 语言覆盖 Tokenizer 策略
mBERT 104 种 共享 WordPiece
XLM-R 100 种 共享 BPE
Qwen 中英为主 针对中文字符优化
GPT-4 多语言 大规模 BPE

7.3 效率优化

问题:长文本的 Tokenization 可能成为瓶颈。

优化策略

  1. 缓存机制:重复文本避免重算
  2. 批处理:同时处理多个文本
  3. 预分词加速:使用 Rust 实现(HuggingFace Tokenizers)
  4. 流式处理:逐块处理超长文本

实际应用示例

8.1 完整的 BERT 输入构建

from transformers import BertTokenizer
import torch

tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

# 单句任务
sentence = "I love AI"
encoded = tokenizer(
    sentence,
    max_length=10,
    padding='max_length',
    truncation=True,
    return_tensors='pt'
)

print("Input IDs:", encoded['input_ids'])
print("Attention Mask:", encoded['attention_mask'])
print("Token Type IDs:", encoded['token_type_ids'])

# Input IDs: tensor([[ 101, 1045, 2293, 9932,  102,    0,    0,    0,    0,    0]])
# Attention Mask: tensor([[1, 1, 1, 1, 1, 0, 0, 0, 0, 0]])
# Token Type IDs: tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

# 句子对任务 (NLI/QA)
sentence1 = "I love AI"
sentence2 = "AI is fascinating"
encoded_pair = tokenizer(
    sentence1,
    sentence2,
    max_length=20,
    padding='max_length',
    truncation=True,
    return_tensors='pt'
)

print("Input IDs shape:", encoded_pair['input_ids'].shape)
# torch.Size([1, 20])

# 解码查看特殊标记
print(tokenizer.decode(encoded_pair['input_ids'][0]))
# "[CLS] i love ai [SEP] ai is fascinating [SEP] [PAD] [PAD] ..."

8.2 批量处理

texts = [
    "I love AI",
    "Machine learning is amazing",
    "Deep learning",
    "NLP"
]

# 批量处理自动 padding
encoded_batch = tokenizer(
    texts,
    padding=True,
    truncation=True,
    max_length=10,
    return_tensors='pt'
)

print("Batch input_ids shape:", encoded_batch['input_ids'].shape)
# torch.Size([4, 10])  # 4 个句子,统一长度 10

# 查看 padding 情况
for i, ids in enumerate(encoded_batch['input_ids']):
    print(f"Sentence {i}: {tokenizer.decode(ids)}")

Tokenizer 与 Embedding 的交互

9.1 前向传播中的角色

import torch.nn as nn

class TransformerModel(nn.Module):
    def __init__(self, vocab_size, d_model):
        super().__init__()
        # Embedding 层依赖 Tokenizer 定义的词汇表大小
        self.embedding = nn.Embedding(vocab_size, d_model)
        self.transformer = nn.TransformerEncoder(...)
        
    def forward(self, text):
        # Step 1: Tokenizer (外部处理)
        # input_ids = tokenizer(text)['input_ids']
        
        # Step 2: Embedding 查找
        x = self.embedding(input_ids)  # [seq_len, d_model]
        
        # Step 3: Transformer 处理
        output = self.transformer(x)
        return output

9.2 梯度传播

重要:Tokenizer 本身不参与梯度传播

原始文本 ──→ Token IDs ──→ Embedding ──→ Transformer ──→ Loss
                ↑              ↑              ↑
            不可学习        可学习          可学习
           (规则/统计)    (梯度更新)      (梯度更新)

总结

10.1 Tokenizer 核心要点

┌─────────────────────────────────────────────────────────────┐
│                    Tokenizer 核心要点                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1️⃣ 什么是 Tokenizer                                        │
│      • 将文本转换为数字 ID 的工具                            │
│      • NLP 流程的第一步,连接人类语言和机器                  │
│                                                             │
│  2️⃣ 三种主要类型                                            │
│      • 词级别:直观但词汇表大                               │
│      • 字符级别:词汇表小但序列长                           │
│      • 子词级别:平衡两者,当前主流                         │
│                                                             │
│  3️⃣ 主流算法                                                │
│      • BPE (GPT):频率驱动,迭代合并                        │
│      • WordPiece (BERT):概率驱动,更精细                   │
│      • Unigram (T5):概率模型,可采样                       │
│                                                             │
│  4️⃣ 与 Embedding 的关系                                     │
│      • Tokenizer:文本 → 数字 (不可学习)                    │
│      • Embedding:数字 → 向量 (可学习)                      │
│      • 共同构成从文本到语义空间的桥梁                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

10.2 简单记忆

概念 类比
Tokenizer 字典的索引系统(找到词的编号)
Embedding 字典的解释(词的向量表示)
Token IDs 词的编号(如 “001” 代表 “猫”)
Embeddings 词的含义(“猫” 的完整描述)

10.3 一句话总结

Tokenizer 负责把文本变成数字 ID,Embedding 负责把这些数字 ID 变成有语义的向量,两者配合让模型能够理解和处理自然语言! 🚀


Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐