检查训练集和测试集是否重复,要分别查业务编号交集和内容指纹交集。同一编号可能被两次导出;相同文本也可能换了编号。只检查其中一层,都可能留下跨集合重复。
下面的目标是审计已经划分好的两个集合,输出可追查的命中对。它不会自动删除样本;应该先查清重复来源和标签冲突,再重新划分、训练与评估。

准备两份数据,先固定比较口径
示例使用 train、test 两张小表,每行有 id、text、label。六行都是虚构输入;代码已在 Windows、Python 3.11.15、pandas 3.0.6 下实际运行。真实文件中的 id 必须在两个集合中使用相同业务含义,编号 001 不能在一个集合表示用户、另一个集合表示文本。
内容指纹只包含模型实际看到的文本,不把标签、随机编号或 split 名称混进去。标签不同而文本相同也需要被找到;把标签写进指纹,反而会漏掉这种冲突。
本例仅做 NFKC 规范化与连续空白折叠,随后用 SHA-256。这样“退款 进度”和含全角空格的“退款 进度”会被归为同一内容。规范化规则是本任务的明确选择,可能改变某些技术文本或符号的区别;用于你的语料前,先用少量真实样本核对规则是否保留了任务需要的差异。
运行编号与内容两层核查
在空目录建立环境,把完整代码保存为 check_split_duplicates.py,再运行:
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install pandas==3.0.6
.\.venv\Scripts\python.exe check_split_duplicates.py
macOS 或 Linux 用 python3 创建环境,并用 .venv/bin/python 运行。Python hashlib 官方文档给出 SHA-256 和 hexdigest 的接口;pandas.merge 官方文档说明 one_to_one 唯一性检查,也提醒空键会彼此匹配。因此代码在连接之前先拒绝缺失编号和文本。
import hashlib
import re
import unicodedata
import pandas as pd
train = pd.DataFrame({
'id':['T001','T002','T003'],
'text':['退款 进度','物流跟踪','更换地址'],
'label':[1,0,0]
})
test = pd.DataFrame({
'id':['T002','T900','T901'],
'text':['物流跟踪','退款\u3000进度','全新问题'],
'label':[0,0,1]
})
def fingerprint(text):
# Only NFKC + whitespace folding; do not include the label.
value = unicodedata.normalize('NFKC', text)
value = re.sub(r'\s+', ' ', value).strip()
return hashlib.sha256(value.encode('utf-8')).hexdigest()
for name, frame in [('train',train),('test',test)]:
if frame[['id','text','label']].isna().any().any():
raise ValueError(f'{name}: missing ID/text/label')
if frame['id'].duplicated().any():
raise ValueError(f'{name}: IDs are not unique')
if not frame['id'].map(lambda x: isinstance(x,str) and bool(x.strip())).all():
raise ValueError(f'{name}: invalid ID')
if not frame['text'].map(lambda x: isinstance(x,str) and bool(x.strip())).all():
raise ValueError(f'{name}: blank or non-string text')
frame['fp'] = frame['text'].map(fingerprint)
id_hits = train.merge(test,on='id',suffixes=('_train','_test'),validate='one_to_one')
content_hits = train.merge(test,on='fp',suffixes=('_train','_test'))
content_hits['label_conflict'] = content_hits['label_train'] != content_hits['label_test']
print('id_overlap=', sorted(id_hits['id'].tolist()))
print(content_hits[['id_train','id_test','label_conflict']].sort_values(
['id_train','id_test']).to_string(index=False))
content_hits.to_csv('cross_split_duplicate_audit.csv',index=False,encoding='utf-8-sig')
assert len(id_hits) == 1
assert len(content_hits) == 2
assert content_hits['label_conflict'].sum() == 1
结果为什么是一个编号命中、两个内容命中?
id_overlap= ['T002']
id_train id_test label_conflict
T001 T900 True
T002 T002 False
T002 同时在两个集合里,所以编号检查命中一次。内容检查还找到训练集 T001 与测试集 T900:编号不同,规范化后的文本相同,标签分别为 1 和 0,label_conflict 为 True。
运行后打开 cross_split_duplicate_audit.csv,逐对核对两个来源编号、原始文本和标签。不要直接把两种检查的行数相加当作重复样本数:T002 在两层中都命中了,计数对象是命中关系而不是互不重复的样本。
内容相同且同集重复很多时,连接会生成多对关系,内存开销可能迅速增长。大语料可先按指纹汇总每个集合的样本数,再按需要展开命中对;本例适合先理解流程和检查小表。
发现重复之后,怎么处理才能继续评测?
- 冻结当前文件,保存命中表和规范化规则,追查各样本来源。
- 区分真实重复、同一用户多条记录、公共固定短语和标签冲突。短文本相同不一定表示同一个事件,人工复核原始上下文。
- 对同源或高度关联样本制定统一分组规则,让相关样本进入同一集合;需要评估新用户时按用户分组,需要评估未来数据时按时间规划。
- 修正标签争议并重新建立独立测试集,从头重新训练。已经受污染的测试分数不能靠事后删除几行就恢复可信。
- 对新划分再次运行编号和指纹检查,并记录样本变化与评估版本。
如果只检查编号,换编号的复制文本会漏掉;如果只检查规范化后相同的哈希,改写文本、相似图片和同一用户的不同记录会漏掉。哈希检查检测的是所选规范化规则下的精确相同,不是语义近似重复检测。
报错和没有命中,分别应该怎么判断?
- Missing ID/text/label:先隔离缺失行,修正来源;不要用空字符串统一填编号。
- IDs are not unique:编号在集合内已有重复,先查清编号粒度;one_to_one 不应强行改成 many_to_many 绕过。
- 读取后编号变成数字:用 pd.read_csv(…, dtype={“id”: “string”}) 保留前导零,并检查导出时是否已经损失原值。
- 没有命中:只能说明这两种规则没发现问题,不能证明没有目标泄漏、时间泄漏或相关样本泄漏。
如果输入改成多列特征,先选择预测时可用的特征列,再按稳定列顺序和一致缺失值规则序列化;不要直接对整行包含标签的 CSV 字符串哈希,也不要忽略字符串“1”和数字 1 的类型差别。
能否只删除测试集里命中的记录?
正式评测需要先确定原因。删除会改变测试样本分布,还可能保留同源近似重复;如果训练时已经看过测试相关内容,应重新划分并重新训练。只有在可解释的数据治理规则下处理好来源、标签与分组,新的评估结果才有明确含义。
Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/30611.html