在大语言模型处理任何文本之前,都需要先将人类的语言转化为机器能理解的数字序列——这个过程就是 Tokenization(分词)。理解它,是理解整个 LLM 技术栈的起点。
引言
当你在 ChatGPT 的对话框中输入"今天天气真好"并按下回车时,模型看到的并不是这几个汉字,而是一串数字,比如[38392, 45668, 8997, 1325]。这个从自然语言到数字序列的转换过程,就是Tokenization(分词)。
分词看似简单,实际上它的设计直接影响模型的词汇量、上下文理解能力、对多语言和罕见词的处理效果,甚至影响推理速度和训练成本。可以说,分词是整个 NLP 系统设计中影响最深远的决策之一。
本文将从基本概念出发,逐步介绍分词的原理、流程和常见方法,帮助你建立对 Tokenization 的系统性理解。
一、什么是 Tokenization
Tokenization(分词)是将文本拆分为更小单元的过程,这些单元称为词元(Token)。词元可以是单词、子词或字符,分词使大语言模型能够以结构化且高效的方式处理语言输入,是 NLP 和 LLM 训练中的基础步骤。
需要注意的是,在深度学习模型中,词元不一定等同于自然语言中的"单词"。例如,"unhappiness"可能被拆分为["un", "happi", "ness"]三个词元,而不是作为一个完整的词处理。
分词不仅仅是将文本拆开,它更像是为计算机构建语言的"理解入口"。模型并不像人类一样能够自然地识别词语和语义边界,因此必须提前将文本切分成便于处理的基本单位。这一过程需要以尽可能保留语义结构和上下文关联的方式来组织数据,从而使模型更准确地理解句子的含义。
1.1 词元(Token)和嵌入(Embedding)
词元和嵌入是理解 LLM 工作原理的两个核心概念。语言模型处理文本时,首先将其切分成词元,然后将每个词元转换为数值表示—即嵌入向量(Embedding)。嵌入向量是一个多维数值向量,能够捕捉词元的含义和上下文关系。
如下图所示,每个词被映射到向量空间中的一个点,语义相近的词在空间中的距离也更近。如果没有对词元和嵌入的深入理解,就无法清楚地了解 LLM 的工作原理、构建方式及其未来的发展方向。
1.2 为什么需要 Tokenization
为什么不能直接把原始文本交给模型处理?主要有两个原因:
统一输入格式
LLM 对固定大小的输入进行操作,但自然语言输入的长度是动态的。分词将文本分解为固定格式的词元序列,使其成为模型可以处理的标准化输入。
降低词汇维度
如果以完整的单词作为最小单位,词汇表将非常庞大(英语中有数十万个单词)。通过使用子词分词,模型可以用一个更小的词汇表来表示几乎所有文本,大幅减少了参数量和计算成本。
此外,不同的分词方式会直接影响模型的表现:
| 影响维度 | 说明 |
|---|---|
| 语义理解能力 | 分词粒度决定了模型能否识别词义的完整性 |
| 罕见词处理 | 子词分词可以将未见过的词拆解为已知的子词 |
| 序列长度与计算效率 | 粒度越细,序列越长,计算成本越高 |
| 模型泛化能力 | 合理的分词策略有助于模型在不同领域间迁移 |
二、Tokenization的完整流程
从原始文本到模型输入,Tokenization 包含以下几个步骤:
文本标准化(Text Normalization):对文本进行预处理以确保一致性,包括转小写、移除标点、Unicode 归一化等。
分词(Tokenization):将标准化文本切分成词元,文本被切分成词向量。例如,
"the dog sat on the couch"可以被分为["the", "dog", "sat", "on", "the", "couch"]。数值编码(Numericalization):每个词元被映射到词汇表中的唯一索引,转换为模型可以处理的数字序列。词元 → Token ID(查词表映射为整数索引)。
嵌入(Embedding):Token ID → 稠密向量,通过 Embedding 层将离散 ID 映射为连续的高维向量
- 在实际操作中,还需要事先完成以下准备工作:
- 选择分词方法:常用方法包括字节对编码(BPE,GPT 系列使用)和 WordPiece(BERT 使用)。
- 设置分词器参数:包括词汇量大小(模型可识别的唯一词元数)以及特殊词元(如句子开头/结尾标记)。
- 在目标数据上训练分词器:分词器应在与推理场景匹配的数据集上训练。在英语文本上训练的分词器,其词汇表与在代码或多语言数据集上训练的分词器会有很大差异。
2.1 文本标准化(Text Normalization)
当我们处理自然语言文本时,看似相似的句子,对计算机而言却可能完全不同。例如:
Dusk fell i was gazing at the sao paulo skyline isnt urban life vibrant dusk fell i gazed at the sao paulo skyline isnt urban life vibrant 尽管人类可以轻松理解这两句话表达的是同一意思,但以下细微差别都会改变文本的表示方式:
- 大写差异:
"Dusk"vs."dusk" - 标点差异:逗号、分号、问号的有无
- 缩写差异:
"Isnt"vs."Isn't" - 特殊字符:
"Sao Paulo"vs."São Paulo"
文本标准化的目标是减少这些无关的文本变异性,使文本在进入模型前变得一致且可控。它是一种典型的特征工程操作:通过消除噪声,强化对模型真正有意义的信息。
(1) 基础标准化方法
基础的文本标准化方法包括:
全部转为小写(Lowercasing):减少大小写导致的重复项。例如Apple与apple被视为同一词。
移除标点符号(Removing Punctuation):对某些任务,标点不影响语义;移除它们可降低干扰。
归一化特殊字符(Unicode Normalization):如将
"ã"→"a",降低编码差异带来的影响。自然语言中存在大量 Unicode 变体:
| 原字符 | Unicode 标准化后 | ASCII |
|---|---|---|
| “São” | “São” (a + ~) | “Sao” |
| “café” | “café” (e + ´) | “cafe” |
(2) Python实现
主要经过 lower → NFKD + ASCII → 去标点 → 压缩空格 四步处理,最终输出干净的文本,供后续分词使用。
转小写:
text=text.lower()“Dusk” → “dusk”,“São” → “são”。消除大小写差异。
Unicode 归一化 + ASCII 转换
text=unicodedata.normalize('NFKD',text)text=text.encode('ascii','ignore').decode('utf-8') 首先,
normalize('NFKD', text)— 将字符分解为基字符 + 组合符号。例如 ã 被分解为 a + (组合波浪号),é 被分解为 e +(组合锐音符)。 之后,
.encode('ascii', 'ignore')— 编码为 ASCII,ignore 参数表示丢弃所有非 ASCII 字符(组合符号会被丢掉)。 最后,
.decode('utf-8')— 再解码回字符串。 效果:“são paulo” → “sao paulo”,“café” → “cafe”
除标点
text = re.sub(r'[^\w\s]', '', text)正则含义:
\w— 匹配字母、数字、下划线\s— 匹配空白字符[^\w\s]— 匹配既不是字母数字也不是空白的字符,即标点符号替换为空字符串
'',等于删除效果:
"isn't urban life vibrant??" → "isnt urban life vibrant"
压缩空格
text=re.sub(r'\s+',' ',text).strip()\s+— 匹配一个或多个连续空白字符替换为单个空格
' '.strip()— 去除首尾空格防止前面的操作(如移除标点)留下多余空格。
完整代码:
importreimportunicodedataimportlogging# 配置 logginglogging.basicConfig(level=logging.INFO,format="%(asctime)s - %(levelname)s - %(message)s")logger=logging.getLogger(__name__)defstandardize_text(text:str)->str:""" 对输入文本执行标准化处理,包括: 1. 转为小写(lowercase) 2. Unicode 规范化并转为 ASCII 3. 移除标点符号 4. 规范空格格式 """logger.info("Original text: %s",text)# 将文本转换为小写text=text.lower()logger.info("After lowercasing: %s",text)# Unicode 规范化为 ASCIItext=unicodedata.normalize('NFKD',text)text=text.encode('ascii','ignore').decode('utf-8')logger.info("After Unicode normalization: %s",text)# 移除标点符号text=re.sub(r'[^\w\s]','',text)logger.info("After removing punctuation: %s",text)# 移除多余的空格text=re.sub(r'\s+',' ',text).strip()logger.info("After removing extra spaces: %s",text)returntext# 例句sentence1="dusk fell, i was gazing at the São Paulo skyline. Isn't urban life vibrant??"sentence2="Dusk fell; I gazed at the São Paulo skyline. Isn't urban life vibrant?"# 标准化句子std_sentence1=standardize_text(sentence1)std_sentence2=standardize_text(sentence2) 运行输出:
2025-11-1810:32:45 - INFO - Original text: dusk fell, i was gazing at the São Paulo skyline. Isn't urban life vibrant?? 2025-11-18 10:32:45 - INFO - After lowercasing: dusk fell, i was gazing at the são paulo skyline. isn't urban life vibrant??2025-11-1810:32:45 - INFO - After Unicode normalization: dusk fell, i was gazing at the sao paulo skyline. isn't urban life vibrant??2025-11-1810:32:45 - INFO - After removing punctuation: dusk fell i was gazing at the sao paulo skyline isnt urban life vibrant2025-11-1810:32:45 - INFO - After removing extra spaces: dusk fell i was gazing at the sao paulo skyline isnt urban life vibrant 标准化后两句的输出为:
dusk fell i was gazing at the sao paulo skyline isnt urban life vibrant dusk fell i gazed at the sao paulo skyline isnt urban life vibrant 此时,文本间只剩下语义上的核心区别(如was gazing atvs.gazed at),而不是无意义的格式差异。
通过文本标准化,我们能最大限度地减少由大小写、标点、字符编码等因素引入的噪声,使模型将注意力集中在真正有用的内容上。
(3) 高级标准化技术
除了上述基础方法,实际应用中还可以使用更高级的标准化技术来进一步减少词形变化。词干提取(Stemming): 通过截断词尾获得词干。例如 gazing → gaz。这种方法速度快,但结果可能不是合法的单词。
词形还原(Lemmatization): 通过查找词典将词还原为标准形式。例如 gazing →
gaze。语义保留更好,但依赖词典资源,速度相对较慢。两者的核心区别在于:Stemming 是基于规则的粗暴截断,Lemmatization是基于词典的精确还原。在对语义精度要求较高的场景中,推荐使用 Lemmatization。
2.2 分词方法
经过文本标准化之后,下一步就是选择合适的分词方法。常见的分词方法有三种:字符级分词、词级分词和子词分词,它们在粒度和语义表达之间做出了不同的权衡。
| 类型 | 示例 | 特点 |
|---|---|---|
| Word Token(词级分词) | “I love NLP” → [“I”, “love”, “NLP”] | 按空格或标点分词,适合英语等语言 |
| Subword Token(子词级分词) | “unhappiness” → [“un”, “happi”, “ness”] | 通过 BPE / WordPiece 等方法,将稀有词拆分为子词 |
| Character Token(字符级分词) | “hello” → [“h”, “e”, “l”, “l”, “o”] | 最小粒度,OOV(未登录词)处理能力强 |
| Byte / Byte-Pair Token | 字节级分词,兼容任意编码 | 支持多语言,常用于 GPT / LLaMA 等模型 |
(1) 字符级分词
字符级分词将文本分解成单个字符,是粒度最细的分词方式。它的词汇表非常小(例如英语只需 26 个字母加少量符号),且天然不存在未登录词(OOV)问题。但缺点是生成的序列很长,单个字符难以承载语义信息。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
defchar_level_tokenizer(text:str)->list:tokens=list(text)returntokensChar level tokenization: ['d', 'u', 's', 'k', ' ', 'f', 'e', 'l', 'l', ' ', ...](2) 词级分词
词级分词根据空格和标点符号将文本拆分成单个单词,是最直观的分词方式。它保留了完整的词义信息,但面对未见过的新词(OOV)时无能为力,且词汇表可能非常庞大。
- 最简单的实现方式是使用 Python 的
split()函数:
defword_level_tokenizer(text:str)->list:tokens=text.split()returntokens- 对之前标准化后的两句话进行词级分词:
Word level tokenization1: ['dusk', 'fell', 'i', 'was', 'gazing', 'at', 'the', 'sao', 'paulo', 'skyline', 'isnt', 'urban', 'life', 'vibrant'] Word level tokenization2: ['dusk', 'fell', 'i', 'gazed', 'at', 'the', 'sao', 'paulo', 'skyline', 'isnt', 'urban', 'life', 'vibrant'](3) 子词分词
子词分词将单词拆分成更小、更有意义的子词单元。这种方法兼顾了字符级分词的灵活性和词级分词的语义丰富性,是目前主流 LLM 采用的方案。字节对编码(BPE)和WordPiece是两种最常用的子词分词算法。
例如,使用 BERT 的 WordPiece 分词器对"I have a new GPU!"进行分词:
fromtransformersimportBertTokenizerdefsubword_level_tokenizer(text:str)->list:tokenizer=BertTokenizer.from_pretrained("bert-base-uncased")tokens=tokenizer.tokenize(text)returntokens# 输出: ['i', 'have', 'a', 'new', 'gp', '##u', '!'] 可以看到,常见词(如have、new)保持完整,而较少见的词GPU被拆分为gp和##u(##前缀表示该子词是前一个词的延续部分)。
子词分词在词汇量和语义表示之间提供了平衡:通过将罕见词分解为常用子词,它在不牺牲语义的前提下保持了可控的词汇量,使模型能够处理更广泛的语言输入。
三、Tokenizer 数据流
了解了分词的概念和方法之后,我们来看看在实际的模型训练流程中,Tokenizer 是如何工作的。下图展示了训练 Tokenizer 并将其应用于 NLP 模型的完整流程:
- 收集语料:首先,收集大量原始文本作为训练语料,语料应覆盖目标语言和领域。
- 训练 Tokenizer:使用训练语料训练分词器(如 BPE、WordPiece、SentencePiece 等),输出词表(vocabulary)和分词规则。
- 文本编码:使用训练好的分词器对预训练语料进行编码,将文本转换为词元序列(Token IDs)。
- 模型输入:Token ID 序列输入神经网络,模型进行向量化、嵌入计算和后续任务处理。
四、总结
本文从 Tokenization 的基本概念出发,介绍了它在 LLM 中的核心作用:将人类语言转化为模型可处理的数字序列。我们依次讨论了文本标准化的预处理技术、三种主流分词方法(字符级、词级、子词级)的原理与优劣,以及 Tokenizer 在实际训练流程中的数据流。
关键要点回顾:
分词是 NLP 的基础——它决定了模型"看到"的是什么。
文本标准化是分词的前置步骤,消除噪声、统一格式。
子词分词(如 BPE、WordPiece)是目前的主流方案,在词汇量和语义表达之间取得了最佳平衡。
分词方法的选择会直接影响模型的性能、效率和泛化能力。
在后续文章中,我们将深入探讨 BPE 和 WordPiece 等子词分词算法的具体实现原理,敬请期待。