微调数据集清洗,过滤脏prompt,提升微调模型效果
微调模型效果差不一定是模型或参数的问题,很多时候是数据集里混入了脏Prompt。
脏Prompt指的是格式错误、内容重复、指令不清、答案与问题不匹配等低质量样本,它们会让模型学到错误映射,导致生成结果不稳定。
本文介绍一套零基础也能执行的清洗流程,包括识别脏Prompt、过滤方法、质量检查和效果验证,帮助你提升微调模型的实际表现。
先搞清楚哪些Prompt属于“脏数据”
动手清洗前,建议先在样本里人工抽查 50-100 条,确认主要问题类型。
常见的脏Prompt包括:
- 完全重复或高度相似:同一句话出现多次,或只有标点、语气词差异。
- 指令与回答不匹配:比如问“怎么重启服务器”,答案却是“优化MySQL参数”。
- Prompt内容残缺:只有半个问句,或截断后语义不完整。
- 格式混乱:混入换行符、HTML标签、Markdown语法被截断、JSON转义错误。
- 安全问题/违规内容:需要按平台规范过滤掉诱导生成恶意代码、黄赌毒等内容。
建议先按以上几类做一次标注,统计每类占比,再决定清洗规则优先级。
清洗前需要准备什么
不需要高端环境,一台普通电脑或服务器即可。
你至少需要:
- 原始微调数据集,JSONL、CSV 或 TXT 格式均可。
- Python 3.8+ 环境,建议用
venv或 conda 隔离。 - 常用的数据处理库:
pandas、json、re。
安装依赖:
pip install pandas
如果你要处理超大数据集,建议用线下脚本分批清洗,避免内存爆掉。
动手清洗:从去重到格式过滤
下面是一套可以直接运行的清洗流程,以 JSONL 格式为例(每行一个 JSON 对象,包含 instruction 和 output 字段)。
第一步:读取数据并查看整体规模
import json
import pandas as pd
with open('raw_data.jsonl', 'r', encoding='utf-8') as f:
lines = [json.loads(line) for line in f if line.strip()]
print(f"原始样本数: {len(lines)}")
print(lines[0])
第二步:去除完全重复样本
df = pd.DataFrame(lines)
df = df.drop_duplicates(subset=['instruction', 'output'])
print(f"去重后样本数: {len(df)}")
第三步:过滤空值和过短内容
def is_valid_prompt(row):
# instruction 和 output 必须存在且非空
if not isinstance(row.get('instruction'), str) or not isinstance(row.get('output'), str):
return False
# instruction 长度至少 5 个字符,output 至少 1 个字符
if len(row['instruction'].strip()) < 5 or len(row['output'].strip()) < 1:
return False
return True
df = df[df.apply(is_valid_prompt, axis=1)]
第四步:用正则过滤格式异常
主要过滤掉包含乱码、异常符号或 HTML 标签的样本:
import re
def has_bad_format(text):
# 包含 HTML 标签
if re.search(r'<[^>]+>', text):
return True
# 包含连续异常重复符号,比如 ??????
if re.search(r'([??!!。])\1{5,}', text):
return True
# 包含乱码常见字符,比如 �
if '\ufffd' in text:
return True
return False
df = df[~df['instruction'].apply(has_bad_format)]
df = df[~df['output'].apply(has_bad_format)]
print(f"格式过滤后样本数: {len(df)}")
第五步:针对性过滤相似重复文本
如果数据里有大量长句只改了一两个词,可以先用 Jaccard 相似度做粗过滤。
对 10 万条以上的数据,建议用 MinHash 或 SimHash 加速。
这里给出一个可运行的小规模示例:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
# 只取前 2000 条示例做相似度检查,防止内存溢出
sample = df['instruction'].head(2000).tolist()
vectorizer = TfidfVectorizer(analyzer='char', ngram_range=(2, 3))
tfidf = vectorizer.fit_transform(sample)
sim_matrix = cosine_similarity(tfidf)
# 如果两条 instruction 相似度超过 0.9,保留第一条
drop_indices = set()
for i in range(len(sample)):
for j in range(i + 1, len(sample)):
if sim_matrix[i][j] > 0.9:
drop_indices.add(j)
drop_indices = list(drop_indices)
print(f"相似重复样本数: {len(drop_indices)}")
线上清洗时,建议按批次处理并控制阈值,避免误删语义相近但合理的样本。
第六步:保存清洗后的数据集
df.to_json('clean_data.jsonl', orient='records', lines=True, force_ascii=False)
保存前可以用 df.info() 和 df.describe() 检查字符长度分布,确认没有极端异常值。
清洗时容易踩的坑
粗暴删除短文本:有些合法指令本身很短,比如“继续”“然后呢”,必须结合对话上下文判断。
如果微调场景是单轮问答,可以设置最小长度;
如果是多轮对话,短指令也可能有意义。
过度去重误伤高价值样本:相似但带有正确纠正的样本要注意区分,比如两个问题表达不同但语义一致,可能有助于增强泛化能力。
建议只删除完全相同或修改一个词导致的重复样本。
忽略标签一致性:很多数据集中,同一个指令对应多个不同质量的答案。
建议检查 output 中是否有“我不知道”“无法回答”等空泛回答,这类样本如果占比过高会拖低模型输出质量。
清洗后不重新抽样检查:脚本过滤后必须人工再抽查至少 200 条,确认没有误删和漏网。
如何验证清洗是否真正提升了效果
清洗不能只看样本数量下降,必须通过模型评测来对比。
推荐的做法是:
- 从清洗前后的数据中分别随机抽取 500-1000 条作为测试集(只取指令,不用作训练)。
- 用同样的微调参数分别训练两个基线模型:一个用原始数据,一个用清洗后数据。
- 设计 20-50 个与业务相关的评测问题,人工打分或使用 GPT-4 等模型辅助打分。
重点关注指标是:输出相关性、格式正确率、幻觉频率。
只要清洗后的模型在这三项上有提升,就说明你的过滤规则是有效的。
如果清洗后效果反而变差,优先检查是否删除了包含关键信息的训练样本,或格式过滤规则是否误伤了特殊领域的专业表达。
建议保留清洗前后的完整数据文件,方便回滚对比。
关于脏Prompt清洗的常见疑问
人人都需要做清洗吗?
如果数据集是从开源社区收集的,建议清洗;如果是人工严格制作的精品数据集,清洗压力会小很多。但即使样本质量高,去重和格式校验也不应省略。
清洗会降低数据量,数据不够怎么办?
先看过滤比例。如果超过 30%,说明原始数据质量很差,不建议直接使用;如果只有 5%-10%,属于正常范围,可通过增加采集来源、人工补写等方式扩充。
过滤规则是否能自动发现所有脏数据?
不能。规则只能处理常见模式,像“指令无意义但格式正确”这类语义层面的问题,需要人为判断或借助语义相似度模型辅助标记。
完成上述清洗流程后,你会得到一份结构清晰、重复度低、格式统一的微调数据集。
下面建议先跑一次小规模微调,用业务测试集验证效果,再逐步调整清洗阈值和训练参数。
如果遇到清洗规则误伤或样本量骤减等问题,回头检查规则阈值和抽样结果即可。