在自然语言处理(NLP)项目中,原始文本数据往往充满噪声与不规则性。无论是构建AI聊天机器人、训练深度学习模型,还是进行文本分类,文本规范化都是确保后续任务高效、准确的基础。本文将深入探讨分词、词规范化和分句这三大核心步骤,帮助你在机器学习与神经网络应用中掌握数据预处理的关键技巧。

【作者主页】Francek Chen
【专栏介绍】 ⌈ ⌈ ⌈自然语言处理 ⌋ ⌋ ⌋ 自然语言处理 (Natural Language Processing, NLP) 是人工智能领域的重要研究方向,是一门集计算机科学、人工智能和语言学于一体的交叉学科,通过语法分析、语义理解等技术,实现机器翻译、智能问答、情感分析等功能。它包含自然语言理解和自然语言生成两个主要方面,研究内容包括字、词、短语、句子、段落和篇章等多种层次,是机器语言和人类语言之间沟通的桥梁。
【GitCode】专栏资源保存在我的GitCode仓库:https://gitcode.com/Morse_Chen/Natural_language_processing。
一、分词:从字符序列到有意义的词元
分词是将连续的文本切分成独立词元(token)的过程。在英语等印欧语系中,空格和标点符号是天然的分隔符,但简单的空格切分往往无法处理标点粘连的问题。以下介绍三种主流分词方法:
(一)基于空格与标点符号的分词
最直观的方式是利用空格分割字符串。但这种方法无法将词与紧邻的标点分开,例如“Hello!”会被视为一个整体。若后续任务(如文本分类)对标点不敏感,可先移除标点再分词:
sentence = "I learn natural language processing with dongshouxueNLP, too."
tokens = sentence.split(' ')
print(f'输入语句:{sentence}')
print(f"分词结果:{tokens}")
#引入正则表达式包
import re
sentence = "I learn natural language processing with dongshouxueNLP, too."
print(f'输入语句:{sentence}')
#去除句子中的“,”和“.”
sentence = re.sub(r'\,|\.','',sentence)
tokens = sentence.split(' ')
print(f"分词结果:{tokens}")

(二)基于正则表达式的分词
正则表达式能以灵活的模式匹配文本,实现更精细的分词。例如:
- 基础空格分词:使用
\S+匹配非空白字符。 - 处理标点:用
\w+匹配单词,[^\w\s]匹配标点。 - 处理连字符:如
state-of-the-art需保留连字符。
组合这些模式可得到兼顾标点与连字符的表达式:
import re
sentence = "Did you spend $3.4 on arxiv.org for your pre-print?"+\
" No, it's free! It's ..."
# 其中,\w表示匹配a-z,A-Z,0-9和“_”这4种类型的字符,等价于[a-zA-Z0-9_],
# +表示匹配前面的表达式1次或者多次。因此\w+表示匹配上述4种类型的字符1次或多次。
pattern = r"\w+"
print(re.findall(pattern, sentence))
# 可以在正则表达式中使用\S来表示除了空格以外的所有字符(\s在正则表达式中表示空格字符,\S则相应的表示\s的补集)
# |表示或运算,*表示匹配前面的表达式0次或多次,\S\w* 表示先匹配除了空格以外的1个字符,后面可以包含0个或多个\w字符。
pattern = r"\w+|\S\w*"
print(re.findall(pattern, sentence))
# -表示匹配连字符-,(?:[-']\w+)*表示匹配0次或多次括号内的模式。(?:...)表示匹配括号内的模式,
# 可以和+/*等符号连用。其中?:表示不保存匹配到的括号中的内容,是re代码库中的特殊标准要求的部分。
pattern = r"\w+(?:[-']\w+)*"
print(re.findall(pattern, sentence))
pattern = r"\w+(?:[-']\w+)*|\S\w*"
print(re.findall(pattern, sentence))

特殊场景匹配:英文简写(如 U.S.A.)和网址(如 arxiv.org)中的点号需保留,可使用 (\w+\.)+\w+(\.)* 匹配。注意点号在正则中是通配符,需转义为 \.。
#新的匹配模式
new_pattern = r"(?:\w+\.)+\w+(?:\.)*"
pattern = new_pattern +r"|"+pattern
print(re.findall(pattern, sentence))

