多语言 Tokenizer 的训练挑战:100 种语言怎么共享一个词表?

想象一个场景:你训练了一个支持100种语言的多语言模型,拿一句英文测试,效果不错;换成荷兰语,也还行;但当你丢进去一句阿姆哈拉语,输出可能直接变成一坨[UNK]。大多数人这时会骂模型不行,但我必须先泼一盆冷水:很多时候,模型还没有开口,分词器已经把这句话毁掉了。

low-angle photography of building interior

分词器(Tokenizer)是模型入口处的第一道关卡,它负责把人类语言切成模型能处理的碎片。多语言模型更特殊,它要把100种语言塞进同一个词表里。这就像让一个厨师同时做川菜、粤菜和法餐,但只给他一口锅。今天我就想聊聊这口锅里发生的事情。

BPE:把高频字符对焊在一起

先说说最常用的分词算法,BPE(Byte Pair Encoding)。它的核心逻辑其实特别简单:从每个字符开始,反复合并出现最频繁的相邻字符对,直到词表达到你想要的大小。

  1. 把训练语料拆成最小单元(比如Unicode字符)。
  2. 统计相邻字符对出现的次数。
  3. 挑出现次数最多的一对,合并成一个新子词。
  4. 重复第2、3步,直到词表数量达标。

说得有画面感一点:BPE是个“贪心焊接工”,哪里出现得多,他就把哪里焊死。比如英语里“th”和“e”极其常见,BPE会先焊出“the”,之后“the”在不同句子里反复出现,它就变成一个稳定整体。这一招很有效,论文《Neural Machine Translation of Rare Words with Subword Units》最早把它引入NMT,后来成了几乎所有大模型的分词标配。

英语凭什么多吃多占?

问题来了。当你把100种语言的语料简单地倒进同一个大池子,BPE会看到什么?它看到的是一片英语的海洋。英语语料在互联网上的总量远超其他语言,“the”“ing”“tion”这些组合出现的频率自然遥遥领先。BPE不在乎语言,它只认频率,于是词表空间被英语子词迅速占领。等到冷门语言的词进来时,词表已经满员了,很多词只能被拆成碎片,甚至直接变成[UNK]。

学界早意识到这个问题。XLM-R论文在训练时引入了温度采样:不按语料的原始分布采样,而是给每个语言一个采样权重,控制高资源和低资源语言出现的比例。论文里设置温度T=5,T越大采样分布越均匀,低资源语言因此能获得更多出场机会。这是一个聪明的补丁,但它并没有完全解决问题。

因为BPE的合并统计发生在一个巨大的全局语料上,真正决定一个子词能否焊牢的,是它在全局里出现的总次数。温度采样能让低资源语言的数据被多采样一些,但如果这种语言本身的形态复杂、原始语料又少,它在全局统计中的贡献依然有限。我见过一个例子:泰语中一个很常见的词,在XLM-R的tokenizer里被拆成五六个片段,而对应的英语词只需要一个token。一个词被切得越碎,模型理解它的难度就越大。

我自己也踩过这个坑。有一次,我拿一个多语言模型做泰语到英语的翻译,发现泰语的平均翻译质量比英语翻译成法语差一大截。我最初以为训练数据不足,后来把泰语分词结果打出来,才意识到一句话被切得稀碎,许多有意义的音节被硬生生拆开。那一刻我才想通:模型的表现上限,从分词那一刻就被锁死了。

分词标准不统一,才是最大的暗坑

语言之间不只是数据量不同,连“词”的定义都不一样。英语按空格分,中文按字或词分,泰语干脆把连续辅音字母写在一起,没有空格。日语还在汉字和平假名之间来回切换。SentencePiece这类工具支持直接在原始文本上做无监督切分,不依赖空格,这解决了“切分依赖”的问题,但类似“computer”和“计算机”之间没有任何共享子词,模型想对齐它们,只能靠下游层的语义学习。

另一个尴尬来自共享字符的“语义冲突”。日语和中文都使用汉字,但同一个字含义可能完全不同。比如中文的“手”是手,日文的“手”有时候是手法或工具。词表里只有一个“手”的embedding,它必须同时讨好两种语言,这种拉锯不可避免。

换句话说,共享词表给模型提供的是一个“公用工具箱”,但工具箱里每一种语言能用的工具多少、大小合适程度,完全不一样。

字节级方案:覆盖率满分,效率不及格

为了彻底消灭[UNK],另一个思路是放弃字符,直接拿UTF-8的字节当原子。GPT-2、LLaMA等模型用的字节级BPE(BBPE)就是这样:任意语言的任意字符都一定能被表达,因为UTF-8会为每个字符生成字节序列。但代价是序列长度变长,一个汉字可能要占2到3个token,训练和推理的算力开销也随之增大。

ByT5论文做得更极端:干脆丢弃词表,直接用字节序列做预训练。这样做的优点是极强的字符覆盖能力,缺点是序列长度暴涨,导致在相同计算量下效果不如标准模型。

方案 原子单位 覆盖率 序列长度 代价
WordPiece Unicode字符+子词 罕见字符会[UNK]
SentencePiece Unicode码点+子词 较广 语言不均衡
BBPE UTF-8字节 全覆盖 偏长 训练成本增加
ByT5 纯字节 全覆盖 很长 计算量巨大

你可能会问,那为什么不直接用更大的词表来兼顾所有语言呢?

词表越大,模型就越胖?

词表大小直接决定Embedding层的参数数量。假如模型隐藏层是768维,词表从10万扩到25万,那Embedding层就多出1.15亿参数。这是一个不可忽视的成本,尤其对多语言模型来说,它的词表已经被迫塞进了太多东西。mT5论文用了250k词表,XLM-R用100k,其实都是在“覆盖率”和“算力”之间妥协。

如何判断你的tokenizer是不是病根?

如果下次你发现某个语言效果特别差,不要急着归咎于模型。先去打印这个语言的tokenizer输出,看看一句话被切成了什么样的子词序列。如果很多常见词被切得七零八落,或者大量出现[UNK],那就说明问题早已在入口处埋下。

回到标题里的问题:100种语言共享一个词表,真的可行吗?我的答案是,可以,但注定不完美。共享词表是一种非常现实的工程妥协,它让模型用一套参数处理所有语言,同时也就意味着每一种语言都在为这种统一性付出代价。对高资源语言,代价几乎可以忽略;对低资源语言,代价经常是语义的严重磨损。

未来可能会有更聪明的办法,比如把词表拆成共享部分和语言特定部分,或者用动态扩展的字节级模型。但至少在今天,如果你发现多语言模型在某些语言上“不聪明”,先检查一下tokenizer的分词结果。很多时候,模型的笨是被分词器逼出来的。

原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/794.html

(0)
上一篇 3小时前
下一篇 2小时前

相关推荐