先确定要泛化到什么对象
这篇方法回顾按主题归档,不对应某次有明确日期的实验。医学体数据常被拆成切片或裁剪块,而这些文件不是天然独立样本。相邻切片共享解剖结构,同一次采集的多个重建版本也可能高度相似。
如果目标是泛化到新患者,划分单位就应至少是患者。先把切片随机打散,再按文件分训练集和验证集,可能让同一患者同时出现在两边。此时结果更多反映对已有对象的适配,不能直接解释为对新患者的能力。
用清单表达数据层级
建议为每个样本保留稳定的去标识化分组键、检查键、体数据键、派生关系和标签版本。分组键应来自经过核对的映射,不应仅根据文件名猜测身份,也不应在公开日志里写入可识别信息。
同一患者的多次检查应沿用相同分组;没有患者信息时,按体数据分组只能防住同一体内的切片泄漏,不能保证患者隔离。这个证据缺口应写在评估限制中。
划分发生在切片提取、裁剪和增强之前。所有派生样本继承源数据的集合身份,避免在后续处理时重新随机分配。
把组间隔离写进代码
下面函数接收每个样本对应的分组键,产生一次组间隔离的划分。示例比例是可配置参数,不代表任何真实数据集的规模或实验设置。
import numpy as np
from sklearn.model_selection import GroupShuffleSplit
def split_by_group(group_ids, held_out_fraction=0.2, seed=0):
groups = np.asarray(group_ids)
if groups.ndim != 1 or len(np.unique(groups)) < 2:
raise ValueError("expected at least two verified groups")
splitter = GroupShuffleSplit(
n_splits=1, test_size=held_out_fraction, random_state=seed
)
indices = np.arange(len(groups))
train_idx, held_out_idx = next(
splitter.split(indices, groups=groups)
)
if set(groups[train_idx]) & set(groups[held_out_idx]):
raise RuntimeError("group overlap")
return train_idx, held_out_idx
GroupShuffleSplit 的比例针对组,而非样本数量;组大小不同,切片数量比例自然可能不同。官方文档
先锁定最终测试组,再在余下组中生成训练与验证划分,并把第二次产生的局部索引映射回原始清单。保存最终索引清单,比只记录随机种子更可靠。
组间隔离还不是全部
归一化统计、特征选择、类别权重和任何需要拟合的预处理,应只从训练部分获得。验证集可以用于选择阈值与配置;最终测试集用于一次冻结方案后的评估。
重复导出、不同窗宽截图、同一数据的增强版本,可能换了文件名却仍共享来源。应检查源数据标识、派生记录和内容摘要;像素完全相同的摘要检查只能发现精确重复,发现不了重采样或压缩后的近似重复。
预训练数据与目标测试集是否重叠,也属于数据来源审查的一部分。无法确认时,应记录信息边界,不能默认不存在。
清单还应接受独立核对:随机抽取源数据与派生样本,确认分组继承正确;检查缺失分组键、重复键和异常命名。未知身份不能简单统一填成某个普通患者编号,也不能给每张切片临时生成不同编号来绕过检查。
划分结果也需要人工审阅
患者隔离后,仍应检查类别、中心、采集协议和目标大小的分布。样本少时,随机划分可能把稀有情况全部放到一侧。可采用组级分层或交叉验证,但不能反复挑选分数最高的划分作为最终结果。
如果目标是跨中心或跨时间泛化,就要设计相应的外部留出,患者级随机划分不足以回答这个问题。发现泄漏后,应从分组与预处理拟合开始重建实验链路;只删掉几张重复图,却继续沿用受影响的配置选择,仍可能保留偏差。
参考
- scikit-learn
GroupShuffleSplit:按组划分及参数含义。 - scikit-learn:Data leakage:预处理拟合与评估隔离的官方说明。