货币与百分比(如 $3.40、3.5%)可用 \$?\d+(\.\d+)?%? 匹配;省略号(...)则需用 \.\.\. 保留。
#新的匹配pattern,匹配价格符号
new_pattern2 = r"\$?\d+(?:\.\d+)?%?"
pattern = new_pattern2 +r"|" + new_pattern +r"|"+pattern
print(re.findall(pattern, sentence))
#新的匹配pattern,匹配价格符号
new_pattern3 = r"\.\.\."
pattern = new_pattern3 +r"|" + new_pattern2 +r"|" +\
new_pattern +r"|"+pattern
print(re.findall(pattern, sentence))


⚠️ 注意:NLTK 工具包提供了便捷的正则分词接口,可简化实现:
import re
import nltk
#引入NLTK分词器
from nltk.tokenize import word_tokenize
from nltk.tokenize import regexp_tokenize
tokens = regexp_tokenize(sentence,pattern)
print(tokens)

(三)基于子词的分词
在深度学习时代,子词分词(如 BPE)成为主流。它通过迭代合并最频繁的字符对,构建出包含子词单元的词汇表。例如对地理名称拼音语料:
nan nan nan nan nan nanjing nanjing beijing beijing beijing beijing beijing beijing dongbei dongbei dongbei bei bei
初始化词汇表包含所有单字符,然后逐步合并:
corpus = "nan nan nan nan nan nanjing nanjing beijing beijing "+\
"beijing beijing beijing beijing dongbei dongbei dongbei bei bei"
tokens = corpus.split(' ')
#构建基于字符的初始词表
vocabulary = set(corpus)
vocabulary.remove(' ')
vocabulary.add('_')
vocabulary = sorted(list(vocabulary))
#根据语料构建词表
corpus_dict = {}
for token in tokens:
key = token+'_'
if key not in corpus_dict:
corpus_dict[key] = {"split": list(key), "count": 0}
corpus_dict[key]['count'] += 1
print(f"语料:")
for key in corpus_dict:
print(corpus_dict[key]['count'], corpus_dict[key]['split'])
print(f"词表:{vocabulary}")
for step in range(9):
# 如果想要将每一步的结果都输出,请读者自行将max_print_step改成999
max_print_step = 3
if step < max_print_step or step == 8:
print(f"第{step+1}次迭代")
split_dict = {}
for key in corpus_dict:
splits = corpus_dict[key]['split']
# 遍历所有符号进行统计
for i in range(len(splits)-1):
# 组合两个符号作为新的符号
current_group = splits[i]+splits[i+1]
if current_group not in split_dict:
split_dict[current_group] = 0
split_dict[current_group] += corpus_dict[key]['count']
group_hist=[(k, v) for k, v in sorted(split_dict.items(), \
key=lambda item: item[1],reverse=True)]
if step < max_print_step or step == 8:
print(f"当前最常出现的前5个符号组合:{group_hist[:5]}")
merge_key = group_hist[0][0]
if step < max_print_step or step == 8:
print(f"本次迭代组合的符号为:{merge_key}")
for key in corpus_dict:
if merge_key in key:
new_splits = []
splits = corpus_dict[key]['split']
i = 0
while i < len(splits):
if i+1>=len(splits):
new_splits.append(splits[i])
i+=1
continue
if merge_key == splits[i]+splits[i+1]:
new_splits.append(merge_key)
i+=2
else:
new_splits.append(splits[i])
i+=1
corpus_dict[key]['split']=new_splits
vocabulary.append(merge_key)
if step < max_print_step or step == 8:
print()
print(f"迭代后的语料为:")
for key in corpus_dict:
print(corpus_dict[key]['count'], corpus_dict[key]['split'])
print(f"词表:{vocabulary}")
print()
print('-------------------------------------')

