精通文本分析——涉及字符串与 Python 正则表达式的 NLP 数据预处理任务

62 阅读16分钟

3.1 为什么你应该阅读本章

首先,说明我们的顶层目标。为简单起见,我们要学习一项新的实操技能:文本分析 / 自然语言处理(NLP)。这项技能在全球就业市场上需求很大,对各类公司都很有用,不论其业务领域为何。若只学习理论或做概念性研究,无法获得希望中的实操能力。

我们需要工具与技术来分析大多数机构中大量可用的文本数据。我们使用面向 NLP 的以及其它的 Python 库,作为从大量非结构化文本中获得可执行洞见的工具。这些 Python 库在分析与评估结果时大量依赖相关的统计模型。要获得实操能力,必须熟悉这些 Python 库与数学算法——归根结底就是用 Python 编程,除非亲自动手,否则无法掌握。因此,准备好在本章进行一些有趣的实战吧。

3.2 用于语言处理的 Python

近几年,Python 因其易用性、丰富的 NLP 专用库与庞大的全球社区,已成为 NLP 的重要语言。其语法易学易用,代码可读性高,因而对初学者与有经验的程序员都很友好。Python 能高效处理大批量文本数据,已有大量实例将其用于文本分类、情感分析、语言翻译等 NLP 任务。Python 还能灵活集成各种机器学习模型,从而提高其在 NLP 任务中的实用性。

NLP 社区广泛使用若干 Python 库,它们因功能强大且易用而成为 NLP 工作的中坚。NLTK(Natural Language Toolkit)是常用的库之一,提供了分词、词干提取、标注等易用工具。spaCy 是另一个以速度和效率著称的工业级 NLP 库,支持深度学习工作流。TextBlob 对初学者友好,提供了简单的 API。其他库如 Gensim 专注主题建模与文档相似度;Hugging Face 则以其最先进的变换器(transformer)模型闻名,广泛用于高级 NLP 项目(包括生成式 AI)。本段中的许多术语你可能尚不熟悉,本章或后续章节会逐一讲解并实操演示。

3.2.1 文本分析项目生命周期(通用 NLP 管道)

在典型的机器学习(ML)项目中,常见步骤包括:数据收集、预处理、特征提取、模型训练、评估与部署。这些按序执行的步骤通常称为 ML 管道。ML 项目中的数据通常是结构化的,包含数值与非数值(类别型)数据,此类数据通常需要标准化、缩放与变换等预处理。一旦数据准备好,便可在其上训练 ML 模型,并用准确率或 F1 等指标评估模型结果。最后一步将模型部署到生产以做真实预测。ML 管道需要持续监控与维护,很多步骤是循环的。图 3-1 展示了通用 ML 管道的步骤。

而 NLP 项目管道则处理文本数据,包含特有的预处理步骤,例如分词(tokenization)、词干提取(stemming)、词形还原(lemmatization)与停用词去除等(后面会逐一学习)。完成这些文本预处理后,需要将文本转换为数值表示(类似 ML 管道),但 NLP 中常用的表示技术不同,典型方法有词频—逆文档频率(TF–IDF)、词嵌入(word embeddings)或基于 Transformer 的模型。你将在本书后续章节学习这些内容。经处理并转为数值表示的文本数据再由合适的机器学习或深度学习模型训练,训练良好的模型可执行文本分类、情感分析、命名实体识别等任务。

到目前为止,你可能已经注意到 ML 与 NLP 在训练与部署模型时有许多相似之处。NLP 管道包含若干文本处理步骤;在生产中对训练过的模型进行运维(operations and maintenance)在两者间也相似,并包含许多重复的循环过程。我们建议参考标准的 MLOps 书籍以获取生产级模型维护与运维的更多细节。图 3-2 展示了通用的 NLP 管道。可将图 3-2 与图 3-1 对比,观察两者的异同。

许多 NLP 管道中的术语此刻对你可能还很陌生,但本章及后续章节会逐步解释这些术语并提供实操技能,帮助你构建和处理此类管道。接下来我们从基本的字符串处理开始,然后进入本章的相对高级主题。

3.2.2 Python 中的字符串处理

