100 条测试样本里 20 条和训练集重复(12 条精确、8 条近似),泄露率 20%; 训练集正类 0.505、测试集 0.590,偏差 0.085——剔掉泄露后反而升到 0.614。
上一节管模型。这一节管它的原料。数据出问题时,模型往往看不出毛病,但上线一定出问题。
一、数据集也要版本
「数据换了模型就等于换了」这句话不是修辞。 同一份代码、同一个随机种子,数据改 5%,训出来的模型可能完全不同。 所以数据集必须有自己的版本号和校验和,而且训练记录里要写清楚用的是哪一版。 否则半年后你没法回答「这个模型为什么这么差」。 最小可用的做法:把数据集文件算个哈希,和模型版本一起存进一张表。 成本几乎为零,但它决定了你以后能不能复现。 更完整的做法是记血缘:这批数据从哪几个源来、经过哪几步清洗、哪一步删了多少条。 血缘比版本号值钱,因为它是排查问题的地图。
二、三种污染,一种比一种隐蔽
精确重复:同一条样本出现在训练集和测试集里。用哈希就能抓,但前提是你真的去抓了。 近似重复:两条样本几乎一样,只差一点噪声或者一个字段的顺序。哈希抓不到,只能算相似度。 分布偏差:两边没有重复,但分布不一样。这类最隐蔽——测试集分数虚高或虚低,都可能是它造成的。 三种污染的后果不一样:前两种让测试分数虚高(模型见过答案), 第三种让测试分数不可信(考的和练的不是一回事)。
三、实测:20% 的测试集是脏的
Demo 造了 400 条训练、100 条测试。 测试集里我故意塞了 12 条从训练集原样复制的样本,再加 8 条加了 0.001 噪声的近复制。 哈希(保留 3 位小数后比对)抓出 12 条;余弦大于 0.9999 的相似度检查抓出另外 8 条。 合计 20 条,占测试集 20%。 这就是为什么检查要做两遍:精确重复的近似重复的哈希值完全不同,只做哈希只能抓到一半。 分布偏差那部分更值得看:训练集正类 0.505、测试集 0.590,差 0.085。 100 条样本里 8.5 个百分点的差距,足以让准确率这个指标失真。
四、清理之后,问题反而更明显了
把落在训练集里的测试样本剔掉,剩 88 条,正类占比变成 0.614。 注意方向:清理前 0.590、清理后 0.614,偏差变大了。 这不是清理做错了,是因为那 20 条泄露样本本来是按训练集的分布复制过来的, 它们一直在把测试集的分布往训练集那边拉。 这件事的教训:清理会暴露原本被掩盖的问题。 剔完发现两边分布差得更远,说明测试集本来就不代表训练分布,只是被泄露样本糊住了。 所以清理之后必须重新看一遍分布。清理不是终点,是让人看清真相的手段。
五、动手 Demo:数据体检:100 条测试样本里 20 条是脏的
造 400 条 8 维训练样本,再单独采 100 条测试样本(前三维整体抬高 0.7,模拟跨批次采集); 往测试集前 20 条里塞 12 条训练样本的精确复制和8 条加微噪声的近复制; 然后用哈希和余弦相似度两种办法分别检测。
Xte[:12] = Xtr[src[:12]] # 精确复制
Xte[12:20] = Xtr[src[12:20]] + rg.normal(0, 0.001, (8, k))
trset = set(tuple(np.round(r, 3)) for r in Xtr)
exact = sum(1 for r in Xte if tuple(np.round(r, 3)) in trset)
near = sum(1 for r in Xte[exact:] if isnear(r)) # 余弦大于 0.9999
实跑输出:
--------------------------------------------------------------------
数据集管理:精确重复、近似重复与训练测试泄露
--------------------------------------------------------------------
训练集 400 条,8 维
测试集 100 条
精确重复(保留 3 位小数后相同) 12 条
近似重复(余弦大于 0.9999) 8 条
合计泄露 20 条,占测试集 20%
再看两边的标签比例:
训练集正类占比 0.505
测试集正类占比 0.590
最大偏差 0.085
清理办法:先把落在训练集里的测试样本剔掉。剔完剩 88 条,标签比例变成 0.614。
但近似重复没法靠「完全相同」判断——它们的四舍五入结果都不一样,只能算相似度。所以数据集检查必须做两遍:先哈希,再相似度。
精确重复 12 条、近似重复 8 条,合计 20%。 这个数是我预先塞进去的,所以它验证的是检测方法有没有漏,不是「真实数据集脏多少」。 两种方法各抓一半,说明它们不能互相替代。 哈希便宜(几毫秒扫几十万条),相似度贵(要两两比),所以顺序应该是先哈希、再对剩下的算相似度。 标签分布那一项:训练 0.505、测试 0.590。 这是因为我让测试集前三维整体抬高 0.7, 模拟跨批次采集——真实业务里「上个月的测试集」和「这个月的线上流量」就是这种关系。 清理后正类占比升到 0.614,偏差反而变大。 这一点最容易误判成「清理出错」,其实是泄露样本一直在稀释差异。
30 秒实验
- 把近复制的噪声从 0.001 加到 0.05,看余弦阈值 0.9999 还能不能抓到。
- 把测试集前三维的抬高量从 0.7 改成 0.2,看标签偏差还剩多少。
- 想一想:为什么近似重复不能靠「前后空格」「大小写」这类规则来检测?
常见误区
- 「数据集不用版本管理」——数据一改模型就变了,不记版本以后无法复现。
- 「哈希能抓所有重复」——加一点噪声哈希就完全不同,近似重复必须算相似度。
- 「清理完就没事了」——清理常常暴露出被掩盖的分布偏差,得再看一遍。
自测三问(点开对答案)
1. 为什么数据集的版本要和模型的版本记在一起? —— 数据换了模型就等于换了,不记版本就无法复现也无法归因。 2. 精确重复和近似重复分别怎么检测? —— 精确重复用哈希或四舍五入后比对,近似重复用余弦相似度或距离。 3. 为什么剔掉泄露样本后,两边的标签偏差反而变大了? —— 泄露样本按训练分布复制,一直在把测试集分布往训练集那边拉,掩盖了真实差距。数据干净了,模型也管住了。下一节讲怎么让它在部署时跑得更省、更快。
- ☐ (本节完,下一节 10.1.3 流程3:模型部署和性能优化)