章节导航 第 7 章 / 共 30 章
安全过滤、去重与隐私合规
毒性/恶意内容过滤、模糊/精确去重、PII 与隐私合规三合一。
三句话定调:去重让训练效果好 1.5 倍,去污染让评测可信,安全过滤让模型合规——这三件事,一件都不能省。
“如果你的训练数据中有 30% 的重复内容,你不是在用 15 万亿 token 训练,你是在用 10 万亿 token 训练——而且那个 ‘10 万亿’ 还有偏差。”
“训练数据是大模型的 DNA。如果 DNA 有毒,模型不会更好——它会更’聪明’地传播毒性。”
本章把安全过滤、数据去重、PII 识别与隐私合规三条原本分散的线索整合为预训练数据工程的统一”合规与质量”主轴。我们以《AI数据工程》(aide) 第 5 章的工程实操为主干,融入《大模型数据工程》(de) 第 4 章的去重体系与第 19 章的安全/隐私/伦理框架。
7.1 综述:一条贯穿数据生命线的红线
预训练数据从全网回流,天然携带四类风险,对应四个处理环节:
| 风险类别 | 典型表现 | 对应环节 | 不做后果 |
|---|---|---|---|
| 毒性/违规内容 | 辱骂、色情、暴力、CSAM、仇恨言论 | 安全过滤 | 法律风险、模型行为失控、合规审查失败 |
| 重复内容 | 完全复制、近似转载、模板填充、语义改写 | 去重(精确+模糊+句级+语义) | 记忆化、泛化下降、模板主导、隐私泄露加剧 |
| 评测污染 | 训练集含 MMLU/GSM8K 题目 | 训练-评测集去污染 | benchmark 成绩虚高、不可信 |
| 个人信息/合规 | PII 明文、版权素材、偏见固化 | PII 脱敏 + 隐私与伦理合规 | 隐私泄露、诉讼、品牌损害、监管下架 |
四个环节不是孤立的。本章后续会反复指出它们的耦合:去重本身就是一条隐私防线(de-ch04 与 de-ch19 共同指出,重复出现的 PII 被模型记住的概率远高于单次出现),而安全过滤的 CSAM 处理与 PII 脱敏共享”发现即处置并报告”的工程范式。
7.2 安全过滤:合规底线不可逾越
7.2.1 为什么要做安全过滤
预训练数据来自全网,包含:
- 毒性内容:辱骂、攻击性言论
- 色情内容:成人内容(可能涉及违法)
- 暴力内容:极端暴力、自残引导
- CSAM:儿童性虐待材料(法律红线,发现必须报告)
- 仇恨言论:种族、性别、宗教歧视
如果不做过滤:
- 法律风险:CSAM、仇恨言论在多国违法
- 模型行为失控:模型会复制有毒模式
- 合规审查失败:模型无法上架(尤其欧盟 AI Act、中国生成式 AI 管理办法)
- 品牌损害:用户用 prompt 引导出有毒输出,舆论危机
更深层的风险来自 19 章的视角:训练数据中的毒性与偏见不是”被模型原样存储”,而是被模型更高效地传播。对齐(RLHF/DPO)旨在抑制这类输出,但数据层的毒性过滤是成本最低、最可控的第一道防线。
7.2.2 主流工具对比
| 工具 | 类型 | 速度 | 准确率 | 推荐场景 | 维护状态 |
|---|---|---|---|---|---|
| Perspective API | Google API | 慢(API限速) | 高 | 大规模过滤 | 托管服务 |
| Detoxify | 开源本地 | 快 | 中-高 | 工业级首选 | PyPI 最新 0.5.2(2024-02-01),近 2 年 8 个月无新版 |
| ShieldGemma | 开源本地 | 中(GPU) | 高 | 高准确率需求 | 模型权重(Gemma 系) |
| OpenAI Moderation API | API | 慢 | 极高 | 最终复核 | 托管服务 |
| 自训练分类器 | 本地 | 快 | 可控 | 定制需求 | 取决于自身维护 |
选型原则:先用本地快模型(Detoxify/ShieldGemma)做大批量粗筛,再用高准确率 API 做最终复核。纯 API 方案在 TB 级数据上因限速与成本不可行。
📌 建议把「维护状态」设为本书所有工具选型表的固定列(2026-10-08):本轮在第 6 章(pycld3 / fasttext / nemo-curator)与本章(Detoxify)都遇到同一类缺口——选型表有”速度 / 准确率 / 场景”列,没有”维护状态”列。给每个工具单独写一句提示会不断重复,设为固定列一次解决;读者需要它来决定”锁版本”还是”找替代”,下轮扫描也只需更新这一列。
7.2.3 Detoxify 实战
from detoxify import Detoxify
# 加载多语种版本(支持英/法/西/俄等多语)
model = Detoxify('multilingual', device='cuda:0')
def detect_toxicity(text):
"""
检测文本毒性
返回各维度概率
"""
if not text or len(text) > 10000:
text = text[:10000] # 长文本截断
results = model.predict(text)
# results 形如:
# {'toxicity': 0.85, 'severe_toxicity': 0.20,
# 'obscene': 0.30, 'identity_attack': 0.75,
# 'insult': 0.80, 'threat': 0.10,
# 'sexual_explicit': 0.05}
return results
# 推荐阈值
TOXICITY_THRESHOLDS = {
'toxicity': 0.7, # 毒性 >0.7 拒绝
'severe_toxicity': 0.3, # 严重毒性 >0.3 拒绝
'identity_attack': 0.5, # 身份攻击 >0.5 拒绝
'threat': 0.4, # 威胁 >0.4 拒绝
'sexual_explicit': 0.6 # 性露骨 >0.6 拒绝
}
def is_safe(text):
scores = detect_toxicity(text)
for dim, threshold in TOXICITY_THRESHOLDS.items():
if scores.get(dim, 0) > threshold:
return False, dim
return True, 'pass'
阈值需要结合语种与业务调整:中文互联网语料中口语化辱骂多,单维阈值过严会误杀大量正常文本;建议先用小样本人工标定为各维度独立阈值。
7.2.4 CSAM 过滤:法律红线,发现即处置并报告
⚠️ 双红线,不是单红线:本节讲的是训练数据侧的发现—移除—报告。但自 2026-12-02 起,能够生成或操控 CSAM 的 AI 系统本身落入 EU AI Act Art.5(1)(bb) 的禁止清单(罚则最高 €3,500 万或全球年营业额 7%)。这意味着“训练数据已清洗”不再自动等价于”合规”——输出端能力需单独评估与拦截。详见 §7.12.1 的修订后时间轴。
CSAM(儿童性虐待材料)是法律红线,任何疑似内容必须立即移除并报告:
def csam_filter(text, image_hashes=None):
"""
CSAM 过滤的工程实现
文本:用关键词 + hash 双路
图片:用 PhotoDNA hash 比对
"""
# 1. 关键词检测(粗筛)
CSAM_KEYWORDS = [...] # 不在此列出,可用 NCMEC 提供的清单
text_lower = text.lower()
for kw in CSAM_KEYWORDS:
if kw in text_lower:
# 触发后必须记录、移除、并按法规报告
log_csam_incident(text)
return False, 'csam_keyword'
# 2. 图片 PhotoDNA 比对
if image_hashes:
known_csam_hashes = load_ncmec_database() # NCMEC 提供的已知 CSAM hash 库
for img_hash in image_hashes:
if img_hash in known_csam_hashes:
log_csam_incident(text, img_hash)
return False, 'csam_image_match'
return True, 'pass'
踩坑经验:CSAM 处理必须严格遵守当地法规(中国《未成年人保护法》、美国 NCMEC 报告义务等)。建议咨询合规法务后再上线流水线。这与 7.9 节的 PII 处理、7.10 节的合规 Checklist 共享同一条工程纪律:发现敏感内容 → 记录 → 处置 → 报规,四步闭环。
7.3 为什么要去重:被低估的关键步骤
去重是预训练数据工程的强制环节。aide 与 de 两书都从实证角度论证其必要性,可归纳为”三重伤害 + 一个事实”。
7.3.1 三重伤害
第一重:记忆化(Memorization)。当一段文本在训练数据中出现多次,模型不是在”学习”知识,而是在”背诵”它。Lee et al.(2022)《Deduplicating Training Data Makes Language Models Better》证明:去重后模型在多个 benchmark 上一致更优,且逐字重复大幅减少;即使训练 token 总数因去重而减少,模型性能反而提升——重复数据的边际价值为负。
第二重:泛化能力下降。重复数据让训练 loss 虚假走低,但模型在分布外场景表现并不好。重复数据等效于增加训练分布的”峰度”,使模型过拟合到特定模式。
第三重:隐私泄露风险(de-ch19 视角)。如果某人的信息(如”电话号码+姓名”)在训练数据中出现 50 次,模型”记住”它的概率远高于只出现 1 次的情况。去重因此是一条隐私防线——它降低敏感信息被记忆化提取的概率。这与 7.9 节的 PII 脱敏形成互补:脱敏消除明文,去重降低残留信息的记忆频率。
7.3.2 互联网数据的重复程度超乎想象
- 完全相同的文档:Common Crawl 一个快照中约 20–30% 的文档是完全相同的副本(同内容不同 URL、多站转载)
- 近似重复的文档:另外约 10–20% 内容高度相似但有细微差异(页眉/页脚、时间戳、评论数不同)
- 段落级重复:即使文档整体不重复,某些段落(法律声明、版权通知、产品描述、新闻通稿)在大量文档中反复出现
定量证据:Lee et al.(2022)显示 C4 数据集中约 3.04% 的样本在训练/测试集间存在重叠——即便经基本去重的主流数据集仍有”评估泄露”。这解释了”去重是免费的午餐”:减少数据量反而提升性能。
7.3.3 去重带来的效果增益
Lee et al.(2022)在 C4 上训练 1.5B 模型的实验:
| 数据 | 训练 tokens | MMLU | TriviaQA |
|---|---|---|---|
| C4 原版(含重复) | 1T | 24.3 | 32.5 |
| C4 去重后 | 0.7T | 28.1 | 38.7 |
用 70% 的数据量,效果反而更好——因为去重让模型更高效利用每个 token。
7.3.4 认知演变:从”越多越好”到”分粒度按需”
2020: "去重?不太需要吧" —— GPT-3 只做了 URL 级别去重
2021: "精确去重就够了" —— 部分团队做文档级 hash,认为模糊去重不值
2022: "必须做模糊去重!" —— Lee et al. 实证,不去重=浪费算力成共识
2023: "去重越多越好" —— 段落级/句级去重、追求最大化去重率
2024: "等等,过度去重也有害" —— FineWeb 发现阈值过低(0.6)误删非重复内容、
减少多样性、不同语境的"看似重复"各有价值
2025-2026: "分粒度去重,按需调节" —— URL → 文档级精确 → 文档级模糊(0.75-0.80)
→ 段落级(可选) → 不建议 n-gram 过细
过度去重有害是 2024 年后的关键修正:去重阈值设得过低会误删大量不是真正重复的内容,减少训练数据多样性。当前最佳实践是按粒度分级、按需调节。
7.4 精确去重:URL 与 SHA256
精确去重是最简单也最快的去重方式:找到内容完全相同的文档并移除副本。它是模糊去重的前置步骤。
7.4.1 URL 去重(第一道粗筛)
方法:对同一 URL 只保留一份文档。
局限:同一内容可有不同 URL(http/https、www. 有无、URL 参数、短链接);不同 URL 可指向相同内容(镜像站、CDN)。只能作为第一步粗筛,远不够用。
from urllib.parse import urlparse, urlunsplit
def normalize_url(url: str) -> str:
"""URL 标准化,合并同一页面的不同 URL 形式"""
parsed = urlparse(url.lower())
netloc = parsed.netloc.replace('www.', '') # 去掉 www.
path = parsed.path.rstrip('/') # 去掉末尾斜杠
return urlunsplit(('', netloc, path, '', '')) # 忽略 scheme
7.4.2 文档级精确去重(Hash-based)
方法:计算每个文档 hash(如 SHA-256),hash 相同视为重复。
优点:100% 精确(无假阳性)、速度快、内存友好(可用 Bloom Filter 节省内存)。
局限:对内容的任何微小变化(多一个空格、时间戳不同)都会产生不同 hash,只能发现”完全相同”的重复。
import hashlib
from collections import defaultdict
def compute_document_hash(text: str) -> str:
"""计算文档的 SHA-256 hash"""
normalized = ' '.join(text.split()) # 统一空白、去首尾空白
return hashlib.sha256(normalized.encode('utf-8')).hexdigest()
class ExactDeduplicator:
def __init__(self):
# 初始容量 1 亿,误报率 0.001
from pybloom_live import ScalableBloomFilter
self.seen = ScalableBloomFilter(
initial_capacity=100_000_000, error_rate=0.001)
def is_duplicate(self, text: str) -> bool:
h = compute_document_hash(text)
if h in self.seen:
return True
self.seen.add(h)
return False
Bloom Filter 的内存优势:存储 10 亿个 hash,直接存需 ~32GB(每个 SHA-256 32 字节),Bloom Filter 只需 ~2GB;代价是 0.1% 误报率(把非重复标为重复),在去重场景可接受。
7.4.3 段落/行级精确去重
有些文档整体不重复,但段落高度模板化:
- 法律声明:“本站内容仅供参考……”(出现在数十万网站)
- Cookie 提示:“我们使用 Cookie 来提升您的体验……”
- 新闻通稿:同一段被数百家媒体原封不动发布
方法:对每个段落(或每 N 行)计算 hash,移除出现频率超阈值的段落。注意:阈值设太低会误删自然重复内容(问候语、格式文本)。建议只对长度 > 100 字符的段落做此去重。
7.4.4 精确去重的局限
精确去重只能抓”完全相同”,对以下无效:微小改动(空格/换行不同)、转载修改(改标题/调段落顺序)、模板填充(同模板不同数据)、抄袭改写(表述略改)。工业级必须用模糊去重补足。
7.5 模糊去重:MinHash + LSH 事实标准
MinHash + LSH(Locality-Sensitive Hashing)是大规模模糊去重的事实标准。aide 与 de 都将其列为主流方案,本节合并两书的原理、参数与中文适配。
7.5.1 核心原理
MinHash 核心思想:用一组哈希函数的”最小值”近似集合的 Jaccard 相似度。
对集合 A、B:P(min(h(A)) == min(h(B))) = Jaccard(A, B) = |A∩B| / |A∪B|
流程三步:
- Shingling:将每个文档转为 n-gram 集合(如”今天天气很好”的 5-gram = {“今天天气很”, “天天气很好”})
- MinHash:用多个 hash 函数对集合签名压缩,每个文档压成固定长度签名向量(如 128 维);两签名的相似度是 Jaccard 相似度的无偏估计
- LSH:将签名分 b 个 band,每 band r 个 row(b×r=签名总长);任意 band 完全匹配则判为候选相似对
🔴 版本声明(2026-10-08 补,本章最高优先级的一条):
datasketch于 2026-07-05 发布 2.0.0,是主版本破坏性升级,而本章 §7.5 全节此前没有任何版本声明。官方说明的四条要点:
- 默认置换方案改为
affine32,修复了大集合上的相似度过高估计偏差(issue #212);- sketch 内存减半;
- update 速度提升约 4×;
- 哈希值与早期版本不同:必须重建已持久化的 sketch 与 LSH 索引,或显式传
MinHash(..., scheme="legacy")与既有数据互通。另有面向十亿级集合的 64 位affine64方案。这四条对本章的具体后果:
- 参数语义变了(影响 §7.5.3 / §7.5.5 / §7.5.6):修复的是过高估计偏差,意味着旧方案下被判为”相似”的一部分文档对,在新方案下不再相似。阈值 0.8(英文)/ 0.75(中文)的去重率会下降、准确率会上升,§7.5.3 那张中文调参实验表是旧方案下测的。
- 升级不重建索引 = 静默错配(影响 §7.11.1 / §7.11.4):§7.11.1 的分布式架构里 LSH 索引跨批次复用,§7.11.4 的在线增量去重更是长期累积索引——新旧方案签名混在同一个索引里,不会报错,只是去重结果悄悄错掉。这是一条会直接导致去重失效而不产生任何告警的运维陷阱。
- 内存算式可减半(影响 §7.11.2):见该节的更新。
- 速度可提 4×(影响 §7.11.1):影响工具栈选型理由。
7.5.2 n-gram + MinHash 流程(aide 实现)
import mmh3 # MurmurHash3,比 hashlib 快约100倍
import numpy as np
from datasketch import MinHash, MinHashLSH
def minhash_dedup(documents, num_perm=128, threshold=0.8):
"""
MinHash + LSH 模糊去重
num_perm: 排列数,推荐 128(生产)/ 256(高精度)
threshold: 相似度阈值,推荐 0.8(保留高质量)/ 0.7(更激进)
"""
minhashes = {}
for doc_id, doc in enumerate(documents):
m = MinHash(num_perm=num_perm)
words = doc['text'].split()
for i in range(len(words) - 2):
ngram = ' '.join(words[i:i+3]) # 3-gram 作为 shingle
m.update(ngram.encode('utf-8'))
minhashes[doc_id] = m
# LSH 把 num_perm 个 hash 分 b 个 band,每 band r 个 hash
# 工业推荐:b=32, r=4(即 num_perm=128)
lsh = MinHashLSH(threshold=threshold, num_perm=num_perm)
for doc_id, m in minhashes.items():
lsh.insert(str(doc_id), m)
duplicates = set()
for doc_id, m in minhashes.items():
similar_docs = lsh.query(m)
for sim_id in similar_docs:
sim_id_int = int(sim_id)
if sim_id_int > doc_id:
duplicates.add(sim_id_int) # 保留 doc_id 最小者
return [doc for i, doc in enumerate(documents) if i not in duplicates]
7.5.3 中文场景的特殊适配(de 视角)
中文没有天然单词边界。英文 5-gram 可能含整个短语,中文 5-gram 可能只有 5 个字——这意味着:
- 中文用较小的 n(如 3–4)更合理,否则 n-gram 太长、不同文档重合率低,相似内容也检测不到
- n 太小(1–2)又导致假阳性过多,任何两段中文都有大量单/双字重合
推荐:中文使用 n=3 或 n=4,配合 num_perm=128、阈值 0.75(英文用 5-gram、阈值 0.8)。de 的调参实验也印证:
实验设置:100M token 中文数据样本
组合 A (n=5, threshold=0.8): 去重率 12.3%, 准确率 96.5%, 召回率 ~65%
组合 B (n=3, threshold=0.75): 去重率 18.7%, 准确率 91.2%, 召回率 ~82% ← 较好平衡
组合 C (n=3, threshold=0.7): 去重率 24.1%, 准确率 83.7%, 召回率 ~89% ← 高召回
组合 D (n=3, threshold=0.65): 去重率 31.5%, 准确率 72.4%(开始误杀), 召回率 ~93%
不建议用 D——72% 准确率意味着近 30% 的”去重”是误杀。推荐 B(平衡)或 C(重召回)。
⚠️ 这张表的口径(2026-10-08 补):以上四组数值是在 datasketch 2.0.0 之前的置换方案下测得的。由于 2.0.0 修复的正是相似度过高估计偏差,同样的阈值在新方案下应表现出去重率下降、准确率上升——也就是说,你按 B 或 C 配置跑出来的数会与这张表对不上,方向是”去重率更低、准确率更高”。
7.5.4 工程参数详解
MINHASH_CONFIG = {
'num_perm': 128, # 排列数
'shingle_size': 3, # n-gram 阶数(中文推荐 3-4)
'shingle_unit': 'word', # word 或 char(中文推荐 char)
'threshold': 0.8, # 相似度阈值(中文 0.75)
# LSH:b*r = num_perm
# b=32, r=4 → num_perm=128(经典默认)
# b=16, r=8 → num_perm=128(更严格,false positive 少)
# b=64, r=4 → num_perm=256
'bands': 32,
'rows': 4,
'batch_size': 100000,
'storage': 'disk', # 大规模用磁盘
'storage_config': {'type': 'leveldb', 'path': '/tmp/lsh_index'}
}
7.5.5 参数选择对比
| num_perm | threshold | 速度 | 准确率 | 内存 | 推荐场景 |
|---|---|---|---|---|---|
| 64 | 0.8 | 极快 | 中 | 低 | 大规模粗筛 |
| 128 | 0.8 | 快 | 高 | 中 | 工业首选 |
| 256 | 0.9 | 中 | 极高 | 高 | 精细去重 |
| 512 | 0.95 | 慢 | 极高 | 极高 | 学术研究 |
7.5.6 LSH 的 band/rows 权衡
LSH 把 num_perm 个 hash 分 b 个 band,每 band r 个 hash。两文档被判定候选的条件:至少一个 band 内 r 个 hash 完全相同。
候选概率与 Jaccard 相似度 J:P(candidate | J) = 1 - (1 - J^r)^b
| (b, r) | J=0.5 候选率 | J=0.8 候选率 | J=0.9 候选率 |
|---|---|---|---|
| (32, 4) | 0.04 | 0.83 | 0.998 |
| (16, 8) | 0.001 | 0.20 | 0.85 |
| (64, 4) | 0.31 | 0.99 | 1.0 |
(b=32, r=4) 是经典默认,适合 threshold=0.8。想更严格抓”近似重复”用 (b=16, r=8);想抓”高度相似”用 (b=64, r=4)。
7.5.7 完整文档级去重管线(de 实现,含中文 char-shingle)
import hashlib
from datasketch import MinHash, MinHashLSH
def exact_dedup(documents: list[str]) -> list[str]:
"""基于 SHA-256 的精确去重"""
seen_hashes = set(); unique_docs = []
for doc in documents:
normalized = ' '.join(doc.split())
h = hashlib.sha256(normalized.encode('utf-8')).hexdigest()
if h not in seen_hashes:
seen_hashes.add(h); unique_docs.append(doc)
return unique_docs
def create_minhash(text: str, n: int = 3, num_perm: int = 128) -> MinHash:
"""为文本创建 MinHash 签名(中文用字符级 n-gram)"""
m = MinHash(num_perm=num_perm)
for i in range(len(text) - n + 1):
m.update(text[i:i+n].encode('utf-8'))
return m
def fuzzy_dedup(documents: list[str], threshold: float = 0.75) -> list[str]:
"""基于 MinHash + LSH 的模糊去重"""
lsh = MinHashLSH(threshold=threshold, num_perm=128)
unique_docs = []
for i, doc in enumerate(documents):
m = create_minhash(doc)
if not lsh.query(m):
lsh.insert(f"doc_{i}", m); unique_docs.append(doc)
return unique_docs
def dedup_pipeline(documents: list[str]) -> list[str]:
docs = exact_dedup(documents)
docs = fuzzy_dedup(docs, threshold=0.75)
return docs
7.6 SimHash:另一种思路
SimHash(Charikar, 2002)是 Google 用于网页去重的算法,思路与 MinHash 不同,基于余弦相似度而非集合相似度。
7.6.1 原理与实现
- 对每个 token 计算一个 64-bit hash
- 对每个 bit 位,hash 该位为 1 则 token 权重 +1,为 0 则 -1
- 累加所有 token 权重得 64 维向量
- sign 函数(>0 取 1,<0 取 0)得 64-bit 签名
- 两文档相似度 = 1 − Hamming 距离 / 64
import mmh3
import numpy as np
def simhash(text, hash_bits=64):
"""SimHash 签名生成"""
tokens = text.split()
weighted_sum = np.zeros(hash_bits)
for token in tokens:
h = mmh3.hash64(token)[0]
for i in range(hash_bits):
bit = (h >> i) & 1
weighted_sum[i] += 1 if bit else -1
signature = 0
for i in range(hash_bits):
if weighted_sum[i] > 0:
signature |= (1 << i)
return signature
def hamming_distance(h1, h2):
return bin(h1 ^ h2).count('1')
def is_similar(text1, text2, threshold=3):
"""Hamming 距离 ≤ threshold 视为相似(64-bit 上约 0.95 相似度)"""
return hamming_distance(simhash(text1), simhash(text2)) <= threshold
7.6.2 MinHash vs SimHash
| 维度 | MinHash + LSH | SimHash |
|---|---|---|
| 相似度度量 | Jaccard | 余弦 |
| 签名长度 | 128-256 bit | 64 bit |
| 适用场景 | 文档长度差异大时更鲁棒 | 文档长度相近时效果更好 |
| 计算效率 | 签名计算较慢,查找快 | 签名与查找都快 |
| 内存占用 | 较高(签名向量) | 较低(每文档 64-128 bit) |
| 大规模 LSH | 成熟 | 较复杂 |
| 参数调优 | 较复杂(n, num_perm, b, r) | 较简单(汉明距离阈值) |
| 推荐 | 文档级近似重复(工业稳健首选) | 网页级近似重复(极快、补充) |
工业实践:MinHash 用于”文档级近似重复”,SimHash 用于”网页级近似重复”,两者可互补。对大规模预训练数据,MinHash + LSH 更稳健(对文档长度差异鲁棒),SimHash 适合快速近似查重补充。
7.7 Suffix Array:句级/段落级去重
文档级去重抓不到”句子级”重复:文章 A、B 整体不同,但都含同一段 Wikipedia 内容,文档级去重抓不到。Suffix Array 解决这类句级/段落级重复。
7.7.1 原理
Suffix Array 是字符串所有后缀的有序索引,能在 O(N log N) 找到所有重复子串。将所有文档拼成超长字符串(用分隔符标记边界),构建后缀数组与 LCP(最长公共前缀)数组,找出 LCP ≥ 阈值的重复子串。
import numpy as np
def suffix_array(text):
suffixes = [(text[i:], i) for i in range(len(text))]
suffixes.sort()
return [s[1] for s in suffixes]
def find_duplicate_substrings(texts, min_length=100):
"""找出所有长度 ≥ min_length 的重复子串"""
SEP = '\x00'
combined = SEP.join(texts)
sa = suffix_array(combined)
lcp = compute_lcp(combined, sa) # 计算 LCP 数组
duplicates = []
for i in range(1, len(lcp)):
if lcp[i] >= min_length:
start, end = sa[i], sa[i] + lcp[i]
duplicates.append(combined[start:end])
return duplicates
7.7.2 优点、缺点与适用场景
优点:能发现文档内部局部重复(两文档各 80% 不同但共享 500 字引用);不依赖 n-gram 大小参数。
缺点:内存消耗巨大——需存储整个语料的后缀数组;万亿 token 级需 TB 级内存;实现复杂。
适用:数据集较小(< 100B token)、需精细段落级去重时,或作 MinHash 后的补充步骤。工业级用 C++ 库(sdsl)或 ccnet(Facebook)的句级去重:
python -m ccnet.dedup \
--input data.jsonl --output dedup.jsonl \
--min-length 100 --method suffix_array
踩坑经验:Suffix Array 内存复杂度 O(N)(N 为所有文本字符数)。10TB 文本约需 20TB 内存——只能用分布式版本(如 Spark 上的实现)。
7.8 语义去重与 Soft Contamination:从 n-gram 到嵌入空间
MinHash 与 SimHash 都基于 token/char n-gram 的浅层匹配,在三类场景失效:模板污染(同模板不同内容,n-gram 无法区分)、语义重复(同语义不同表述,n-gram 重叠低导致漏检)、短文本扰动(极短片段嵌入长文本,信噪比过低)。2026 年的前沿工作把去重推进到”语义等价”判断。
7.8.1 Soft Contamination:n-gram 方法的盲区
arXiv:2602.12413(2026-02)揭示 Soft Contamination(软污染):训练数据泄漏了 benchmark 的语义近重复(内容等价但字符串空间不接近)。传统 n-gram 去污染检测不到——模型通过浅层模式匹配而非真实分布外推理”作弊”。典型案例:
- “如何重置密码?” vs “忘记密码怎么办?”
- “解决内存泄漏的方法” vs “修复 out-of-memory 错误”
- 同一道数学题的不同表述
7.8.2 SemDeDup:Embedding + 聚类
用 embedding 模型把文档映射到向量空间,计算余弦相似度聚类去重:
from sentence_transformers import SentenceTransformer
import numpy as np
def semantic_dedup(documents: list[str], threshold: float = 0.88) -> list[str]:
"""基于 embedding 的语义去重"""
model = SentenceTransformer('shibing624/text2vec-base-chinese')
embeddings = model.encode(documents, show_progress_bar=True)
norms = embeddings / np.linalg.norm(embeddings, axis=1, keepdims=True)
similarity = norms @ norms.T
n = len(documents); seen = set(); unique_docs = []
for i in range(n):
if i in seen:
continue
unique_docs.append(documents[i])
for j in range(i + 1, n):
if similarity[i, j] > threshold:
seen.add(j)
return unique_docs
阈值建议:0.85–0.90(太低误杀语义相近但内容不同的文档,太高检测不到真正语义重复)。成本约 MinHash 的 10 倍,但召回率显著更高。实践建议:在 SFT/RL 数据上优先部署(量小、语义质量更关键);预训练语料先 MinHash 筛掉 95%+ 明显非重复,再做语义去重。
7.8.3 MinHashLSH vs SemDeDup 工程权衡
| 维度 | MinHashLSH | SemDeDup |
|---|---|---|
| 计算复杂度 | O(N) | O(N) embedding + O(N²) 聚类 |
| 内存 | 整型签名(低) | float32 向量(高) |
| 句法精度 | 极高 | 高 |
| 语义召回 | 极低 | 高 |
| 硬件 | CPU 集群 | GPU(embeddings + Faiss) |
| 可扩展性 | PB 级 | TB 级(受显存限制) |
推荐组合:先 MinHashLSH 过滤 95%+ 明显非重复,再对剩余做 SemDeDup,避免 GPU 成本爆炸。直接对亿级全量做 SemDeDup 不现实。
7.8.4 多粒度融合:SemHash-LLM
SemHash-LLM(Fang et al., arXiv:2607.01601v1, 2026-07-02)尝试打破”MinHashLSH vs SemDeDup”的二选一困境,核心思路是门控融合——从字符级(n-gram MinHash)、token 级(注意力加权)、文档级(蒸馏 LLM embedding 哈希)三粒度提取信号,可学习门控融合:
- 语义投影哈希(SPH):将 LLM 嵌入蒸馏为紧凑二进制码,克服 SimHash 无法捕捉语义相似性的缺陷
- 注意力加权 MinHash:利用 Transformer 注意力权重抑制模板噪声(导航栏、广告),强调内容核心
- 级联过滤:SPH 粗筛 → 注意力 MinHash 精筛 → LLM 裁决(<1% 神经成本)
⚠️ SemHash-LLM 为 2026 年 7 月预印本,尚未经同行评议,建议标注”待验证前沿”。其核心思路——用 LLM 嵌入空间判定”语义等价”而非”n-gram 重叠”——代表 2026 H2 语义去重重要趋势。
工业趋势:NeMo Curator 26.04 已将语义去重作为内置模块(默认启用),后端从 SentenceTransformers 切到 vLLM,默认模型从 all-MiniLM-L6-v2 升级为 google/embeddinggemma-300m。标注工程定位:语义去重计算成本显著高于 MinHash(需 GPU+嵌入推理),建议在 MinHash 粗筛后做精筛,而非替代 MinHash。
7.9 PII 识别与脱敏:把个人信息挡在门外
安全过滤守住”内容毒性”红线,PII 脱敏守住”个人信息”红线。两者共享”发现即处置”的范式,但 PII 更强调识别的召回率与处理的可逆性权衡。
7.9.1 模型记忆化:PII 泄露的根因
大模型会”记住”训练数据,在特定提示下完整输出这些内容。记忆化条件:数据重复出现次数多、模式独特(如特定格式个人信息)、模型越大记忆越强。
实际案例:GPT-2 能在特定 prompt 下输出完整邮箱与电话;更大模型记忆化更严重。2026 最新攻击研究:生产级 LLM 的 PII 提取成功率已达 16.9%,从简单前缀匹配进化为多轮对话引导提取。
⚠️ 2026 新威胁向量:激活导向 PII 越狱(TrustNLP 2026, Nakka et al.)。攻击者无需对抗性提示,而是通过轻量线性探针定位模型负责”拒绝回答隐私问题”的注意力头,再操控其激活值(activation steering)诱导模型输出记忆的真实 PII。三主流 LLM 上披露率超 80%,速度比 GCG 快 900 倍。这意味着:对齐机制在输入层的安全保障可能被内部激活干预完全绕过——传统 PII 检测与输出过滤无法防御此攻击面。
关键关联:去重本身就是 PII 防线。重复出现 50 次的 PII 比出现 1 次的更易被提取。7.3 节的去重与本节脱敏协同降低记忆化风险。
7.9.2 正则为主的多语言 PII 检测
⚠️ 在读下面的方案之前,先立一条基线:PII 检测远未解决(2026-10-08 补)
PIIBench 把 10 个公开数据集、80+ 种标签方案归一化成统一多源语料,测试 8 个检测系统(含规则引擎、通用 NER、PII 专用模型、金融 NER 专家):所有系统的 span 级 F1 都低于 0.14,最好的一个只有 0.1385,且多数实体类型在所有系统上召回为零。
三个必须点明的细节:
- 得分最高的那个系统是 Microsoft Presidio(F1 = 0.1385)——正是本节下文 §7.9.3 要推荐的生产方案。这不是否定该推荐,而是说明:在跨源真实分布上,目前最好的工具也只能做到这个水平。
- 0.76 与 0.1385 不是同一个难度。§7.9.3 引用的 RECAP 在中文(zh_SG)上 F1 0.76 是单源条件下的成绩,PIIBench 的 0.1385 是跨源成绩。两者不可互相印证,也不可互相否证——引用任一方时都必须写明评测条件。
- 论文的判断是:为窄领域开发的 PII 检测工具在多源场景下失效。而预训练语料恰恰是最”多源”的输入。
⚠️ 版本分歧待核:PIIBench 论文摘要称 2,369,883 序列 / 3.35M 实体提及 / 48 类;HF 数据集卡(
pritesh-2711/pii-bench)称 999,940 条 / 82 实体类型 / 165 BIO 标签(注明当前版本带--include-text重新生成)。两组数字不一致,本轮未确认何者为当前发布版,引用规模数字前请回两处核对。
import re
from typing import List, Tuple
class PIIDetector:
"""多语言 PII 检测器"""
def __init__(self):
self.patterns = {
"cn_phone": r'1[3-9]\d{9}',
"cn_id_card": r'[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]',
"cn_bank_card": r'[1-9]\d{15,18}',
"email": r'[\w.+-]+@[\w-]+\.[\w.]+',
"ip_address": r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b',
"url_with_params": r'https?://\S+[?&]\S*(?:token|key|password|secret)\S*',
}
def detect(self, text: str) -> List[dict]:
findings = []
for pii_type, pattern in self.patterns.items():
for match in re.finditer(pattern, text):
findings.append({"type": pii_type, "value": match.group(),
"start": match.start(), "end": match.end()})
return findings
def mask(self, text: str, strategy: str = "replace") -> str:
"""PII 处理:replace(占位符)/hash(哈希)/redact(删除)"""
findings = self.detect(text)
for f in sorted(findings, key=lambda x: x["start"], reverse=True):
if strategy == "replace":
placeholder = f"[{f['type'].upper()}]"
elif strategy == "hash":
import hashlib
placeholder = hashlib.sha256(f["value"].encode()).hexdigest()[:8]
else:
placeholder = ""
text = text[:f["start"]] + placeholder + text[f["end"]:]
return text
7.9.3 超越正则:上下文 PII 与 NER
正则只能捕获格式化 PII,上下文 PII 是更大挑战——“姓名+单位""住址”等隐含身份的组合信息需要 NER(命名实体识别)模型辅助。增强版思路:
CONTEXT_PATTERNS = [
# "我叫 XXX,在 YYY 工作" 暴露姓名+单位
(r'我叫([\u4e00-\u9fa5]{2,4}).*?在([\u4e00-\u9fa5]{2,10}(?:公司|集团|大学|医院|银行))(?:工作|上班|任职)',
"name_org_combo", 4, "姓名+单位组合可高度定位个人"),
# "家住 XXX" 暴露住址
(r'(?:家住|住在|地址[是为]?)([\u4e00-\u9fa5]{5,30})',
"home_address", 4, "家庭住址属于敏感 PII"),
]
# 生产推荐:用 Presidio(Microsoft)做 NER 级 PII 识别
# Presidio 同时支持预训练识别器 + 自定义识别器(正则/模型),
# 覆盖 PERSON / LOCATION / DATE_TIME / ID / PHONE_NUMBER 等实体,
# 并输出置信度供分级处理。
风险分级(决定处理优先级,并非所有 PII 同等对待):
| PII 类型 | 风险等级 | 推荐策略 | 理由 |
|---|---|---|---|
| 身份证号 | 5 | 哈希/删除 | 可唯一定位个人 |
| 银行卡号 | 5 | 删除 | 涉及财务安全 |
| 手机号 | 4 | 替换 | 可定位个人 |
| URL 含密钥 | 4 | 删除 | 密钥泄露 |
| 姓名+单位 | 4 | 替换 | 组合可识别个人 |
| 邮箱/地址/生日 | 3 | 替换 | 准标识符 |
| IP 地址 | 2 | 替换 | 可追溯网络位置 |
策略选择问答(来自 de 练习):
- 下游含信息抽取(IE)→ 选哈希(保留可链接性,去除明文)
- 审计需还原原始数据 → 选哈希(可映射回,但需妥善保管密钥)
- 对训练质量影响最小 → 替换(占位符保留结构,模型仍可学语言模式)
⚠️ 风险等级高 ≠ 能检出来(2026-10-08 补)
检测的可靠性还有一条独立于”类型”的轴:输入分布。Zafar 与 Nowaczyk(arXiv:2609.03464《Mind the Gap: Robustness Risks in PII Detection Systems》,2026-09-03)构造了一个覆盖 7 类自然分布偏移的压力测试基准,评估三类主流方案——编码器 NER(SpaCy)、规则混合检测(Presidio)、生成式 LLM 提取(Qwen2.5-3B)——结果是三者在面对嘈杂、非结构化、非正式的 OOD 输入时都显著退化,但失败模式不同且互补:编码器模型主要失败在未见过的表层形式与边界判定,规则系统失败在非标准格式,LLM 则出现实体类型混淆与生成不稳定。论文结论是”聚合基准分数掩盖了部署关键的弱点,没有任何单一架构在所有 PII 类别上可靠”。
对本章的具体含义:风险分级表应增加一个前置自检步骤——在你的语料分布上抽样实测每一类 PII 的召回率,而不是默认”部署即可用”。预训练语料恰恰是分布最杂的输入(网页、论坛、扫描件、代码混合),正是本章反复强调的”静默退化”高发区。⚠️ 该论文为 2026-09 预印本,未经同行评议。
7.9.4 差分隐私:数学可证明的保护
差分隐私(DP)在数据层面提供数学可证明的隐私保护:
DP 在 LLM 训练中的应用:
1. DP-SGD:训练时给梯度加噪声
→ 效果:模型无法记住单条数据;代价:质量下降(隐私-效用权衡)
→ 适用:医疗、金融等极高隐私要求场景
2. 数据层面 DP:预处理时匿名化
→ 效果:去除可识别信息;代价:可能损失有价值上下文
→ 适用:大多数场景
7.9.5 推理时隐私泄露:PANOPTICON 基准
以上方法关注训练数据静态 PII。PANOPTICON(Thornton et al., arXiv:2607.22695, 2026-07)提出新威胁模型:推理时隐私泄露——用户与模型交互中不可避免输入 PII(如”我的社保号是 XXX”),这些 PII 进入上下文窗口,可能被 Prompt Inversion Attack 等提取。该基准含 67,718 个带 PII 标记提示(来自 9,674 合成用户画像,7 类 PII、38 场景),引入 PII-F1 指标区分实体级恢复与标记级准确率。工程启示:推理时 PII 过滤(如 OpenAI Privacy Filter)正成为与训练前清洗并列的独立安全层。
7.9.6 第四条面:对话日志(agent 与助手的会话记录)
还有一类 PII 载体既不是训练数据、也不是推理上下文,而是对话日志——但它的归宿往往是训练数据。
LLM 助手与 agent 系统会记录长多轮对话,服务方通常在存储或处理前扫描并掩码 PII。而日志是训练数据的常见来源——下一轮训练往往就把这批日志回收进去。这让”日志 PII”在性质上更接近训练数据问题,而不是推理时问题。
PII-TRACE 是首个针对该场景的基准,含 13,148 段合成多轮对话、13 种语言、字符级 span 与标识符簇。它的关键设计是评估检测器能否覆盖同一个跨轮重复出现的标识符的每一次提及。结果:11 个基线(含前沿 LLM)中,没有一个能在不对无 PII 对话产生大量误报的前提下达成完整实体级覆盖;单遍读取在长对话上会丢失三分之一的金标字符。
这与本章 §7.3.1 的第三重伤害是同一个机制:重复出现的 PII 被记住的概率远高于单次出现。日志里跨轮重复的标识符,正是被回收进训练后最危险的那部分数据——而按 PII-TRACE 的结果,这类重复恰恰是当前检测器最容易漏掉的部分。⚠️ 该工作为 2026-08 提交、2026-09 发布的预印本,数字取自摘要,正文未逐条读。
7.10 训练-评测集去污染:让 benchmark 可信
去污染是去重的反向应用——不是去除训练集内部重复,而是去除训练集中与评测集重叠的部分。它是 7.8 节软污染的”硬匹配”对应手段。
7.10.1 为什么必须去污染
若训练集含 MMLU 测试题,模型”刷榜”成绩虚高,实际能力不强。
| 评测集 | 题目数 | 估计污染率 |
|---|---|---|
| MMLU | 14,042 | 5-15% |
| GSM8K | 1,319 | 10-20% |
| HumanEval | 164 | 20-40% |
| TriviaQA | 95K | 15-25% |
不去污染,benchmark 成绩不可信。
7.10.2 13-gram 去污染(工业标准)
def decontaminate(train_texts, eval_texts, n=13):
"""13-gram 去污染:训练集含与评测集任一 13-gram 重叠则整篇删除"""
eval_ngrams = set()
for text in eval_texts:
words = text.lower().split()
for i in range(len(words) - n + 1):
eval_ngrams.add(' '.join(words[i:i+n]))
clean_train = []; contaminated = 0
for text in train_texts:
words = text.lower().split()
is_contaminated = False
for i in range(len(words) - n + 1):
if ' '.join(words[i:i+n]) in eval_ngrams:
is_contaminated = True; break
if not is_contaminated:
clean_train.append(text)
else:
contaminated += 1
print(f'Contaminated: {contaminated} / {len(train_texts)}')
return clean_train
EVAL_SETS = ['mmlu','gsm8k','human_eval','trivia_qa','natural_questions',
'arc_challenge','arc_easy','winogrande','hellaswag','piqa',
'siqa','boolq','openbookqa','math','mbpp','copa','wsc','rte','cb','wic']
为什么是 13-gram:太短(5-gram)误杀大量巧合重叠;太长(50-gram)漏掉轻微改写。Brown et al.(2020)表明 13 是”原文泄露”与”过度匹配”的合理平衡点。
7.10.3 多层污染检测(de 扩展)
| 方法 | 原理 | 捕获范围 |
|---|---|---|
| 13-gram 精确匹配 | 字符串 n-gram 重叠 | 原文泄露 |
| 编辑距离 | 文本间最小编辑操作数 | 轻微改写 |
| Embedding 相似度 | 向量空间余弦 | 语义级变体 |
| Llama 3(token 10-gram) | token 级 n-gram 重叠率超阈值则移除 | 原文泄露 |
| DiffMIA(代码) | 对代码做语义保持变换,比损失差异 | 代码基准污染 |
| BeyondBench(动态) | 算法化动态生成题,组合空间 >10¹⁵ | 从根本抗静态污染 |
| Self-Critique(RL 阶段) | 检测”策略塌陷”——多条解法高度相似 | RL 后训练污染 |
| CDD(峰度) | 输出分布峰度检测小模型污染 | 小模型污染(LoRA 下失效) |
去污染管线最佳实践:
- 收集所有需保护 benchmark(含 train/val/test split,含中文 C-Eval/CMMLU)
- 多层检测:13-gram 精确 → 编辑距离 → embedding 相似(可选)
- 处理:高度匹配(>80% 重叠)移除;中度(50-80%)标记并评估时关注;低度(<50%)保留
- 评估时同时报”去污染后分数”,若与标准分差异 >2% 说明严重泄露
去污染三坑:
- 只去题目没去周围解释 → 用”题目+答案+解释”拼接做 13-gram
- 评测集翻译版未去污染 → 所有语言评测集都做
- 评测集更新后没重做 → 每次更新重新去污染
⚠️ 补第四坑:顺序(2026-10-08 补)——去污染与质量过滤的先后顺序会改变结果
神经质量过滤器会放大基准污染:DCLM 分类器系统性地把 benchmark 样本排进前 5 百分位。机制是 benchmark 样本天然具备”教学性、指令性、结构良好”这些过滤器偏爱的特点。
由此得到的顺序纪律:先去污染,再质量过滤。若反过来(先过滤再去污染),过滤器已经把评测集的近邻集中到了保留集里,去污染面对的是一个被富集过的目标——漏检的代价被放大。本章 §7.11.3 讨论”去重与过滤的顺序”,此处是同一类问题的另一侧:去污染与质量过滤的顺序同样不是中性的。第 6 章 §6.4.5d 已建立指向本节的反向交叉引用。
另需扩展上面第 3 条坑:Gaperon 还发现延迟注入测试集能恢复有竞争力的基准分数,且连带抬高 held-out 基准。这意味着本节的”Embedding 相似度 / 编辑距离”这类检测只做一次并不充分——“评测集更新后没重做”应扩展为“每次语料变更都要重做”,因为语料变更本身就可能引入新的污染,而不只是评测集变了才会。
后训练阶段的污染动态(arXiv:2601.06103):污染发生在预训练,但 SFT/RL 可能放大。向预训练注入 5 份 GSM8K/MBPP 测试题后,SFT 进一步放大虚高。污染检测需贯穿整个训练流程,而非只在预训练做一次。
7.11 大规模去重的工程挑战与前沿
当数据达万亿 token(约 100 亿文档),去重本身成为严肃工程问题。
7.11.1 分布式架构
O(n²) 的文档两两比较不可行,需分片 + LSH 分桶 + 连通分量:
数据分片层 (Spark):按 hash(shingle) 分桶,相同 shingle 进同一桶
↓
Shard 1/N:独立计算 MinHash 签名 + 本地 LSH 索引
↓
合并去重层:分片内去重后,再做跨分片二阶段去重(连通分量:A≈B,B≈C → 同组保留一个)
↓
结果存储:unique_docs.parquet + dedup_report.json
推荐工具栈:
| 工具 | 用途 |
|---|---|
| datasketch | 单机 MinHash+LSH,适合 <100GB |
| Spark + datasketch | 分布式 MinHash,适合 TB 级 |
| datatrove(HuggingFace) | FineWeb 框架,MinHash 去重支持好 |
| ccnet(Facebook) | 工业级流水线,含句级去重 |
| Milvus 2.6 原生 MinHash LSH | 向量库原生集成,百亿级文档,速度 2x+、成本优化 3-5x |
| LSHBloom(Khan et al., 2025-26) | 布隆过滤器替代 LSHIndex:peS2o 仅需 MinHashLSH 的 0.6% 磁盘、2.7× 加速、假阳性 1e-5 |
| FED(arXiv:2501.01046) | GPU 加速 MinHash,>100GB,滚动哈希数量级加速 |
| NeMo Curator Semantic Dedup | GPU 语义去重(vLLM + EmbeddingGemma-300M),TB 级 |
7.11.2 内存管理
100 亿文档 × 128 维 uint32 签名 ≈ 5.12TB 内存。解法:按 LSH band 分桶流式处理(每次只加载一个 band);签名写磁盘按需加载;uint16 替代 uint32(精度损失小,内存减半);签名计算与 LSH 分桶流水线化。
⚠️ 这个算式建立在 datasketch 2.0.0 之前(2026-10-08 补):官方称 2.0.0 起 sketch 内存减半(
affine32方案),因此 5.12TB 这一估算在新方案下应相应下调。
7.11.3 去重顺序:先去重后过滤
方案 A(先去重→后过滤):去重在完整数据上,覆盖全面;代价是计算量大 ← 推荐
方案 B(先过滤→后去重):量小更快;但某重复文档一份被过滤一份保留,去重不完整
折中:至少在粗过滤(去掉明显垃圾)后去重
7.11.4 在线增量去重:RAD
传统 MinHash LSH 痛点:每次加新批次都要重扫全库,LSH 桶随数据膨胀、扫描成本上升、吞吐下降。RAD / FOLD(Bore et al.,arXiv:2606.03001,2026-06-02,机构:McGill University、University of Toronto、IBM Research)用 HNSW 图索引替代 LSH 桶,新文档在图中检索最近邻候选(O(log N))。挑战:Jaccard 在 HNSW 中出现”距离分数拥挤”,该系统用 bitmap 提供更具区分度的对齐信号。实测语料:LM1B / C4 / RealNews / Common Crawl,以 1M 文档为周期连续注入,累积至 30M 文档。适用:每月合并新 CC 快照的团队。
⚠️ 这篇论文的”名字”和”数字”都跟着版本变,引用时必须带版本。arXiv 落地页(abs)与 v2 全文的主体用 RAD(Retrieval-Augmented Deduplication),但论文标题与 v1 全文用的是 FOLD(Fuzzy Online Deduplication)——按 “RAD” 去检索会搜不到,搜索请用标题 FOLD: Fuzzy Online Deduplication for Very Large Evolving Datasets via Approximate Nearest Neighbor Search。数字同样分叉:
版本 系统名 recall 吞吐 abs 页摘要 / v2 结论句 RAD 0.94–0.97(相对 SOTA LSH 方案,@30M 文档) 最高 8× 提升 v1 全文摘要 FOLD 93–97%(最大评测规模) 最高 2.09×(相对 competing alternatives,后者的最好 recall 仅 76%) 本章正文沿用 abs 页口径(0.94–0.97 / 8×)。注意 2.09× 与 8× 的对照系不同:前者对比的是”同类替代方案”(其 best recall 仅 76%),后者对比的是 SOTA LSH 方案——量级差异主要来自基线选择,不是同一测度,切勿混引。
🔴 升级 datasketch 2.0.0 前的运维告警(2026-10-08 补):本节的图索引是长期累积的(以 1M 文档为周期连续注入、累积至 30M 文档),比 §7.11.1 的一次性 LSH 索引更危险——索引里会同时存在新旧两种置换方案产生的签名,而它们不可比。datasketch 2.0.0 官方明确要求 rebuild persisted sketches and LSH indexes。升级路径:要么全量重建索引,要么在切版时刻打一个明确的索引分界线,绝不跨分界线检索。这是一条会直接导致去重失效而不产生任何告警的运维陷阱。
7.11.5 跨源去重的质量信号价值:MixMinMatch
传统去重把跨源重复当”噪声”消除,但 MixMinMatch(Abdelaziz et al., 2025, arXiv:2512.18834)提出颠覆性视角:跨源重复本身是免费、无模型的质量信号。
当多个独立 Web 管线(FineWeb、C4、RedPajama)各自从原始 CC 筛选去重后,若同一文档被多管线同时保留,说明它通过多个独立质量标准——等价于”共识投票”。
- 多源 Web 语料间约 40%+ token 存在跨源重复
- 被多源独立恢复的文档质量显著更高
- 阿拉伯语 4.5% 提升(vs ArabicWeb24)、土耳其语 5.5% 提升(vs FineWeb-2),同时提供 4× 更多独特 token
实操启示:合并多个数据集(自建 + FineWeb + CC-Main)时,不要简单去重——用 MixMinMatch 共识信号作质量权重,被多源确认的文档获更高权重。这一工作标志去重从”纯清洗步骤”向”质量发现工具”的认知跃迁。
这条线在 2026 年已推进到完整形态:质量 × 冗余 × 多样性三元组。
MixMinMatch 做的是”把冗余转成质量信号”(两个目标),而 CuraWeb 做的是”把质量、冗余、多样性放进同一个目标函数联合优化”——双轨清洗(规则 + 模型驱动)配混合去重(n-gram + 语义),再用多目标采样器平衡信息质量与分布广度。与本书已有内容的关系是:第 6 章 §6.7.4 的 QuaDMix 做的是”质量 × 多样性”两元联合优化,本节做的是”把冗余转成质量信号”,CuraWeb 是把两者合起来。建议读者把这三者当作同一条线上的三个节点,而不是三个独立技巧。
同批另两篇方向相近:arXiv:2608.03089(频率与长度感知的子文档级可扩展去重,把重复检测与副本保留策略解耦)、arXiv:2603.06603(规模依赖的数据重复:随模型能力上升,语义等价文档的梯度趋于对齐,“重复”本身是随规模变化的概念)。⚠️ 这两篇本轮只读摘要,未读正文,标为待验证前沿。
7.12 隐私与伦理合规:没有就不能上线
安全过滤与 PII 脱敏处理”内容”与”个人信息”,本节上升到法律与伦理框架(整合 de-ch19)。核心结论:合规检查不再是锦上添花,而是没有就不能上线的硬性要求。
7.12.1 各地法律现状(截至 2026 年初)
| 地区 | 法律框架 | 对 AI 训练数据态度 | 关键进展(2025-2026) |
|---|---|---|---|
| 美国 | Fair Use 原则 | 个案判定,无统一立法 | 版权局 2025.05 发布 Part 3 报告;NYT vs OpenAI 2026.01 进入证据发现(裁定提交 2000 万条日志);多案并进 |
| 欧盟 | EU AI Act | 强制合规 | 第二波义务 2025.08.02 生效:通用 AI 模型须发布训练数据透明度报告、遵守版权法、支持机器可读 opt-out |
| 中国 | 生成式 AI 管理办法 | 合规要求明确 | 数据来源合法、内容审查、用户投诉处理机制 |
| 日本 | 著作权法修正 | 相对宽松 | 非营利使用更自由,商业应用仍需谨慎 |
| 德国 | 著作权法 + EU AI Act | 严格 | GEMA vs OpenAI(2025.11):慕尼黑地院判训练+输出均侵权,全球首例 AI 训练数据版权侵权判决 |
⚠️ EU AI Act 时间线已于 2026 年被改写,本节表格止步于 2025.08.02,需配合下表阅读。
EU AI Act 修订后时间轴(Regulation (EU) 2026/1744「Digital Omnibus on AI」)
2026 年通过的修订条例 Regulation (EU) 2026/1744 对原 Regulation (EU) 2024/1689 作了自生效以来最大的一次改动。欧盟委员会官方 AI Act Service Desk 时间轴页(该页自述 “The timeline takes into account the AI Act amendments introduced by Digital Omnibus on AI.”)现行节点如下:
| 日期 | 事项 |
|---|---|
| 2024-08-01 | 条例生效(entry into force) |
| 2025-02-02 | 总则(定义、AI 素养)与禁止性条款适用 |
| 2025-08-02 | 通用 AI(GPAI)规则适用 + 治理架构到位 |
| 2026-08-02 | 大部分规则适用;透明义务 Art.50 开始适用;创新支持措施;执法启动(成员国 + 欧盟层面,涵盖 GPAI、禁止性条款、透明规则、AI 素养) |
| 2026-12-02 | 两条新增禁令适用(生成非自愿性亲密影像的 AI 系统、生成儿童性虐待材料的 AI 系统);存量系统的 Art.50(2) 机器可读标注过渡期截止 |
| 2027-08-02 | 每成员国至少一个 AI 监管沙箱投入运行 |
| 2027-12-02 | Annex III 高风险义务适用(原为 2026-08-02,延后 16 个月) |
| 2028-08-02 | Annex I(产品内嵌)高风险义务适用(原为 2027-08-02,延后 12 个月) |
三个必须同时传达的方向——只讲其中一个都会误导:
- 延后:Annex III 高风险从 2026-08-02 推到 2027-12-02,Annex I 推到 2028-08-02。这是”缓”的方向。
- 新增:Digital Omnibus 不只是延期。它在 Art.5 中新增 Art.5(1)(ba)(非自愿性亲密影像)与 Art.5(1)(bb)(儿童性虐待材料)两条禁令,使 Art.5 禁止清单从 8 条增加到 10 条,2026-12-02 适用,罚则沿用 Art.99 最高档——最高 €3,500 万或全球年营业额 7%(两者取高者;SME 取较低者)。这是”紧”的方向,且适用日早于所有高风险义务。
- 执法启动:2026-08-02 起成员国与欧盟层面的执法正式开动,覆盖 GPAI、禁止性条款、透明规则与 AI 素养——即便高风险义务已延后,这一条没有延后。
对数据工程团队最直接的后果:修订后的 Art.113 规定,2026-08-02 前已投放市场的 AI 系统(含 GPAI 系统),其合成内容必须完成更新后 Art.50 合规改造的过渡期截止日为 2026-12-02;而 Art.50 本身对新系统从 2026-08-02 起适用。因此”标注机制”这件事存在两个截止日,取决于系统何时上市,不能一刀切。
7.12.2 标志案例:NYT vs OpenAI 与 GEMA vs OpenAI
NYT vs OpenAI(美国,审理中):2023.12 起诉,主张模型”回吐”受版权内容。2026.01.05 关键裁定——法官要求 OpenAI 提交 2000 万条去标识化聊天记录,以回答三个问题:回吐频率、用户意图(是否绕过付费墙)、使用性质(转化性使用 vs 直接替代)。Fair Use 四要素对峙:
| 要素 | OpenAI 论点 | NYT 论点 |
|---|---|---|
| 使用目的 | 训练是转化性使用 | 输出直接替代文章 |
| 作品性质 | 事实报道不受强保护 | 调查报道有高度创造性 |
| 使用比例 | NYT 占比极小 | 但回吐大量完整文章 |
| 市场影响 | AI 非 NYT 替代品 | 已有流量下降证据 |
GEMA vs OpenAI(德国,2025.11 已判决):首次司法确认使用受版权作品训练 AI 本身即构成侵权——不要求证明模型”回吐”。opt-out 机制不足以免责,需明确授权。与 NYT 对比:
| 维度 | GEMA(德/欧) | NYT(美) | Bartz v. Anthropic(美) |
|---|---|---|---|
| 状态 | 已判决(2025.11) | 审理中 | 已和解($1.5B) |
| 核心主张 | 训练行为侵权 | 回吐构成替代 | 未经授权使用作品训练 |
| 信号 | 欧洲合规门槛大幅提高 | Fair Use 边界待定 | 和解成本极高 |
行业影响:Axel Springer、AP、新闻集团等已与 OpenAI 签数百万美元许可协议。“快速行动、打破常规”的抓取时代正在结束,数据许可正成为行业惯例。GEMA 判决 + EU AI Act 第二波义务形成双重要求——不仅要合规声明,还要确保训练数据本身具合法授权。
7.12.3 合规 Checklist(分级版)
P0(必须满足,不满足禁止进入训练管线):
□ 数据来源有明确许可证或合法使用依据
□ PII 已检测处理(至少 Level 4+ 必须清除)
□ 敏感内容已过滤(含 CSAM 红线处置)
□ 数据存储有访问控制
□ EU AI Act Article 50 合规:AI 生成内容标注机制就绪(2026.08.02 起对新投放系统适用)
□ 存量系统(2026.08.02 前已投放市场,含 GPAI)的 Art.50(2) 机器可读标注改造:2026.12.02 截止
<!-- edit: 2026-09-19 原 P0 条目只写「2026.08.02 强制」,会让存量系统误以为当天必须完成改造;按官方时间轴拆成两条,分别对应新系统与存量系统 -->
P1(应该满足,不满足需负责人审批):
□ 遵守 robots.txt
□ 建立 opt-out 机制(EU AI Act 强制)
□ 记录数据溯源(provenance)、保留审计日志
□ 评估商业许可需求(新闻/出版/图像数据)
□ EU AI Act 透明度报告就绪
□ 版权和解风险准备(基于 GEMA 判决与 Anthropic \$1.5B 和解)
P2(建议满足,最佳实践):
□ 记录数据许可费用预算(已是合规成本一部分)
□ 版权内容自动检测过滤(机器可读 opt-out + 内容指纹)
□ 定期法律框架更新追踪
7.12.4 偏见与公平性
互联网数据天然携带偏见:人口统计偏见(某些群体过多/过少)、语言偏见(英语主导)、地理偏见(发达国家视角)、时间偏见(近期权重过大)。偏见检测应成为数据管线标配:
def detect_demographic_bias(dataset, protected_terms, sample_size=10000):
"""检测数据集人口统计偏见"""
import random
sample = random.sample(dataset, min(sample_size, len(dataset)))
results = {}
for dimension, terms in protected_terms.items():
counts = {term: 0 for term in terms}
for doc in sample:
doc_lower = doc.lower()
for term in terms:
if term in doc_lower:
counts[term] += 1
total = sum(counts.values())
distribution = {t: c / max(total, 1) for t, c in counts.items()}
max_ratio = max(distribution.values()) / max(min(distribution.values()), 0.001)
results[dimension] = {
"distribution": distribution,
"max_ratio": max_ratio,
"is_balanced": max_ratio < 3,
}
return results
偏见修复的权衡:朴素修复是上采样不足群体,但会引入重复——与 7.5 节去重目标矛盾。更好方法:生成合成数据补充不足群体表示。偏见没有”绝对零点”,重要的是有意识地检测与记录,而非追求完美平衡。
7.12.5 数据安全与下毒防御
数据下毒风险:
1. 后门攻击:注入特定触发模式 → 用户输入触发词时模型产生恶意输出
2. 偏见注入:系统性注入偏见内容 → 特定话题有偏输出
防御:数据来源追踪验证、异常检测(统计偏离)、多来源交叉验证、定期安全评估
⚠️ 拒绝机制的几何脆弱性(TrustNLP 2026, CLS, arXiv:2606.22686):对齐后模型在收到不安全请求时应拒绝,但 CLS 证明拒绝行为在 LLM 表示空间中形成低秩线性方向(Refusal Vector),可被轻易操控——JailbreakBench 上以 1 秒/查询达 95% 攻击成功率(GCG 为 15 分钟/5%)。更根本:脆弱性源于拒绝机制的几何结构本身,无论 RLHF/DPO 都有效。PCA 显示拒绝/良性查询在最终层隐藏状态严格线性可分,且不同模型拒绝决策深度不同(Llama 95% 深度后才出现,Qwen ~40% 深度即启动)。这对所有对齐方法的安全性提出底层挑战——数据层的毒性过滤因此更显必要。
7.13 整合架构:四道防线串成一条流水线
把本章四块内容落到一条可执行的预训练数据合规-质量流水线:
原始语料
│
├─[防线1: 安全过滤] Detoxify/ShieldGemma 毒性过滤 → CSAM 红线处置并报告
│ ↓
├─[防线2: 精确去重] URL 归一 + SHA256(+Bloom Filter) + 段落级 hash
│ ↓
├─[防线3: 模糊去重] MinHash+LSH(文档级, n=3/0.75中文) → SimHash(网页级补充)
│ → Suffix Array(句级) → 语义去重(精筛, GPU)
│ ↓
├─[防线4: 去污染] 13-gram 跨评测集去污染 + 多层检测(编辑距离/Embedding)
│ ↓
├─[PII 脱敏] 正则 + 上下文 + NER/Presidio 检测 → 替换/哈希/删除(分级)
│ ↓
└─[合规闸门] 来源许可核查 + 偏见审计 + 下毒异常检测 + 分级 Checklist
→ 通过则进入训练,否则打回
关键耦合再强调:去重(防线2/3)降低 PII 记忆频率(7.9.1),安全过滤与 CSAM 处置共享”发现即处置”范式(7.2.4),去污染是去重的反向应用(7.10),合规闸门是前三者的法律收口(7.12)。四者不是串联的”可选模块”,而是缺一不可的合规闭环。
多模态特化:图像去重与视觉 PII
本章的去重工具箱(SHA256、MinHash+LSH、SimHash、Suffix Array)全部建立在离散符号序列的 n-gram 之上。图像没有 n-gram,这套方法一个字也用不上——多模态语料必须在去重与 PII 两条线上各补一套视觉侧实现。
图像去重:两级结构,缺一不可
| 级别 | 方法 | 能抓到的重复 | 代价 |
|---|---|---|---|
| L1 精确 | 文件级 SHA256 | 字节完全相同 | 极低,只抓镜像 |
| L2 感知 | pHash / dHash + 汉明距离 | 改格式、改尺寸、重压缩 | 低,可建哈希索引 |
| L3 语义 | CLIP / DINOv2 嵌入近邻检索 | 裁剪、加边框、加水印、改色调 | 高,需 GPU 与向量库 |
pHash / dHash 走汉明距离阈值,实践中常从 8-10 这一量级起调再按误杀率回扫;DINOv2 比 CLIP 更适合抓”同一张图的不同裁切”,因为自监督特征对局部内容更敏感。真正危险的不是精确重复,而是近重复:同一张商品主图被上百个站点加水印转载,L1 完全抓不到,却足以让模型对这张图过拟合——这与 7.8 节 Soft Contamination 的教训同构,n-gram 抓不到的语义重复在图像侧是常态而非例外。
跨模态泄漏需要联合指纹。同一张图配不同文本出现在多个样本里是常态,但不能只按图去重:一张图同时配 caption、VQA、OCR 三类标注是合法且高价值的数据,按图去重会误杀。正确做法是存(图像指纹, 文本指纹)的组合键,两者同时近重复才判为重复样本。
视觉 PII:检测 + 模糊,而不是正则。7.9 节的正则与 Presidio 在图像上完全失效——人脸、车牌、门牌、屏幕截图里的账号都是像素而非字符串。流程是先检测后脱敏:人脸检测 + 车牌检测 + OCR 识别屏幕文字,OCR 出的文本再走 7.9 的正则与 NER 判级;处置方式是局部模糊/马赛克/裁剪,因为图像没有”占位符替换”这个选项。
NSFW / CSAM 过滤必须前置且独立审计。LAION-5B 因被检出 CSAM 内容而下线修订,是这一行的标志性事故:它不是模型能力问题,而是数据管线的审计缺位。结论是把安全过滤(7.2)放在去重之前,并保留可追溯的审计日志与黑名单——先看住图,再谈去重与配比。
(详见第 26 章多模态数据工程)
7.14 全章要点回顾
- 安全过滤是合规底线——Detoxify/ShieldGemma 工业首选,CSAM 为法律红线,发现即处置并报告
- 去重三重收益——记忆化下降、泛化提升、隐私泄露降低;用 70% 数据量反获更好效果
- 精确去重必须做——URL 归一 + SHA256(+Bloom Filter),去除 5-15% 完全重复;段落级 hash 谨慎用于长模板
- MinHash+LSH 是模糊去重事实标准——参数 (128, 32, 4, 0.8),中文用 n=3/4、阈值 0.75
- SimHash 作网页级补充——基于余弦、极快、64-bit 签名
- Suffix Array 抓句级重复——但内存 O(N),仅适合中小规模或补充
- 语义去重是 2026 前沿——Soft Contamination 揭示 n-gram 盲区;SemDeDup 需 GPU;SemHash-LLM 多粒度融合待验证;先 MinHash 后语义精筛
- 去污染让评测可信——13-gram 工业标准,多层检测(编辑距离/Embedding/DiffMIA/BeyondBench/Self-Critique),贯穿全流程
- PII 脱敏分级处理——正则+上下文+NER,替换/哈希/删除按风险等级;去重本身是 PII 防线;差分隐私提供数学保证;PANOPTICON 揭示推理时泄露新面
- 合规是硬上线门槛——EU AI Act 第二波已生效、GEMA 首判训练即侵权;分级 Checklist(P0/P1/P2)管理;偏见检测是标配;下毒需数据层防御
- 过度去重有害——2024 后共识:分粒度按需调节,避免误杀多样性
- 跨源共识是质量信号——MixMinMatch 把去重副产品转化为免费质量权重,去重从”清洗”升级为”质量发现”
🛠️ 本章衍生工具
数据工程·去重策略竞技场
《AI 数据全书》第 7 章「安全过滤、去重与隐私合规」配套——URL 归一 / SHA-256 / MinHash+LSH / SimHash / SuffixArray / SemDeDup 六算法在 24 篇带真值标注的样本上同台竞技,去重率·误杀率·漏检率三维对比 + 三色连线关系图(绿=命中/红=误杀/琥珀虚线=漏检,#13 只有句级去重抓得到、#9#11 只有语义去重抓得到) · MinHash 调参实验台:LSH S 曲线 P=1−(1−J^r)^b + 中文调参四组合 PR 散点(τ 拉到 0.65 准确率跌到 72.4% 红色禁区) + 签名内存条(100亿×128维 uint32≈5.12TB) · Soft Contamination 攻防矩阵(13-gram 对语义改写检出率趋近 0,arXiv:2602.12413 实测 Codeforces 78% 语义重复 / 精确重复≈0) · MixMinMatch 跨源共识(40%+ token 跨源重复,阿拉伯语 +4.5% / 土耳其语 +5.5%) 与 RAD 增量去重(8× 吞吐、recall 0.94–0.97)
图像近重复指纹实验室
《AI 数据全书》第 7 章「图像去重」配套——canvas 生成基准图 + 6 种扰动变体(裁剪/水印/亮度/模糊/翻转),aHash/dHash/pHash 三算法 64-bit 指纹手写实现与汉明距离矩阵实时判定,阈值滑杆 + 三条动态结论(翻转失效/模糊鲁棒/两级流水线)——文本 MinHash 为何不适用图像
数据工坊·小语料去重器
《AI 数据全书》第 7 章「安全过滤 · 去重 · PII · 去污染」配套产品原型(系列第三个实用工具)——四道工序真跑一遍:① 精确去重(归一化后哈希,可选统一空白/小写/去标点三档强度)② MinHash + LSH 模糊去重,不是伪代码演示:shingle 切分 → numPerm 组通用哈希取最小值 → b×r 分带捞候选 → 再用真实 Jaccard 逐对验证 ③ 模板段落剥离(出现 ≥N 次且够长的段落,先精确去重再统计,否则重复文档的正文会被误判成模板)④ 13-gram 去污染 ⭐ 算法自检面板:MinHash 估计 vs 真实 Jaccard 的平均误差、候选对精度、LSH 漏检对数、S 曲线 50% 拐点 ⭐ 参数实验台:LSH 的 P=1−(1−sʳ)ᵇ 曲线可交互对比、b×r 扫描表(b=8/r=16 时召回掉到 25%)、n×阈值网格热力(⚠ 标记擦边判定) ⭐ 相似对审查:点任一对看两篇原文并高亮共享片段,判断是真重复还是误杀 ⭐ 短文档保护:低于字数的不参与模糊去重,宁可漏也不误杀 · 导出语料 .txt + 报告 .json,语料不出本机