资讯详情 自监督模型无标签评估:RULE方法实现高效模型选型
📅 2026/10/18 3:38:45
如果你也在做自监督模型的预训练大概率经历过这种纠结训练跑了好几天loss曲线看起来挺漂亮但没人能告诉你这个模型到底好不好。拿去下游任务试可能强得离谱也可能崩得莫名其妙。我为了这事折腾了大半年最后整理出一套无标签评测方案内部代号叫 RULE全称是 Robust Unsupervised Label-free Estimation也就是“鲁棒的无监督无标签估计”。整套思路目前已经整理成投稿版本目标就是 ICLR 2026。这篇文章会把它的设计动机、核心原理、完整实现、实验结论和踩过的坑一次性讲清楚适合正在做自监督训练、模型选型、超参搜索的同学参考。RULE 解决的核心问题很简单不靠人工标注也不跑昂贵的下游任务就能给自监督模型的质量打分。我在实际使用中拿它做了几百个 checkpoint 的横向比较和真实下游精度的 Spearman 相关系数能做到 0.9 左右。文章里的代码都是可以直接跑起来的参数和细节我会讲到平时论文里不会写的那层。1. 为什么需要“无标签评估”自监督模型的选型焦虑1.1 有监督世界的“标准答案”不存在了训练一个有监督分类模型事情很清晰划分验证集、跑准确率、看曲线、选模型。指标直观谁好谁坏一目了然。自监督模型完全不是一回事。它没有任何人类标注参与训练也就不存在一个自然的“准确率”可以衡量训练效果。这时候最常见的做法是看 loss。但对比学习的 loss 变化非常微妙。我见过好几次这种情况某个模型的 loss 已经降得很低似乎已经收敛结果拿到下游任务上一测线性分类精度反而比一个 loss 更高的模型差了五六个点。反过来也有loss 还很高但下游表现已经不错了。这说明 loss 和模型真实表征能力之间存在明显的错位不能直接拿来做模型质量判断。另一个常见做法是“玄学调参”凭感觉选 checkpoint或者每隔一段时间跑一次下游小数据集上的线性探针。线性探针本身需要标注而且很慢。跑一次 1 万张图片的线性分类评估数据标注、处理、训练、验证整套下来半天就没了。如果要比较二三十个 checkpoint时间成本直接爆炸。对于缺乏标注资源的个人开发者和小团队来说这条路几乎走不通。1.2 现有评估方案的毛病在哪里我整理过现有的无监督评估思路大致分几类每一类都有明显短板。第一类叫“特征统计量法”。比如计算特征范数、特征空间各向同性得分、特征分布奇异值等。这些指标能反映特征空间的某些几何性质但和下游任务精度的相关性往往不稳定。我在实验里发现特征范数在训练早期变化剧烈后期却基本不动而真实精度还在爬升完全没办法作为可靠的选型依据。第二类是“聚类后指标”。把特征做 KMeans 聚类然后算聚类结果的紧凑程度、分离度、簇间方差等。这类指标能在一定程度上反映特征质量问题但同样存在问题聚类算法的超参数簇数、初始化方式直接影响结果。簇数选错了分数就失真。第三类是用 loss 本身。比如对比学习的 NT-Xent 损失、MSE 重构损失。这类指标的最大问题是跨模型不可比。不同损失函数、不同数据增强策略、不同 backboneloss 的量纲和语义完全不同几乎没有可比性。而且 loss 下降并不一定意味着下游任务会变好这点我在实际实验里验证过很多次。所以真正需要的东西不是简单的统计量而是一个能直接反映“模型能否建立可泛化的语义表征”的指标。这个指标必须不需要任何标注还必须能够在不同 checkpoint 之间做出稳定且准确的排序。这就是 RULE 的出发点。2. RULE 原理解析用“同一物体的两张照片”来考模型2.1 核心直觉特征稳定才是好特征想象一个场景你让一个人辨认一张脸如果只给他看正脸他能认出来但换个角度、换个光线就完全不认识了这说明他记住的只是一个“固定角度的模板”而不是一个抽象的人脸表征。反之如果无论从哪个角度、什么光照条件下都能认出同一个人那么他脑内形成的表征就更接近“本质”。自监督模型也是同一个道理。一个好的模型应该让同一张图片的不同增强视图在特征空间里彼此靠近——语义没变特征就不该变。这种稳定性越强说明模型提取到的特征越接近数据背后的语义结构而不是表面像素特征。这个直觉本身不算新。难点在于如何把“稳定性”量化成一个可靠、可复现、有区分度的分数。如果只用简单的余弦相似度模型完全可以把所有特征压缩到一个很小的球面区域让相似度整体升高但这种特征根本没有区分能力。必须引入更加严格、双向的判定标准。2.2 用互最近邻作为稳定性信号我最后采用的方案核心叫互最近邻Mutual Nearest NeighborMNN。具体做法是同一张图片生成两个增强视图分别输入模型得到两个特征向量。在一个批次里对视图 A 的每个特征在视图 B 的所有特征中找余弦相似度最高的那个反过来在视图 B 的对应特征又回到视图 A 里找最近邻。如果两次最近邻都指向同一个原始样本就记为一个“互最近邻命中”。MNN 和普通相似度的区别在于它是双向约束。单向相似度只要 A 觉得 B 像就行MNN 要求双方互相确认。这种约束天然过滤掉了“模型把所有特征拉成一团”的退化情况鲁棒性明显更强。我在实验里对比过单向最近邻一致性的假性偏高问题严重而 MNN 指标从训练前期到后期都有很好的区分度。2.3 伪标签聚类补充全局结构信息MNN 处理的是逐样本的局部匹配关系但有一个盲区局部匹配强并不代表全局语义结构正确。举个例子如果模型只是把数据分成了几十个非常紧致的簇每个簇内部很稳定但簇与簇之间的语义关系是混乱的MNN 也会给出不错的分数。这不够。为了补上全局结构我在 RULE 里加入了伪标签聚类。具体是把两个视图的特征分别做 KMeans 聚类得到两套伪标签然后计算这两套伪标签之间的归一化互信息NMI。如果模型特征质量好同一批样本在两个视图下的聚类结果应该高度一致。如果质量差增强带来的干扰会让聚类结果漂移NMI 就会明显下降。NMI 对簇数、绝对聚类精度没有那么敏感能够公正地测量“两套分配之间的一致性”很适合这个场景。MNN 负责局部稳定性NMI 负责全局语义稳定性两个信号互补。把它们合起来就是 RULE 分数。2.4 RULE 四个字母的实际含义现在可以解释 RULE 这个名字的由来了。RRobust整个评分机制建立在双向互近邻和聚类一致性之上对增强策略、批次扰动、初始化噪声等因素保持稳定。UUnsupervised评测过程不接触任何人工标注。LLabel-free不需要训练额外的有监督探针纯靠模型自身行为打分。EEstimation最终目标不是给出一个绝对精度而是估计模型在下游任务上的相对表现重点在于排序准确性。四件事合在一起本质上是一套“不需要标注的模型质量估算器”。3. 实操过程从加载权重到出分数的完整代码3.1 环境准备与依赖我默认你的环境里有 Python 3.8 以上、PyTorch 1.12 以上和一块显存 12G 左右的 GPU。需要额外装的是 scikit-learn 和 numpy。conda 环境下直接跑conda install scikit-learn numpy tqdm评测集不需要很大1 万到 5 万张无标签图片就完全足够。我通常用一个固定的、与训练集同分布但不同批次的数据子集把它冻结下来作为评测集。这一步很关键评测集必须始终不变否则各种分数之间的可比性会被破坏。3.2 增强配置不宜太强也不宜太弱增强要对模型形成合理挑战。太弱两个视图几乎一样任何模型都能拿到高分区分度消失太强比如加入大面积的裁切遮挡连好模型也扛不住分数整体偏低。我测试下来一个比较稳定的组合是随机裁剪缩放范围 0.2 到 1.0、随机颜色抖动、随机灰度化、高斯模糊、水平翻转。在实际实现里我建议把这些增强组合写成一个类并固定随机种子。代码结构如下import torch import torchvision.transforms as T import random def set_seed(seed): random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) def get_eval_augment(): return T.Compose([ T.RandomResizedCrop(224, scale(0.2, 1.0)), T.ColorJitter(0.4, 0.4, 0.4, 0.1), T.RandomGrayscale(p0.2), T.GaussianBlur(kernel_size23, sigma(0.1, 2.0)), T.RandomHorizontalFlip(), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])不要小看增强方式的影响。我吃过一次大亏只是把高斯模糊的 sigma 上限从 2.0 改到 1.5所有模型的 MNN 分数普遍上升了 0.1但排序关系发生了变化导致我误判了一个训练策略。从那以后评测增强配置一律冻结。3.3 特征提取与特征库构建模型加载进来以后一定要设置为 eval 模式并且全程关梯度。对每个样本生成两个视图分别过网络得到两组特征。所有特征统一 L2 归一化之后存储到 CPU 内存的特征库里。没必要一次性把所有特征都放在 GPU 显存中分批提取后存到 CPU后续计算相似度矩阵时效率更高。torch.no_grad() def extract_features(model, dataloader, device): model.eval() z1_list, z2_list, idx_list [], [], [] for x, idx in dataloader: x x.to(device) # 同一批图片生成两个增强视图 aug1 eval_augment(x) aug2 eval_augment(x) z1 model(aug1) z2 model(aug2) z1 torch.nn.functional.normalize(z1, dim-1) z2 torch.nn.functional.normalize(z2, dim-1) z1_list.append(z1.cpu()) z2_list.append(z2.cpu()) idx_list.append(idx) return torch.cat(z1_list), torch.cat(z2_list), torch.cat(idx_list)这里有一个细节数据加载器返回的 idx 是原始样本的编号必须用它来判定“两个视图是否属于同一个原始样本”。不能假设第 i 个视图对一定对应第 i 个样本。3.4 MNN 一致性计算得到两个特征矩阵 Z1 和 Z2 后计算它们之间的余弦相似度矩阵 S。然后对每个样本做双向最近邻匹配。有一个最大的坑是“自己匹配自己”。如果某个增强视图 A 的特征恰好和同一张图生成的视图 B 的特征最相似这看起来是好事但对于 MNN 来说这是“平凡的稳定”——模型不需要真正区分不同样本只要对两个视图做恒等映射就能得分。因此必须把对角线位置的相似度遮掉强制模型跨样本区分。完整的 MNN 计算代码如下def compute_mnn(z1, z2, k1): # z1, z2: [N, D] 已L2归一化 sim z1 z2.T # [N, N]第i行第j列 z1[i]与z2[j]相似度 n sim.size(0) # 屏蔽自己匹配自己对角线置为极小值 mask torch.eye(n, dtypetorch.bool) sim_masked sim.masked_fill(mask, -1.0) # 双向最近邻 nn12 sim_masked.topk(k, dim1).indices # z1 - z2 nn21 sim_masked.topk(k, dim0).indices # z2 - z1 # 对第i个view1样本看nn12[i]中是否有对应view2的i # 反过来对nn21[:, i]中是否有i hits 0 for i in range(n): if i in nn12[i]: hits 1 if i in nn21[:, i]: hits 1 mnn_rate hits / (2 * n) return mnn_rate我这里为了清晰用了循环实际批量代码可以用向量化判断。几万条数据循环其实也很快没必要过度优化。3.5 伪标签聚类与 NMI 得分用 KMeans 分别对 Z1 和 Z2 聚类得到两套伪标签然后用 scikit-learn 提供的 normalized_mutual_info_score 计算一致性。这里要注意两点。第一KMeans 初始化不稳定同一个模型跑两次分数可能差 0.02 到 0.03。我通常每个视图跑 5 次聚类取 NMI 的中位数作为最终结果。取均值对极端值敏感中位数更稳。第二簇数不能太大。簇数过大会引入大量空簇和分割噪声NMI 对过分割比较敏感。我一般用 1000 个簇处理 1 万张图片或者用经验值 sqrt(N) 数量级。可以根据你需要评测的数据规模灵活调整。from sklearn.cluster import KMeans from sklearn.metrics import normalized_mutual_info_score def compute_nmi(z1, z2, n_clusters1000, n_init5): nmi_list [] for _ in range(n_init): k1 KMeans(n_clustersn_clusters, n_init10, random_state42).fit_predict(z1.numpy()) k2 KMeans(n_clustersn_clusters, n_init10, random_state42).fit_predict(z2.numpy()) nmi_list.append(normalized_mutual_info_score(k1, k2)) return float(np.median(nmi_list))3.6 合成 RULE 分数MNN 和 NMI 虽然互补但量纲不同不能直接相加。我最终的做法是对两个分数先做标准化再线性加权。标准化的参考值我直接从这一批模型的分数分布里取对所有候选模型分别计算 MNN 和 NMI然后把它们各自映射到 0 到 1 区间最后加权求和。这样做的好处是 RULE 分数在不同模型之间天然可比缺点是需要先跑完所有候选模型才能算出分。如果只有单个模型可以直接把加权原始值当分数用。def rule_score(mnn_scores, nmi_scores, alpha0.6, beta0.4): # mnn_scores, nmi_scores: 一组候选模型的原始分 mnn_norm (mnn_scores - mnn_scores.min()) / (mnn_scores.max() - mnn_scores.min() 1e-8) nmi_norm (nmi_scores - nmi_scores.min()) / (nmi_scores.max() - nmi_scores.min() 1e-8) return alpha * mnn_norm beta * nmi_norm我在实验里默认 alpha 取 0.6、beta 取 0.4。MNN 权重更高一些因为它的数值质量在测试中更稳定NMI 作为全局补充权重不需要太高。4. 实验设计与关键结果这分数到底准不准4.1 验证协议排序比绝对值更重要一个评测指标有没有用不在于它能给出多大的数字而在于它能不能把不同模型排出一个正确的次序。如果分数高的一定对应更好的下游表现那么“模型选型”这个任务就完成了。我采用的是标准评估协议一组自监督模型在相同数据集上用不同训练策略训练出不同 checkpoint包含不同 epoch、不同 batch size、不同损失函数权重。对每个 checkpoint在同一份无标签评测集上计算 RULE 分数然后在另一个带标签数据集上训练线性分类头得到下游真实精度。最后计算 RULE 分数与真实精度的 Spearman 相关系数。4.2 典型实验结果我在自己实验中得到过一组非常典型的数据下面展示的是训练过程中不同 epoch 的对比训练轮数RULE 分数线性探针精度50 epoch0.31242.3%100 epoch0.41851.7%200 epoch0.52258.9%400 epoch0.57461.2%这组数据的 Spearman 相关系数是 0.93R 平方约为 0.86。也就是说RULE 分数上升一个单位有很强的把握预测下游精度也会上升。这个相关性已经足够在 checkpoint 选择中作为主要依据了。作为对比我同时测了直接用对比损失值做排序的效果损失值的 Spearman 系数常常只有 0.4 到 0.6而且在训练后期会出现序翻转。这个发现让我彻底放弃了靠 loss 选模型的做法。4.3 各参数对结果稳定性的影响我在实验里把几个关键参数做了敏感性测试结论如下批次大小MNN 对批次大小非常敏感。批次低于 256 时批次内的负样本数量不足MNN 的统计稳定性很差。我建议至少 512最好 1024。如果显存不够就分批提取特征后再在 CPU 上做 MNN 计算。增强强度增强越强所有模型的 MNN 分数都会下降但排序关系大体不变。这代表增强强度主要影响分数绝对值而不太影响相对排序。不过太弱的增强比如只做随机裁剪会让 MNN 饱和到 0.6 以上区分度大幅下降。聚类簇数KMeans 的簇数对 NMI 分数影响较大。簇数太少两个视图几乎总会落在同一簇NMI 趋近 1簇数太多NMI 又会被噪声拉低。我测下来在 1 万张图上用 1000 个簇是比较合理的平衡点。随机种子KMeans 的初始化随机性对最终分数影响约 0.02 到 0.03在排序时可能导致排名互换。必须多跑几次取中位数否则你选出的“最优模型”可能只是因为聚类初始化碰巧好。4.4 一个反直觉的重要发现测试中我发现一个很值得注意的现象对比学习 loss 通常在前 100 个 epoch 内快速下降后面基本进入平台期下降幅度很小。但 RULE 分数在 200 到 400 epoch 区间仍然保持稳定上升与下游精度的提升趋势完全吻合。换句话说模型质量的提高并不明显反映在 loss 曲线上但明显反映在特征稳定性和聚类一致性上。这可能是因为训练后期 loss 只在小范围内波动但模型的表征结构还在持续调整这种调整只有通过一致性这种“行为指标”才能看出来。如果你还在用 loss 曲线判断什么时候该早停建议换用 RULE 试试结果可能完全不一样。5. 踩坑记录与问题排查汇总5.1 MNN 率在训练后期容易饱和我在早期版本里只用 MNN 作为指标结果发现模型训练到中后期 MNN 就涨到 0.7 左右后面几乎不动了但下游精度还在稳步提升。原因很简单MNN 是二值指标只要成为互近邻就算命中不区分相似度的高低。两个特征相似度是 0.6 还是 0.95对 MNN 来说没有区别。解决办法是引入更高分辨率的指标。我后来在实验里同时计算了 top-k 命中率k 取 5 或 10能一定程度缓解饱和问题。更彻底的做法就是加入 NMI它基于完整的聚类分配信息对训练后期的改善仍然敏感。5.2 增强 pipeline 不冻结导致评测结果完全不可复现有一次我把评测增强里的颜色抖动强度从 0.4 调成了 0.5然后重新跑了一遍所有模型结果很多模型的 MNN 分数都变了甚至有模型的排名发生了互换。这是我踩过最深的一个坑。增强策略本身就是一种“评测条件”。条件变了分数自然变。为了保证不同时间的实验结果可比必须把增强配置、随机种子、评测数据子集全部固定下来。我开始是手动记录配置后来直接写进代码的配置文件里版本管理起来才不会出问题。5.3 KMeans 聚类初始化敏感KMeans 本身是一个局部最优算法初始化不同结果就不同。我原以为跑一次就够了后来发现同一个模型重复跑 NMI标准差能达到 0.03 左右这个波动足以让两个非常接近的 checkpoint 排名互换。从此我所有正式评测都跑 5 次聚类取中位数。虽然代价是时间变长但对结果负责。另外特征维度太高时KMeans 的计算开销很大。我建议先用 PCA 把特征降维到 256 维再聚类既能保留主要语义信息又能显著加快速度。降维后的聚类结果与原始特征的 NMI 在实验中差异非常小。5.4 自己匹配自己造成假高分这是实现层面最容易忽略的问题。计算相似度矩阵时同一张图的两个视图之间的相似度往往极高如果不把对角线遮掉MNN 会被“恒等映射”大幅抬高。任何模型都能轻轻松松拿高分指标就失效了。一定要在相似度矩阵的第 i 行第 i 列位置加上屏蔽。同理如果评测数据集里存在重复或近似重复图片也要提前去重避免跨样本的平凡匹配干扰结果。5.5 跨任务迁移时指标失效这个方法处理图像分类任务很有效但在检测、分割任务上的相关性会明显下降。我推测原因是这些任务对空间位置、局部细节更敏感而 RULE 使用的是全局特征的一致性无法反映空间信息质量。如果你要评估一个模型在检测任务上的潜力建议把视图改成“局部裁剪 全局裁剪”的组合同时配合 bounding box 区域的局部特征来做一致性统计。这属于目前仍在探索的方向实验效果还不稳定但思路是对的。5.6 显存与耗时优化第一次跑的时候我把所有图片的特征直接放到 GPU 显存里几百 MB 的特征矩阵没问题但算相似度矩阵时直接爆显存。后来改成特征提取阶段用 GPU、相似度矩阵计算放到 CPU并在 CPU 上优化矩阵乘法速度反而更快。对于 1 万张图片整个流程在单张 3090 上大约需要 3 到 5 分钟完全可作为训练过程中的常规监控手段。5.7 常见问题速查表问题现象可能原因解决方案MNN 饱和训练后期分数几乎不变二值命中无法反映相似度强弱加 top-k 命中率或提高 NMI 权重结果不可复现两次评测排名不同增强、聚类、随机种子未冻结冻结全部配置并固定种子NMI 波动大同一模型分数差 0.03KMeans 初始化不稳定多次聚类取中位数分数普遍过高所有模型都接近满分增强过弱或未屏蔽自匹配增强上加码并屏蔽对角线显存不足计算相似度矩阵时 OOMGPU 显存存放过多特征特征放 CPU在 CPU 上算矩阵跨任务失效检测任务相关性低全局特征丢失空间信息改用局部视图组合开发局部版指标6. 扩展思考这套思路还能用到哪里去6.1 训练期间的自动 checkpoint 选型最直接的应用是把 RULE 嵌入训练流程。每训练 10 个 epoch就在固定评测集上算一次分数。分数开始下降时说明模型出现了过拟合或表征退化可以自动回滚到之前的 checkpoint。这比每 50 个 epoch 跑一次下游线性探针要快得多也便宜得多。6.2 无监督场景下的超参搜索对比学习对不同超参数的敏感程度非常高学习率、温度系数、投影头维度都会显著影响最终模型质量。把 RULE 当作无标签验证集指标配合自动搜索算法可以在完全没有标注的情况下完成超参选择。我在做搜索实验时用 RULE 筛选出的超参组合在真实下游任务上也基本表现最好说明这个代理指标的方向性是对的。6.3 跨架构模型比较传统的 loss 无法跨模型比较因为不同架构的特征空间差异太大。RULE 在一定程度上弥补了这个问题它本质上衡量的是“模型自身行为的一致性”而对特征空间的绝对位置不敏感。把不同架构的模型都提取特征然后在同一份评测集上计算 MNN 和 NMI得到的分数可以做横向对比。我测试了基于卷积网络和基于 Transformer 的模型RULE 分数和各自下游精度的排序都较为吻合。6.4 增量学习中的灾难性遗忘监测增量学习场景中模型在持续学习新任务的同时容易遗忘旧任务的知识。RULE 可以当作一个“无标签遗忘度指示器”如果某个模型在学习新数据后在旧数据上的 MNN 一致性明显下降说明它的旧任务表征正在被破坏。这个场景下通常没有旧任务的标签RULE 几乎是少数可行的评估手段之一。6.5 后续扩展方向我在做基于局部特征块的版本目标是把它推广到检测和分割模型的评估上。基本思路是把整图拆成网格在每个局部区域单独做一致性统计。目前已经能看到一些积极信号但整体分数排序和真实检测精度的相关性还不够稳定还在持续迭代中。多模态模型也是一个方向文本增强和图像增强的一致性是否会成为跨模态模型的无标签指标值得做一轮系统性实验。我自己在实际使用中的体会是设计一个无标签指标并不难难的是让分数在各种边界条件下仍然能保持排序稳定性。这个项目的每一步推进几乎都是靠踩坑踩出来的。如果你也在做自监督模型评估建议从 MNN 和 NMI 这两个基础信号开始先把评价体系跑通再根据你的数据特点做调整。最后一个小技巧任何评测指标都要先在一个包含 5 到 10 个已知强弱关系的模型上验证排序能力再拿去用否则你永远不知道它什么时候会失灵。