最简单的分词方法:基于空格与标点符号
说到分词,很多人第一反应就是那些高大上的统计模型、神经网络。
但说实话,在实际项目中,我用得最多的反而是最简单的方法。
为什么?因为简单就意味着稳定、好维护、不出幺蛾子。
这一章我们就聊聊两个最基础的分词方法——基于空格和基于标点符号。别看它们简单,但在我手里的几个生产项目中,它们依然在干活。
基于空格的分词
这是最直觉的做法。直接用空格把句子切开,每个片段就是一个。
我最早做英文文本处理时,就是从这起步的。
def split_by_space(text):
return text.split()
text = "Tokenization is the first step"
tokens = split_by_space(text)
print(tokens)
# 输出:['Tokenization', 'is', 'the', 'first', 'step']
代码只有一行。split() 默认按任意空白字符切分,连续空格也不会有问题。
split() 比 split(' ') 更好用。前者会自动处理多个连续空格、Tab、换行等。我习惯用不带参数的那个版本,省心。
基于标点符号的分词
光靠空格不够。你看看这句话:"Hello, world!"
空格切完是 ["Hello,", "world!"]。逗号和感叹号还粘在单词上。
这时候就要对标点下手了。
import re
def split_by_punctuation(text):
# 用正则把常见标点替换为空格,再做空格切分
text = re.sub(r'[.,!?;:\'\"()\[\]{}]', ' ', text)
# 处理连字符和撇号?先不碰它们
return text.split()
text = "Hello, world! How's it going?"
tokens = split_by_punctuation(text)
print(tokens)
# 输出:['Hello', 'world', "How's", 'it', 'going']
你看,逗号和感叹号被清理掉了。但 "How's" 保留了下来。
"state-of-the-art" 变成了 ["state", "of", "the", "art"],语义完全变了。有些标点其实是单词的一部分,不能一刀切。
把两者结合
实际项目中,我一般把空格分词和标点分词组合起来用。
import re
def simple_tokenize(text):
# 1. 标点替换为空格
text = re.sub(r'[.,!?;:\'\"()\[\]{}]', ' ', text)
# 2. 空格切分
tokens = text.split()
return tokens
text = "Hello, world! This is NLP (Natural Language Processing)."
tokens = simple_tokenize(text)
print(tokens)
# 输出:['Hello', 'world', 'This', 'is', 'NLP', 'Natural', 'Language', 'Processing']
嗯,效果还行。括号和句号都被去掉了。
局限性分析
别急着高兴。这两个方法的问题不少。我自己在实际项目中吃过不少亏。
| 问题 | 示例 | 说明 |
|---|---|---|
| 中文失效 | "我爱自然语言处理" | 中文词之间没有空格,切出来是一个整体 |
| 连写词 | "state-of-the-art" | 连字符是否该保留?拆开会丢语义 |
| 英文缩写 | "U.S.A." | 点号是缩写的一部分,删掉变成 "U S A" |
| 数字和单位 | "100kg", "3.14" | 数字和单位连在一起,或者小数点被当作标点 |
| 特殊符号 | "@username", "#topic" | @ 和 # 是有意义的,不该删 |
我们一个一个来看。
中文问题
中文没有空格。直接用空格分词,一个句子变成一个词。这显然是行不通的。
text = "人工智能正在改变世界"
tokens = text.split()
print(tokens)
# 输出:['人工智能正在改变世界'] # 这不叫分词
中文需要专门的分词工具,比如 jieba、HanLP 等。后面的章节会细讲。
连写词与缩写
英文里,连字符和撇号是否保留,得看场景。
我曾被客户吐槽过分词结果。他们做搜索引擎,用户搜 "state-of-the-art",系统把它拆成了四个词,用户怎么搜都匹配不上原文。后来我改了策略:保留常见连写词,不拆分。
"state-of-the-art"、"user-friendly" 这些固定搭配,直接保留。虽然增加了复杂度,但召回率提升了近15%。
数字与符号
数字和单位连在一起,比如 "100kg",到底该不该拆?
我个人习惯是:数字和字母组合时,不拆分。但小数点 "3.14" 要保留,不能把点号当标点去掉。
def smarter_tokenize(text):
# 先替换标点,但保留小数点
text = re.sub(r'[!,?;:\'\"()\[\]{}]', ' ', text)
# 这里不碰 . - 等可能有特殊含义的字符
return text.split()
text = "The cost is $3.14 per kg, down from $4.00."
tokens = smarter_tokenize(text)
print(tokens)
# 输出:['The', 'cost', 'is', '$3.14', 'per', 'kg', 'down', 'from', '$4.00']
你看,货币符号和数字连在一起,效果还行。但 "$3.14" 这种,严格来说还需要进一步处理。如果你要保留数字结构,最好单独写规则。
"10,000" 中的逗号也删了,数字变成了 "10000",数据统计直接出了乱子。所以现在我做标点分词时,总会保留那些有语法意义的符号。
什么时候该用这些简单方法?
你可能会问:既然局限性这么多,为什么还要学?
我的答案是:场景决定方案。
- 快速原型:你要快速验证一个想法,先跑通流程。简单分词够用。
- 英文邮件/文档:结构规整,标点用法标准。简单方法基本够用。
- 代码注释或日志:分词结果不需要多精准,快和稳才是关键。
- 日志分析:服务器日志格式固定,空格分隔就是最好的分词方式。
反过来,如果你在处理中文、社交媒体文本、产品评论等场景,就别偷懒了。老老实实上工具包或者训练自己的模型。
总结一下:基于空格和基于标点符号的分词,是所有分词方法的地基。它们简单、直观、好调试。但地基就是地基,盖高楼还得往上加东西——语言模型、统计方法、字典匹配,这些都是后续章节的内容。
不过在此之前,我建议你把今天这两个方法在真实文本上跑一跑。看看它们能处理什么,不能处理什么。亲手踩过坑,你才能理解后面那些复杂方法为什么要那么设计。