训练文本去重时,同样的文本且标签一致可以保留首条并记下重复映射;同文本却标签不同,应把整组送去复核。下面用 Datasets 保存审计字段,用 Python 按规范化文本分组,避免只调用“去重”就丢掉标注冲突。
先说明相同文本的定义
本例假定文本和标签已经通过基本有效性检查,ID 唯一。规则为 NFKC Unicode 规范化、去掉首尾空白、忽略大小写;不会删除文本内部空格、标点或数字。NFKC 的含义可查 Python 官方 Unicode 文档。该规则会合并部分全角和半角写法,正式任务若区分这些差异,就不能直接采用。

示例于 2026-10-01 在 Windows、Python 3.11.15、Datasets 4.8.3 环境实际执行通过;数据全部人为构造,只验证数据处理接口,没有训练模型或测量业务效果。安装可参考 Datasets 官方安装说明。先在新的练习目录建立环境:
python -m venv .venv
# Windows 使用 .venv\Scripts\python.exe;macOS/Linux 使用 .venv/bin/python
.venv\Scripts\python.exe -m pip install "datasets==4.8.3"
下文运行命令按 Windows 写法给出。使用 macOS 或 Linux 时,把解释器路径换为 .venv/bin/python;后续版本请先用同一份玩具数据复核,再替换生产数据。
生成重复映射与冲突表
把下面代码存为 dedup_demo.py,运行 .venv\Scripts\python.exe dedup_demo.py。Datasets 处理文档支撑 map() 添加字段和 filter() 分离结果表;Python hashlib 文档支撑 SHA-256 摘要的生成。
import hashlib
import unicodedata
from datasets import Dataset
ds = Dataset.from_list([
{'id': 101, 'text': ' AI教程 ', 'label': 1},
{'id': 102, 'text': 'AI教程', 'label': 1},
{'id': 103, 'text': '等待较长', 'label': 0},
{'id': 104, 'text': '等待较长', 'label': 1},
{'id': 105, 'text': '不同样本', 'label': 0},
{'id': 106, 'text': ' \t', 'label': 0},
{'id': 107, 'text': 'AI 教程', 'label': 1},
])
def normalize(row):
key = unicodedata.normalize('NFKC', row['text']).strip().casefold()
return {'normalized_text': key,
'text_sha256': hashlib.sha256(key.encode('utf-8')).hexdigest()}
audited = ds.map(normalize)
groups = {}
for row in audited:
if row['normalized_text']:
groups.setdefault(row['normalized_text'], []).append(row)
decisions = {}
for group in groups.values():
if len({row['label'] for row in group}) > 1:
for row in group:
decisions[row['id']] = ('label_conflict', None)
else:
first_id = group[0]['id']
for row in group:
decisions[row['id']] = ('keep', None) if row['id'] == first_id else ('duplicate', first_id)
def classify(row):
decision, duplicate_of = decisions.get(row['id'], ('blank_text', None))
return {'decision': decision, 'duplicate_of': duplicate_of}
audited = audited.map(classify)
kept = audited.filter(lambda row: row['decision'] == 'keep')
duplicates = audited.filter(lambda row: row['decision'] == 'duplicate')
conflicts = audited.filter(lambda row: row['decision'] == 'label_conflict')
blank = audited.filter(lambda row: row['decision'] == 'blank_text')
assert list(kept['id']) == [101, 105, 107]
assert duplicates[0]['id'] == 102 and duplicates[0]['duplicate_of'] == 101
assert list(conflicts['id']) == [103, 104]
assert len(kept) + len(duplicates) + len(conflicts) + len(blank) == len(ds)
print('kept_ids:', list(kept['id']))
print('duplicate_mapping:', [(row['id'], row['duplicate_of']) for row in duplicates])
print('conflict_ids:', list(conflicts['id']))
print('blank_ids:', list(blank['id']))
text_sha256 是可记录的文本摘要,分组实际仍比较 normalized_text 原文,并没有把哈希相同当成绝对证明。代码不使用 Python 内置 hash() 作为跨进程标识;它也没有宣称 SHA-256 能识别近义句或自动判断文本版权。
一组中只要出现不同标签,本例将整组标为 label_conflict。不能只保留第一条标签,否则错误可能被掩盖;应回查原始标注和任务定义。这里只演示单标签规则,多标注者、多标签或软标签要先设计自己的冲突含义。
实测结果及检查方法
实测保留 ID 101、105、107;ID 102 通过 duplicate_of 指向 101;冲突表包含 103、104;空白表包含 106。3 条保留 + 1 条重复 + 2 条冲突 + 1 条空白 = 7 条输入,代码用断言核对没有记录消失。
101 的“AI教程”与 102 的全角写法经规范化相同;107 的“AI 教程”保留了内部空格,所以仍是另一条文本。先用这两个例子判断规则是否符合你的任务,再修改规范化函数;不能为提高去重率一概删光空格和标点。
正式运行时把 audited 整表连同原始文本、ID、decision 和 duplicate_of 保存,并单独输出冲突表。抽查重复记录和保留首条的对应关系,确认标签一致;人工解决冲突后,按有记录的标注决定重新处理。
精确去重的能力边界
- 这只是规范化后的精确去重,改写、翻译或近义句仍可能保留;没有做语义相似去重。
- 分组字典将规范化文本和记录放在内存里,适合可容纳的小中型样本审计,不是百万行低内存方案。海量数据应改为磁盘分组或数据库排序,并保留相同的冲突与来源规则。
- 处理前要确认标签、ID 和文本类型有效;缺失字段或相同 ID 会破坏本例的 decisions 索引。
- 先确定去重范围再划分训练与验证:同一规范文本同时出现在两边,会污染评估。已有划分需要单独核对跨 split 的文本交集。
不要把保留行数减少理解为模型效果提高。去重规则改变了样本权重与分布,仍需在独立评估集验证下游任务。
Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/30291.html