
做机器学习这几年我越来越觉得“无标签数据”这件事被严重低估了。大家聊模型性能、聊指标提升第一反应通常是“再标注一批数据”“找人清洗一下数据”可落到实际项目里标注成本高、周期长、很多场景甚至根本请不到专业标注人员。反而是硬盘里、数据库里、日志里躺着的那几百万条没有标签的数据一直没人用。这篇是“无标签的数据指南”系列第二篇上一篇聊的是无标签数据是什么、从哪里来、为什么脏乱差这篇直接进入正题——在真实项目里怎么把无标签数据真正用起来。这篇内容适合正在做分类、检测、检索、推荐这一类机器学习任务的同学不管你是算法工程师、数据分析师还是研究生只要你手里有一小撮标注数据、一大坨没标注的数据这篇文章就可以直接照着操作。我会把自监督预训练、伪标签、半监督训练、聚类清洗这几条路线拆开讲清楚包括原理、参数、代码思路和踩坑记录不玩虚的。1. 无标签数据的价值为什么常被低估1.1 大家都盯着标注数据但你真算过这笔账吗很多团队立项的时候第一件事就是列标注需求这批数据要打标签需要多少人力、多少预算、多久能完成。我见过一个做工业质检的项目检测目标是一种特殊的表面缺陷能标注这个缺陷的老师傅全公司就两三个人而且他们本身还有产线巡检任务。结果项目组花了两周排队等标注才拿到3000张图。可同一时间产线上无标签的图片流已经积累了20万张就躺在NAS里没人动。这个现象背后有一个核心问题大多数人默认“数据 标注数据”觉得没有标签的数据就是“废料”。但实际上标注数据只是从无标签数据里“挖”出来的一小块。机器学习模型真正需要的不是那一小块标注里的表面信息而是数据本身的分布、结构、模式。无标签数据里恰恰藏着这些东西。我算过一笔实际的账一个二分类场景标注成本按每样本1.5元算5000条标注要7500元听上去不多。但如果要求模型在某个长尾分布上稳定5000条真的不够。你到1万条、2万条成本直线上升。而且标注质量不稳定的话花出去的钱还会带来噪声标签反而把模型带偏。相比之下你手头假如有50万条无标签数据先做一遍自监督预训练或者伪标签半监督迭代标注量可能只需要原来的1/5到1/10。1.2 无标签数据能在哪些环节起作用无标签数据的价值不只是“扩充训练集”。我拆成四个层面你对照自己的项目看看缺哪块。第一预训练层面。用大量无标签数据做自监督预训练让模型先学会通用的表征。这个表征不是针对某个具体任务的而是对数据内部结构的理解。等你有标注数据之后在这个预训练模型上做微调收敛更快、效果更稳尤其适合小标注量场景。第二半监督训练层面。你有一小部分标注数据和大量无标签数据可以通过伪标签或者一致性正则的方式把无标签数据直接变成训练信号。这个路线在图像分类、文本分类、目标检测里都有成熟实践关键是控制伪标签的质量。第三数据洞察层面。无标签数据可以做聚类、异常检测、分布分析。比如跑一个KMeans或者用一个训练好的模型看无标签样本的输出熵你能很快发现数据里有没有异常批次、有没有重复样本、类别分布是不是和你预想的一致。这一步被很多项目跳过了真正做了的人少之又少。第四测试侧校准。如果你在无标签数据上的预测置信度和真实表现有偏差可以用它做temperature scaling或者置信度校准让模型在“知道自己不知道”这件事上更靠谱。这个在开放场景里极其重要比如自动驾驶、医疗辅助诊断模型自信地出错比犹豫不决更可怕。2. 利用无标签数据的几条主流技术路线2.1 自监督学习让模型先“自学成才”自监督学习这两年火得不行核心思路就是自己给自己造监督信号。你不需要标签只需要想办法从数据本身构造一个“伪任务”让模型在这个伪任务上学习学完之后就具备了对数据的理解能力。最常见的伪任务有两种。一是对比学习思路是把一个样本做两种不同的增强得到两个“视图”让模型把同一个样本的两个视图在表征空间里拉近把不同样本的表征推远。SimCLR、MoCo、SwAV等都是这个路子。二是掩码重建最典型的就是BERT里的掩码语言模型以及图像领域的MAE。BERT把一句话里15%的词盖住让模型去猜猜得越多对语言的语义和语法结构理解就越深。MAE把一张图75%的区域盖住让解码器去重建学会的是图像的语义结构。我用生活化的类比解释一下。你把一张猫的照片裁掉左边一半、调个色、转了20度正常人肯定还认识这是猫。模型在你强迫它把两个变换后的版本识别为同一个东西的过程中不得不学会“不管颜色怎么变、位置怎么变这就是同一只猫”于是它学到的特征就有了不变性和语义性。这就是对比学习的内核。实际操作上自监督预训练的一个巨大好处是不需要人工介入。数据直接进模型跑完一轮存一个checkpoint后面所有下游任务都从这个checkpoint开始。代价是训练成本高尤其在ImageNet这种规模上。所以现实项目里大家更多用已经开源的自监督预训练权重而不是什么都从零训起。2.2 半监督学习把无标签数据变成“编外训练员”半监督学习的假设很直接无标签数据虽然不知道类别但它们的位置能告诉模型很多信息。分类边界应该尽量穿过低密度区域同一个簇里的样本大概率是同类的这些信息都可以被挖掘出来。目前最实用的两个分支是伪标签和一致性正则。伪标签的做法是先用少量标注数据训练一个baseline用这个模型去预测无标签数据把高置信度的预测结果当成“伪标签”然后把伪标签样本加进训练集继续训练。一致性正则的核心是让模型对同一个样本的不同增强版本给出尽可能一致的预测典型代表是FixMatch、MixMatch、Mean Teacher。这个方法更像是个软约束不要求模型给出确定的类别只要求“你每次看这同一个东西得出的看法别变来变去就行”。这两条路线我都用过。伪标签简单直接效果提升明显但容易踩坑尤其是类别不平衡和错误伪标签累积的问题。一致性正则训起来更稳但调参项多、训练时间也长。后面第3节、第4节我分别展开讲。2.3 无监督聚类先搞清楚数据长什么样再训练聚类不是一个训练模型的方法但它对利用无标签数据的价值非常大。很多人一上来就纠结用什么模型却没想过先花半小时跑一次聚类看看数据里的真实分布。我用KMeans跑过一张几万条的表格数据结果发现有个簇里全是某个月份产生的样本特征和别的批次明显不一样但标签分布却应该是一样的。后来一查是上游采集设备在这段时间换过参数。这个信息如果是训练之后再发现就得返工了。所以聚类的作用不只是“看看”它能帮你发现批次差异、异常分布、重复数据这些才是无标签数据里最需要清理的坑。3. 实操一伪标签流程从零到一3.1 前置条件与baseline训练伪标签这条路你至少需要一撮质量还不错的标注数据。多少算“一撮”我的经验是你要能用这撮数据训出一个比随机好一截的模型最好准确率能到70%到80%左右。如果baseline太烂伪标签基本都是乱猜错误信号会把后面全带崩。如果你的标注数据少到训不出像样的模型优先考虑自监督预训练那条路而不是硬上伪标签。baseline训练的时候有几个把控点。第一类别均衡如果你的标注集类别分布不均匀要么采样均衡一批要么弄一个类别权重别让模型一开始就偏向数量多的类。第二做简单的数据增强不一定非要离线保存增强后的样本在线做就够了。第三训练轮数不要太多我一般控制在验证集指标不再上升后多训5个epoch就停baseline的价值在于“半生不熟”的时候就去预测太熟了反而容易对标注集过拟合泛化到无标签数据上的能力反而弱。3.2 伪标签生成与筛选的完整步骤baseline有了接下来的流程我贴一下完整操作你照着做基本能跑通。第一步准备无标签数据集。把那些不符合格式的样本踢掉图像去做个去重文本按长度过滤空文档表格数据把缺失率高的列先处理掉。这一步别省脏数据进模型损耗很大。第二步用训练好的baseline对无标签数据做预测拿到每一类的概率。分类问题直接用softmax概率检测问题用类别的置信度分数文本分类同理。第三步筛选高置信度样本。先按全局阈值0.9筛一遍。但如果你的任务类别多全局阈值不靠谱我建议按类别的分位数来。比如说某个类别预测出来的置信度排前20%的样本把它作为伪标签候选。这个方法比拍脑袋定一个阈值更稳。第四步平衡比例。伪标签样本量通常远大于你的真实标注量而且大量高置信度样本往往集中在少数容易分类的类里。所以我是限制每类最多加入多少伪标签样本一般不超过该类真实标注样本的2倍然后整体伪标签与真实标签的比例控制在1比1到3比1之间。超过3比1风险很大模型会逐渐被伪标签主导。第五步给伪标签样本设一个较小的损失权重。常见做法是伪标签样本的loss乘0.5到0.8真实标注样本权重为1。这样即使混进来一些错误伪标签模型也不会一下子被带偏。下面是训练的伪代码思路# 训练baseline baseline train_model(labeled_loader, arch, epochs50) # 预测无标签数据 probs predict_prob(baseline, unlabeled_loader) # 筛选置信度 mask probs.max(dim-1).values 0.9 pseudo_indices mask.nonzero(as_tupleTrue)[0] # 构建混合数据 pseudo_dataset Subset(unlabeled_dataset, pseudo_indices) pseudo_loader DataLoader(pseudo_dataset, batch_size64) # 混合训练伪标签样本权重降低 for batch_l, batch_p in zip(labeled_loader, pseudo_loader): loss criterion(model(batch_l[0]), batch_l[1]) pseudo_logits model(batch_p[0]) pseudo_target batch_p[1].detach() # 伪标签不参与梯度 loss 0.5 * criterion(pseudo_logits, pseudo_target) loss.backward()第六步迭代。我建议前两轮用固定伪标签就是训的过程中不再重新预测让模型先稳定下来。之后再更新伪标签每5个epoch重新预测一次无标签数据刷新高置信度集合。3.3 阈值到底怎么定阈值这个事值得单独拿出来说因为太多人卡在这。很多论文里写0.8、0.9这种数字你会照着试效果却不好原因在于不同模型的置信度分布完全不一样。一个在该场景上训练充分的模型预测概率可能普遍很高0.9阈值筛下来可能还是几千个样本一个baseline训练不太充分预测概率普遍偏低0.9阈值筛下来可能只有几十个样本。所以我的建议是用“相对阈值”替代“绝对阈值”先算出模型在无标签数据上预测概率的分布然后按置信度从高到低排序取前10%到20%。你也可以分两步走先在验证集上看模型预测正确样本的置信度大概落在什么区间以此为参考选阈值。还有一个细节是阈值可以和训练轮数联动。第一轮你不希望噪声太多阈值设高一点随着模型越来越强阈值可以逐步放宽比如从0.95降到0.8让更多样本进来。手动调也行用心跳控制也行效果都不错。3.4 伪标签最容易踩的三个坑第一个坑是类别不平衡导致伪标签比例失衡。假设你的任务里“正常”类占到80%“异常”类只占10%“异常”类本来就是小样本模型又偏向把不确定样本往“正常”类推结果按全局阈值筛出来的伪标签里可能95%都是“正常”类。这样加进去训练模型对“异常”类的识别能力不升反降。解决办法就是前面说的按类筛选加数量上限。第二个坑是噪声伪标签的滚雪球效应。一开始错误伪标签只是零星几个但下一轮模型会被它们影响然后在更多样本上犯错产生更多错误伪标签。滚雪球之后整个训练集就被污染了。所以宁可筛严一点也不要放宽到出现明显怪异样本。我每次都会抽查一批难以分类的伪标签样本人眼过一遍看看是边界模糊的正常样本还是标注体系漏掉的某种新形态数据。如果是后者这一块数据要单独处理不能一股脑丢进去。第三个坑是目标数据分布变化。有的项目无标签数据和老标注数据的分布不是完全一致的。比如标注数据是白天拍的无标签数据里一半是夜间的。模型在高置信度阈值下可能还是会筛选出不少夜间样本但如果夜间样本占多数模型学到的东西会发生偏移。我一般会在筛选之后统计一下伪标签样本在几个关键属性上的分布和标注集做对比如果差异太大就要警惕。4. 实操二自监督预训练的具体做法4.1 对比学习的一个简化落地版自监督预训练的完整复现很费功夫但在真实项目里我们需要的往往不是刷榜而是让模型在我们自己的无标签数据上“先学一遍”。我给出一个可以在单卡上跑起来的简化流程以图像分类为例。整体结构是encoder加projection head。encoder可以用ResNet、ViT或者更轻量的MobileNetprojection head是两层MLP把encoder输出的特征映射到一个对比空间。对比空间的维度一般128维或者256维就够太大会带来额外的内存开销收益却不大。训练的核心是两个batch构成的对比。对每个样本做两个不同的随机增强让同一张图的两个增强版本在对比空间里距离近让batch内其他样本的距离远。这里的损失函数我直接用InfoNCE温度参数很重要。温度越低模型对困难负样本越敏感但过低容易学崩掉温度太高则所有样本都变得差不多学不到区分度。我常用的值是0.1或者0.07如果loss抖动厉害往0.2调。关键的训练参数我整理成表参数常用值备注batch size256 或 512越大对比方向越多但单卡注意显存temperature0.1温度过低loss容易炸projection dim128对比空间维度optimizerAdamW / LARS大batch下LARS更稳learning rate0.3cosine decay小batch要降到0.03增强强度随机裁剪色彩抖动灰度模糊增强太弱学不到不变性增强这块是灵魂。我用生活类比给你讲透如果你想让人通过看清一张猫的照片来认识猫他记住的可能只是这只猫的毛色、姿势、背景里那块地毯。可如果你给他看这张猫在楼上、楼下、白天、晚上、模糊、翻转、调色之后的样子他会认出“这是个猫”而不是“这是那块地毯边的猫”。对比学习就是强制模型在不同增强下保持对“猫”的同一性认知所以增强越丰富学到的特征越通用。4.2 用预训练模型做下游任务的正确姿势预训练完成后你有两种方式用这个模型。第一种是直接把encoder拿出来把projection head扔掉然后接一个你任务需要的分类头用标注数据做微调。微调时可以把encoder的前面若干层冻结只训练后半部分和分类头这样参数少、不容易过拟合在小标注集上效果尤其好。第二种是只把预训练模型当成特征提取器不微调。这对线性分类或者相似度检索场景非常有用。你可以先用模型把无标签数据全部过一遍提取特征存起来后续做聚类、近邻检索、相似样本查找速度飞快而且这个特征比直接拿原始像素做聚类靠谱得多。我自己做文本方向的时候也经常用BERT那套掩码语言模型做预训练思路一样只是伪任务从“对比同一张图的增强版本”变成了“预测被盖住的词”。如果你手里有大量领域内的无标签文本比如大量工单、客服记录、日志用这套流程可以让模型学到非常强的领域语义后面做分类微调哪怕标注只有几百条效果也比直接用通用预训练模型好。我这里说一个常见误解很多人以为预训练必须要和下游任务的数据规模匹配其实不是。预训练的规模可以远大于下游任务的规模关键是领域分布要对上。你的无标签数据来自这个应用场景预训练学到的特征就一定比来自通用数据集的模型更贴合场景。4.3 预训练成本太高怎么办自监督预训练最大的瓶颈就是算力。SimCLR原作者用了几百块TPU我们不可能这么玩。但真实场景里你压根不需要完全复现论文三个折中方案给你参考。第一个方案是缩小输入分辨率。图像分类场景输入从224缩到112甚至96训练速度翻了差不多4倍预训练特征质量下降有限因为预训练阶段关心的是结构不是精细细节。第二个方案是缩短预训练轮数。论文里动不动训200个epoch甚至400个epoch实际上我们只需要让模型经历一个完整的学习曲线我用50到100个epoch就能拿到一个堪用的checkpoint。再往下游任务微调时它比从随机初始化开始训还是要好很多尤其是标注数据少的场景。第三个方案是直接用开源权重。现在HuggingFace、Model Zoo上有大量自监督预训练权重比如DINOv2、CLIP、MAE、SimMIM。你可以直接加载这些权重然后在自己的无标签数据上做少量适配微调而不必从零开始。这个方案对绝大多数项目来说是最划算的。5. 实操三用聚类和无监督方法做数据洞察与清洗5.1 用特征聚类快速发现问题数据在动手训练之前我强烈建议你先拿无标签数据做一次“体检”。这个步骤可以帮助你提前发现数据里的脏东西而这些脏东西如果不处理后面无论是伪标签还是自监督都会放大它们的影响。体检的标准做法是先用一个训练好的模型或者预训练模型把无标签数据全部过一遍得到特征向量然后把特征向量做聚类。聚类算法我首选小规模数据用KMeans大规模数据用MiniBatchKMeans稀疏文本数据用BIRCH或者层次聚类。拿到聚类结果之后我会做几件事第一看每个簇的大小。如果某个簇特别大另一个特别小说明数据分布严重失衡后面做伪标签筛选就必须考虑类别权重。第二看每个簇里的原始样本。抽几个簇中心附近的样本和簇边缘的样本人眼扫一遍很容易发现某个簇里混进来一批完全不相关的东西。比如做商品分类一个应该全是电子产品的簇里混进了几十张沙发图那八成是爬虫数据串了。第三看有没有“孤岛簇”。如果聚出二三十个只有一两个样本的小簇这些样本大概率是异常样本或者采集噪声。可以把它们挑出来单独看别让它们在下游训练里捣乱。我自己的经验是这个体检步骤看起来“不产生直接价值”但它能省下后面反复调参的时间。你花一晚上把数据里的坑填了后面训练一遍过。你跳过这一步后面可能是连续一周被诡异loss折磨。5.2 用模型输出熵判断样本置信度聚类看的是结构模型输出熵看的则是“模型对这个样本有没有底”。熵是信息论里的概念放在分类任务里就是模型预测概率分布的混乱程度。如果一个样本的预测概率是九成猫、一成狗那熵很低模型很有底气。如果一个样本预测概率是猫46%、狗42%、鸟12%熵很高说明模型很挣扎。实际操作中我会让baseline模型预测所有无标签数据然后算出每个样本的熵再排序。熵最低的那批样本可以用来做伪标签熵最高的那批直接丢进“待人工检查”队列。这个方法比只看置信度更全面因为它能发现那种“模型在两个类之间犹豫”的样本而这些样本往往是新出现的类或者边界模糊类最需要人工介入。低置信度样本也很可能是噪声样本比如图像失焦、文本乱码。我会把熵最高的2%到5%样本抽出来看一遍很多时候你会惊讶地发现数据里混着空白页、重复截图、乱码日志这类东西。把这些脏样本清洗掉之后哪怕不用任何无标签数据参与训练你的标注数据训练效果都会提升。5.3 聚类结果怎么反馈到训练里聚类和熵做完了接下来有三个运用方向。第一个给伪标签筛选加约束。比如某个样本被分到了高置信度伪标签组但它所在的聚类簇和这个类别的中心离得很远我就把这类样本标记为可疑不放进去。这个规则有点像是“模型说这个样本是猫但所有猫样本都聚在一起这个样本却和沙发聚在一起那模型估计错了”。实测下来加了这个约束之后伪标签精度能提升几个点。第二个基于聚类做类目的二次校准。如果你的项目类别定义比较宽聚类可以发现一些模型很难区分的子类。比如做款式分类你原本定义的是裙子、T恤但聚类发现“裙子”里其实有两波数据一波是长裙一波是短裙。你可以考虑把类别标签细化或者至少针对这两波数据分别做处理。这背后反映的是真实标注体系和数据分布之间的差异不解决的话后续再怎么调参都是治标不治本。第三个做数据划分。有些场景要求训练集和验证集分布一致但直接随机划分往往会出问题。聚类之后按簇分层划分保证训练集和验证集里都包含每个簇的样本这种划分方式能更真实地反映模型在完整数据分布上的表现。6. 常见问题与排查技巧实录6.1 伪标签越训越差怎么办我在很多次分享里都强调过这个现象加了伪标签之后第一轮效果不错第二轮开始变差第三轮直接崩。原因大概率是错误伪标签开始滚雪球。解决办法有三条路按优先级排列。第一条回到筛选环节把阈值调高同时加上类别数量上限。如果你上一轮用了0.8的全局阈值可以改成0.9加每类上限。宁可牺牲伪标签数量也要保证质量。第二条把伪标签样本的loss权重降下来从1降到0.3甚至0.2让真实标注数据主导优化方向。第三条做“伪标签重生成”的时候不要全量重生成而是保留上一轮模型认为稳定的一批只更新变化最大的那部分。这个方法相当于给伪标签加了个“记忆力”不容易来回横跳。6.2 自监督训练的loss迟迟不降怎么办自监督训练loss不降大多数人第一反应是学习率问题但我在实际排查里发现真正的大头往往是数据增强强度不够或者batch size太小。对比学习这类方法非常依赖batch内的负样本多样性。你把batch size从64提到256loss下降速度肉眼可见地变快。如果单卡显存实在不够可以考虑用梯度累积等价于扩大batch size。另一个非常隐蔽的问题是你的增强方式太弱了模型很容易就能认出两个增强视图是同一个样本它不需要学深层语义就能把loss降下来但这种模型的表征是肤浅的。你可以做一个小实验把增强强度调大看loss是不是反而开始有意义地波动。还有一个常见坑projection head输出维度太大。我见过有人把projection向量设成1024维然后loss死活不降。降到128维之后问题迎刃而解。这个问题的本质是维度诅咒高维空间里所有样本距离都很远对比损失在太高的维度下难以形成有效梯度。6.3 无标签数据量到底要多大才有用这是我被问得最多的问题之一。说实话没有一个绝对的数字但我们可以算一笔简单的账。如果你的标注数据量是N无标签数据量是M伪标签路线一般要求M至少是N的2到3倍才有明显的收益。M和N的比例小于1.5倍的时候伪标签带来的提升很小噪音风险却实实在在性价比不高。自监督预训练的要求更高一些。一般经验是M至少在1万到10万这个量级而且M越大越好。但这里有一个容易被忽略的点领域匹配度比数量更重要。你拿100万张通用图片域的无标签数据做预训练不如拿10万张和目标场景同域的无标签数据效果好。领域不对齐的话模型学到的东西和你的任务反而会形成冲突。6.4 快速排查速查表我把常见问题整理成一张表你可以对照排查现象最可能的原因优先处理的方案伪标签训练后效果变差伪标签噪声大、类别失衡提高阈值、降权重、按类限量自监督loss不降batch太小或增强太弱梯度累积提升等效batch增强调强预训练权重微调后不升反降领域差异太大在目标无标签数据上再做适配微调聚类结果完全无意义特征没有区分度换更强的预训练权重提特征置信度普遍偏高/偏低模型校准缺失做temperature scaling校准伪标签集中在少数类别类别不平衡按类别分位数筛选这里再补充一个小技巧。如果你的任务是文本分类无标签数据里经常有一批重复或者近乎重复的文本。不要直接塞进伪标签流程它们会导致训练集里同一句话成百上千次出现模型会对这句话过拟合泛化能力反而下降。先用MinHash去重或者SimHash做一下粗筛去重之后再跑流程。7. 写在最后的几点体会做了这么多无标签数据相关项目我自己最深的体会是无标签数据的价值不在于“它是什么”而在于“你怎么用它”。同样一堆没人要的数据有人能用它把模型从72%干到86%有人把它塞进去之后模型反而掉点差别就在于处理流程里有没有做质量筛选、类别控制、分布检查这三件事。还有一个比较反直觉的经验无标签数据越“脏”清洗得到的收益越大。很多人看到数据脏就直接放弃但其实脏数据里蕴含的信息量反而高。关键在于你要有能力区分“脏”和“错”。“脏”是可以洗的比如缺失值、重复样本、增强噪声而“错”是标注体系层面的比如把A类当成B类标注了这种问题清洗解决不了要靠人去看样本、修订标注规范。最后分享一个我最近在用的工作习惯每次拿到新的无标签数据我不会急着训练而是先写一个“数据体检报告”记录数据量、去重率、聚类分布、置信度分布保存成一个markdown文件。等到模型出了问题回来翻这个报告往往能很快定位到是数据哪个环节出了问题。这个习惯帮我省了很多次反复试错的功夫你可以试一试。