字符串(String)是 Python 的一种数据结构,表示字符序列。字符串在 Python 中是不可变类型:一旦创建就不能修改。Python 字符串在大多数文本应用中被广泛使用,用于存储与操作文本数据(如人名、地址、城市名等)。高效处理字符串操作是任何 NLP 任务的基础。下面用 Python 代码示例演示基本字符串操作;W3Schools 网站是学习与练习 Python 字符串操作的不错资源。

创建字符串

可以用单引号或双引号创建字符串,如 Listing 3-1 所示。

# Creating a demo string using single quotes.
string_single_quotes = 'This is a single quote string for demo'
print(string_single_quotes)
# 输出:
# This is a single quote string for demo

# Creating a demo string using double quotes.
string_double_quotes = "This is a single quote string for demo"
print(string_double_quotes)
# 输出:
# This is a double quote string for demo

你也可以用三引号(''' ... ''' 或 """ ... """)创建多行字符串(例如段落)。若遇到困难,可参考 W3Schools。

访问字符串中的字符

字符串在 Python 中可视为数组。图 3-3 以字符串 "Python" 为例,展示字符的索引:索引 0 对应首字符 'P',最后字符 'n' 对应索引 5。示例代码见 Listing 3-2。

# Accessing characters of a string by index numbers
string_single_quotes = 'This is a single quote string for demo'
print(string_single_quotes)
# 输出整个字符串
len(string_single_quotes)
# 输出 38

# 访问该字符串中第一个 'i'
string_single_quotes[2]
# 输出:'i'

# 获取字符串中第一个 'i' 的索引位置
string_single_quotes = 'This is a single quote string for demo'
index_of_i = string_single_quotes.index('i')
print("The index of the first 'i' is:", index_of_i)
# 输出:The index of the first 'i' is: 2

更基本的字符串操作

表 3-1 列出若干常用字符串方法;Listing 3-3 展示了这些方法的代码用法示例。

# Slicing
for_slice_string = "String for slicing"
print(for_slice_string[2:5])   # rin
# Slicing mid-way till end.
print(for_slice_string[11:])   # slicing
# Slicing from beginning till mid-way.
print(for_slice_string[:6])    # String

# Altering strings - converting in upper case.
demo_string = "this is a demo string in small letters"
print(demo_string.upper())
# THIS IS A DEMO STRING IN SMALL LETTERS

# Converting in lower case
demo_string = "THIS IS A DEMO STRING IN capital LETTERS"
print(demo_string.lower())
# this is a demo string in capital letters

# Removing the extra blank spaces.
demo_string = " Remove blank spaces from this string "
print(demo_string.strip())
# Remove blank spaces from this string

# Replacing a substring within a string with an alternative substring.
demo_string = "Replace this with THAT"
print(demo_string.replace("this", "THAT"))
# Replace THAT with THAT

# Splitting a string at ','.
split_demo = "String, for, split, demo"
print(split_demo.split(",")) # Creates a Python list with individual words.
# ['String', ' for', ' split', ' demo']

# Splitting a string at a specific character.
split_demo = "My house # is 7076 at #22nd street"
print(split_demo.split("#")) # Creates a Python list with individual words.
# ['My house ', ' is 7076 at ', '22nd street']

3.2.3 引介:用于文本处理的正则表达式(Regular Expressions)

正则表达式(RegEx)是文本数据预处理中的重要工具。RegEx 可用于抽取特定模式、清洗文本数据,从而为训练 NLP 模型准备数据。本节介绍正则表达式并演示其在数据预处理中如何使用。

正则表达式由一系列字符组成,用以构造搜索模式,可用于判断文本是否匹配指定模式。现在通过代码示例更好地理解 RegEx。先访问 www.w3schools.com/python/pyth… 查阅 RegEx 元字符表;没有掌握这些元字符就无法编写或理解 RegEx 代码。Listing 3-4 展示了 Python 中常用的 RegEx 方法示例。

# Python RegEx examples.
# A list of all matches.
import re # the RegEx library.
# Below is a demo sentence.
demo_txt = "The referee had to match the players' skills to ensure a fair match."
# Find all occurrences of the word "match".
lst = re.findall("match", demo_txt)
print(lst)
# ['match', 'match']

