2. 文本预处理基础:清洗、归一化、Unicode标准化
好,咱们正式开始第二讲。在把文本丢给 Tokenizer 之前,有一道绕不开的工序,就是预处理。
说白了,Tokenizer 很笨。你给它一坨脏数据,它就给你吐一坨乱七八糟的 。我这些年踩过最大的坑,基本都是预处理没做干净导致的。
这一节,我带着你把预处理讲透。核心就三件事:清洗、归一化、Unicode 标准化。
2.1 清洗:把垃圾扫出门
清洗的目的很单纯——去掉那些对后续分析没有意义、甚至有害的信息。
常见清洗项:
- HTML 标签:从网页扒下来的文本,经常带一堆
<div>、<p>。Tokenizer 不需要这些。 - 特殊符号:一些奇怪的 Unicode 符号,比如emoji里的某些控制字符,很容易让分词器卡住。
- 多余空白:多个空格、制表符、换行符,最好统一成一个空格或直接去掉。
- 不可见字符:像零宽空格(
\u200B)这种东西,肉眼看不见,但程序不认,很容易造成 对齐错误。
我记得有一次,线上模型预测某条数据的标签总是错。排查了好久,才发现源文本里藏了一个零宽空格。清洗代码加上 \u200B 过滤之后,问题就解决了。
ftfy 或 textcleaner 这类现成的库,它们能覆盖很多你意想不到的边界情况。
# 一个欠完备的清洗示例(Python)
import re
def basic_clean(text):
text = re.sub(r'<[^>]+>', '', text) # 去 HTML 标签
text = re.sub(r'\s+', ' ', text).strip() # 合并空白
text = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f]', '', text) # 去控制字符
return text
2.2 归一化:把形式统一起来
归一化和清洗不一样。清洗是“去掉错的”,归一化是“把对的统一”。
几个典型场景:
- 大小写统一:中文不用管,但英文最好全转成小写。否则
Apple和apple会变成两个 ,白白增加词表大小。 - 数字占位符:像
666和123,在大多数任务里,具体数值不重要。可以用一个统一的 如<NUM>代替。 - 缩写展开:像 “it's” 可以展开为 “it is”。
- 简繁转换:如果数据集里既有简体又有繁体,最好统一转成一种。我个人习惯用 opencc 来做。
我遇到过最典型的归一化问题,是用户输入的 “3.14” 和 “3,14”。如果不对标点做归一化,分词器会把逗号和小数点当成不同的东西。你想想看,训练语料里“3.14”出现1000次,“3,14”出现1次,那模型肯定不认识后面那个。
# 一个简单的归一化流程
def normalize(text):
text = text.lower() # 小写
text = re.sub(r'\d+', '<NUM>', text) # 数字归一化
text = re.sub(r'[\.,,。]', ' ', text) # 标点统一成空格
return text
2.3 Unicode 标准化:别让编码坑了你
这部分容易被忽视,但它真的很重要。
你看,“é”这个字符,在 Unicode 里可以有多种表示方式:
- NFD:分解成 “e” + 组合符号 ◌́
- NFC:合成单个字符 “é”
如果没有统一标准化,“café” 和 “café”(第二种写法)在字节级别就是完全不同的字符串。Tokenizer 会认为它们是两个词,这显然不对。
常见标准化形式:
| 形式 | 说明 | 使用场景 |
|---|---|---|
| NFC | 优先合成 | 大多数通用场景,比如社交文本 |
| NFD | 优先分解 | 音标、注音符号等需要分析基字符的场景 |
| NFKC | 兼容分解后再合成 | 金融、数字处理;能把“②”转为“2” |
| NFKD | 兼容分解后保持分解 | 搜索、索引,尽可能保留可搜索性 |
我曾经在做一个多语言 OCR 项目时,发现同一篇文档里“ü”的编码不一致。有的地方是 NFC 合成字,有的是 NFD 分解字。结果分词器把 “Müller” 拆成了 “Muller” 和 “ü”。后来加上 unicodedata.normalize('NFC', text) 才搞定。
import unicodedata
def unicode_normalize(text, form='NFC'):
return unicodedata.normalize(form, text)
2.4 预处理整体流程
这三个步骤串在一起,才算完整的预处理流水线。我画了一张图,帮你理清它们之间的关系。
你看,流程是线性的。先清洗,再归一化,最后做 Unicode 标准化。做完这三步,文本基本就干净了,可以喂给 Tokenizer 了。
我在实际项目中,一般会把这三步封装成一个函数。线上推理和线下训练用同一套预处理逻辑。为什么这么做?因为训练语料和线上输入如果预处理不一致,再好的模型也会扑街。
2.5 避坑指南:我踩过的几个坑
- 不要相信“原始文本”:很多公开数据集号称已经清洗过了。我建议你拿到手后,自己再跑一遍清洗流程。不信你试试,随便找一个 text 文件,隐藏字符可能多到让你怀疑人生。
- 归一化别过度:比如“3.14”和“3,14”,有时候真的不能统一。能不能统一,取决于下游任务。如果是数值解析任务,逗号和小数点都得保留。我之前在金融领域的一个 NER 项目里,把小数点误归一化成了空格,结果金额识别全错了。
- Unicode 标准化必须做在分词前:如果你先分词再标准化,编码不一致导致的分词错误已经发生了,标准化救不了你。所以顺序一定要对。
好了,文本预处理这块我就讲到这里。字数不多,但全是我实战中的血泪经验。你把这些基础打牢了,后面讲 Tokenizer 的时候,咱们才能真正深入下去。