自然语言处理NLP的数据预处理:从原始文本到模型输入(MindSpore版)
自然语言处理(NLP)任务中,数据预处理的质量直接影响模型性能。即使是最先进的大模型,在未经处理的原始文本上也难以发挥作用。本文将详细介绍 NLP 数据预处理的核心步骤,并基于 MindSpore 框架提供代码实现,帮助你构建完整的预处理流水线。
为什么数据预处理至关重要?
原始文本数据通常存在噪声(如错别字、特殊符号)、格式不一致(如大小写混合)、冗余信息(如重复段落)等问题。预处理的目标是:
- 统一数据格式,降低模型学习难度
- 去除噪声,减少干扰信息
- 将文本转化为模型可理解的数值形式
- 适应特定任务需求(如分类、翻译、摘要)
下面以情感分析任务为例(输入文本,输出正面 / 负面标签),演示完整预处理流程。
数据预处理核心步骤
1. 数据加载与探索
首先需要加载数据并初步探索,了解数据分布(如文本长度、标签分布),为后续处理提供依据。
示例数据格式(CSV):
text,label
"这部电影太精彩了!推荐大家去看",1
"剧情拖沓,浪费时间...",0
"演员演技在线,但结局有点仓促",1
...
MindSpore 加载代码:
import mindspore.dataset as ds
import pandas as pd
import matplotlib.pyplot as plt# 加载CSV数据
df = pd.read_csv("reviews.csv")
print(f"数据集规模:{len(df)}条")
print(f"标签分布:{df['label'].value_counts().to_dict()}")# 可视化文本长度分布
df["text_len"] = df["text"].apply(lambda x: len(x))
plt.hist(df["text_len"], bins=50)
plt.title("文本长度分布")
plt.xlabel("长度")
plt.ylabel("数量")
plt.show()# 转换为MindSpore数据集
dataset = ds.NumpySlicesDataset(df[["text", "label"]].values, column_names=["text", "label"])
2. 文本清洗
去除无关字符、统一格式,减少噪声干扰。常见操作包括:
- 去除特殊符号、标点
- 大小写转换(通常转为小写)
- 去除多余空格
- 处理中英文混合场景(如保留中文,清理无意义符号)
清洗函数实现:
import redef clean_text(text):# 去除URLtext = re.sub(r"http\S+", "", text)# 去除特殊符号和标点(保留中文、字母、数字)text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9\s]", "", text)# 转为小写(英文)text = text.lower()# 去除多余空格text = re.sub(r"\s+", " ", text).strip()return text# 应用清洗函数
dataset = dataset.map(operations=lambda x: (clean_text(x[0]), x[1]), input_columns=["text", "label"])
3. 分词(Tokenization)
将连续文本拆分为最小语义单位(词语或子词),是 NLP 的基础步骤。中文通常使用 jieba、THULAC 等工具,英文可直接按空格分词(或使用更复杂的子词分词)。
中文分词实现:
import jiebadef tokenize(text):# 中文分词,过滤空字符串tokens = [token for token in jieba.cut(text) if token.strip()]return tokens# 应用分词
dataset = dataset.map(operations=lambda x: (tokenize(x[0]), x[1]), input_columns=["text", "label"])
示例效果:
- 原始文本:"这部电影太精彩了!推荐大家去看"
- 清洗后:"这部电影太精彩了推荐大家去看"
- 分词后:["这部", "电影", "太", "精彩", "了", "推荐", "大家", "去看"]
4. 去除停用词
停用词是指在文本中频繁出现但语义贡献小的词(如 “的”、“是”、“在”),去除它们可以减少冗余,提升效率。
停用词处理:
# 加载停用词表(可自定义或使用公开表)
with open("stopwords.txt", "r", encoding="utf-8") as f:stopwords = set(f.read().splitlines())def remove_stopwords(tokens):return [token for token in tokens if token not in stopwords]# 应用去停用词
dataset = dataset.map(operations=lambda x: (remove_stopwords(x[0]), x[1]), input_columns=["text", "label"])
示例效果:
- 分词后:["这部", "电影", "太", "精彩", "了", "推荐", "大家", "去看"]
- 去停用词后:["电影", "精彩", "推荐", "大家"]
5. 词表构建与数值映射
模型无法直接处理文本,需要将词语转换为数值。这一步需要:
- 构建词表(记录词语与索引的映射)
- 将分词后的文本转换为索引序列
词表构建实现:
from collections import Counter# 收集所有词,统计频率
all_tokens = []
for data in dataset.create_dict_iterator():all_tokens.extend(data["text"])# 按频率排序,保留前5000个词(超参数)
word_counts = Counter(all_tokens).most_common(5000)
# 构建词表:预留0(PAD)、1(UNK)
vocab = {"<PAD>": 0, "<UNK>": 1}
for word, _ in word_counts:vocab[word] = len(vocab)print(f"词表大小:{len(vocab)}")# 保存词表
import json
with open("vocab.json", "w", encoding="utf-8") as f:json.dump(vocab, f, ensure_ascii=False)
数值映射:
def tokens_to_ids(tokens):# 未在词表中的词用<UNK>代替return [vocab.get(token, 1) for token in tokens]# 应用映射
dataset = dataset.map(operations=lambda x: (tokens_to_ids(x[0]), x[1]), input_columns=["text", "label"])
示例效果:
- 去停用词后:["电影", "精彩", "推荐", "大家"]
- 数值映射后:[56, 128, 34, 92](假设词表中对应索引)
6. 序列长度对齐(Padding/Truncation)
模型输入需要固定长度,因此需对序列进行截断(超长)或填充(不足)。
长度对齐实现:
import mindspore.dataset.transforms as C
import mindspore.dataset.vision as vision# 设定最大长度(根据前面的长度分布统计结果)
max_seq_len = 32def pad_sequence(ids):# 截断超长序列if len(ids) > max_seq_len:return ids[:max_seq_len]# 填充短序列(用<PAD>的索引0)else:return ids + [0] * (max_seq_len - len(ids))# 应用长度对齐
dataset = dataset.map(operations=lambda x: (pad_sequence(x[0]), x[1]), input_columns=["text", "label"])
7. 数据划分与格式转换
最后将数据集划分为训练集、验证集,并转换为模型可接受的格式(如 Tensor)。
最终处理步骤:
# 划分训练集(80%)和验证集(20%)
dataset = dataset.shuffle(buffer_size=len(df)) # 打乱数据
train_size = int(0.8 * len(df))
train_dataset, val_dataset = dataset.split([train_size, len(df) - train_size])# 转换为Tensor格式
train_dataset = train_dataset.map(operations=C.TypeCast(mstype.int32), input_columns=["text"])
train_dataset = train_dataset.map(operations=C.TypeCast(mstype.int32), input_columns=["label"])
val_dataset = val_dataset.map(operations=C.TypeCast(mstype.int32), input_columns=["text"])
val_dataset = val_dataset.map(operations=C.TypeCast(mstype.int32), input_columns=["label"])# 批量处理(设置batch_size)
train_dataset = train_dataset.batch(batch_size=32)
val_dataset = val_dataset.batch(batch_size=32)# 查看最终数据格式
for batch in train_dataset.create_dict_iterator():print("文本张量形状:", batch["text"].shape) # (32, 32)print("标签张量形状:", batch["label"].shape) # (32,)break
完整预处理流水线总结
将上述步骤整合,可构建一个可复用的预处理函数:
def preprocess_pipeline(file_path, max_seq_len=32, vocab_path=None):# 1. 加载数据df = pd.read_csv(file_path)dataset = ds.NumpySlicesDataset(df[["text", "label"]].values, column_names=["text", "label"])# 2. 文本清洗dataset = dataset.map(operations=lambda x: (clean_text(x[0]), x[1]), input_columns=["text", "label"])# 3. 分词dataset = dataset.map(operations=lambda x: (tokenize(x[0]), x[1]), input_columns=["text", "label"])# 4. 去停用词dataset = dataset.map(operations=lambda x: (remove_stopwords(x[0]), x[1]), input_columns=["text", "label"])# 5. 词表构建/加载与映射if vocab_path:with open(vocab_path, "r", encoding="utf-8") as f:vocab = json.load(f)else:all_tokens = []for data in dataset.create_dict_iterator():all_tokens.extend(data["text"])word_counts = Counter(all_tokens).most_common(5000)vocab = {"<PAD>": 0, "<UNK>": 1}for word, _ in word_counts:vocab[word] = len(vocab)with open("vocab.json", "w", encoding="utf-8") as f:json.dump(vocab, f, ensure_ascii=False)dataset = dataset.map(operations=lambda x: (tokens_to_ids(x[0]), x[1]), input_columns=["text", "label"])# 6. 长度对齐dataset = dataset.map(operations=lambda x: (pad_sequence(x[0], max_seq_len), x[1]), input_columns=["text", "label"])# 7. 划分与转换dataset = dataset.shuffle(buffer_size=len(df))train_size = int(0.8 * len(df))train_dataset, val_dataset = dataset.split([train_size, len(df) - train_size])train_dataset = train_dataset.map(operations=C.TypeCast(mstype.int32), input_columns=["text", "label"]).batch(32)val_dataset = val_dataset.map(operations=C.TypeCast(mstype.int32), input_columns=["text", "label"]).batch(32)return train_dataset, val_dataset, vocab
注意事项
- 预处理的任务相关性:不同任务(如机器翻译、命名实体识别)可能需要调整步骤(如翻译需保留句子结构,NER 需保留特殊符号)。
- 超参数调整:max_seq_len、词表大小等需根据数据分布调整,过长会增加计算量,过短会丢失信息。
- 效率优化:MindSpore 的 map 操作支持多线程加速(通过
num_parallel_workers
参数),大规模数据可开启。 - 可复现性:保存词表等中间结果,确保测试 / 部署时使用相同的预处理规则。