# Split the string at each white space.
demo_txt = "The referee had to match the players' skills to ensure a fair match."
lst = re.split("\s", demo_txt)
print(lst)
# ['The', 'referee', 'had', 'to', 'match', 'the', "players'", 'skills', 'to', 'ensure', 'a', 'fair', 'match.']

# Let's now print the part of the string with a match.
demo_txt = "Last week I visited Spain"
# Visit W3school to find meaning of metacharacter "\bS\w+"
match = re.search(r"\bS\w+", demo_txt)
print(match.group())
# Spain

# The search functions. Search for the first white-space.
demo_txt = "I visited Europe and the US last year."
result_index = re.search("\s", demo_txt)
print("The location of first white-space character is at index:", result_index.start())
# The location of first white-space character is at index: 1

# Replace each blank space character with "X".
demo_txt = "I visited Europe and the US last year."
result_string = re.sub("\s", "X", demo_txt)
print(result_string)
# IXvisitedXEuropeXandXtheXUSXlastXyear.

# Replace the first three blanks with "X"
demo_txt = "I visited Europe and the US last year. It was freezing cold."
result_string = re.sub("\s", "X", demo_txt, 3)
print(result_string)
# IXvisitedXEuropeXand the US last year. It was freezing cold.

# Return a Matching Object.
demo_txt = "The rained in Mumbai last night. Mumbai is biggest metro of India."
result_string = re.search("Mumbai", demo_txt)
print(result_string)
# <re.Match object; span=(14, 20), match='Mumbai'>

# Find the start-and end-position of the first match.
import re
demo_txt = "The rained in entire last night. Spain is cold these days."
result_string = re.search(r"\bS\w+", demo_txt)
if result_string:
    print(result_string.span())
else:
    print("No match found.")
# (33, 38)

# Find the matching part of the string.
demo_txt = "The rained in entire Spain last night. Spain is cold."
result_string= re.search(r"\bS\w+", demo_txt)
print(result_string.group())
# Spain

(Listing 3-4:Python 正则表达式示例)

3.3 RegEx 在真实(NLP)场景中的应用

在文本分析(或 NLP)任务中,正则表达式(RegEx)在模式匹配与字符串操作方面非常有用。Python 的 RegEx 提供了基于预定义模式快速匹配、搜索与处理文本的方法。下面列出若干现实中常见的 Python RegEx 应用场景。以下代码片段可用于数据预处理与清洗步骤,为训练 NLP 模型准备数据。

3.3.1 验证电话号码格式

可以用 RegEx 验证一组电话号码是否符合精确格式(例如 234-567-9873)。Listing 3-5 给出该应用的 Python 代码示例。

import re
# 创建一个示例电话号码列表。
# 有效的电话号码格式为 "123-456-7890"(其余为无效)。
phone_numbers_lst = [
    "567-789-7870",
    "986-666-3220",
    "1234567890",
    "135-42-7786",
    "xyz-abc-cdef"
]
# 用于验证电话号码的正则模式
re_for_phone_pattern = r"^\d{3}-\d{3}-\d{4}$"
# 使用此正则模式验证列表中的电话号码。
for number in phone_numbers_lst:
    if re.match(re_for_phone_pattern, number):
        print(f"'{number}' is a valid phone number format.")
    else:
        print(f"'{number}' is invalid phone number format.")

示例输出(原文):

'567-789-7870' is a valid phone number format.
'986-666-3220' is a valid phone number format.
'1234567890' is invalid phone number format.
'135-42-7786' is invalid phone number format.
'xyz-abc-cdef' is invalid phone number format.

3.3.2 查找与替换(Search and Replace)

“查找与替换”是许多文本或表格工具的常见功能。Python 的 re 模块提供更强大的查找与替换能力,便于进行精确且灵活的数据操作(见 Listing 3-6)。

import re
# 创建示例句子。
demo_text = "The rain, rain, rain fell all day."
# 我们将把 "rain" 替换为 "water"。
pattern = r"rain"
replaced_demo_text = re.sub(pattern, "water", demo_text)
print("Original text:", demo_text)
print("Replaced text:", replaced_demo_text)

示例输出:

Original text: The rain, rain, rain fell all day.
Replaced text: The water, water, water fell all day.

3.3.3 日期格式化

