1. Tokenization入门:什么是,为什么需要Tokenization?
大家好,我是你们这门课的主讲工程师。在进入NLP的世界之前,我们必须先搞定一个最基础、也最容易被忽视的概念—— 和 Tokenization。
说白了,你把一段话交给计算机处理,计算机不认识「字」和「词」,它只认识数字。Tokenization就是那台「翻译机」,它把人类语言切成小块(),再把这些小块映射成计算机能算的编号。
我记得我第一次接触这个概念是在做一个中文情感分析项目。当时直接把一句话当作一个整体丢进模型,结果模型直接崩溃——因为它根本不知道「我喜欢」和「我不喜欢」之间的微妙差别。嗯,从那以后我才意识到:没有好的Tokenization,所有上层模型都是空中楼阁。
1.1 到底什么是?
一个,是你文本中一个最小的、有意义的单元。在英文里它可能是一个单词、一个标点,或是像"es"这样的词缀。在中文里,它可以是一个字、一个词,甚至是一个短语——取决于你怎么切。
核心观点:不是固定的物理单位,而是你选择的一种语义原子。
举个例子:
- 句子:
I love NLP - 按空格切:
["I", "love", "NLP"]→ 3个 - 按子词切(比如BPE):
["I", "lov", "e", "N", "L", "P"]→ 6个
你会问:为什么不能直接按单词切?我在项目里踩过这个坑——英语单词动不动就几十万个,而很多单词其实很少出现。你如果给每个单词一个编号,词汇表会膨胀到百万级,模型根本训不动。
1.2 为什么我们需要Tokenization?
这个问题我曾经被实习生问过:「为什么不直接把汉字编码成Unicode丢进去?」答案是:模型需要捕捉「语义结构」,而不是字符的字节码。
你想想看:吃 和 吃饭 在Unicode里没有任何相似度,但语义上吃饭比吃更完整。Tokenizer帮我们做了一件事:把文本变成一串整数,同时保留尽可能多的语义信息。
| 必要性 | 说明 |
|---|---|
| 计算机只认数字 | 神经网络吃的是浮点数矩阵,不是文字 |
| 效率 | 一个=一个维度索引,计算远快于字级别操作 |
| 语义单元 | 词/子词比单字携带更强的句法语义信号 |
| OOV处理 | 好的Tokenizer能处理未登录词(比如这种新词) |
避坑指南:我曾经在项目里直接拿Python的.split()作为Tokenizer,结果模型遇到"don't"时把它切成["don", "'", "t"],导致"t"这种无意义反复出现,训练损失一直降不下去。后来才改用BPE解决。
1.3 Tokenization的核心流程
整个过程分为三步:切分 → 映射 → 编码。下面这张图帮你一秒理清脉络。
你看到没?切分这一步是整个流程里最需要动脑子、也最容易出错的环节。
1.4 主流切分策略一览
我根据经验给大家整理了一份对比,你在选Tokenizer时可以照着这张表来。
| 策略 | 典型代表 | 优点 | 缺点 |
|---|---|---|---|
| 字符级 | Unicode字节编码 | 词汇表特小(几百个),无OOV | 序列太长,丢失词义信息 |
| 词级 | 空格+标点拆分 | 语义准确,序列短 | 词汇表超大,OOV处理差 |
| 子词级 | BPE、WordPiece、Unigram | 平衡词汇表大小与语义保留 | 需要预先训练合并规则 |
| 多语言共享 | SentencePiece | 零门槛处理中文、日文等 | 对罕见语种可能效率不高 |
实战建议:我个人习惯先用预训练模型自带的Tokenizer(比如BERT用WordPiece,用BPE)。如果做垂直领域,再基于语料增量训练一个子词词表。千万别手写规则切分——我在医疗病历项目里试过,结果"心电监护"被切成了"心电/监/护",模型彻底蒙圈。
1.5 为什么Tokenizer的好坏直接决定模型效果?
你想想看:就是模型的基本输入单元。如果这个单元切得不对,模型再怎么调参都白搭。
举个例子。中文句子:「武汉市长江大桥」
- 差的分法:
武汉/市长/江大桥(意思全变了,变成「武汉的市长叫江大桥」) - 好的分法:
武汉市/长江大桥
我踩过这个坑——曾经在一个舆情分析项目中,模型把「武汉市长江大桥」理解成「武汉市长」,结果所有和「武汉市长」有关的负面舆情全被误分到桥梁安全类别。嗯,那之后我再也不敢用简单最大匹配分词了。
重要提醒:Tokenization不是一次性的预处理步骤。很多现代模型把它和网络前向计算融合在一起(比如T5的Span Corruption里的随机掩码)。你要把Tokenizer当成模型的一个组件,而不是一个独立工具。
1.6 一个简单的代码侧写(伪代码版)
为了让你直观理解,我写一段极简的Python风格伪代码(仅示意流程,不涉及具体库)。
def tokenize(text, vocab):
# 1. 切分(这里简单按空格分)
tokens = text.split() # ["I", "love", "NLP"]
# 2. 查词汇表,若不在则替换为 [UNK]
ids = []
for in tokens:
if in vocab:
ids.append(vocab[])
else:
ids.append(vocab["[UNK]"])
# 3. 加上特殊标记(CLS, SEP等)
ids = [vocab["[CLS]"]] + ids + [vocab["[SEP]"]]
return ids
# 示例
text = "I love NLP"
vocab = {"[CLS]":101, "[SEP]":102, "[UNK]":100,
"I":105, "love":230, "NLP":3019}
print(tokenize(text, vocab))
# 输出:[101, 105, 230, 3019, 102]
在实际项目里,你用的Tokenizer会复杂得多——比如BPE要维护合并规则、SentencePiece还有语言模型评分。但核心思想不变:先切,再映射,最后组装成特定格式的ID序列。
1.7 本章小结
Tokenization不是「切词」那么简单。它是一个从文本到整数序列的语义化编码过程。
- 是模型能处理的最小语义单元
- 好的Tokenizer能大幅减少OOV、控制序列长度、保留词义
- 主流的子词级方案(BPE、WordPiece)是目前工业界标配
- 务必把Tokenizer当成模型的一部分来维护和调优
一句话记住:
没有合理的Tokenization,就没有可靠的NLP模型。
好了,这一章就到这里。你脑海中现在应该有一个清晰的图景:文本→切分→列表→ID序列。下一章我们专门拆解BPE(Byte Pair Encoding)——也是目前、BERT等主流模型都在用的子词切分算法。到时我会带上我在实际训练中遇到的坑和调参经验,咱们好好聊一聊。