跳转到内容

Tokenization:模型如何看见文字

LLM 不是直接“读汉字”或“读英文单词”,而是先把输入文本切成 token。token 可以是一个汉字、一个英文子词、一个标点,甚至是空格加单词的一部分。模型的任务可以理解为:看见前面的 token 序列,预测下一个 token 的概率分布。

这种切分很重要:同一句话如果被切得太碎,序列会变长,注意力计算更贵;如果词表太大,嵌入矩阵和输出层会变大,低频词还容易学不好。BPE 一类子词算法的直觉是反复合并常见片段,在“字符级通用性”和“词级效率”之间折中。

字符:30Token:14
语言模型文本 token,再预测一个 token

BPE 训练流程图:

graph TD
A[原始语料库] --> B[字符级初始化]
B --> C{是否达到目标词表大小?}
C -->|否| D[统计所有相邻字符对频率]
D --> E[找出最高频的字符对]
E --> F[合并该字符对为新token]
F --> C
C -->|是| G[最终BPE词表]
style A fill:#f9f,stroke:#333
style G fill:#9f9,stroke:#333
style C fill:#ff9,stroke:#333

编码示例:

  • 输入文本: low
  • 初始字符: l o w </w>
  • 经过训练后的词表合并: lowlo + w 或保持字符级

下面是一个简化的 BPE tokenization 实现,展示了如何通过反复合并高频 pair 来构造子词词表:

可运行示例
from collections import Counter

def get_vocab(corpus):
    """将文本拆分为字符级词表"""
    vocab = Counter()
    for word in corpus:
        vocab[' '.join(word) + ' </w>'] += 1
    return vocab

def get_pairs(vocab):
    """获取所有相邻 token pair 及其频率"""
    pairs = Counter()
    for word, freq in vocab.items():
        symbols = word.split()
        for i in range(len(symbols) - 1):
            pairs[(symbols[i], symbols[i+1])] += freq
    return pairs

def merge_vocab(pair, vocab):
    """合并词表中的指定 pair"""
    new_vocab = {}
    bigram = ' '.join(pair)
    replacement = ''.join(pair)
    for word in vocab:
        new_word = word.replace(bigram, replacement)
        new_vocab[new_word] = vocab[word]
    return new_vocab

# 示例:训练 BPE
corpus = ['low', 'lower', 'newest', 'widest']
vocab = get_vocab(corpus)
print("初始词表:", vocab)

# 迭代合并最高频的 pair
num_merges = 3
for i in range(num_merges):
    pairs = get_pairs(vocab)
    if not pairs:
        break
    best_pair = pairs.most_common(1)[0][0]
    vocab = merge_vocab(best_pair, vocab)
    print(f"合并 {best_pair}: {vocab}")

工程版:tokenizer 是模型接口契约

Section titled “工程版:tokenizer 是模型接口契约”

工程上,tokenizer 决定了上下文窗口、计费、截断和缓存命中。常见流程包括规范化文本、按规则预切分、把片段映射到词表 ID,再把 ID 输入嵌入表。生产系统要固定 tokenizer 版本:换一个词表会改变所有 token ID,旧 prompt、微调数据和缓存都可能失效。

多语言场景尤其要关注公平性。英文常见词往往由少量 token 表示,中文、代码、emoji 或冷门语言可能更碎,导致同样语义占用更多上下文。做 RAG 或长文摘要时,应按 token 而不是字符估算长度,并给系统提示、引用片段和回答预留预算。

研究版:词表构造的语言学边界

Section titled “研究版:词表构造的语言学边界”

研究上,tokenization 不仅是工程问题,也涉及语言学假设。子词算法(BPE、WordPiece、SentencePiece、Unigram)对模型学习形态学和构词法有深远影响。例如,BPE 的贪心合并是否会让模型偏向高频复合词,而忽略罕见但有意义的前缀/后缀?

多语言模型的 tokenization 公平性是一个活跃领域:不同语言的”每词 token 数”差异巨大,可能导致模型在资源匮乏语言上的表示质量系统性偏低。Byte-level BPE(如 GPT-2 所用)试图用字节而非 Unicode 字符作为基础单元,改善对未知字符和代码的覆盖,但也带来了更长的序列。

🔬 开放研究问题

该领域的关键问题与研究方向:

  1. BPE 的贪心合并策略是否系统性偏向高频复合词?如何量化这种偏差对模型语言学习的影响?
  2. 多语言 tokenization 公平性如何量化?不同语言的"每词 token 数"差异如何影响模型性能?
  3. Byte-level vs character-level tokenization:序列长度与覆盖度的权衡是否还能进一步优化?
  4. 针对代码、数学符号、emoji 等特殊领域,是否需要设计专门的 tokenization 策略?

本文引用论文

  • Efficient Estimation of Word Representations in Vector Space — Tomas Mikolov et al. (2013)

    Word2Vec 提出了词向量(词嵌入)的概念:通过在大规模文本上训练神经网络,让语义相近的词 在向量空间中距离相近。"king - man + woman ≈ queen"的类比关系让世人看到了词嵌入的威力, 为后来所有语言模型的嵌入层奠定了基础。

  • Neural Machine Translation of Rare Words with Subword Units — Rico Sennrich et al. (2016)

    提出将 BPE(字节对编码)应用于神经机器翻译的分词。通过迭代地合并出现频率最高的字符对, BPE 在词汇表大小和对罕见词的处理能力之间取得平衡。这是 GPT 系列等大多数现代 LLM 分词器的直接原型。