Tokenization

Tokenization (分词)

分词是将原始文本转换为模型可以理解的数字序列(Token IDs)的过程。

1. 粒度 (Granularity)

  • Character-level (字符级): a, b, c. 词表小,但序列极长,缺少语义。
  • Word-level (词级): apple, banana. 词表极大(数十万),会有 OOV (Out of Vocabulary) 问题。
  • Subword-level (子词级): 介于两者之间。常用词保持完整,罕见词拆分为子词。平衡了词表大小和序列长度。这是 LLM 的标准做法。

2. 常用算法 (Common Algorithms)

2.1 BPE (Byte Pair Encoding)

  • 原理:迭代合并最频繁出现的字符对。
  • 流程
    1. 初始化词表为单字符。
    2. 统计所有相邻字符对的频率。
    3. 合并频率最高的对(如 e, s -> es),加入词表。
    4. 重复直到达到预设词表大小。
  • Byte-level BPE: 基于 UTF-8 字节而不是 Unicode 字符进行 BPE。这确保了可以处理任何字符串而不会出现 <UNK>。GPT-2, GPT-3, LLaMA 都使用此方法。

2.2 WordPiece

  • 原理:类似 BPE,但合并标准是最大化训练数据的似然概率(Likelihood),而不是频率。
  • 应用:BERT, DistilBERT。

2.3 SentencePiece

  • 特点
    • 将输入视为原始字节流(Raw Stream),不依赖空格分词(对中文、日文友好)。
    • 集成了 BPE 和 Unigram 算法。
  • 应用:T5, LLaMA (虽然 LLaMA 用的是 BPE 算法,但实现上常使用 SentencePiece 库或 Tokenizers 库)。

3. Special Tokens (特殊 Token)

  • <BOS> / <s>: Beginning of Sentence.
  • <EOS> / </s>: End of Sentence.
  • <PAD>: Padding token,用于将 batch 中的序列补齐到相同长度。
  • <UNK>: Unknown token.

4. HuggingFace Transformers 实战

from transformers import AutoTokenizer

# 加载预训练的 Tokenizer
tokenizer = AutoTokenizer.from_pretrained("gpt2")

text = "Hello, world! transforming text."

# 1. 编码 (Encoding): Text -> Token IDs
encoded = tokenizer(text)
print(encoded)
# {'input_ids': [15496, 11, 995, 0, 44265, 2420, 13], 'attention_mask': [1, 1, 1, 1, 1, 1, 1]}

# 查看每个 Token 对应的字符串
tokens = tokenizer.convert_ids_to_tokens(encoded['input_ids'])
print(tokens)
# ['Hello', ',', 'Ġworld', '!', 'Ġtransforming', 'Ġtext', '.']
# 注意:GPT2 使用 Ġ (G with dot) 表示前导空格

# 2. 解码 (Decoding): Token IDs -> Text
decoded = tokenizer.decode(encoded['input_ids'])
print(decoded)
# "Hello, world! transforming text."

扩充词表 (Adding Tokens)

如果需要微调特定领域的模型(如代码、医学),可能需要扩充词表。

new_tokens = ["<SPECIAL_TAG>", "python code"]
num_added_toks = tokenizer.add_tokens(new_tokens)
print('We have added', num_added_toks, 'tokens')

# 注意:扩充词表后,必须调整模型 Embedding 层的大小!!
# model.resize_token_embeddings(len(tokenizer))