无监督聚类不应“偷看答案”:Spatial-MGCN等方法用 ARI 做早停与模型选择的标签泄露问题(以空间转录组为例)

摘要

在无监督任务中,用 ARI(Adjusted Rand Index)等外部指标评估聚类与已知标签的一致性是常规做法。但如果在训练阶段用 ARI 选择“最佳 epoch”或早停,就把监督信息引入了本应无监督的流程,造成标签泄露与对标签的过拟合。本文以空间转录组典型代表方法 Spatial-MGCN (https://github.com/cs-wangbo/Spatial-MGCN/blob/master/Spatial-MGCN/DLPFC_test.py)的训练脚本为例,指出这一问题,并给出不依赖标签的早停和模型选择策略。

背景:ARI 的定位与正确用法

  • ARI 属于外部评估指标,前提是有“ground truth”标签。
  • 正确用法:训练完成后作为基准评估;不应参与训练决策(早停/模型选择)。
  • 无监督设定下,训练过程不应接触真实标签。

问题:用 ARI 驱动早停/模型选择的后果

典型错误流程:每个 epoch 先算嵌入,再 KMeans 聚类,与真实标签算 ARI,ARI 变好就保存该 epoch。后果:

  • 标签泄露:训练过程直接“偷看答案”。
  • 对标签过拟合:模型选择被训练集标签驱动,泛化与可信度下降。
  • 评价偏乐观:最终报告的 ARI 被同一批标签指导过,性能被夸大。

空间转录组场景下的风险

空间组学的“ground truth”常不完备或含噪。若在训练阶段用这些标签做选择:

  • 结构发现不再纯粹数据驱动;
  • 对含噪标签过拟合,削弱生物学解读的可靠性;
  • 公平比较与复现性受损。

更合理的无监督模型选择方案

  1. 基于训练损失的早停

    • 监控总无监督损失(如 ZINB 重构 + 一致性 + 正则)。
    • 设定 patience,长期无改进则早停。
  2. 内部聚类指标(无需标签)

    • 对嵌入使用 Silhouette、Calinski–Harabasz、Davies–Bouldin 等内部指标。
    • 以这些指标最佳的 epoch 作为模型选择依据。
  3. 固定训练步数

    • 预设足够轮次到收敛,不用标签驱动中途决策。
  4. 无标签验证拆分

    • 若目标含重构,可保留一部分样本/特征做验证损失,而非用标签。
  5. 外部指标仅用于训练后报告

    • ARI/AMI/NMI 只在训练结束后计算,用于结果报告,不反向影响模型选择。

概念性重构:用无监督损失保存快照

  • 每个 epoch 记录总损失,若创下新低则保存模型参数与当前嵌入(detach + cpu,emb/mean 做 NaN 处理)。
  • 训练结束后,再用保存的嵌入做一次聚类。
  • 若有真实标签,仅在此时计算 ARI 作为报告,不参与选择。

代码实现的细节坑点

  • 邻接矩阵一致性:前向传播与预处理的 nsadj/nfadj 保持一致。
  • 保存状态:保存前 detach 并转 CPU,避免显存泄漏。
  • 随机性控制:同时固定 NumPy、PyTorch、CUDA 种子。
  • 数值稳定:聚类前对嵌入做 NaN 替换或标准化。

如果确实需要监督

  • 明确声明为半监督/监督;训练-验证拆分;在独立测试集报告指标,避免乐观偏差。

自查清单

  • 训练或早停是否用了真实标签派生的指标?
  • 内部评估是否完全不依赖标签?
  • 外部指标是否只在训练后计算?
  • 选用的嵌入/聚类是否来自无标签的模型选择?
  • 随机性与复现实验是否完整记录?

最小伪代码(无标签早停)

best_loss = float("inf")
best_state = None
patience = P
no_improve = 0

for epoch in range(E):
    emb, mean, losses = train_one_epoch()  # losses['total'] 含重构/一致性/正则
    total = losses["total"]
    if total < best_loss:
        best_loss = total
        no_improve = 0
        best_state = {
            "model": {k: v.detach().cpu() for k, v in model.state_dict().items()},
            "emb": np.nan_to_num(emb),
            "mean": np.nan_to_num(mean),
        }
    else:
        no_improve += 1
        if no_improve >= patience:
            break

# 训练完成后再做聚类与外部评估
model.load_state_dict(best_state["model"])
emb_final = best_state["emb"]
labels = KMeans(n_clusters=K).fit(emb_final).labels_
ari = adjusted_rand_score(true_labels, labels)  # 仅报告,不参与训练决策
print(f"ARI (report only): {ari:.3f}")

结论

无监督聚类的训练阶段不应“偷看”真实标签。用损失或内部聚类指标替代 ARI 驱动的早停/模型选择,能避免标签泄露与过拟合,提升空间转录组等场景下的严谨性与可信度。