在 NLP/数据处理里,经常需要把日期从一种格式转换为另一种格式。下例展示如何用正则把 MM-DD-YYYY 格式转换为 YYYY-MM-DD(Listing 3-7)。

import re
# 示例日期,格式为 MM-DD-YYYY。
original_date_str = "10-08-2024"
# 匹配原始格式 MM-DD-YYYY 的正则模式
original_pattern = r"(\d{2})-(\d{2})-(\d{4})"
# 将日期重格式化为 YYYY-MM-DD
newly_formatted_date = re.sub(original_pattern, r"\3-\1-\2", original_date_str)
print(newly_formatted_date)

输出:

2024-10-08

3.3.4 词频统计(Word Counting)

词频统计是计算文本中每个单词出现频率的过程,有助于识别常用词或关键主题。可以使用正则提取单词,再用 collections.Counter 统计频率(Listing 3-8)。

import re
from collections import Counter
Sample_text_para = """
            Word counting is finding the frequency of every word in a given text sentence or paragraph. Word count is useful for analyzing of the content. The interest here is in finding the most frequently used words. Python RegEx module can be utilized for this purpose. Below we will demonstrate how to do it.
            """
# 使用正则匹配单词(全部小写以归一化)
words = re.findall(r'\b\w+\b', Sample_text_para.lower())
# 统计词频
word_count = Counter(words)
print(word_count)

示例输出(原文):

Counter({'the': 4, 'word': 3, 'is': 3, 'finding': 2, 'of': 2, 'in': 2, 'for': 2, 'counting': 1, ... })

3.3.5 日志文件分析(Log File Analysis)

日志文件记录了服务器或设备的大量活动信息。可用正则解析日志、识别模式、提取特定数据或筛选错误记录(ERROR)。下面示例演示如何匹配 ERROR 条目(Listing 3-9)。

import re
# 构造一个示例日志文件,然后仅筛选出 ERROR 消息。
sample_log_data = """
2023-04-10 12:14:22 ERROR Could not connect to the server
2023-04-10 12:15:12 INFO User login timeout
2023-04-10 12:16:35 WARNING Out of memory
2023-04-10 12:17:34 ERROR Server module failure
"""
# 匹配 ERROR 条目的正则模式
re_pattern_for_error = r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ERROR .*"
# 提取所有 ERROR 条目
error_messages = re.findall(re_pattern_for_error, sample_log_data)
# 逐行打印每个 ERROR 信息
for error in error_messages:
    print(error)

输出:

2023-04-10 12:14:22 ERROR Could not connect to the server
2023-04-10 12:17:34 ERROR Server module failure

3.3.6 字符串清洗(String Cleaning)

文本预处理中常需清除特殊字符、URLs、无用符号等以便后续分析。下面示例用正则去除特殊字符和 URL(Listing 3-10)。

import re
# 示例推文文本
sample_text = "Hi!!! I am John. Welcome @my house. You can get more info @ www.sk.com"
# 用正则去除特殊字符与 URL
# 先移除指定特殊字符
usable_text = re.sub(r"[!@#%&*:]", "", sample_text)
# 再移除 URL
usable_text = re.sub(r"http\S+|www.\S+", "", usable_text)
print(usable_text)

输出:

Hi I am John. Welcome my house. You can get more info

3.3.7 分词(Tokenization)

分词是将较大文本切分为单词或句子的过程,是多数 NLP 工作流中的核心预处理步骤。可以用正则基于空格、标点或其他分隔符来切分文本。下面展示一个简单方法与一个更“专业”的函数版本(Listing 3-11)。

简单分词示例:

import re
sample_text = """ Tokenization is a central step in any NLP task.
In this process, larger text chunk is broken down into words
or sentences, to keep text analysis simple.
"""
tokens = re.findall(r'\b\w+\b', sample_text)
print(tokens)

更专业的分词函数:

import re
def text_tokenizer(text_to_tokenize):
    """
    将输入文本按非单词字符切分为单词列表。
    Args:
        text_to_tokenize (str): 待分词的文本
    Returns:
        list: 分词后得到的单词列表
    """
    # 按非单词字符切分
    tokens = re.split(r'\W+', text_to_tokenize)
    # 过滤空字符串
    tokens = [token for token in tokens if token]
    return tokens

