首页 / 资讯中心 / 文章详情

图像分类半监督学习实战:从标注匮乏到准确率89%的完整方案

图像分类半监督学习实战:从标注匮乏到准确率89%的完整方案 ★ FEATURED ARTICLE
做图像分类项目时最扎心的不是模型选得不够花而是标注数据根本不够用。我去年接手一个真实业务场景总共只有不到一千张人工标注图手里却压着几十万张完全没标注的原始图片。外包标一轮问了下报价直接放弃了。后来靠半监督学习硬生生把分类准确率从78%拉到89%整个过程没额外标过一张图。今天就把这套图像分类半监督学习的完整思路和实操经验整理出来核心方法怎么拆解、损失函数怎么设、训练时踩过哪些坑、什么情况下别硬上一条条说清楚你们可以直接拿着调。1. 半监督学习的核心思路与方案选型1.1 为什么要用半监督学习标注瓶颈才是真问题很多人一提到图像分类第一反应就是堆数据、上大模型。但真实项目里干净、准确、覆盖所有类别分布的标注数据永远是稀缺资源。标注成本高不只是钱的问题还有时间成本医学影像需要专业医生逐张看工业质检缺陷图需要老师傅确认电商图片需要熟悉商品体系的人打标。这个过程又慢又贵而且标注质量不稳定返工率高。反过来看无标注数据几乎遍地都是。监控视频抽帧、服务器上积压的历史图片、爬虫采集的公开图像、生产线自动拍摄的样张这些数据没有标签但数量可能是标注数据的几百倍。半监督学习解决的就是这个问题利用大量无标注数据来提升模型的泛化能力让模型在没有额外标注的情况下把分类边界学得更准。它的适用人群也很明确手里有少量干净标注数据、同时有大量无标注数据、但预算不足以继续扩标注集的工程师和研究者。如果你已经有十万张完美标注的数据那直接走监督学习就行半监督的收益边际不大。反过来如果标注只有几百张、每类几十张半监督几乎是你成本最低的提点手段。1.2 半监督到底在学什么三个基础假设要理解半监督学习必须弄明白它依赖的三个前提假设。这些假设既是方法有效性的来源也是翻车的原因。第一个是聚类假设。同类样本在特征空间中应当彼此靠近。如果两个样本的特征表示很接近那它们的类别大概率相同。模型可以利用无标注数据来感知这种聚集结构让决策边界更贴合数据分布。第二个是平滑假设。在数据分布的高密度区域决策边界应当是平滑的。也就是说对特征空间中的某个样本做小扰动其预测结果不应发生剧烈变化。这给了半监督方法一个操作抓手让模型对“变形后的同一张图”也保持稳定输出。第三个是低密度分离假设。不同类别之间的决策边界应该穿过数据密度较低的区域。如果模型能把大量无标注样本分到明确的类别簇中而类别之间的空白地带没有样本那说明分类边界大概率是对的。这三个假设用大白话讲就是长得像的应该归一类、换了个说法意思不变、类别中间最好留出空地。所有半监督方法本质上都是在用不同方式让模型满足这些假设。1.3 三条技术路线和我的选型建议目前图像分类半监督主流有三条技术路线。第一条是一致性正则代表思路有 Π-Model、Temporal Ensembling、Mean Teacher。核心做法是对同一个输入做两次不同的随机增强要求模型输出一致。这种方式不依赖伪标签但收敛慢对增强策略敏感。第二条是伪标签法也就是自训练。先用标注数据训练一个模型对无标注数据预测出伪标签挑高置信度的样本加入训练集迭代循环。实现简单但存在确认偏差问题模型一旦预测错了错误会反复自我强化。第三条是混合方案把两者结合起来比如 FixMatch 这类思路。弱增强生成伪标签强增强输入模型训练两者用同一个模型。现在学术界和工业界的基线基本都锚定这一方向。我的实际选型建议是如果无标注数据量很大、但标注数据每类只有几十张优先尝试混合方案性价比最高。如果标注数据稍微充足一些但计算资源有限一致性正则也够用。伪标签法最容易实现但必须要配高置信度阈值和较强的数据增强否则很容易训练崩。2. 关键方法拆解一致性正则与伪标签是怎么干活的2.1 一致性正则的直观理解与损失设计一致性正则的思想非常朴素。你给模型看一张猫的照片模型说这是猫你把照片水平翻转、轻微裁剪、调一下亮度再让模型看模型还是应该说这是猫。对无标注数据来说我们不知道它到底是什么类别但我们知道同一张图的不同增强版本类别一定相同。这个约束落到损失函数上就是让模型对两个增强版本的预测分布尽量一致。常见的做法是算两个预测分布之间的 KL 散度或交叉熵。以 Mean Teacher 思路为例一个学生模型做梯度更新一个教师模型用学生权重的指数移动平均来维护训练时让学生模型的预测去逼近教师模型的预测同时学生模型也接受有标注数据的监督损失。教师模型的输出比单次预测更稳定相当于给学生提供了更可靠的学习目标。实操中有一个需要注意的点一致性正则的损失权重不能从一开始就拉满否则模型还没学好特征就先被“一致性”给僵住了。我常用的做法是前若干个 epoch 让监督损失主导无监督损失权重从一个很小的值逐渐上升到目标值类似一个升温过程。2.2 伪标签的流程、阈值与确认偏差伪标签法就更好理解了。模型先在标注数据上学出初始能力然后对无标注数据做预测把置信度高的预测结果当作“伪标签”加入训练。听上去很简单但这里有个致命的坑确认偏差。模型对某个样本如果第一次就预测错了而且置信度还很高那么把它当伪标签训练等于把错误再教一遍。等到下一轮迭代模型对这类样本的错误倾向会更强。几轮下来模型就彻底被自己带偏了。应对确认偏差的手段主要是两个方向。一是设置高置信度阈值比如只有 max 概率超过 0.95 的伪标签才参与训练。阈值高虽然会丢掉很多候选样本但留下的伪标签整体可靠性更高。二是增加伪标签的噪声容忍度比如把硬标签改成软标签用模型预测的概率分布直接做监督信号这样即使预测不完全正确梯度也不至于太“狠”。我在实际操作中通常先用高阈值跑通一个版本再考虑软标签变体。2.3 为什么最终选 FixMatch 思路而不是 Mean Teacher 或 MixMatch我在项目里最终采用的是 FixMatch 这一类混合思路。它的关键设计是对无标注图片打两次增强一个弱增强、一个强增强。弱增强版本送入模型得到预测分布取 argmax 和对应的置信度如果置信度超过阈值就把这个 argmax 当作伪标签然后用强增强版本作为输入让模型预测去拟合这个伪标签。整个过程只有一个模型、一份无监督损失实现起来非常干净。这里弱增强和强增强的分工特别关键。弱增强的目的是生成相对可靠的伪标签所以扰动幅度要小不能让模型“看花了眼”。强增强的目的是让模型在更大的数据扰动范围内保持稳定所以要足够“狠”逼模型去学习真正的语义特征而不是浅层的像素模式。我测试下来弱增强通常用随机水平翻转和裁剪强增强用 RandAugment 或 AutoAugment 加 CutOut效果比较稳。为什么不选 Mean Teacher因为双模型结构训练成本高且 EMA 更新需要额外调参收敛速度慢。为什么不选 MixMatch它用了多个增强分支和标签锐化性能不错但复杂度高工程上维护成本偏高。FixMatch 思路在效果和实现复杂度之间最平衡特别适合这类“标注少、无标注多”的真实场景。方法思路核心机制优点代价一致性正则Mean Teacher学生模型逼近 EMA 教师输出目标稳定无需伪标签训练慢双模型成本高伪标签Self-training高置信度伪标签自举实现简单直接确认偏差风险高混合方案FixMatch弱增强生成标签强增强训练效果好实现干净对增强策略和阈值敏感3. 实操过程从数据准备到训练收敛3.1 数据划分与增强策略我拿一个公开的十类图像数据集举例类别数不多比较典型。假设每类只有 40 张标注图总共 400 张标注数据另有 4 万张无标注数据。先把标注集按 8:2 切出训练子集和验证子集无标注数据不参与验证和测试集划分因为它的分布和标注集可能有偏移拿它做评估会虚高。数据加载时我把一个 batch 设计成两部分一部分是标注样本另一部分是无标注样本。比如每个 batch 包含 64 张标注图和 64 张无标注图无标注图又同时生成两个版本弱增强版本和强增强版本。这样一次前向计算就能同时拿到监督损失和无监督损失所需的全部输入。弱增强我用随机水平翻转加随机裁剪后缩放回原尺寸这是图像分类里最常见的组合。强增强我用 RandAugment配合 CutOut。RandAugment 的幅度和操作数可以调整我常用幅度 10、操作数 2 起步如果发现无监督损失下降困难再把幅度调小。CutOut 遮蔽的像素块大小在 16 到 32 之间具体看图像分辨率。3.2 损失函数、采样配置与训练超参整体损失函数就是三部分相加监督交叉熵损失、无监督伪标签交叉熵损失和一个权重系数 λ。无监督损失只在高置信度 mask 为真的样本上计算这样做的本质是对伪标签做了一次筛选。参数设置方面我给出一套经过多次实验验证的初始配置优化器SGDmomentum 0.9权重衰减 5e-4学习率0.03带 5 个 epoch 的线性 warmup之后 cosine 衰减总训练轮数200 epoch无监督损失权重 λ前 20 个 epoch 从 0 线性升到 1之后保持 1高置信度阈值0.95batch 构成64 张标注图 64 张无标注图含强弱两个版本EMA对模型参数做指数移动平均decay 0.999用于最终评估这里有个容易忽略的点无标注样本的批次大小不要太少。半监督的效果依赖无标注数据的多样性batch 太小会导致伪标签分布不稳定训练震荡。如果有显存余量我建议无标注部分至少和标注部分一样大。3.3 核心训练伪代码我按 PyTorch 风格整理一份核心训练逻辑把关键部分做了精简方便理解整体流程for step, batch in enumerate(loader): # batch 包含标注图像、标注标签、无标注弱增强、无标注强增强 x_l, y_l, x_u_w, x_u_s batch # 1. 监督损失 logits_l model(x_l) loss_s ce_loss(logits_l, y_l) # 2. 无监督损失先获取弱增强的预测结果 logits_u_w model(x_u_w) probs_u_w softmax(logits_u_w, dim1) conf, pseudo_label torch.max(probs_u_w, dim1) # 3. 高置信度样本的 mask mask (conf threshold).float() # 4. 强增强版本的预测与伪标签做交叉熵 logits_u_s model(x_u_s) loss_u ce_loss(logits_u_s, pseudo_label, reductionnone) loss_u (loss_u * mask).sum() / mask.sum().clamp(min1.0) # 5. 总损失 loss loss_s lambda_u * loss_u optimizer.zero_grad() loss.backward() optimizer.step()这条逻辑里最关键的就是 mask 的操作它直接决定了无监督哪些样本的反向传播是激活的。我见过有人直接把所有样本的无监督损失都算进去结果低置信度样本的噪声把模型带崩了。mask 的作用等价于只让模型在“自己比较有把握”的样本上吸收信息其余样本干脆忽略。3.4 评估、模型保存与监控指标训练过程中我保留一份单独的标注验证集每个 epoch 结束后用 EMA 模型跑一次验证集准确率。EMA 模型的作用是平滑震荡它的准确率通常比原始模型稳定不少也更适合作为最终的预测版本。监控指标我会同时记录四类信息监督损失值、无监督损失值、伪标签启用比例即 mask 的平均值、验证集准确率。伪标签启用比例是一个特别有用的早期预警信号。如果阈值设得太高可能大部分无标注样本都被过滤掉了导致无监督损失几乎不更新整个半监督分支形同虚设。如果这个比例长期在 90% 以上说明阈值设得太低伪标签噪声已经被大量注入。模型保存方面我一般不只存最后一轮权重而是存验证集准确率最高的 EMA 权重快照。这比“跑到最后一个 epoch 就收工”要稳得多因为训练后期往往有些震荡峰值往往在最后几轮之前。4. 常见问题与排查技巧实录4.1 无监督损失不下降模型根本不收敛这个现象通常有两种表现。一种是无监督损失持续停在某个固定值附近波动另一种是验证集准确率反而比纯监督基线还低。我排查的顺序如下。先看伪标签启用比例。如果低于 10%说明高置信度阈值挡住了绝大多数样本。原因可能是模型本身还没学到足够强的特征或者强增强太猛导致预测分布普遍低置信度。解决方法是把阈值从 0.95 降到 0.9 甚至 0.8 试一下同时把 RandAugment 的幅度调低。再看学习率。半监督训练对学习率比纯监督更敏感学习率过大时模型很快震荡过小时无监督信息根本推不动模型。我在实际项目中碰到的多数情况是学习率偏大SGD 配 0.03 只是一个起点如果你用的是 Adam建议从 1e-3 往下调。最后确认无监督数据分布是否和标注数据差距太大。如果无标注数据里混入了大量不属于任何目标类别的图片伪标签无论怎么筛选都学不到有效信息。这时候需要先对无标注数据做一轮清洗或聚类把离群部分剔除。4.2 伪标签噪声太大模型被自己带偏这里说的噪声大不是指置信度阈值低而是即使置信度很高预测结果依然是错的。高频类别尤其容易出现这种现象模型对“狗”这类样本见得多了把一些背景相似但不属于狗的图片也高置信度地标成狗。伪标签和真实标签不一致又互相强化就是典型的确认偏差。我的处理策略有三层。第一层是提升数据增强强度让强增强真正制造出足够的扰动逼模型必须依赖语义特征而非纹理背景。第二层是临时冻结监督分支先用纯监督损失训练 10 到 20 个 epoch让模型初步收敛再打开无监督分支。不要一开始就让两个分支同时优化否则噪声会污染早期特征学习。第三层是把硬伪标签换成软标签即用模型的预测概率分布作为监督目标而不是只取 argmax这样可以在一定程度上缓解错误标签的“过度自信”。如果上述手段都不理想还可以试试改变伪标签的产生方式用一个 EMA 模型的输出来生成伪标签而不是用当前快速更新的模型。EMA 模型变化慢输出目标更稳定能明显降低噪声波动。4.3 显存翻倍与训练时间变长的问题半监督训练相比纯监督训练最直接的工程代价就是多了一路强增强输入。每个 batch 里无标注数据需要同时保存弱增强和强增强的中间特征显存占用几乎翻倍。训练时间也会因此变长。我的处理经验是分三步优化。第一步是混精度训练模型前向和反向用 FP16 计算损失计算保留 FP32显存和速度都能明显改善。第二步是调整 batch 构成不一定非得标注和无标注各 64可以把标注压到 32、无标注保持 64这样显存压力小一些整体效果损失不大。第三步是如果实在显存不够就把无监督损失改成每隔一个 step 计算一次相当于把无监督更新的频率减半虽然训练速度会变慢但至少能跑起来。这里还要提醒一句不要把无标注数据全部一股脑塞进内存再预处理。最好用在线增强让 GPU 边训练边处理否则数据 IO 会成为新的瓶颈。我头几次实验时直接在磁盘上预生成全部强增强版本结果磁盘读写把训练速度拖垮了一半。4.4 类别不平衡时半监督反而拖后腿如果你标注数据里的类别分布不是均匀的比如 A 类有 200 张、B 类只有 20 张半监督的收益会明显下降甚至比纯监督还差。原因是模型会在高频类别上先形成高置信度的伪标签这些伪标签数量多在无监督损失里占比大导致模型向高频类偏移低频类几乎学不到。针对这种情况我采取两个办法。一是对每个类别单独计算置信度阈值而不是用全局同一个阈值。高频类可以设 0.95低频类降到 0.8让低频类也有机会产生伪标签参与训练。二是无监督损失按类别做重加权计算每一类的样本数用类频率的倒数对损失加权避免高频类主导梯度更新。还有一个比较取巧的做法先统计无标注数据在所有类别上的预测分布如果发现某些类几乎没有无标注样本被选中直接手动对那些类别的伪标签阈值做退火从 0.95 阶梯降到 0.7。这样虽然会引入一部分噪声但至少让低频类别在训练中“有存在感”。我试过之后低频类的单类准确率提升明显整体平均准确率也涨了。5. 几个工程上的心得判断5.1 半监督不是万能药什么时候用它收益最大经过这个项目我对半监督学习的适用边界有了更清晰的认识。它最适用的场景是标注数据极度稀缺每类几十张甚至更少同时无标注数据量大且分布与标注集基本一致。在这种情况下半监督能带来从 70% 到 85% 量级的提升几乎等于免费标注。但如果标注数据已经比较充足比如每类超过几百张半监督的提升就明显减弱。这时候与其花时间调半监督参数不如把算力留给数据清洗或更强的网络结构。另外如果无标注数据本身噪声极大包含大量无关类别或重复图片收益也会缩水需要先做一轮去重和过滤。从资源分配角度看我现在的习惯是先跑一个纯监督基线如果准确率已经超过 90%半监督基本不用碰。如果准确率只有 75% 以下半监督就值得全力投入。在标注和无标注的比例上我一般要求无标注量至少达到标注量的 50 倍以上不然收益不明显。5.2 如果还想再进一步值得尝试的方向这套流程稳定跑通之后有几条扩展路径可以明显再拔高效果。一条是把半监督和预训练特征结合。现在很多图像模型都用大规模无监督预训练初始化把初始化权重换成预训练模型再叠加伪标签训练收敛速度更快最终准确率也更高。我测试过用预训练初始化的半监督训练相比随机初始化最终准确率能再涨两到三个点。另一条是引入更强的数据增强策略。RandAugment 的幅度调参范围很宽如果进一步搜索幅度和操作数的组合或者在强增强里加入更激进的裁剪策略无监督损失能提供更强的正则化信号。还有一条是和主动学习结合。先用半监督模型对无标注数据打分挑出置信度最低、模型最“不确定”的样本去做人工标注再把新标注的样本加入标注集继续训练。这样标注人力极少但标注的目标样本价值非常高形成了一个小成本高收益的闭环。结尾我个人在实际操作中的体会是半监督不应该被当成一个花哨的学术技巧而是当作一种免费的“数据扩充”手段。先用小规模数据快速跑通纯监督基线确认模型架构和增强策略没有大问题再启动半监督分支按照三件事来检查监督损失是否正常下降、无监督损失是否有效更新、伪标签启用比例是否维持在一个健康的区间。最后再分享一个小技巧训练过程中每 100 步打印一次无标注样本的置信度均值也就是所有无标注样本 max 概率的平均值。它比 loss 值更能反映训练动态。如果这个均值一直低于 0.7说明增强太强或阈值太高模型根本没有从无标注数据里学到任何东西趁早调整别等训完一轮再回头看。
阅读完成 · 觉得有帮助?
咨询建站