CS336 Lecture Notes 1

1537 字
8 分钟
CS336 Lecture Notes 1
2026-04-06
Warning

本文为本人学习相关开源课程过程中,整理的个人学习笔记及作业解答,核心目的仅用于记录个人学习轨迹、巩固所学知识、梳理学习思路,全程为个人自主学习使用,不具备任何商业用途,也不构成任何形式的课程辅导或标准答案参考。

需特别说明的是,由于本人学习进度及知识储备有限,笔记内容及作业解答中可能存在大量纰漏、思路偏差甚至错误,仅代表本人当时的学习理解,不具备权威性和准确性。

在此郑重提醒:请勿将本文中的任何作业解答复制粘贴,作为自身所修课程的提交答案。任何因抄袭本文内容导致的课程成绩问题、学术诚信问题,均由抄袭者自行承担全部责任,本人不承担任何相关连带责任。

同时,本文所分享的内容均基于开源课程的公开内容整理,尊重原课程创作者的知识产权,若涉及相关内容的版权问题,请及时联系本人,本人将第一时间进行调整或删除。

感谢各位读者的理解与支持,也欢迎大家针对笔记及解答中的问题提出宝贵建议,共同交流学习、共同进步。

Important

Tokenization#

为什么需要 Tokenization?#

原始文本通常以 Unicode 字符串表示,但语言模型需要处理的是 token 序列(通常用整数索引表示)。因此,我们需要:

  • 编码 (Encode):将字符串转换为 token 索引序列
  • 解码 (Decode):将 token 索引序列转换回字符串
# 示例
string = "Hello, 🌍! 你好!"
indices = tokenizer.encode(string) # [15496, 11, 995, 0]
reconstructed_string = tokenizer.decode(indices)

Tokenizer 的核心功能:实现 encode()decode() 方法,确保能够往返转换。

关键观察(通过 GPT-5 Tokenizer)#

通过 tiktoken 交互式工具可以观察到:

  1. 空格处理:单词及其前导空格通常属于同一个 token(如 " world"
  2. 位置敏感性:同一单词在开头和中间的表示不同(如 "hello hello"
  3. 数字处理:数字被切分成每几位一个 token

压缩比 (Compression Ratio)#

compression_ratio = get_compression_ratio(string, indices)
  • 含义:每个 token 对应的平均字节数
  • 意义:压缩比越高,序列越短(注意力机制复杂度与序列长度平方成正比)
  • 权衡:增加词汇表大小可以提高压缩比,但会导致稀疏性问题

字符级 Tokenizer (Character Tokenizer)#

原理#

Unicode 字符串是 Unicode 字符的序列,每个字符可通过 ord() 转换为码点(整数)。

assert ord("a") == 97
assert ord("🌍") == 127757
assert chr(97) == "a"

优缺点分析#

优点缺点
实现简单词汇表巨大(约 150K Unicode 字符)
可处理任意文本字符分布不均(如 🌍 很罕见)
压缩比低

问题:词汇表大 + 压缩比低 = 最差组合

字节级 Tokenizer (Byte Tokenizer)#

原理#

Unicode 字符串可以表示为字节序列(UTF-8 编码),每个字节是 0-255 的整数。

# 单字节字符
assert bytes("a", encoding="utf-8") == b"a"
# 多字节字符
assert bytes("🌍", encoding="utf-8") == b"\xf0\x9f\x8c\x8d"

优缺点分析#

优点缺点
词汇表小(256)压缩比 = 1(最差)
可处理任意文本序列过长
受 Transformer 上下文长度限制

核心问题:序列太长,导致注意力计算开销过大

词级 Tokenizer (Word Tokenizer)#

原理#

使用正则表达式将文本切分为单词。

import regex
string = "I'll say supercalifragilisticexpialidocious!"
chunks = regex.findall(r"\w+|.", string) # 分词

优缺点分析#

优点缺点
每个 token 有语义词汇表可能很大
压缩比较高稀有词难以学习
无法固定词汇表大小
未登录词需要 UNK token

BPE (Byte Pair Encoding) Tokenizer#

历史背景#

  • 1994:Philip Gage 提出用于数据压缩
  • 2015:Sennrich 等人将其引入神经机器翻译 论文
  • 2019:GPT-2 采用 BPE 论文

核心思想#

在原始文本上训练 tokenizer,构建针对数据的定制词汇表:

  • 常见字节序列 → 单个 token
  • 罕见字节序列 → 多个 token

训练算法#

def train_bpe(string: str, num_merges: int) -> BPETokenizerParams:
# 1. 从字节列表开始
indices = list(map(int, string.encode("utf-8")))
# 2. 初始化合并规则和词汇表
merges: dict[tuple[int, int], int] = {} # (idx1, idx2) -> merged_idx
vocab: dict[int, bytes] = {x: bytes([x]) for x in range(256)}
for i in range(num_merges):
# 3. 统计相邻 token 对的出现频率
counts = count_adjacent_pairs(indices)
# 4. 找到最常见的相邻对
pair = max(counts, key=counts.get)
# 5. 合并该对
new_index = 256 + i
merges[pair] = new_index
vocab[new_index] = vocab[pair[0]] + vocab[pair[1]]
# 6. 更新序列
indices = merge(indices, pair, new_index)
return BPETokenizerParams(vocab=vocab, merges=merges)

统计相邻对的辅助函数#

def count_adjacent_pairs(indices: list[int]) -> dict[tuple[int, int], int]:
"""返回每个相邻 token 对的出现次数"""
counts = defaultdict(int)
for index1, index2 in zip(indices, indices[1:]):
counts[(index1, index2)] += 1
return counts

Tokenizer 方法对比#

方法词汇表大小压缩比优点缺点
Character~150K简单、通用词汇表大、序列长
Byte2561 (最差)词汇表小、通用序列太长
Word很大有语义稀有词问题、UNK
BPE可控较高平衡、自适应需要训练

Assignment 1 扩展方向#

基础实现之外,作业要求:

  1. 优化编码效率:只遍历相关的合并规则,而非全部
  2. 特殊 token 处理:检测并保留如 <|endoftext|> 等特殊 token
  3. 预分词 (Pre-tokenization):使用 GPT-2 的正则表达式预分词
  4. 性能优化:尽可能提升实现速度

关键要点总结#

  1. Tokenization 是连接文本和模型的桥梁

    • 编码:文本 → token 索引
    • 解码:token 索引 → 文本
  2. 设计权衡

    • 词汇表大小 vs. 压缩比
    • 通用性 vs. 效率
    • 语义完整性 vs. 稀疏性
  3. BPE 是现代 LLM 的主流选择

    • 从字节开始,自底向上构建
    • 自动适应数据分布
    • 平衡词汇表大小和压缩效率
  4. 压缩比的重要性

    • 序列长度影响注意力计算复杂度(O(n²))
    • 更高的压缩比 → 更短的序列 → 更快的训练/推理

参考资料#

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
CS336 Lecture Notes 1
https://llm-tech.com.cn/posts/cs336-lec-notes-1/
作者
Ming
发布于
2026-04-06
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Ming
你是来找 Ming 学习的吗
🎉 欢迎来到 Ming 的博客
这里是我的个人博客,分享 AI Infra、LLM 等技术内容。欢迎关注交流!
分类
标签
站点统计
文章
8
分类
6
标签
8
总字数
11,954
运行时长
0
最后活动
0 天前

目录