【自然语言处理-NLP】文本预处理技术

以下内容将从 基本概念实用代码 分步骤、分场景地详细介绍 NLP 常见文本预处理 方法及其背后的思想。如果无法从外部导入数据,我们会模拟一份简易文本数据(如字符串列表),并在此基础上演示预处理代码及详细解释,确保在常规 Python 环境下可以运行。


一、文本预处理的常见需求和作用

在自然语言处理(NLP)任务(如机器学习、深度学习、大模型开发)中,原始文本数据通常会包含各种噪声,例如:

  • 多余的空格、换行符、特殊符号
  • HTML 标签、URL、邮箱等
  • 语言层面的问题:大小写不统一、拼写错误
  • 词形变化(went/walked 等过去式与原型 go/walk;dogs/buses 等名词复数形式)
  • 不必要的词(常见停用词,如 the, is, at, on 等)

常见预处理目标

  1. 降噪 (Noise Reduction)
    去除与任务无关或影响模型训练的噪声特征。
  2. 统一化 (Normalization)
    包括大小写统一、词形还原(Lemma)或词干提取(Stem)、去除标点符号等。
  3. 结构化 (Tokenization 等)
    将文本拆分成词元(tokens),或更细粒度的子词元(subwords)。
  4. 过滤 (Filtering)
    去除某些非必要信息,如停用词(stop words)等。
  5. 增强 (Augmentation)
    并非必需,但常见于深度学习中,如同义词替换、随机删除等,以增加鲁棒性。

二、常见的文本预处理方法概览

以下方法往往会按需组合使用:

  1. 大小写转换(Lowercasing / Uppercasing)
    • 便于词频统计、一致性处理。多数英文 NLP 任务倾向统一转换为小写(除非对大小写敏感)。
  2. 去除 HTML 标签、URL、邮箱、货币符号等
    • 使用正则表达式或简单字符串操作。
  3. 去除标点符号 (Punctuation Removal)
    • 常见标点,如 . , ! ? : ; " ' - 等。
  4. 分词 (Tokenization)
    • 将句子拆分为词或子词。英文可使用空格拆分或更复杂的工具,中文需使用专门的分词器。
  5. 停用词去除 (Stop Words Removal)
    • 语言中出现频率高但对上下文意义贡献不大的词,如 the, a, an, of, is 等。
  6. 词干提取 (Stemming)
    • 如 Porter Stemmer 或 Snowball Stemmer,把单词简化为词干(如 “studies”→“studi”)。
  7. 词形还原 (Lemmatization)
    • 基于词典/词法规则,将不同时态、复数形式统一到词的原型(如 “studies”→“study”)。
  8. 子词切分 (Subword Tokenization)
    • 用于大模型(如 BERT、GPT)或深度学习中的细粒度切分。典型算法有 Byte-Pair Encoding (BPE)WordPiece 等。
  9. 数字化、特殊符号处理
    • 将数字统一替换为 <num>,或保持数字原状,视任务需要。

以下我们将使用 Python 及相关库演示以上方法的大部分实现。


三、示例数据准备

我们先模拟一段文本数据(用一个字符串列表表示)。请注意,此示例仅用于演示,真实场景可来自数据库、爬虫、csv 等形式。

# 模拟一段文本数据
text_corpus = [
    "I love Machine Learning! It's awesome.",
    "Data science is an interdisciplinary field. The quick brown fox jumps over the lazy dog, doesn't it?",
    "Visit us at https://example.com or send an email to contact@example.com!",
    "<html>This is a sample HTML snippet.</html> NLP can be fun :)"
]

四、分步骤预处理示例

下面演示从最基础的清洗开始,逐步进行各种预处理操作。为了演示效果,我们使用一些常见 Python 库(re, nltk),并保证无需外部数据文件即可运行。若你的环境没有安装 nltk,请先执行 pip install nltk

说明:如果环境中无法使用 NLTK,也可根据需求自行编写简单函数或使用其他库(如 spaCy,但需 pip install spacy 并下载语言模型)。

