前言
在自然语言处理(NLP)项目中,数据预处理往往占据整个流程的60%-80%的工作量。一个高质量的预处理流程,直接决定了模型训练的成败。
本文将以英法双语句对为例,手把手带你实现一个完整的NLP数据预处理流水线,涵盖:
-
文本清洗与规范化
-
双语语料构建
-
词表生成与索引映射
一、数据清洗:让文本"规矩"起来
原始文本通常包含大小写混杂、多余空格、特殊符号等问题,我们需要一个标准化函数来处理。
1.1 清洗函数的设计思路
import re
def normalizeString(s):
"""
字符串规范化处理
流程:转小写 → 标点符号标准化 → 过滤非法字符
"""
# 1. 转小写并去除首尾空白
s = s.lower().strip()
# 2. 在 .!? 前加空格(便于后续分词)
s = re.sub('([.!?])', r' \1', s)
# 3. 保留字母和基本标点,其余替换为空格
# [^a-zA-Z.!?] 表示:除大小写字母、.!?外的任意字符
# + 表示匹配1次以上
s = re.sub('[^a-zA-Z.!?]+', ' ', s)
return s
1.2 为什么这样设计?
| 步骤 | 目的 | 示例 |
|---|---|---|
lower().strip() | 统一大小写,去除脏数据 | " I'm Tired " → "i'm tired" |
| 标点前加空格 | 让标点成为独立token | "tired." → "tired ." |
| 过滤非法字符 | 只保留有效字符 | "hello@world!" → "hello world !" |
二、构建双语语料:将原始数据转化为结构化数据
2.1 数据格式说明
原始文件每行格式为:英文句子\t法文句子
i m tired . je suis fatigue !
how are you comment allez vous
2.2 读取并清洗数据
def load_and_clean_data(data_path):
"""读取原始文件,生成清洗后的双语句对"""
with open(data_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 嵌套推导式:外层遍历行,内层遍历该行的英文和法文
# 对每个语言文本独立应用清洗函数
pairs = [
[normalizeString(s) for s in line.split('\t')]
for line in lines
]
return pairs
关键点:内层循环 for s in line.split('\t') 是必须的,因为:
-
normalizeString接收的是字符串,而非列表 -
需要分别清洗英文和法文,保持双语独立处理
三、构建词表:从文本到数字的桥梁
神经网络无法直接处理字符串,我们需要建立单词→索引的映射关系。
3.1 词表设计要点
| 特殊标记 | 用途 | 索引 |
|---|---|---|
SOS | 句子起始标记 | 0 |
EOS | 句子结束标记 | 1 |
3.2 完整的词表构建流程
def build_vocab(pairs):
"""
构建双语词表
返回:英法两种语言的 正向映射、反向映射、词表大小
"""
# 初始化:预置SOS和EOS
eng_word2idx = {'SOS': 0, 'EOS': 1}
fre_word2idx = {'SOS': 0, 'EOS': 1}
eng_vocab_size = 2 # 已有2个特殊标记
fre_vocab_size = 2
# 遍历所有句对,收集单词
for eng_sent, fre_sent in pairs:
# 处理英文句子
for word in eng_sent.split(' '):
if word not in eng_word2idx:
eng_word2idx[word] = eng_vocab_size
eng_vocab_size += 1
# 处理法文句子
for word in fre_sent.split(' '):
if word not in fre_word2idx:
fre_word2idx[word] = fre_vocab_size
fre_vocab_size += 1
# 构建反向映射(用于解码输出)
eng_idx2word = {v: k for k, v in eng_word2idx.items()}
fre_idx2word = {v: k for k, v in fre_word2idx.items()}
return eng_word2idx, eng_idx2word, eng_vocab_size, \
fre_word2idx, fre_idx2word, fre_vocab_size
3.3 为什么需要反向映射?
-
正向映射(word→index):编码输入,送入模型
-
反向映射(index→word):解码输出,转换为人类可读的文本
四、完整的预处理流水线
将以上模块整合为一个完整的函数:
def data_preprocessing(data_path):
"""
完整的数据预处理流程
返回:英法词表、反向映射、词表大小、清洗后的句对
"""
# Step 1: 读取并清洗数据
with open(data_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
pairs = [[normalizeString(s) for s in line.split('\t')] for line in lines]
print(f'总句对数: {len(pairs)}')
# Step 2: 构建词表
eng_w2i, eng_i2w, eng_size, fre_w2i, fre_i2w, fre_size = build_vocab(pairs)
# Step 3: 统计信息
print(f'英语词表大小: {eng_size}')
print(f'法语词表大小: {fre_size}')
return eng_w2i, eng_i2w, eng_size, fre_w2i, fre_i2w, fre_size, pairs
五、最佳实践与注意事项
5.1 数据质量检查
# 检查是否有空行或格式错误的行
valid_pairs = []
for pair in pairs:
if len(pair) == 2 and pair[0].strip() and pair[1].strip():
valid_pairs.append(pair)
5.2 词表大小控制
对于大规模语料,建议设置最大词表大小,将低频词替换为<UNK>标记:
MAX_VOCAB_SIZE = 50000
if eng_vocab_size > MAX_VOCAB_SIZE:
# 保留高频词,其余映射为<UNK>
pass
5.3 性能优化
-
使用
set加速词表查询(word in vocab操作) -
批量处理代替逐行处理
六、总结
本文实现了一个完整的NLP数据预处理流水线:
| 阶段 | 输入 | 输出 | 核心技术 |
|---|---|---|---|
| 文本清洗 | 原始文本 | 规范化文本 | 正则表达式 |
| 语料构建 | 原始行数据 | 双语列表对 | 列表推导式 |
| 词表生成 | 清洗后语料 | 词表映射 | 字典+计数器 |
核心思想:将非结构化的自然语言,转化为结构化的数值索引,为神经网络模型提供"可消化"的输入。
掌握数据预处理,你就掌握了NLP项目的半壁江山。希望本文能帮你少走弯路,构建更健壮的NLP系统!
852

被折叠的 条评论
为什么被折叠?



