05

Unicode——计算机怎么存一个汉字

阅读约 6 分钟第 5/60 课

上一个单元结尾留下了一个问题:模型只能处理数字,文本是怎么「进来」的?

这个问题需要从更底层说起。不只是模型——整台计算机都只能处理数字。屏幕上显示的每一个字符,无论是英文字母 A、汉字「你」、日文假名「あ」还是一个 emoji 🌸,在计算机内部都是一个数字。计算机不认识字——它只认识数字,然后按照某种约定把数字显示成字。

这个「约定」就是编码。

最早的约定:ASCII

1963 年,美国制定了一套编码标准,叫 ASCII(美国信息交换标准代码)。它规定了 128 个字符各自对应哪个数字:

'A' → 65
'B' → 66
'a' → 97
'0' → 48
' ' → 32(空格)
'\n' → 10(换行)

128 个位置够用吗?对英文来说够了:26 个大写字母、26 个小写字母、10 个数字、一些标点符号和控制字符(比如换行、退格),加起来刚好装得下。

但对世界上其他语言来说,128 个位置远远不够。

编码的混乱年代

中文有几万个常用汉字。日文有假名和汉字。韩文有谚文。阿拉伯文从右往左写。这些都不可能塞进 128 个位置。

于是各个国家和地区各自制定了自己的编码:中国大陆用 GB2312(后来扩展成 GBK、GB18030),台湾用 Big5,日本用 Shift_JIS,韩国用 EUC-KR。

每一套编码都给自己语言的字符分配了数字。问题是:同一个数字在不同编码里代表不同的字。一段用 GBK 编码的中文文本,如果用 Shift_JIS 去解读,就会变成一堆乱码——因为两套编码对同一组数字的解释完全不同。

你可能见过这种乱码——网页上本该是中文的地方显示成一串问号或奇怪的符号。这往往就是编码不匹配导致的。

Unicode:一张全球统一的表

1991 年,Unicode 联盟发布了一个野心很大的标准:给世界上所有文字系统的每一个字符分配一个唯一的编号。

这个编号叫做码点(code point),写成 U+ 开头的十六进制数:

'A'  → U+0041
'你' → U+4F60
'好' → U+597D
'あ' → U+3042
'α'  → U+03B1
'🌸' → U+1F338

截至 2024 年,Unicode 收录了超过 15 万个字符,涵盖了现存的和已经消亡的文字系统——从拉丁字母到甲骨文,从数学符号到 emoji。每一个字符都有且只有一个码点,全世界通用,不会冲突。

UTF-8:怎么把码点存成字节

Unicode 解决了「每个字符对应哪个数字」的问题,但还剩一个问题:这些数字在计算机里怎么存?

计算机存储的基本单位是字节(byte),一个字节是 8 个比特(bit),能表示 0 到 255 之间的数字。ASCII 的字符编号都在 0-127 之间,一个字节就够了。但 Unicode 的码点范围从 0 到超过 10 万,一个字节存不下。

UTF-8 是目前最通用的 Unicode 存储方案。它的巧妙之处在于:用变长编码——简单的字符用少的字节,复杂的字符用多的字节:

U+0000 到 U+007F  → 1 个字节(和 ASCII 完全一致)
U+0080 到 U+07FF  → 2 个字节
U+0800 到 U+FFFF  → 3 个字节
U+10000 到 U+10FFFF → 4 个字节

英文字母、数字、基本标点都在第一个范围内,所以一个英文字母在 UTF-8 里只占 1 个字节。而中文的常用汉字码点基本在 U+4E00 到 U+9FFF 之间,落在第三个范围,所以一个汉字在 UTF-8 里占 3 个字节。

这意味着什么?存储同样长度的文本,中文比英文占的空间大约是三倍。一个英文单词 “hello” 占 5 个字节,而「你好」两个字占 6 个字节——两个汉字比五个英文字母还多一个字节。

这个差异在训练语言模型时会成为一个实际问题——下一章节会看到。

从编码到分词

回到我们的核心问题:文本怎么变成模型能处理的数字。

现在你知道了,每个字符在计算机里已经是数字(Unicode 码点)。那能不能直接把这些码点喂给模型?

理论上可以。「你好世界」四个字的码点分别是 20320、22909、19990、30028,你可以直接把这四个数字作为输入。

但这样做有两个大问题。

第一,步骤太多。 模型每次只预测一个单位。如果一个字符就是一个单位,那生成一段 500 字的回复需要预测 500 步。而如果把常见的词组(比如「机器学习」)合并成一个单位,500 字的回复可能只需要 300 步——速度快了将近一倍。

第二,单个字符缺少语义。 英文字母 t、h、e 各自没有什么意义,但 “the” 作为一个整体是一个完整的词。如果模型每次只看一个字母,它需要花更多的层来「拼读」出词义。如果直接给它 “the” 作为一个单位,它就可以把计算资源花在更高层的理解上。

中文在这方面稍好一些——每个汉字本身已经有含义(「机」「器」「学」「习」各自有义)。但即便如此,「机器学习」四个字合在一起是一个固定搭配,把它作为一个整体处理仍然比拆成四步更高效。

所以,直接用字符编码作为模型输入不是最好的方案。我们需要一种介于「字符」和「词」之间的切分方式——这就是 tokenizer(分词器)要做的事情。

下一章节来看为什么「按字切」和「按词切」各有什么问题,以及 tokenizer 怎么在两者之间找到平衡。