sample_text = """ Tokenization is a central step in any NLP task.
In this process, larger text chunk is broken down into words
or sentences, to keep text analysis simple.
"""
tokens = text_tokenizer(sample_text)
print(tokens)

3.3.8 文本标准化(Text Normalization)

文本标准化是把文本转换为统一格式以减少变体带来的影响的过程,常包括小写化、去标点、展开缩写、替换特殊字符等。标准化能让同一词或短语的不同形式被视为同一项,从而提升训练 NLP 模型时的准确性。以下为示例函数(Listing 3-12)。

import re
def text_normalizer(text_to_normalize):
    """
    将输入文本标准化:小写化、移除标点并将特殊字符替换为空格。
    Args:
        text_to_normalize (str): 待标准化文本
    Returns:
        str: 标准化后的文本
    """
    # 小写化
    text_to_normalize = text_to_normalize.lower()
    # 将非字母数字与非空白字符替换为空格
    text_to_normalize = re.sub(r'[^\w\s]', ' ', text_to_normalize)
    # 合并多余空白并去除首尾空格
    text_to_normalize = re.sub(r'\s+', ' ', text_to_normalize).strip()
    return text_to_normalize

sample_text = "Hi!!! This is Stella here. I'll come this Evening with you to market!!!!!!"
normalized_text = text_normalizer(sample_text)
print(normalized_text)

输出:

hi this is stella here i ll come this evening with you to market

本节展示了 RegEx 的多种有用场景。RegEx 示例能帮助你理解其在现实问题中的广泛应用。不过,要针对特定任务设计精确的正则模式,你需要熟悉各个元字符与语法规则。建议参考 W3Schools 的 RegEx 页面以及网络上的大量教程与文章以深入掌握正则表达式的写法与调试技巧。

3.4 本章回顾

本章首先介绍了 Python 作为处理 NLP 任务的一种多功能工具的重要性。由于其强大的库(无论是否专门用于 NLP)与全球范围内活跃的社区支持,Python 在工业界被广泛采用。接着我们考察了通用的 NLP 管道,梳理了文本分析项目的各个阶段。随后将注意力转向 Python 中的字符串处理;在学习各种字符串处理方法的过程中,你掌握了如何高效地操作与分析文本字符串。我们还学习了正则表达式(RegEx),并重点讲解了它们在数据预处理中的应用。从数据清洗到文本抽取,Python 的 RegEx 模块已被证明是文本处理工具箱中不可或缺的利器。掌握这一关于 Python 的坚实基础,将为后续更高级的语言处理与文本分析内容打下良好基础。在后续章节中,我们的目标是把概念讲解与动手技能做到平衡,请继续关注!

3.5 练习

本章侧重实操技能,主要是正则表达式(RegEx)。请为下列 NLP 数据预处理任务编写 Python RegEx 程序:

  • 电子邮件地址验证
  • URL 解析
  • 从文本中提取日期
  • 去除 HTML 标签
  • 密码强度检测
  • 验证信用卡号
  • 从段落中抽取句子
  • 检测重复单词

从练习题 1 以及本章示例中,你可能发现为特定任务构造合适的 RegEx 模式有时较为繁琐。在这种情况下,寻找替代且更简单的方法往往更好。请上网查找并比较是否存在本章所述任务的替代实现方式,聚焦 Python RegEx 模块在现实应用中的实务做法与替代方案。

3.1 参考文献

  1. NTNU (2020). NLP Pipeline #. Available at: alvinntnu.github.io/python-note…. 最后访问:2024 年 8 月 7 日。
  2. Oracle Cloud (2023). Pipelines. Available at: docs.oracle.com/en-us/iaas/…. 最后访问:2024 年 8 月 7 日。
  3. W3Schools (2024). Available at: www.w3schools.com/python/pyth…. 最后访问:2024 年 8 月 7 日。
  4. Scientech Easy (2023). Available at: www.scientecheasy.com/2023/01/str…. 最后访问:2024 年 8 月 7 日。
  5. W3Schools (2024). Python RegEx. Available at: www.w3schools.com/python/pyth…. 最后访问:2024 年 8 月 7 日。