这一节全部真跑:手写一个字符级 BPE,在三种语言上各训一张词表,看「拿谁的词表切谁」的压缩率; 再实测温度采样;最后用两种方式量困惑度。 结论一句话:数据预处理决定能不能学,词表和采样决定学得好不好,训练任务决定怎么量。
上一节讲「料够不够」,这一节讲「料到了之后怎么办」。 分词与 BPE 的通用原理在别的课里零散讲过; 这一节只讲多语言下词表名额怎么分,以及为什么「统一词表」这件事本身就是一次资源分配。
一、数据预处理:每一步都可能误杀长尾
第一步是语言识别。给一段文本判它是什么语言,模型是现成的分类器,但它在小语种上的准确率本身就低。 判错的后果很严重:一段斯瓦希里语被判成祖鲁语,就进了错误的桶,从此再也不会被这个语言的训练用到。 第二步是质量过滤。 常见做法是用一个「像不像百科」的打分器筛,而这类打分器是在高资源语料上训的, 它认为「不像」的,往往只是「不像英语」。
一个具体的坑:很多小语种的语料是宗教或法律文本,句式古老。 质量过滤会把它们当低质砍掉,于是唯一成规模的语料也没了。 第三步是去重。跨语言去重时,直译或平行语料会被误判成重复,而平行语料恰恰是最值钱的那种。 所以多语言的数据管线要多一道人工检查:每加一条过滤规则,都要分语言看它的通过率。总通过率正常、 某个语言全被砍掉,这种事故并不少见。
二、Tokenizer:词表是长出来的,不是分出来的
演示里手写了一个字符级 BPE:反复统计相邻两个 token 一起出现的次数, 把最高频的那一对合并成新 token,重复一百五十次。 这个过程不看语言,只看频次。 所以谁的字组重复得多,谁就占名额——词表倾斜不是谁规定的,是它自己长出来的。 三种语言各训一张词表,再拿三张词表互相切。对角线是「用自己的词表切自己」,压缩率 2.07 到 2.39。
非对角线就惨了:拿中文词表切英语,压缩率 1.00——等于逐字符切,一个 token 只装一个字符。 有一个细节值得看:拿英语词表切斯瓦希里语是 1.08,比切中文的 1.00 高一点。 因为两者共用拉丁字母,有些字母组合能命中。 但也就高这么一点。 字母一样,词不一样——斯瓦希里的词缀(ki-、ku-、wa-)英语里一个都没有,靠字母表救不了。
三语混合训出来的词表是 261 个 token,比任何单语词表都大。 它对英语几乎无损,对中文却要付 1.59 倍——因为混合语料里中文的字符数最少,分到的名额也最少。
三、采样策略:改 α 就是改签筒里的签数
上一节算的是理论比例,这一节真抽。按语料量的 α 次方加权,抽四千次,统计各语言实际落到的比例。 实测和理论对得上:α 等于 1 时英语八成,α 等于 0.3 时英语三成三、僧伽罗语从 0.05% 涨到 2.5%。 这说明温度采样没什么玄机,就是按权重抽签。落地时要盯的是另一件事:抽签的粒度和位置。
按文档抽和按句子抽,效果不一样。按文档抽,一篇长文档整个进来,同一语言连着出现很多条; 按句子抽,一个 batch 里语言混得更匀。 还有一种做法是「按 batch 配额」:直接规定每个 batch 里各语言占多少条,不靠概率。 它比采样更硬,也更容易在小 batch 上失衡。 三种都能用。 判断标准是你的 batch 有多大:batch 越大,概率采样越接近配额;batch 小到几十条,就得显式配额。
四、训练任务:困惑度有两种量法,别混着比
量多语言效果最便宜的指标是困惑度:给一段没见过的文本,看模型有多「意外」。越低越熟。 但演示里出现了一个必须解释的现象:三语混训之后,英语 46.5、斯瓦希里语 59.6, 中文却是 104.8——中文最差。 这个数不能直接下结论。字符级模型对汉字天然不利:拉丁字母只有二十几个,到处都是重复; 汉字几乎字字不同,三阶统计几乎全靠平滑兜底。
所以跨语言比困惑度有个前提:要么同文字体系,要么换成 token 级再比。 不同书写方式之间的困惑度,是两把不同刻度的尺子。 干净的对照是「同一语言换配比」:同一个斯瓦希里语测试句,在语料里占 9% 时困惑度 59.6, 占 38% 时降到 34.0。 这个对照没有文字体系混进来,只改了份量。它才是「语料占比影响效果」的直接证据。
五、以 Llama 2 为例:一份能照做的清单
Llama 2 的词表是三万二千,语料以英语为主,官方文档也明说非英语能力有限。 所以拿它做中文或小语种,顺序是有讲究的。 第一步,先量不改。拿目标语言的一段文本,数它的 token 数,跟同义英语比。 膨胀超过 1.5 倍,说明词表值得动;不到 1.2 倍,别折腾词表。 第二步,扩词表。把目标语言的高频字组加进去,词表大小涨几千。注意嵌入矩阵和输出层要一起扩。
第三步,重训嵌入。 新加的 token 是随机初始化的,得让它们先单独训一阵,或者用一个较小的学习率带着原模型一起训, 避免把老能力冲掉。 第四步,继续预训练。混一部分原始英语语料,比例别低于三成,否则英语能力会退化。 目标语言的占比靠温度采样抬,不靠一股脑倒进去。 第五步,评测用原生题。别用从英语翻过来的题,那测的是翻译能力,不是这种语言的能力。
这五步里最容易跳的是第一步。不量就扩词表,等于不知道自己在修什么。
六、动手 Demo:真训一个 BPE,三种语言各训一张词表
三种语言各八句技术规范,句子一一对应。 手写字符级 BPE,合并一百五十次,得到四张词表:三张单语、一张三语混合。 然后拿四张词表互相切,看压缩率矩阵;再真抽四千次验证温度采样; 最后用两种方式量困惑度,并且说明哪一种才干净。
def train_bpe(sents, merges=150):
toks = [list(s) for s in sents]
for _ in range(merges):
pairs = Counter()
for t in toks:
for i in range(len(t) - 1):
pairs[(t[i], t[i + 1])] += 1
best = pairs.most_common(1)[0][0] # 最高频的相邻对
toks = merge(toks, best) # 合并成一个新 token
return vocab
实跑输出:
--------------------------------------------------------------------
真训一个 BPE,三种语言各训一张词表
--------------------------------------------------------------------
① 语料:三种语言各八句
--------------------------------------------------------------------
| 语言 | 句数 | 字符数 |
|---|---:|---:|
| 中文 | 8 | 116 |
| 英语 | 8 | 444 |
| 斯瓦希里语 | 8 | 420 |
内容是同一份技术规范的三种语言版本,句子一一对应。
! 斯瓦希里语和英语共用拉丁字母——共享字母表不等于共享词表,它的词缀(ki-、ku-、
wa-)英语里一个都没有。
② 训三张词表:合并 150 次
--------------------------------------------------------------------
| 词表 | token 数 |
|---|---:|
| 中文 | 171 |
| 英语 | 182 |
| 斯瓦希里语 | 186 |
| 三语混合 | 261 |
BPE 不看语言,只看频次。同一份语料里谁的字组重复得多,谁就占名额。
③ 压缩率矩阵:拿谁的词表去切谁
--------------------------------------------------------------------
| 词表来源 | 切中文 | 切英语 | 切斯瓦希里语 |
|---|---:|---:|---:|
| 用中文词表 | 2.07 | 1.00 | 1.00 |
| 用英语词表 | 1.00 | 2.32 | 1.08 |
| 用斯瓦希里语词表 | 1.00 | 1.12 | 2.39 |
每格是「每 token 装几个字符」,越大越省。对角线是各语言用自己词表的成绩。
④ 膨胀倍率:跨语言借用词表的代价
--------------------------------------------------------------------
| 语言 | 混合词表 | 英语词表 |
|---|---:|---:|
| 中文 | 1.59 | 2.07 |
| 英语 | 0.97 | 1.00 |
| 斯瓦希里语 | 1.01 | 2.21 |
! 混合词表对英语几乎无损,对斯瓦希里语也差不多,
中文却涨到 1.59 倍——因为三种语言里中文的字符数最少(116 个,英语 444 个),分到的名额最少。
! 语料少,词表就小;词表小,token 就多;token 多,同样的窗口装得更少。
这条链子从小语种一直通到账单。
! 用英语词表切斯瓦希里语,token 数涨到 2.21 倍。
斯瓦希里语虽然用同样的字母,照样吃亏——字母一样,词不一样。
⑤ 温度采样实测:真抽 4000 次
--------------------------------------------------------------------
| 语言 | α=1.0 实测 | α=0.3 实测 |
|---|---:|---:|
| 英语 | 80.60% | 33.15% |
| 中文 | 12.78% | 19.12% |
| 日语 | 2.97% | 12.22% |
| 阿拉伯语 | 1.73% | 11.38% |
| 越南语 | 1.12% | 9.98% |
| 泰语 | 0.68% | 7.67% |
| 斯瓦希里语 | 0.07% | 3.98% |
| 僧伽罗语 | 0.05% | 2.50% |
4000 次抽样,跟上一节算出来的理论值对得上。
! 这说明温度采样不是玄学,就是按权重抽签。改 α 就是在改签筒里的签数。
⑥ 训练任务:三语混训后的困惑度
--------------------------------------------------------------------
| 语言 | 训练占比 | 测试句字符 | 困惑度 |
|---|---:|---:|---:|
| 中文 | 29% | 14 | 104.8 |
| 英语 | 61% | 55 | 46.5 |
| 斯瓦希里语 | 9% | 63 | 59.6 |
英语占六成、中文三成、斯瓦希里语一成。错得越少,困惑度越低。
! 英语最低,斯瓦希里语次之,中文最高。
但中文这个数不能直接跟英语比——字符级模型对汉字天然不利:拉丁字母只有 26 个,到处都是重复;
汉字几乎字字不同,3-gram 几乎全靠平滑兜底。
! 所以跨语言比困惑度有个前提:要么同文字体系,要么换成 token 级再比。
不同书写方式之间的困惑度,是两把不同刻度的尺子。
⑦ 同一语言换配比:这才是干净的对照
--------------------------------------------------------------------
| 斯瓦希里语重复 | 训练占比 | 困惑度 |
|---|---:|---:|
| 1 份 | 9% | 59.6 |
| 6 份 | 38% | 34.0 |
! 同一语言、同一个测试句,只改它在语料里的份量,困惑度从 59.6 降到 34.0。
这才是「语料占比影响效果」的干净证据——没有文字体系混进来。
! 推论很直白:想让某语言变好,最有效的动作是给它加语料。
词表、采样、模型结构都是在这之后才有意义的优化。
①三种语言八句,字符数差得远:中文 116,英语 444,斯瓦希里语 420。同一份内容,中文用字最少。 斯瓦希里语和英语共用拉丁字母,看着亲近。但共享字母表不等于共享词表,它的词缀英语里一个都没有。 ②四张词表都是合并一百五十次的结果。单语词表一百七十到一百九十个 token,混合词表 261 个。 注意 BPE 不看语言,只看频次。 同一份语料里谁的字组重复得多,谁就占名额——词表倾斜是自己长出来的,不是谁规定的。 ③这张矩阵是全节的核心。对角线 2.07 到 2.39,是各语言用自己词表的成绩。 非对角线几乎全是 1.00,也就是逐字符切。 唯一的例外是英语词表切斯瓦希里语 1.08、斯瓦希里语词表切英语 1.12——共享字母,能蹭到一点。 但也就一点。所以「都是拉丁字母,词表应该能通用」这个直觉是错的。 ④换成倍率看更清楚:混合词表对英语 0.97(几乎无损),斯瓦希里语 1.01,中文 1.59。 中文为什么最吃亏?因为混合语料里中文只有 116 个字符,英语 444 个,名额按频次分,中文自然拿得少。 语料少,词表就小;词表小,token 就多;token 多,同样的窗口装得更少。 这条链子从小语种一直通到账单。 ⑤温度采样那一格是实测,跟上一节的理论值对得上。 α=1 时英语 80.6%,α=0.3 时 33.2%,僧伽罗语从 0.05% 涨到 2.5%。 这说明它不玄,就是按权重抽签。改 α 就是在改签筒里的签数。 ⑥困惑度这张表要看仔细:英语 46.5 最低,斯瓦希里语 59.6,中文 104.8 最高。 中文最高,一半是语料只占三成,另一半是字符级模型对汉字天然不利——拉丁字母反复出现, 汉字几乎字字不同。 所以这个数不能直接跨文字体系比。要么同文字体系,要么换 token 级。 ⑦这才是干净的对照:同一语言、同一个测试句,只改它在语料里的份量。 斯瓦希里语占 9% 时困惑度 59.6,占 38% 时降到 34.0。没有文字体系混进来,只改了份量,结果就清楚。 推论很直白:想让某语言变好,最有效的动作是给它加语料。词表、采样、模型结构都排在这之后。
30 秒实验
- 把 BPE 的合并次数从 150 改成 40,重跑第 ③ 步。对角线会掉到多少?非对角线还是 1.00 吗?
- 在三语混合语料里让中文重复三次,重新训一张混合词表,重跑第 ④ 步。中文的 1.59 会降到多少?
- 把困惑度的阶数从 3 改成 2(只看相邻两字),重跑第 ⑥ 步。中文那个 104.8 会变大还是变小? 这说明什么?
常见误区
- 用一份「通用清洗脚本」洗所有语言。 规则是按高资源语料调的,跑在小语种上会把它整个砍掉, 而且不报错——总通过率看着正常,某个语言已经空了。
- 以为拉丁字母的语言之间词表可以通用。演示里 1.08 就是答案:能蹭一点,但离「够用」差得远。
- 跨文字体系比困惑度。中文 104.8 对英语 46.5,看着像中文差一倍,实际是字符级模型对汉字不利。 换个量法结论就变了。
- 扩词表之后不重训嵌入,直接继续预训练。 新 token 是随机初始化的,跟老词不在一个空间里,训出来的效果比不扩还差。
- 继续预训练时一股脑只喂目标语言。 英语能力退化之后,指令跟随、推理这些能力会一起掉——它们是拿英语语料学来的。
自测三问(点开对答案)
1. 为什么混合词表对中文的损失比对英语大得多? —— 因为词表名额按频次分,而混合语料里中文的字符数只有英语的四分之一。 名额少了,中文字组覆盖不全,只能退回逐字切,token 数就涨了。 2. 跨语言比困惑度的前提是什么? —— 要么两种语言同文字体系(比如英语和斯瓦希里语),要么把度量换成 token 级。 字符级下,拉丁字母与汉字的两把尺子刻度不一样,直接比会得出错误结论。 3. 为什么「同一语言换配比」的困惑度对照更干净? —— 因为它只动了一个变量:这种语言在语料里的份量。 语言本身、文字体系、测试句全都没变,所以困惑度的变化只能归因于份量。技术讲完了,最后一节换到生意视角:三个真实场景,各算一笔账。
- ☐ (本篇完,下一篇 7.4 多语言大模型的工业应用)
相关笔记
- 资源与评测:语料分布与温度采样
- 研究现状:词表为什么倾斜
- 文档问答评估:切块与粒度取舍
- 🦙 Llama 大模型实践 · 索引