文字怎么变成数字
这一章在整条主线的哪里
整门课围绕一条流水线:从人写的一句话,到「下一个 token 是谁」的概率。C1 是这条流水线的入口——把文字变成模型能算的数。本章结束时,一句话变成了一个 [T × 5120] 的矩阵(T 是 token 数,5,120 是锚点模型 Qwen3.8-27B 的宽度)。
贯穿本章的例句是「小明把苹果给了小王」(后面各章沿用),再加上对话样本里的「我喜欢机器学习」。用锚点模型的真分词器走一遍:「小明把苹果给了小王」→ 5 个 token(小明 / 把 / 苹果 / 给了 / 小王)→ 5 个 ID(115942、96373、99203、100105、126835)→ 查表得到 [5 × 5120]。同一句话用 GPT-2 的词表要 21 个 token——这一章就是在解释这中间的每一步。
点任一已学环节,看这一步的输入 → 输出形状、参数量,以及它解决了什么问题(数字取自 Qwen/Qwen3.8-27B 真实 config)。
上游: 深度学习地基 D(可选:损失、梯度下降、反向传播)与 C0 序章(模型的任务是预测下一个 token)。旧版课程这里写的「M0 算力盘点」并不存在,新结构里以地基章代替。
本章: 文字 → 切分 → Token ID → Embedding 向量。
下游: C2 位置——查表得到的向量只由 ID 决定,「小明打了小王」和「小王打了小明」得到的是同一组向量,模型分不出谁在前。
七个问题,一个接一个被逼出来
- 一句话怎么变成数字?——先切成词表里的块,再换成行号;码点方案在「表要 1,114,112 行」上失败。
- 为什么不一字一个 Token,也不一词一个 Token?——按字切,注意力打分按 T² 涨;按词切,总有没见过的词。
- 岔路:模型为什么数不清 strawberry 里有几个 r?——问句里的「 strawberry」只是一个编号。
- BPE 怎么学会「该怎么切」?——反复合并最高频的相邻对;含我们项目分词器在三国演义上的真实合并日志。
- 岔路:为什么中文更「贵」?——字节级 BPE 永不缺字,但没学过中文合并的词表会把汉字拆成字节。
- 词表该多大?——参数和序列长度之间的交易,用五档实测来定。
- Token ID 为什么不能直接拿来算?——ID 的大小只记录合并先后;Embedding 就是查表。
- Embedding 的数字从哪来?——随机初始化,和整个模型一起被 loss 推着走。
- Embedding 维度越大越好吗?——参数按 d² 涨,宽度靠实验和预算选。
- 实验课:同一段话交给三个真分词器,token 数差多少?
演进图:把「现代做法」按被问题推动的顺序排开。每一课都会在这张图上标出自己在哪一代。
- 解决了不用训练,任何文字都能编号。卡在码点从 0 排到 1,114,111,embedding 表得开 1,114,112 行,是 Qwen3.8 实际 248,320 行的 4.5 倍;而且一个字一个 ID,序列一点不缩短。
- 解决了字符集封闭,词表小。卡在序列最长:209 字的段落按字切要做 43,681 次注意力打分,是 Qwen3.8 子词切分(106 个 token)的 3.9 倍。
- 解决了一个词一块,序列短。卡在词是开放集合:我们的语料按空白和标点切出 70,310 种块,94.8% 只出现过一次;验证集里连训练集没见过的字都有 83 种——含它们的词只能记成 UNK。
- 解决了按频次反复合并相邻对:常见的整块、罕见的拆碎,词表大小由合并次数控制。卡在初始单元是字符:要覆盖全部 Unicode,基础词表就超过 13 万(GPT-2 论文 §2.2);只收见过的字,我们的验证集照样缺 83 种字。
- 解决了初始词表就是 256 个字节,任何文字都能编码,永远不出 UNK。卡在词表没学过的语言会被切碎:同义的中文段落,GPT-2 切 461 个 token,Qwen3.8 切 106 个,差 4.3 倍;同义英文段落两者一样多。
- 解决了中文常用字串整块进词表:「我喜欢机器学习」只要 2 个 token(GPT-2 要 17 个)。各家词表:DeepSeek-V3 129,280 · Qwen3 151,936 · GLM-5.3 154,880 · Kimi K3 163,840 · Qwen3.8 248,320。卡在两头各一张 V × d 的表:2 × 248,320 × 5,120 = 2,542,796,800 个参数,占 Qwen3.8 的 9.2%;同样的词表搬到我们 d = 256 的小模型上,embedding 占 95.3%。
- 解决了ID 只是行号,取出一行 5,120 维向量;one-hot 乘矩阵与查表等价,但查表只拷现成的数。卡在表里的 1,271,398,400 个数(输入端一张表)从哪来?人填不完,也不知道每一维该是什么意思。
- 解决了用「预测下一个 token」的 loss 端到端训练,用法相近的 token 被推到相近的位置。卡在只在出现时才被推:我们的项目里一个 batch 8,192 个位置,平均只出现 1,453 个不同 token;罕见 token 学得少——MiniMax 的「嘉祺」在后训练里被推偏,输出层那一行与原来的余弦只剩 0.329。
- 解决了维度越高,每个 token 能装的信息越多。卡在参数按 d² 涨:我们的模型 d 从 128 到 256,总参数 1,066,112 → 3,688,704,val loss 4.776 → 4.641(d = 256 这一档是参照 run ref_baseline,与项目基线同配置同种子;基线记录的 4.626 与它之差是重跑波动,见 C14);没有公式能给出「最优的 d」。
三层交互: 概念动画(第 1 课「一句话的旅程」)→ 算法模拟器(第 3 课真 BPE 小语料训练、页面内用真合并表切新句子)→ 真模型 / 真数据(真 Qwen3.8 与 GPT-2 分词器的切分、我们项目的合并日志、五档词表扫描、训练中的 embedding 快照、三档宽度对比、实验课的实时分词)。
里程碑 M1:训练我们自己的分词器
对应 04-实践项目/MILESTONES.md 的「C1 文字 → 数字」。分工照旧:代码 AI 来写,决策你来做。分词器代码 minillm/tokenizer.py 已实现(字节级 BPE + GPT-2 预切分正则,测试 TestBPE 5 项);对照实验脚本 experiments/exp_c1_tokens.py 导出本章组件读取的 c1-tokens.json(字段约定写在 C1 设计卡末尾)。
你要拍板的决策
| # | 决策 | 依据(在哪一课看) |
|---|---|---|
| 1 | 词表多大:512 / 1,024 / 2,048 / 4,096 / 8,192 选一档 | 第 4 课五档实测:压缩率、embedding 占比、各档模型的 val bpb(词表不同不能比 val loss) |
| 2 | 分词器在哪些数据上训练:只用训练集,还是训练集 + 验证集?为什么验证集不能参与 | 第 3 课:词表由统计长出来——让验证集参与统计,等于提前「见过」考题,压缩率与 bpb 都会虚高 |
| 3 | 输入 embedding 与输出 lm_head 共享一张表吗(tie_embeddings) | 第 4 课:小模型里两张表占的比例;Qwen3.8 这样的大模型不共享 |
| 4 | 语料配比:只用三国演义(繁体古白话),还是混入简体现代文、英文 | 第 3 课压轴与实验课:我们的分词器切简体现代句时的碎片化程度 |
验收标准
- 编码 → 解码在完整验证集上逐字节还原:项目测试
test_project_tokenizer_roundtrip通过(原理见第 1 课的往返性质)。 - 能说出本项目的真实数字(词表 2,048 档):训练集 1,605,213 个字节(也就是合并前的 token 数)经 1,791 次合并;验证集压缩率 1.022 字/token、3.05 字节/token;合并产物里 1,059 个单字、433 个多字、299 个不完整字节;验证集里出现的 2,319 个不同汉字里 1,015 个整字是单 token;这一档训练出的模型 val bpb 为 2.194。(灰色「待实测」表示实验数据还没导出。)
- 词表对照表:五档各自的压缩率、模型参数量、val bpb,并写出选择理由。压缩率的及格线按五档实测定——旧版课程写的「中文 ≥ 1.5 字符/token」没有实测过,在 2,048 这样的小词表上很可能达不到,不再沿用。
- 冻结:选定的词表与合并表存进
04-实践项目/data/processed/,git 提交。之后不再改——原因见第 5 课的 Dig(embedding 按 ID 绑定)。
预告下一个坑(C7 数据章): 训练语料里如果混有大量重复的网页模板,BPE 会把整段模板学成 token,白白占掉词表名额。去重在 C7 讲。
本章的误解与带走清单
各课就地纠正过的误解
| 误解 | 真相 | 课 |
|---|---|---|
| Token = 一个字 / 一个词 | Qwen3.8 把「我喜欢机器学习」切成 2 块,「人工智能」整个是 1 块 | 第 1 课 |
| 中文模型就是一字一 token | Qwen3.8 7 字 2 token,GPT-2 7 字 17 token | 第 2 课 |
| 模型看得到字母 | 问句里的「 strawberry」是 1 个编号 | 岔路 B1 |
| 词表是人编的字典 | 每一项都是某一步频次合并的产物 | 第 3 课 |
| � 是 bug;一个汉字固定 3 个 token | 单个字节不是合法 UTF-8;3 个字节只是上限 | 岔路 B2 |
| 词表拍脑袋定;vocab_size = 分词器 token 数 | 要按规模与语料实测;Qwen3.8 分词器 248,077 项,补齐到 248,320 行 | 第 4 课 |
| ID 大 / ID 相近有含义 | ID 只记录合并先后 | 第 5 课 |
| 每一维是一个可解释特征;token embedding = RAG 的 embedding | 语义分布在很多维里,且坐标轴不唯一;两者输出和训练目标都不同 | 第 6 课 |
| 4,096、5,120 是算出来的最优值 | 没有定理,是预算与硬件对齐下的实验选择 | 第 7 课 |
工程上的坑(旧版 M1 坑表,数字已重算)
| 坑 | 症状 / 后果 | 怎么办 |
|---|---|---|
| 用英文向词表处理中文 | 同义中文段落 GPT-2 切 461 个 token、Qwen3.8 切 106 个,训练量、API 费用、上下文窗口都按 4.3 倍消耗 | 中文项目用中文友好的词表或自己训;先在自己的语料上测(实验课) |
| 特殊 token 漏配 | 模型不知道一段文字在哪结束,生成时停不下来 | 我们的分词器已预留一个「文本结束」特殊 token;对话模板里的特殊 token 在 C12 后训练登场 |
| 训练后换分词器 | 同一个词换了 ID,embedding 查到别的行,权重作废 | 训练前定死、存档,和模型一起发布(第 5 课) |
| 数不清 strawberry 的 r | 被当成「模型太笨」 | 这是 token 粒度问题;需要字符级操作时让模型把词拼开写(岔路 B1) |
| 词表大小拍脑袋 | d = 256 的小模型配 15 万词表,embedding 占 92.5%(旧版写「70%」,未实算) | 按模型规模与语料做五档扫描,比压缩率与 val bpb(第 4 课) |
带走清单(各课的一句话)
- 分词器先按词表把文字切成块,再把每块换成它在词表里的行号;从这一刻起,模型只看得到整数。
- 切得越细,序列越长;切得越粗,词表越大还会遇到没见过的词——子词取中间。
- BPE 从单个字节出发,每一步把最常挨在一起的一对粘成新 token;切法是统计出来的。
- 模型数的是 token,不是字母。
- 字节级 BPE 永远不缺字;中文的「贵」来自词表,不来自中文本身。
- 词表大小是在参数和序列长度之间做交易,要用实测来定。
- ID 只是行号;Embedding 就是按行号查表,one-hot 乘矩阵和查表是同一件事。
- Embedding 表从随机数开始,和整个模型一起被预测下一个 token 的 loss 训练出来。
- 维度 d 让每层参数按 d² 增长,是靠实验和预算选的超参数。
概念索引: token · Token ID · 词表 · BPE · 字节级 BPE · 预切分 · UTF-8 · 压缩率 · fertility · one-hot · Embedding · hidden size · tie_word_embeddings;预告:位置编码(C2)、特殊 token 与对话模板(C12)、数据去重(C7)。
有不明白的? 直接在对话里问。每个问题和解答会按课归档到 05-问答记录/,成为你自己的 FAQ 库;有价值的问题会展开成新的岔路课。
想往下钻,按你愿意花的时间选
下面每一条都在 2026-09-26 实际打开核对过(页面标题、作者、时长、关键数字);本地论文可直接打开。
🎮 想更直观 10–30 分钟
🔬 想吃透原理 1–3 小时
📄 想读原文 按需
我替你排除了什么(也是筛选标准): 「5 分钟搞懂 BPE」类速通视频(只给结论无推导)· CSDN、掘金上互相转载的同质化模板文 · strawberry 问题的猎奇新闻稿(停在「AI 好笨」)· 2023 年以前只讲 jieba 中文分词的旧文(那是词级切分,已被子词范式取代)· 把 embedding 讲成「每一维一个特征」的科普文(与第 6 课的纠正冲突)· 讲 RAG embedding 模型的教程(和本章的 token embedding 不是一回事)· GitHub 上各种个人 tokenizer 可视化小项目(不承诺维护,与 Tiktokenizer 同质)。
另注:李沐、李宏毅的频道整体质量很高,但没有专门讲分词或 BPE 的深度单集,所以本章没有列;他们的内容会在后面的章出现。