最终对句子“nanjing beijing”的分词结果为:
ordered_vocabulary = {key: x for x, key in enumerate(vocabulary)}
sentence = "nanjing beijing"
print(f"输入语句:{sentence}")
tokens = sentence.split(' ')
tokenized_string = []
for token in tokens:
key = token+'_'
splits = list(key)
#用于在没有更新的时候跳出
flag = 1
while flag:
flag = 0
split_dict = {}
#遍历所有符号进行统计
for i in range(len(splits)-1):
#组合两个符号作为新的符号
current_group = splits[i]+splits[i+1]
if current_group not in ordered_vocabulary:
continue
if current_group not in split_dict:
#判断当前组合是否在词表里,如果是的话加入split_dict
split_dict[current_group] = ordered_vocabulary[current_group]
flag = 1
if not flag:
continue
#对每个组合进行优先级的排序(此处为从小到大)
group_hist=[(k, v) for k, v in sorted(split_dict.items(),\
key=lambda item: item[1])]
#优先级最高的组合
merge_key = group_hist[0][0]
new_splits = []
i = 0
# 根据优先级最高的组合产生新的分词
while i < len(splits):
if i+1>=len(splits):
new_splits.append(splits[i])
i+=1
continue
if merge_key == splits[i]+splits[i+1]:
new_splits.append(merge_key)
i+=2
else:
new_splits.append(splits[i])
i+=1
splits=new_splits
tokenized_string+=splits
print(f"分词结果:{tokenized_string}")

二、词规范化:统一词汇形态
词规范化旨在将同一词汇的不同变体统一为标准形式,降低词汇表大小,提升模型性能。
(一)大小写折叠
在搜索引擎或文本分类中,用户输入往往不区分大小写。大小写折叠将所有字母转为小写,避免因大小写差异导致的匹配失败:
# Case Folding
sentence = "Let's study Hands-on-NLP"
print(sentence.lower())

(二)词目还原
英语中存在大量形态变化(如 am/is/are → be,cat/cats/cat’s → cat)。词目还原通过词典或规则将单词还原为原型。手动建立词典或利用 NLTK 内置词库均可实现:
# 构建词典
lemma_dict = {'am': 'be','is': 'be','are': 'be','cats': 'cat',\
"cats'": 'cat',"cat's": 'cat','dogs': 'dog',"dogs'": 'dog',\
"dog's": 'dog', 'chasing': "chase"}
sentence = "Two dogs are chasing three cats"
words = sentence.split(' ')
print(f'词目还原前:{words}')
lemmatized_words = []
for word in words:
if word in lemma_dict:
lemmatized_words.append(lemma_dict[word])
else:
lemmatized_words.append(word)
print(f'词目还原后:{lemmatized_words}')
import nltk
#引入nltk分词器、lemmatizer,引入wordnet还原动词
from nltk.tokenize import word_tokenize
from nltk.stem import WordNetLemmatizer
from nltk.corpus import wordnet
#下载分词包、wordnet包
nltk.download('punkt', quiet=True)
nltk.download('wordnet', quiet=True)
lemmatizer = WordNetLemmatizer()
sentence = "Two dogs are chasing three cats"
words = word_tokenize(sentence)
print(f'词目还原前:{words}')
lemmatized_words = []
for word in words:
lemmatized_words.append(lemmatizer.lemmatize(word, wordnet.VERB))
print(f'词目还原后:{lemmatized_words}')

三、分句:将长文本拆解为句子单元
处理新闻、财报等长文本时,直接输入模型会面临计算和语义挑战。分句通常基于标点符号(如句号、问号、感叹号)进行。但英文句号存在歧义(如“Inc.”、“Ph.D.”、小数点),需先分词再判断边界:
sentence_spliter = set([".","?",'!','...'])
sentence = "Did you spend $3.4 on arxiv.org for your pre-print? " + \
"No, it's free! It's ..."
tokens = regexp_tokenize(sentence,pattern)
sentences = []
boundary = [0]
for token_id, token in enumerate(tokens):
# 判断句子边界
if token in sentence_spliter:
#如果是句子边界,则把分句结果加入进去
sentences.append(tokens[boundary[-1]:token_id+1])
#将下一句句子起始位置加入boundary
boundary.append(token_id+1)
if boundary[-1]!=len(tokens):
sentences.append(tokens[boundary[-1]:])
print(f"分句结果:")
for seg_sentence in sentences:
print(seg_sentence)

欢迎 点赞 | 收藏⭐ | 评论✍ | 关注
[AFFILIATE_SLOT_1]
结语
文本规范化是自然语言处理管道中的基石。从分词到词规范化再到分句,每一步都直接影响后续 AI 模型的性能与鲁棒性。掌握这些技术,你将能更高效地清洗和准备数据,为深度学习、神经网络等高级应用奠定坚实基础。
[AFFILIATE_SLOT_2]
浙公网安备 33010602011771号