AI 数据集的集合记号:隶属关系、划分与泄漏
通过发现并修复 AI 训练、验证和测试划分中的重叠,学习集合、隶属关系、并集、交集和分割。
假设一个图像分类器在验证数据上取得了 96% 的分数。随后有人发现,img-04 同时被用于训练和验证。这个分数看似精确,但其中一个示例已经不再是未见过的示例。
集合为陈述和检验这一失败提供了一种简洁方式。集合记录哪些对象属于某个集合。由此,交集揭示重叠,并集检查覆盖范围,而分割描述把每个示例干净地分配给且只分配给一个数据集划分。
学完本课后,你将能够阅读隶属关系和集合构造记号,计算常见的集合运算,并为训练、验证和测试 ID 实现第一道审计。这个内容推进了第 1 课中介绍的变量、模型和学习图景的数据一侧。
隶属关系把集合变成真假命题
集合是一个隶属关系明确的对象集合。如果 是小型数据集中所有示例 ID 的集合,可以写成
花括号表示“包含这些元素的集合”。命题
读作“img-02 是 的一个元素”。符号 否定这一命题,因此 。
有限集合中不同元素的数量称为它的基数,写作 。在上面的例子中,。
集合构造记号描述规则,而不是冗长列表
对于真实数据集,列出每一个元素并不实际。集合构造记号用条件定义隶属关系:
从左到右读作:“ 是 中满足 的标签为 cat 的示例 的集合。”冒号表示“使得”。竖线 也常在相同位置使用。
冒号后的条件像过滤器一样工作。对于
如果 img-01 和 img-04 的标签是 cat,那么
这个记号把正在考虑的全集(这里是 )与隶属规则分开。如果没有 ,“所有猫图像”可能指现实中存在的每一张猫图像,而不是这个项目可用的示例。
三种运算审计数据集划分
令 、 和 分别表示训练、验证和测试 ID 集合。三种运算回答不同的工程问题。
| 运算 | 记号 | 数据集问题 |
|---|---|---|
| 交集 | 哪些 ID 同时出现在两个集合中? | |
| 并集 | 哪些不同的 ID 至少出现在一个集合中? | |
| 差集 | 哪些训练 ID 不在验证集中? |
空集写作 ,不包含任何元素。因此, 表示训练集和验证集没有共享 ID。这样的集合称为不相交。
干净的划分就是一个分割
假设完整数据集是 。当训练集、验证集和测试集满足两个要求时,它们构成 的分割。
第一,它们的并集覆盖整个数据集:
第二,每一对集合都不相交:
覆盖范围防止示例消失;两两不相交防止示例出现在两个划分中。合在一起,它们说明 的每个元素恰好属于一个部分。
| 样本 | 训练 | 验证 | 测试 |
|---|---|---|---|
img-01虎斑猫 | |||
img-02金毛寻回犬 | |||
img-03城市公交车 | |||
img-04红色自行车 | |||
img-05麻雀 | |||
img-06帆船 | |||
img-07橡树 |
这还不是一个划分 请修复标出的重叠项或未覆盖 ID。
- Dtrain
- {img-01, img-02, img-03, img-04}
- Dvalidation
- {img-04, img-05}
- Dtest
- {img-06, img-07}
重叠: {img-04}
未覆盖: ∅
初始状态覆盖全部七个 ID,但没有通过分割测试,因为 img-04 同时属于 和 。取消这两个成员关系中的任意一个,就能产生有效分割。然后尝试让某个 ID 在所有集合中都不被选中:重叠消失了,但覆盖条件失败。
Python 集合对应这项审计
Python 官方文档把set 类型定义为由可哈希对象组成的无序不同元素集合。它支持上面使用的并集、交集、差集、隶属关系和不相交运算。
all_ids = {f"img-{i:02d}" for i in range(1, 8)}
train_ids = {"img-01", "img-02", "img-03", "img-04"}
validation_ids = {"img-04", "img-05"}
test_ids = {"img-06", "img-07"}
overlap = (
(train_ids & validation_ids)
| (train_ids & test_ids)
| (validation_ids & test_ids)
)
covered = train_ids | validation_ids | test_ids
missing = all_ids - covered
is_partition = not overlap and not missing and covered == all_ids
print(sorted(overlap))
print(sorted(missing))
print(is_partition)
经过验证的输出是:
['img-04']
[]
False
运算符 & 计算交集,| 计算并集,- 计算差集。只有重叠集合为空时,not overlap 才为真。
为了重复使用这项检查,把条件明确写出来:
def is_partition(universe, *parts):
covered = set().union(*parts)
total_memberships = sum(len(part) for part in parts)
no_overlap = total_memberships == len(covered)
return no_overlap and covered == universe
如果某个 ID 出现在两个部分中,单独大小之和就会超过并集大小。如果某个 ID 缺失,并集就会不同于全集。
不相交 ID 是防止泄漏的必要条件,而非充分条件
开头的重叠是一种数据泄漏:训练边界之外的信息影响了模型构建或评估。Scikit-learn 的泄漏指南解释了这种泄漏如何让评估看起来过于乐观,并建议在拟合预处理步骤前分离数据。
空的 ID 交集只能捕获 ID 的精确复用,并不能证明评估集真正独立。两个不同的 ID 可能指向同一张照片的缩放副本。一个视频的帧可能被分到训练和验证两边。同一位患者、客户或未来时间段的记录,即使行 ID 不同,也可能携带共享信息。
数学与实现之间还有另一个边界:Python 集合不保留序列位置,并会丢弃重复值。训练时保留有序样本列表,并为隶属关系检查派生稳定 ID 集合。
第 5 课将加入索引和下标,让我们区分示例的身份与它在批次或张量中的位置。一旦数据集本身有清晰的隶属边界,这一区分就更容易表述。
检查你的理解
问题 1
令 D = {a, b, c}。判断 b ∈ D 以及 d ∉ D 是否成立。
显示分步解答
检查 中列出的元素。
- 出现在花括号中,因此 为真。
- 没有出现,因此 为真。
隶属关系只询问元素是否属于集合;它在书面列表中的位置无关紧要。
问题 2
对于 D = {1, 2, 3, 4, 5},计算 E = {x ∈ D : x 是偶数}。
显示分步解答
根据“ 是偶数”这一条件检查 的每个元素:
只保留满足规则的元素:
这个条件过滤的是已声明的全集 ,并不会加入 6 这样的其他偶数。
问题 3
令 T = {a, b, c, d} 且 V = {d, e}。求 T ∩ V、T ∪ V 和 |T ∪ V|。
显示分步解答
交集包含同时出现在两个集合中的元素。只有 符合:
并集包含出现在任一集合中的每个不同元素:
因此 。也可以由下面的式子得到相同结果:
减法去掉了 的第二次计数。
问题 4
数据集 D = {1, 2, 3, 4} 被划分为 T = {1, 2}、V = {3} 和 E = {4}。证明这三个集合构成一个分割。
显示分步解答
先检查覆盖范围:
再检查每一对集合:
并集覆盖 的每个元素,并且没有元素出现在两个部分中。两个分割要求都满足。
问题 5
训练/验证 ID 审计报告 T ∩ V = ∅。为什么这不能证明不存在任何数据泄漏?
显示分步解答
这个等式只证明没有某个精确的被审计元素同时属于两个集合。如果元素是行 ID,两行不同的记录仍可能包含同一图像的副本、同一视频的帧或同一个人的记录。
推理边界是:
应审计与泄漏风险相匹配的分组单位,例如患者 ID 或源视频 ID;当不同 ID 可能包含近重复内容时,还要加入相似性检查。
问题 6
调试这个分割检查:return not (train & validation) and not (train & test)。缺少什么条件?
显示分步解答
这段代码检查了两个交集,但遗漏了验证集/测试集这一对,也遗漏了对全集覆盖范围的检查。
完整的三方检查需要:
no_overlap = not (
(train & validation)
or (train & test)
or (validation & test)
)
covered = (train | validation | test) == all_ids
return no_overlap and covered第一项保证两两不相交,第二项保证没有数据集 ID 被留在未分配状态。分割需要同时满足两者。