1. 基础清洗与正则替换

目标:

  • 将文本统一为小写
  • 移除 HTML 标签
  • 移除 URL
  • 移除邮箱
  • 去除多余的标点或特殊字符(可视情况而定)
import re

def basic_cleaning(text: str) -> str:
    # 1) 转小写
    text = text.lower()
    
    # 2) 移除 HTML 标签,如 <html>, <p> 等
    text = re.sub(r"<.*?>", " ", text)
    
    # 3) 移除 URL (http://xxx 或 https://xxx)
    text = re.sub(r"http\S+|www\S+", " ", text)
    
    # 4) 移除邮箱 (xxx@xxx.com)
    text = re.sub(r"\S+@\S+\.\S+", " ", text)
    
    # 5) 去除多余的标点符号,仅保留字母、数字、常见的符号
    #   视任务而定,这里演示删除大部分标点,只保留句子间空格
    text = re.sub(r"[^a-z0-9\s.,!?]", " ", text)
    
    # 6) 去除多余空格
    text = re.sub(r"\s+", " ", text).strip()
    
    return text

# 测试:
cleaned_corpus = [basic_cleaning(sentence) for sentence in text_corpus]
for i, (orig, c) in enumerate(zip(text_corpus, cleaned_corpus)):
    print(f"原文 {i}: {orig}")
    print(f"清洗后: {c}")
    print("-"*50)

解释

  • re.sub(r"<.*?>", " ", text):用空格替换任何形如 <...> 的 HTML 标签。
  • re.sub(r"http\S+|www\S+", " ", text):匹配并移除所有 URL。
  • re.sub(r"\S+@\S+\.\S+", " ", text):匹配并移除邮箱格式(极简匹配)。
  • re.sub(r"[^a-z0-9\s.,!?]", " ", text):只保留字母、数字、空格和部分标点(.,!?),其余替换为空格。
  • text.lower():统一转为小写。
  • \s+ 匹配连续空格,将其替换成单个空格;然后 .strip() 去掉首尾空格。

此时得到的是一个基本“干净”、大小写统一的语句列表。


2. 分词 (Tokenization)

常见做法:

  • 基于空格:适合简单场景,但对带标点的情况处理不佳。
  • NLTK 的 word_tokenize(对英文较友好)。
  • 自定义正则:如 re.split(r"\W+", text) 等。

此处演示 nltk.tokenize.word_tokenize

import nltk
# 第一次使用需要下载 punkt 包:
# nltk.download('punkt')

from nltk.tokenize import word_tokenize

def tokenize_text(text: str) -> list:
    tokens = word_tokenize(text)
    return tokens

tokenized_corpus = [tokenize_text(sentence) for sentence in cleaned_corpus]
for i, (clean_sen, toks) in enumerate(zip(cleaned_corpus, tokenized_corpus)):
    print(f"清洗后文本 {i}: {clean_sen}")
    print(f"分词结果: {toks}")
    print("-"*50)

解释

  • word_tokenize 会把标点符号、撇号等也当成单独词元拆分,比如 "doesn't" 会被拆成 ["does", "n't"],这在英文 NLP 里比较常见。

3. 停用词去除 (Stop Words Removal)

停用词指在文本中出现频率极高,但对主语义贡献不大或会干扰主题建模的词汇,如英语中的 the, a, an, of, is, are, was, were, ... 等。在 NLTK 中,可以使用内置的停用词表(需要 stopwords 数据包)。

# nltk.download('stopwords')
from nltk.corpus import stopwords

stop_words = set(stopwords.words('english'))  # 这里是英文停用词表

def remove_stopwords(tokens: list) -> list:
    filtered_tokens = [tok for tok in tokens if tok not in stop_words]
    return filtered_tokens

no_stopwords_corpus = [remove_stopwords(toks) for toks in tokenized_corpus]
for i, (toks, fs) in enumerate(zip(tokenized_corpus, no_stopwords_corpus)):
    print(f"分词结果 {i}: {toks}")
    print(f"去停用词后: {fs}")
    print("-"*50)

