在构建现代自然语言处理(NLP)应用的旅途中,我们遇到的第一道关卡,也是最为关键的基石,就是“分词”。想象一下,如果我们要让计算机阅读莎士比亚的十四行诗,或者分析成千上万条客户反馈,计算机并不能像人类那样直观地“看到”单词或句子。它看到的只是一长串连续的字符流。为了让机器能够理解人类的语言,我们需要将这个庞大的文本流切分成更小的、易于管理的单位,这些单位就是我们所说的“词元”。
根据我们分析需求的精细程度,这些词元可以小到单个字符,大到完整的单词甚至短语。这个过程——将原始文本转换为结构化的词元序列——就是分词。在这篇文章中,我们将深入探讨分词的内部机制,分析不同类型的分词策略,并亲自动手通过代码实现来掌握这一核心技能。
目录
- 1 确保你已经下载了必要的分词模型
- 2 nltk.download(‘punkt‘)
- 3 nltk.download(‘punkt_tab‘) # 如果是较新版本,可能需要这个
- 4 句子分词
- 5 我们使用 sent_tokenize 来识别句子边界
- 6 单词分词
- 7 word_tokenize 会处理标点符号,并将其作为单独的词元
- 8 加载英文模型
- 9 首次使用需要运行:python -m spacy download encoreweb_sm
- 10 处理文本
- 11 SpaCy 会在处理过程中自动完成分词、词性标注等步骤
- 12 加载预训练的 BERT 分词器
- 13 进行分词
- 14 将词元转换为索引
- 15 初始词汇表 (字符级别,用空格分隔)
- 16 模拟 BPE 迭代过程
什么是分词?
我们可以将分词比作教一个人学习新语言的过程:首先,我们要从字母表开始,然后过渡到音节,最后到完整的单词和句子。这种层级结构让我们能够将复杂的文本分解为机器更容易处理的部分。
让我们看一个简单的例子。假设我们有这样一个句子:
> "Chatbots are helpful."
单词级分词:如果我们按单词进行分词,它会变成一个包含三个元素的列表:
["Chatbots", "are", "helpful"]
字符级分词:如果按字符进行分词,它会变成一个更长的列表,包含空格和标点:
["C", "h", "a", "t", "b", "o", "t", "s", " ", "a", "r", "e", " ", "h", "e", "l", "p", "f", "u", "l"]
根据具体的应用场景和手头的 NLP 任务,每种方法都有其独特的优势。选择正确的粒度(是单词、子词还是字符)往往是模型性能好坏的关键。
## 分词的主要类型
根据文本分割方式的不同,分词技术主要可以分为几种类型。让我们来看看这些主要的分词策略,并分析它们各自的优缺点。
### 1. 单词分词
这是最直观的方法,文本被划分为单个单词。它非常适用于像英语这样具有明确词边界的语言(通常使用空格分隔)。
**优点**:
- 保留了单词的完整语义,人类易于理解。
- 词汇表的大小适中。
**缺点**:
- 对未登录词(OOV, Out-of-Vocabulary)处理能力差。如果测试集中出现了训练集里没有的单词,模型就会报错或无法识别。
### 2. 字符分词
在这种方法中,文本被分割成单个字符。
**优点**:
- 词汇表非常小(通常只有几十到几百个字符)。
- 不会出现未登录词的问题,因为任何单词都可以由字符组成。
- 对于拼写检查等任务非常有效。
**缺点**:
- 失去了单词层面的语义信息。例如,“read”(读)和“red”(红)在字符级别非常相似,但意思完全不同。
- 输入序列变长,增加了计算成本。
### 3. 子词分词
这是一种在单词分词和字符分词之间取得平衡的现代方法。它通过将文本分解为大于单个字符但小于完整单词的单位(如词根、前缀、后缀)来工作。目前最先进的大语言模型(如 GPT-4, BERT)都使用这种技术。
**优点**:
- 解决了未登录词问题,通过组合子词来表示稀有词。
- 保留了单词的部分语义信息。
- 词汇表大小可控。
### 4. 句子分词
这用于将段落或大量的句子集分割成以独立句子为单位的词元。这通常作为预处理步骤,在单词分词之前进行。
### 5. N-gram 分词
N-gram 分词将单词分割成固定大小的数据块(大小 = n)。这种方法常用于统计语言模型中,用于捕捉词与词之间的共现关系。
## 代码实战:如何实现分词
理论知识固然重要,但让我们撸起袖子,看看如何在实际代码中实现这些分词技术。我们将使用 Python 和一些常用的 NLP 库。
### 1. 使用 NLTK (Natural Language Toolkit)
NLTK 是一个经典的 Python 库,非常适合初学者理解和学习 NLP 的基本原理。它提供了单词和句子分词的功能。
**场景**:我们需要对一段包含专有名词和缩写的文本进行分词。
python
import nltk
确保你已经下载了必要的分词模型
nltk.download(‘punkt‘)
nltk.download(‘punkt_tab‘) # 如果是较新版本,可能需要这个
text = "Hello Mr. Smith, how are you doing today? The weather is great!"
句子分词
我们使用 sent_tokenize 来识别句子边界
data = nltk.sent_tokenize(text)
print("句子分词结果:")
for sentence in data:
print(f"- {sentence}")
单词分词
word_tokenize 会处理标点符号,并将其作为单独的词元
print("
单词分词结果:")
words = nltk.word_tokenize(text)
print(words)
**代码解析**:
- `nltk.sent_tokenize` 非常智能,它知道 "Mr." 后面的点号不代表句子的结束,而不是今天 "great!" 后面的感叹号代表结束。
- `nltk.word_tokenize` 会将 "Smith," 拆分为 "Smith" 和 ","。这对于后续的词性标注非常重要。
### 2. 使用 SpaCy
SpaCy 是一个现代且高效的 Python NLP 库。与 NLTK 不同,SpaCy 专注于工业级应用,它的速度非常快,并且对多语言的支持非常好。
**场景**:处理大规模数据,需要高效的性能。
pythonnimport spacy
加载英文模型
首次使用需要运行:python -m spacy download encoreweb_sm
try:
nlp = spacy.load("encoreweb_sm")
except OSError:
print("请先在终端运行: python -m spacy download encoreweb_sm")
exit()
text = "Apple is looking at buying U.K. startup for $1 billion."
处理文本
SpaCy 会在处理过程中自动完成分词、词性标注等步骤
doc = nlp(text)
print("SpaCy 分词结果 (迭代 Doc 对象):")nfor token in doc:
# token.text 是词元文本
# token.is_punct 告诉我们这是否是标点符号
if not token.is_punct:
print(f"单词: {token.text}")
**代码解析**:
- SpaCy 返回的是一个 `Doc` 对象,它包含了丰富的信息。
- 我们可以通过遍历 `doc` 来获取每一个 `Token` 对象。
- SpaCy 的分词器是基于规则的,非常健壮,能够处理 "U.K." 和 "$1" 这样的复杂情况。
### 3. 使用 BERT Tokenizer (子词分词实战)
为了应对前面提到的挑战,现代模型通常使用子词分词。让我们看看 Hugging Face 的 Transformers 库中是如何使用 BERT 分词器的。
**场景**:我们需要处理包含生僻词或合成词的文本,这是传统单词分词的噩梦。
pythonn# 需要安装: pip install transformers
from transformers import BertTokenizer
加载预训练的 BERT 分词器
tokenizer = BertTokenizer.from_pretrained(‘bert-base-uncased‘)
text = "I love playing football and embeddingization is cool."
进行分词
tokens = tokenizer.tokenize(text)
print("BERT 子词分词结果:")
print(tokens)
将词元转换为索引
tokenids = tokenizer.converttokenstoids(tokens)
print("
对应的词元索引:")
print(token_ids)
**代码解析**:
- 注意看输出中的 `##izing` 和 `##embed`。`##` 符号表示这个词元是前一个词元的延续。
- 对于单词 "embeddingization"(生造词),BERT 将其拆解为了 `embedding` + `##ization`。这极大地降低了词汇表的大小,同时保证了模型能够理解它没见过的单词。
### 4. 实现自定义的 Byte-Pair Encoding (BPE)
BPE 是一种自适应的分词方法,它通过迭代地合并最频繁出现的字节对来构建词汇表。下面我们用原生 Python 模拟一个简单的 BPE 过程。
**场景**:理解子词分词背后的算法逻辑。
python
import re
from collections import defaultdict
def get_stats(vocab):
"""
统计词元对出现的频率
"""
pairs = defaultdict(int)
for word, freq in vocab.items():
symbols = word.split()
for i in range(len(symbols) – 1):
pairs[symbols[i], symbols[i+1]] += freq
return pairs
def mergevocab(pair, vin):
"""
合并频率最高的词元对
"""
v_out = {}
bigram = re.escape(‘ ‘.join(pair))
p = re.compile(r‘(?<!\S)' + bigram + r'(?!\S)')
for word in v_in:
w_out = p.sub(‘‘.join(pair), word)
vout[wout] = v_in[word]
return v_out
初始词汇表 (字符级别,用空格分隔)
vocab = {‘l o w ‘: 5, ‘l o w e r ‘: 2, ‘n e w e s t ‘: 6, ‘w i d e r ‘: 3}
模拟 BPE 迭代过程
num_merges = 10
for i in range(num_merges):
pairs = get_stats(vocab)
if not pairs:
break
# 获取频率最高的对
best = max(pairs, key=pairs.get)
vocab = merge_vocab(best, vocab)
print(f"Step {i+1}: Merging {best}")
print("
最终学习到的合并规则示例:")nprint(vocab)
“INLINECODE3804b93deINLINECODEca485cb9rINLINECODEfd68a6fferINLINECODEf37547e4unINLINECODE8824c8af##ableINLINECODEfb98818f##lyINLINECODE[email protected]70af79d0usefast=TrueINLINECODE9c9e2a8asplit()` 操作,它是连接人类语言与机器算法的桥梁。我们对比了单词、字符和子词分词的区别,并亲自编写了使用 NLTK、SpaCy 和 BERT 的代码示例。最后,我们探讨了实际应用中可能遇到的挑战和优化策略。
掌握了分词,你就已经迈出了构建复杂 NLP 应用(如情感分析、机器翻译或智能聊天机器人)的最重要一步。接下来,你可以尝试将这些分词技术应用到自己的数据集中,看看它们是如何将杂乱的文本转化为结构化数据的。祝你编码愉快!