接手一个质检项目时我手里只有1500张标注好的产品外观图而仓库里躺着近8万张完全没有标签的图片。标注预算不够模型精度卡在瓶颈期催数据催不动标注员排期又排到两周后——这是很多做视觉、做文本、做推荐的同学都碰到过的局面。上一篇《无标签的数据指南一》聊了怎么获取、清洗、组织这些无标签数据这篇我们直接进入更硬核的部分怎么让这些“没人管”的数据真正变成模型的战斗力。先说清楚一个前提这篇指南适合手里有一定量的已标注数据、同时握有大量无标签数据的团队。无论是几百张人工标注配几万张裸数据的工业视觉项目还是只有类别名没有正文标注的文本语料库都可以在下面这套思路里找到对应的手段。如果你连少量标注都没有那顺序要换一下——先去标注几百个样本作为种子再来看这篇文章。1. 无标签数据为什么难用以及“难用”背后藏着的三条路很多人第一反应是无标签数据没用理由很简单模型训练需要监督信号每个样本得有个y才能算loss没有y就没有梯度没有梯度就训不动。这个说法对但只对了一半。数据本身其实携带大量隐藏的结构比如物品图片中的纹理、形状、背景关系文本语料里的词序、共现、段落结构。这些结构本身就是信号只是没法直接当监督labels用得靠特定的方法把它“撬”出来。所以无标签数据的核心问题从来不是“它有没有信息”而是“怎么把它的信息转成模型能消化的东西”。目前主流做法就三条线伪标签、自监督预训练、半监督协同训练。再加上一条主动学习作为辅助用来把人工标注的钱花在刀刃上。路线核心思路优点缺点标注依赖伪标签Pseudo Labeling用小标注集训模型给无标签数据打标签再回灌实现快、成本低、见效直接错误会传播确认偏差严重需要少量种子标注自监督预训练SSL Pretraining用构造任务从无标签数据学通用表征再下游微调表征泛化强对标注量容忍度最高训练成本高需要调增强策略微调阶段仍需少量标注半监督协同训练Semi-supervised多视角/多模型互相约束一致性正则打底精度上限高适合标注量极少的场景框架复杂度高训练不稳定需要一小组高质量标注主动学习Active Learning模型挑最难样本交给人标注迭代用最少标注换最高精度标注流程要重新设计不适合纯离线必须有实时标注通道怎么选我给个经验值如果你只有几百条标注优先自监督预训练半监督结合如果已有几千条标注且数据分布还算干净伪标签是最快的提分路径如果标注预算还能动加上主动学习效果会再上一个台阶。2. 伪标签让模型自己给自己出题但小心答案带毒2.1 伪标签的生成与置信度过滤伪标签的思路说出来很简单先用小标注集训练一个老师模型拿它对所有无标签数据做预测把高置信度的预测结果当作标签混进训练集里再训一轮。这个办法从半监督学习诞生那天就存在到现在依然是很多比赛和工业项目里拉精度最直接的手段。但直接做会翻车。我第一次跑伪标签时把无标签集全部预测了一遍置信度0.5以上的全塞进去结果训练loss漂亮地下降验证精度却掉了一个多点。问题出在低置信度的样本里混了大量错误标签模型照着错误答案学越学越歪。所以门槛要卡死。常见的做法是置信度阈值过滤并且针对类别做平衡处理。二分类任务阈值建议至少0.9多分类任务0.95以上有些难分类别可以单独放到0.98。阈值太高会导致可用样本太少提分不明显太低噪声会吃掉信号。实际操作中我会先做一个分布统计看看各阈值下能保留多少样本、各类别数量是否均衡再定具体值。import torch # teacher_model: 已在小标注集上训练好的模型 # unlabeled_loader: 无标签数据加载器 # threshold: 置信度阈值根据类别平衡情况调整 pseudo_samples [] pseudo_labels [] pseudo_scores [] teacher_model.eval() with torch.no_grad(): for batch in unlabeled_loader: logits teacher_model(batch[image]) probs torch.softmax(logits, dim-1) max_probs, preds torch.max(probs, dim-1) mask max_probs threshold pseudo_samples.append(batch[image][mask]) pseudo_labels.append(preds[mask]) pseudo_scores.append(max_probs[mask]) pseudo_samples torch.cat(pseudo_samples) pseudo_labels torch.cat(pseudo_labels)2.2 确认偏差伪标签最毒的坑伪标签最大的隐患是确认偏差Confirmation Bias。老师模型自己在某个类别上判断不准它打出的错误伪标签会在训练中强化这个错误下一轮模型在这个类别上错得更狠形成一个恶性循环。这在长尾分布的数据集上尤其明显尾类别样本少模型本来就没见过多少伪标签还会把它推向“只预测头部类”的极端。踩过坑之后我总结了一套相对稳的流程。第一老师模型和学生模型分离开不要把同一个模型既当老师又当学生反复迭代这样错误很难被纠正。第二伪标签不是一次性加全量分批加入比如每轮加2000-5000条新样本训练一个epoch就评估一次精度掉了就停。第三对类别均衡做显式控制最简单的是按类别数量设定配额头部类少取一点尾部类多取一点。第四如果算力允许用EMA Teacher或者交叉训练两个模型互相打标签能有效减少单模型系统性错误。3. 自监督预训练从无标签数据里提炼“通用底座”3.1 掩码重建派让模型自己做完形填空自监督的主流做法之一是把输入的一部分遮掉让模型去重建。MAEMasked Autoencoder是其中最有代表性的。思路是把图像随机抠掉75%的块让编码器只看剩下25%的可见块然后解码器尝试还原整张图。文本这边类似BERT的掩码语言模型就是随机遮掉一部分token让模型去猜被遮住的词。用“完形填空”来理解最直观你给一个孩子一篇缺了很多词的文章让他根据上下文把空填上。在这个过程中他被迫学会了语法、词汇搭配、逻辑关系。模型也一样为了把被遮住的像素或token还原出来它必须学到图像/文本内部的规律这正是我们要的表征。实际操作中掩码重建的预训练成本不低尤其是像素级重建数据和算力需求都不小。我第一次在8万张工业图上跑MAE单卡V100要跑一周多后来换成了4卡才压到3天。如果你没有这类算力可以考虑只预训练几十个epoch用来初始化backbone或者干脆用开源的成熟自监督权重做初始化再在你的无标签数据上继续预训练。对多数中小团队来说这种“迁移继续预训练”的路线性价比最高。3.2 对比学习派让模型学会分辨“像与不像”以SimCLR为代表的对比学习走的是另一条路同一个样本做两次随机增强得到两个视图模型需要学会把这两个视图的特征拉近同时把不同样本的特征推开。这就像两个人对着暗号辨认老朋友——如果两张照片只是裁剪、翻转、颜色变化不同它们本质上还是同一个人模型应该给出一致的高相似度。对比学习的工程实现比掩码重建轻不少不需要解码器核心就是那个对比损失import torch.nn.functional as F def contrastive_loss(z1, z2, temperature0.1): # z1, z2: 同一样本的两个增强视图编码形状 [B, D] z1 F.normalize(z1, dim-1) z2 F.normalize(z2, dim-1) logits torch.matmul(z1, z2.T) / temperature labels torch.arange(z1.size(0)).to(z1.device) loss F.cross_entropy(logits, labels) return lossSimCLR当年有一个反直觉的发现增强策略比网络结构更影响效果。随机裁剪、颜色抖动、高斯模糊、灰度化这些组合起来用效果远超架构改动。所以在对比学习里最值得调的是增强组合其次才是batch size和温度系数。batch size大的时候InfoNCE能提供更多负样本效果更好温度系数太小会让训练不稳定太大则拉不开同类和异类0.1是个不错的起点。3.3 自监督预训练与伪标签怎么配合这两条不是二选一而是可以串起来的。先用自监督预训练拿到一个不错的backbone后面不管走伪标签还是半监督都会比随机初始化强很多。一个常见的落地链路是自监督预训练 → 少量标注微调出一个老师模型 → 老师模型打伪标签 → 合并训练最终模型。每一步都用无标签数据把模型往上推一层效果叠加比任何单一路线都稳。4. 半监督协同训练让两个模型互相“批改作业”4.1 一致性正则一个样本的两次预测必须一致半监督方法的核心思想比伪标签更稳不仅要求模型对高置信度样本给出正确预测还要求它对同一个样本的不同增强视图给出稳定的预测。这里面最有名的框架是FixMatch它在两路增强之间做交叉约束对无标签样本做弱增强得到一个预测如果置信度超过阈值就把它当作“软伪标签”再用同一个样本的强增强版本去预测让强增强的预测去拟合这个软标签。等于说模型看到同一只猫的模糊照片和清晰照片判断必须一致。我试过在同一批数据上对比纯伪标签和FixMatch结果是FixMatch的验证精度比纯伪标签高出大约2个百分点。原因是强增强迫使模型学到更鲁棒的特征而不是简单记住训练集的表面模式。整条训练过程不需要额外标注成本只比普通训练多一个增强分支。4.2 双模型协同分歧最大的样本最有价值FixMatch是一个模型的自问自答双模型协同则更进一步训练两个初始结构不同、训练数据视角不同的模型让它们对无标签数据各自打标签然后规定只有两个模型达成一致的样本才进入训练集也不一定分歧最大的样本反而更有价值。我用过一个实际有效的策略两个模型预测不一致的样本交给第三个验证如果还是不统一就把这类样本挑出来人工确认一下。这个思路很像论文评审——一个审稿人可能有偏见两个审稿人意见不同时你至少知道这篇稿子是值得关注的争议稿。这样做的直接收益是伪标签的噪声大幅下降间接收益是你能快速定位模型的知识盲区。在多分类工业缺陷检测里两个模型经常在“划痕”和“裂纹”两个相似类别上打架说明这两个类别的特征确实不好区分优先用主动学习去补这两个类别的真实标签效果比随机标注好太多。4.3 阈值与增强强度的搭配细节半监督训练里最常翻车的是阈值设太高。置信度阈值0.95以上时能通过过滤的样本量可能只有几十分之一训练基本没有进展。建议阈值从0.9起步观察每轮被纳入的样本量如果太少就降到0.85如果loss曲线抖动厉害就调上去。增强强度同样要克制强增强太狠会制造大量模型无法识别的失真样本反而带来噪声弱增强太弱又不足以逼模型学鲁棒特征。这里没有万能参数但有一个判断标准强增强后的样本人眼还能大概认出是什么类别这个强度就是合适的。5. 主动学习与其标一万条不如让模型告诉你标哪几条5.1 不确定性采样先挑模型拿不准的主动学习的逻辑很简单你手里有预算但不知道该标哪些数据那就让模型自己说。模型给出预测时如果概率接近均匀分布说明它根本拿不准如果在几个类别之间犹豫说明这个样本处于决策边界附近最值得标。实际操作中有几个常用的不确定性指标。熵值是最直观的把所有类别概率的负对数求和熵越高越不确定。Margin是最大概率减去次大概率Margin越小越说明模型在两个类别间摇摆。Least Confidence则是直接用最高置信度排序从最小的开始标。如果资源只够开一个在绝大多数分类任务里我推荐Margin因为它对多分类的边界样本更敏感。import numpy as np def margin_sampling(logits, top_k100): # logits: [N, C] 模型对所有无标签样本的预测结果 probs np.exp(logits - logits.max(axis1, keepdimsTrue)) probs / probs.sum(axis1, keepdimsTrue) sorted_probs np.sort(probs, axis1) margin sorted_probs[:, -1] - sorted_probs[:, -2] # margin 越小说明模型越不确定 return np.argsort(margin)[:top_k]5.2 多样性采样别让模型盯着同一堆样本标不确定性采样有一个隐藏问题模型不确定的样本往往扎堆在特征空间的同一区域。比如在商品图分类里模型可能对一批背景杂乱的新品图全都不确定但如果你只按不确定性排序标注标出来的样本可能都是同一个货号的相似图片信息冗余非常大。这时候需要加上多样性约束。我的做法是先用不确定性采样选出候选集比如500条然后在候选集上做特征聚类再从每个簇中按比例抽样本。这样既保证了样本有标注价值又覆盖了不同特征区域。用预训练backbone提特征向量然后用KMeans聚类就行实现成本很低。5.3 主动学习半监督的闭环怎么搭这两者天然是一对搭档。半监督把大量无标签数据的信号用了主动学习则负责把最难啃的部分挑出来给人来标。推荐的闭环流程是这样的先用现有的小标注集训练一个基础模型。用半监督方式或伪标签把无标签数据吃一轮精度往上拉一截。用Margin不确定性多样性采样从无标签池里挑出100-500条样本。人工标注这批样本加入标注集。回到第1步迭代3-5轮。我实际跑过一个客服意图分类项目初始只有800条标注通过这个闭环迭代三轮每轮只新标200条样本最终在5000条测试集上把F1从0.72拉到0.86。单论标注量从头到尾只标了1400条和“先标5000条再训练”的老路比省了接近七成的人力。6. 无标签数据并非越多越好先做分布体检再动手我在早期吃过的最大亏是拿到数据后直接甩给模型跑预训练结果数据里有大量和业务无关的重复图片、异常拍摄角度、甚至错误类别样本模型学了一堆没用的模式。现在我看到一个新无标签数据集先做体检搞清楚三件事分布漂移、长尾比例、种子标注质量。分布漂移的检测可以用“对抗验证”。把有标签数据标为0、无标签数据标为1训练一个二分类器如果分类器能在验证集上轻松区分这两批数据说明它们的分布差异很大直接把无标签数据拿来做半监督可能适得其反。更简单的做法是分别提取特征用PCA可视化观测两组特征是否重叠不行就看KL散度。长尾问题在无标签数据里更隐蔽。很多无标签池子是从线上直接扒下来的头部类别占了80%以上模型学了再多头部样本对尾部小类也没什么帮助。当遇到这种情况我的方案是类平衡采样加上重加权损失打伪标签时对尾部类别降低阈值、提高配额保证尾部样本能进训练集。种子标注质量同样会限制整条链路的收益。半监督和伪标签都会放大标签噪声如果原始标注里本身就有5%的错误这些错误会在迭代中被当成正确答案传播下去。所以在动手之前我会从小标注集里抽几十个样本人工复核如果错误率超过3%先花人力把种子标注清一遍不然后面所有的自我增强都是在给错误打地基。7. 从实验到落地数据管道、实验追踪与迭代节奏7.1 数据管道要防重复计算自监督预训练、对比学习、半监督训练每一轮都在做特征提取和增强变换这些计算量很容易重复。我之前跑SimCLR的时候光增强样本就占了大量本地磁盘空间。后来改成把预训练得到的特征向量直接落盘存成npy文件下游任务直接读特征省了将近一半的预处理时间。类似地伪标签的推理结果、置信度分数、聚类结果也都应该缓存下来。一个无标签数据集会被反复用三到五次如果每次都要重新推理一遍时间成本上是不能接受的。7.2 实验管理参数和过程比结果更重要无标签训练的可调参数非常多阈值、增强强度、温度系数、伪标签批次大小、预训练epoch数这些参数组合起来几乎不可能靠记忆管理。我习惯了把所有实验配置写到实验管理工具里每一步记录下阈值、增强策略、伪标签命中率、验证精度。别人问起来为什么你的伪标签阈值是0.93我能直接翻出历史实验比对说明在0.9、0.93、0.95三档下各自的表现。这里也是一样的原则——可以用开源工具来记录不局限于某个指定平台。7.3 给一条可直接复用的落地流水线把前面所有东西串成一条可以直接照做的流水线大致是这个节奏无标签数据集采集、去重、清洗做分布体检。用自监督预训练训练通用表征backbone。用小标注集微调得到初始老师模型。老师模型在无标签集上打伪标签置信度过滤后分批加入训练集。同时开一条半监督训练线用一致性正则做交叉验证。每完成一轮迭代就用主动学习挑一批最难样本交给人标注。标注完的样本回填标注集重复3-6步。这套流程不需要一次全上可以先只跑伪标签那条线跑通了再加半监督最后再接主动学习。每加一条线你都能在验证集上看到增量而不是盲目堆算力。我个人在实际操作中的体会是无标签数据的价值不在于某个单独的模型有多先进而在于你愿不愿意把数据管道、评估闭环、标注节奏这些基础设施先搭扎实。花两周时间把管线跑顺后面每一次迭代都在为你的模型持续加码这个功夫是值得的。
阅读完成 · 觉得有帮助?