解释

  • 使用 set(stopwords.words('english')) 来加速 “是否在停用词表中” 的判断。
  • 如果用户有自定义停用词表,可以自行扩充或删减。

4. 词干化(Stemming)和词形还原(Lemmatization)

Stemming:用算法直接截断或替换单词末尾,得到词干(例如 using PorterStemmerSnowballStemmer)。
Lemmatization:基于词典规则,还原到词的原型(例如 studiesstudywentgo)。英文中常用 WordNetLemmatizer

下面以 NLTK 为例。

# nltk.download('wordnet')
from nltk.stem import PorterStemmer, WordNetLemmatizer

stemmer = PorterStemmer()
lemmatizer = WordNetLemmatizer()

def stem_and_lemmatize(tokens: list):
    stemmed = [stemmer.stem(tok) for tok in tokens]
    # WordNetLemmatizer 需要指定词性(pos),默认为名词,此处示例只做简单还原
    lemmed = [lemmatizer.lemmatize(tok) for tok in tokens]
    return stemmed, lemmed

for i, tokens in enumerate(no_stopwords_corpus):
    s, l = stem_and_lemmatize(tokens)
    print(f"去停用词后:{tokens}")
    print(f"Stem 结果:{s}")
    print(f"Lemma结果:{l}")
    print("-"*50)

解释

  • PorterStemmer 的规则较简单,可能产生非真实单词,如 “studies”→“studi”。
  • WordNetLemmatizer 依赖词典,根据单词本身的原形进行还原,但是需确定词性会更准确(如 lemmatizer.lemmatize(word, pos='v') 来处理动词)。

具体业务中,可二选一,或在不同场景尝试哪种更有效。


5. 子词切分 (Subword Tokenization)

在大模型(如 BERT、GPT)中,子词切分是非常重要的一步。常见算法:

  • Byte-Pair Encoding (BPE)
  • WordPiece
  • SentencePiece

这里示例一个简单的 BPE 思想演示(并不完整,只作概念说明)。现实中常用库如 tokenizers(HuggingFace) 或 sentencepiece(Google)来训练和使用子词模型。

BPE 核心思路

  1. 统计语料中出现频率最高的字符对(bigram)。
  2. 将该字符对合并为一个新的“子词符号”。
  3. 反复迭代,直到达到子词表大小或满足其他停止条件。

下面的示例仅演示核心过程的简化版本(不涉及多轮迭代训练),让你了解大致机制。

# 一个极简BPE演示:基于当前句子的字符频率合并
from collections import Counter, defaultdict

def bpe_tokenize(sentence, merges=2):
    """
    sentence: 输入字符串(假设已清洗、转小写)
    merges: 合并次数(越大,子词越长)
    此函数仅用于演示BPE核心合并机制,不是完整实现
    """
    # 初始:把句子视作字符列表,用空格隔开
    # 例如 "data" -> ["d", "a", "t", "a"]
    tokens = list(sentence.replace(" ", "▁"))  # 用下划线表示空格
    #▁可以视为空格符号,用来和字符区分
    
    for _ in range(merges):
        # 1. 统计所有相邻字符对频率
        pairs = Counter()
        for i in range(len(tokens)-1):
            pair = (tokens[i], tokens[i+1])
            pairs[pair] += 1
        
        if not pairs:
            break
        
        # 2. 找到出现次数最多的字符对
        best_pair = max(pairs, key=pairs.get)
        
        # 3. 合并该字符对
        #   在 tokens 中出现 (a,b) 的地方替换成 ab
        merged = []
        skip = False
        for i in range(len(tokens)):
            if skip:
                skip = False
                continue
            if i < len(tokens)-1 and (tokens[i], tokens[i+1]) == best_pair:
                merged.append(tokens[i] + tokens[i+1])
                skip = True
            else:
                merged.append(tokens[i])
        tokens = merged
    
    return tokens

