词级别分词:空格分词与标点分词

讲完了字节对编码这些花哨的东西,我们回过头来聊聊最朴素的方案。说实话,你真正上手做 NLP 项目时,很可能第一个遇到的就是词级别分词——而且大多数场景下,它就是最简单的那个答案。

1. 空格分词:最直接的切法

把文本按空格劈开,每个连续的非空字符块就是一个 。这在英语等用空格隔开单词的语言里特别自然。我当年刚学 NLP 时觉得这有啥好讲的,不就是 split(" ") 吗?

核心思路:用空白字符(空格、制表符、换行)做分隔符,保留连续字符序列作为 。

# 一个最简单的空格分词器
def whitespace_tokenize(text):
    return text.split()

text = "Hello world! This is a test."
tokens = whitespace_tokenize(text)
print(tokens)
# 输出: ['Hello', 'world!', 'This', 'is', 'a', 'test.']

你注意看结果里的 "world!" 和 "test."——标点还粘在单词上。这既是问题,也是后面要聊的标点分词要解决的事情。

空格分词的优缺点

优点缺点
实现极简单,零逻辑标点粘连,词汇表膨胀
推理速度飞快无法处理连字符、缩写(如 "don't")
无训练成本对中文等无空格语言完全失效
可解释性高,每个 就是一个原词多语言场景下空格规则不统一

我曾经在一个英文新闻分类项目里直接用了空格分词,跑完精度还行。但一看字典大小,接近 50 万——因为那些粘连的标点("Hello?"、"hello?"、"hello!")全成了不同词。嗯,那时候我才意识到小细节能搞出多大麻烦。

2. 标点分词:让标点独立出来

空格分词的问题在于标点被当作单词的一部分。标点分词就是在空格分词的基础上,把标点符号单独切出来。

一个小技巧:大多数标点分词器会维护一个标点集合,用正则或遍历的方式把每个 再拆一遍。

import re

def punctuation_tokenize(text):
    # 先用空格分词
    tokens = text.split()
    result = []
    punct_set = set("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")
    
    for  in tokens:
        # 如果  全由标点组成,直接加进去
        if all(ch in punct_set for ch in ):
            result.append()
            continue
        
        # 否则按标点拆分
        current = []
        for ch in :
            if ch in punct_set:
                if current:
                    result.append(''.join(current))
                    current = []
                result.append(ch)
            else:
                current.append(ch)
        if current:
            result.append(''.join(current))
    
    return result

text = "Hello world! This is a test."
print(punctuation_tokenize(text))
# 输出: ['Hello', 'world', '!', 'This', 'is', 'a', 'test', '.']

注意:不是所有标点都要切。例如 '('与')'在中英文混排里最好保持原样;URL 里的 '/' 和 '.' 如果切开就破坏了结构。我曾经踩过一个坑:把 IP 地址 "192.168.1.1" 切成了 7 个 ,导致下游模型学不到 IP 的整体含义。

3. 常见标点分词规则

标点分词没有唯一标准,但工业界有一组常规做法。我整理了一张表,可以帮你快速对照:

标点类别典型处理示例
句尾标点 (.!?)独立成 "Hello." → ['Hello', '.']
逗号、分号、冒号独立成 "a, b" → ['a', ',', 'b']
引号、括号左右分别处理'"hi"' → ['"', 'hi', '"']
连字符通常保留为词的一部分"high-level" → 不切,保留原样
撇号(缩写)按语言习惯处理"don't" → 可切为 ['do', "n't"] 或保留
数字中的标点保持数字完整性"3.14" → 不切,"1,000" 中的逗号可保留或切

说白了,规则是死的,场景是活的。你在做英文电商评论分析时,就把 "!!!" 这种连续感叹号单独切出来,模型才能学到感叹号的情绪强度。但做技术文档分词时,把 "C++" 里的加号切开就闹笑话了。

4. 词级别分词的局限性

到这里你可能已经感觉到了:词级别分词对固定语言、固定领域能跑得很顺,但一旦跨语言或者遇到 OOV(未登录词),它就自动崩了。我参与过一个多语言舆情系统,英文部分用空格+标点分词跑得好好的,换成德语复合词 "Donaudampfschifffahrtsgesellschaftskapitän"(多瑙河汽船公司船长)——一个词五十多个字符,字典里根本不可能收录,直接被 OOV 吞掉。

说白了,词级别分词的优缺点很清楚:简单、快、可解释;但词汇表大、OOV 多、对形态丰富的语言不友好。你在原型期、资源受限的设备上、或者目标语言词汇规模可控时,它依然是上佳选择。

本章知识点结构

词级别分词知识框架 词级别分词 空格分词 按空格 / 制表符 / 换行切分 标点与单词粘连 标点分词 将标点独立成 规则可定制(句尾/括号/数字) 适用:原型快跑、资源受限、词汇量可控场景

实用避坑指南:我曾经在做一个英文简历解析系统时,直接用空格+标点分词。结果简历里大量 "2019-Present"、"iOS/Android"、"C++" 等格式,全被拆得七零八落。后来不得不加了一个“保护规则”——正则匹配常见复合词和数字范围,跳过标点拆分。教训就是:没有任何分词器能通用,你必须为你的数据做定制化“打补丁”。

好了,词级别分词的核心就这些。它简单到让你觉得没什么技术含量,但正是这种“低技术门槛”让它成为无数生产系统的第一选择。记住了,当你问“该用哪种分词器”时,先试试空格+标点——如果够用,就别折腾别的。保持简单,保持可维护,这才是工程师的直觉。