# 示例句子
example_sentence = "i love machine"
print("原句:", example_sentence)
bpe_result = bpe_tokenize(example_sentence, merges=3)
print("BPE切分结果:", bpe_result)

解释

  • 在真实应用中,BPE 需要在整个语料库上迭代多次统计频率最高的字符对,并更新“子词”表,直至达到预期词表大小。
  • 以上代码只是演示如何把相邻字符合并,不具有生产环境可用性;但可以帮助理解 BPE 的基本流程。

五、综合示例:完整预处理流程

结合前面的步骤,可以组合成为一个 pipeline。示例:

def preprocess_pipeline(text_list):
    """
    综合使用:
    1) 基础清洗 (含大小写转化、去HTML/URL/邮箱、去标点等)
    2) 分词
    3) 去停用词
    4) Lemmatization
    """
    # 1) 基础清洗
    cleaned = [basic_cleaning(t) for t in text_list]
    
    # 2) 分词
    tokenized = [word_tokenize(t) for t in cleaned]
    
    # 3) 去停用词
    filtered = []
    for tokens in tokenized:
        filtered_tokens = [tok for tok in tokens if tok not in stop_words]
        filtered.append(filtered_tokens)
    
    # 4) 词形还原 (Lemmatization) 简化示例
    final_output = []
    for tokens in filtered:
        lemmed = [lemmatizer.lemmatize(tok) for tok in tokens]
        final_output.append(lemmed)
    
    return final_output

processed_corpus = preprocess_pipeline(text_corpus)
for i, (orig, pro) in enumerate(zip(text_corpus, processed_corpus)):
    print(f"原文 {i}: {orig}")
    print(f"最终预处理结果: {pro}")
    print("="*70)

解释

  • 这里使用了前面定义的 basic_cleaningword_tokenizestop_wordsWordNetLemmatizer
  • 在实际项目中,你可以进一步细化或调整步骤,例如是否需要大小写转换、是否需要词干化、是否保留数字符号等。

六、常见的注意事项

  1. 根据任务需求选择步骤:并非所有预处理步骤都需要。例如,在情感分析中,有时保留标点符号(如 !?)可能帮助模型理解情绪表达;去除或保留停用词会影响模型效果,应当视情况调参。
  2. 语言差异:中文、阿拉伯语等语言不依赖空格分词,需要额外的分词工具(如 jieba, pkuseg, spaCy 中文模型等)。
  3. 大小写敏感性:命名实体识别、关键词搜索等任务,可能需要保留原有的大小写信息。
  4. 保留原始文本:有些复杂任务(如纠错、摘要)需要在模型预测后映射回原文本,应保留映射关系。
  5. 性能影响:预处理操作可能在大型数据集上耗费大量时间,需要考虑并行化(多进程、多线程)或分批预处理等策略。

七、总结

在 NLP 中,无论是传统机器学习、深度学习还是大模型(如 BERT/GPT)开发,都需要对文本数据做一定程度的预处理。这些预处理操作往往包括:

  • 噪声清理(HTML、URL、邮箱、特殊字符等)
  • 正则化(大小写统一、去除标点、数字处理等)
  • 分词与停用词过滤(减少无关词)
  • 词形还原或词干化(减少形态变体对模型的干扰)
  • 子词切分(BPE/WordPiece 等)(对大模型极为重要)

具体使用哪种预处理步骤,需根据 任务目标、数据特点 来决定,并结合实验结果进行调整和优化。

以上所有代码都可以本地直接运行,所需外部数据仅用 Python 列表 text_corpus 模拟。同时,为了更好地运用预处理结果,建议在实际场景中对完整语料执行这些步骤,并做相应的性能评估


至此,我们详尽介绍了 NLP 常见的文本预处理方法及其思路,并提供可运行的示例代码。希望能帮助你在文本数据清理、模型训练中快速上手并灵活改进。

哈佛博后带小白玩转机器学习】 哔哩哔哩_bilibili

总课时超400+,时长75+小时

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值