1. 项目背景与思路拆解
先说一个很现实的场景:你手头拿到了几千张甚至上万张食物照片,想训练一个分类模型,把中餐里的宫保鸡丁、鱼香肉丝、回锅肉分清楚。但打开图片一看,标注工作量大到让人绝望——每张图都得人工确认“这是什么菜”,标注几百张还能坚持,标注到第两千张就开始怀疑人生。这时候,半监督学习的价值就冒出来了。
这个项目的核心思路非常明确:利用大量未标注的食物图像,配合少量人工标注的种子数据,训练出一个接近全监督效果的分类模型。说白了,就是把“人工标注”这个最贵的环节压缩到最小,让模型自己从无标注数据里“偷学”特征。
我最早接触这类任务,是在一个餐饮行业SaaS需求里。客户需要识别外卖餐品图片,用来做环保餐盒回收奖励的自动化判定。一开始走了全监督的路线,标注了一万张图,效果好是好,但标注成本接近总成本的六成。后来换了半监督的思路,标注量砍到两千张,配合剩下的无标注图像做训练,最终Top-1准确率只掉了不到两个点。这一轮对比下来,让我对半监督方法在食物分类里的潜力彻底改观。
1.1 核心需求拆解
先把这个项目的需求拆成三个层面:
- 数据层面:有标注数据占20%不到,其余80%以上是无标签图像。食物图像有其特殊性——相同菜品的摆盘、光线、角度差异极大,而不同菜品之间又可能出现视觉上的高相似度(比如干煸四季豆和豇豆炒肉末,远看真的很难分)。这就意味着模型既要学“类内一致”,又要学“类间区分”,恰好是半监督方法擅长的领域。
- 训练层面:需要一套能循环自举的训练流程——先用少量标注数据训练一个初始模型,用它对无标注数据打伪标签,再把高置信度的伪标签样本加入训练,形成闭环。这里面最关键的是“伪标签怎么选、怎么用”,直接决定训练会不会跑偏。
- 评价层面:终端业务往往不止看Top-1准确率,还要看混淆矩阵、各类别的查准查全,甚至要考虑新品类出现时的冷启动。半监督方案如果只在公开数据集上刷点,落地到真实场景会碰很多壁。
1.2 适合谁参考
如果你手头有这些情况,这个项目对你特别有用:
- 你在做菜品识别、饮食健康记录、餐饮结算系统等方向,图片好收集但标注人手不够。
- 你手里图片数量很多,但预算只够标注其中一小部分,想最大化利用这批无标注数据。
- 你在学半监督学习,想找一个不那么俗套的领域练手,而不是一直做CIFAR-10。
2. 方案选型:为什么用一致性正则与伪标签的组合
半监督图像分类的套路,这几年基本收敛到两条主线:一致性正则化(Consistency Regularization)和伪标签(Pseudo-Labeling)。这两条线各有各的道理,但单独用都有各自的坑。
2.1 两条主流路径的对比
一致性正则化的思路是:同一张图,做两次不同的随机增强(比如一个只做翻转裁剪,另一个做强颜色扰动),喂给模型,要求两次输出的概率分布尽量一致。背后的假设是——图像增强不应该改变语义类别,所以模型的输出也应该不受增强方式影响。这个思路在早期的Mean Teacher、Π-Model里就有雏形,属于比较“温柔”地利用无标注数据。
伪标签的思路更直接:先用当前模型预测无标注样本,然后挑出置信度最高的那些样本,把它们当作标注数据加进训练集。优点是简单有效,缺点是模型会越来越自信,一旦预测错了,错误会被一遍遍放大,训练到后期容易坍缩到某一类。
真正让这个项目稳定跑起来的关键,是引入现代的混合方案——伪标签负责“选样本”,一致性正则负责“教模型稳定输出”。以FixMatch为代表的做法是这样运作的:
- 对无标注图像做弱增强(比如随机水平翻转加平移裁剪),得到模型预测,如果最高类别的置信度超过阈值 τ,就把它转成one-hot伪标签。
- 同一张图再做一次强增强(比如RandAugment),喂给模型,要求输出和伪标签一致,损失函数用交叉熵。
- 伪标签只在置信度足够高时才参与计算,避免低质量伪标签污染模型。
这种组合在我看来很聪明,它把两个方法最稳的部分组合起来:伪标签动态地从无标注数据里“淘金”,而一致性正则强制模型对增强扰动不敏感。两个机制互相钳制,不会像单独用伪标签那样容易跑偏。
2.2 针对食物场景的特殊调整
光有算法还不够,食物图像有一个训练中常见的坑——类间距离近。比如红烧排骨和糖醋排骨,颜色都是红亮亮的,一些角度下连人都容易搞混。如果在模型刚开始训练、特征还没学出来时就激进地打伪标签,很容易把这两类的特征混在一起。
我在实际项目中做了两个针对性的调整,实测有明显增益:
- 把伪标签的置信度阈值从 FixMatch 默认的 0.95 降到一个区间,比如 0.90~0.93,允许更多中等置信度的样本参与训练,但配上一个“类别均匀性”约束,防止某类样本被过度选中。
- 对无标注样本的强增强里,尽量避免过度使用“旋转”这类会破坏食物纹理的变换。食物识别的关键点是质地和色泽,旋转变换虽然能提升模型泛化能力,但对这类任务反而可能引入误导特征。RandAugment 的幅度要有所收敛,尤其要把颜色相关的变换幅度控制在合理范围。
这些看似细节的选择,本质上都在回答一个问题:如何处理这些无标注数据的“不确定性”。半监督的核心功力不在模型上,而在“怎么定义样本该不该信任”。
2.3 模型骨架与训练框架的取舍
骨架网络我建议直接用残差网络这类经典结构起步,这个项目主推 ResNet-50。原因很简单:半监督训练需要比较大的batch size来稳定伪标签的分布统计,深层的ResNet在显存占用上比较友好。如果你追求精度,可以换成更强一些的ResNet变体或者近期的一些视觉骨干网络,但没有必要在一开始就把工程复杂度拉高。
训练框架上,我的习惯是用PyTorch自己写一套训练循环,而不是直接套用现成工具箱。原因有一点很现实——食物分类的需求千奇百怪,很可能需要动态改损失函数、自定义类别重加权、或者插入一些新的增强策略。自己维护一套可改的代码,比去读别人的封装库要高效得多。
3. 实操流程与核心环节实现
下面这部分,我会给出一个可以直接照抄的实操流程。整个流程分为四个阶段,从数据准备到最终训练,每一步我都会写上关键参数和踩坑记录。
3.1 数据准备与标注切分
数据集我以公认的食物分类公开集Food-101为基准来设计实验。Food-101有101个类别、每类1000张图,常规划分是750张训练+250张测试,但这个项目要模拟“少标注、多无标注”的场景,所以需要重做划分。
我的做法是这样的:
- 先把每类的750张训练图随机分成两份:一份是“标注池”,一份是“无标注池”。
- 模拟不同的标注预算:比如每个类别只保留10张、25张、50张、100张作为有标注数据,剩下的都归入无标注池。
- 测试集保持原样的250张不动,方便和其他方法做对比。
这样从10张到100张,五个挡位,可以直接画出“标注量—准确率”曲线,特别直观。
整个过程有一个小坑要提醒:切分时务必保证各类别的标注数量是均衡的。半监督模型特别吃分布的稳定性,如果某几个类只分到两张标注图,伪标签很容易把这几类学崩。
3.2 训练循环的搭建
训练阶段的轮廓如下,我直接按关键模块写清楚:
- 有监督分支:标注样本走正常的交叉熵损失,这个没有悬念。
- 无监督分支:无标注样本同时做弱增强和强增强。弱增强结果用来生成伪标签,强增强结果用来计算一致性损失。只有置信度 ≥ τ 的样本才会被计入损失。
- 优化器:SGD加动量0.9,权重衰减设为5e-4。初始学习率我习惯设在0.03,用余弦退火调度到0。
- Batch组成:每个batch包含64张标注图加64张无标注图。这个比例的设定很关键,如果无标注图的占比太高,模型的训练方向会被伪标签主导,丢失真实标签的锚定作用;太低则半监督的收益不明显。64+64是我在多个数据集上试下来比较稳的配置。
Loss的计算方式可以写成:
总损失 = 有监督交叉熵损失 + λu × 无监督伪标签交叉熵损失λu取1.0,这个系数也可以动态调整,但初期不建议把无监督损失的权重调太高。
一个细节:伪标签生成后,不要立刻把它当成硬标签去算交叉熵,而是先用温度参数软化一下。温度设0.5左右能让损失更平滑,训练初期的震荡会明显减少。食物类别多且相似度高,对置信度的变化很敏感,这个处理对收敛稳定性的帮助很直接。
3.3 判断训练是否正常的信号
我总结了一些中期检查的信号,方便对照着看训练是否健康:
- 每个epoch结束后,统计一下无标注数据中通过置信度阈值筛选的样本比例。理想情况下,这个比例应该逐步上升,说明模型越来越有把握。如果比例突然从20%涨到90%,反而要警惕——大概率模型已经坍缩到只会选几个容易类了。
- 查看伪标签的类别分布,尽量不要和标注样本的类别分布差太远。
- 每训练几轮用测试集跑一次分类准确率,要是上升趋势中断或者掉点明显,可能就是伪标签噪声开始起副作用了。
3.4 实验效果复盘
我在Food-101上用每类25张标注数据的配置跑过一版,Top-1准确率在75%上下。同样的数据量如果只训有监督baseline,准确率大概只能到63%左右;如果把标注数据加到每类200张做全监督训练,大概能到88%~90%。也就是说,半监督用12.5%的标注量接近了75%的准确率,这个增益在真实业务里足以省下大量的标注预算。
在更细粒度的一类食品食材数据集Food-256上,每类50张标注时半监督模型能跑到68%。这类数据集类别更多、相似度更高,伪标签质量对训练的拖累会更明显,所以FC阈值我建议从0.95回调到0.90。
4. 常见问题与排查技巧实录
这部分把我在这个项目里踩过的坑,挑几类典型的做解读。
4.1 伪标签噪声怎么压制
这是半监督训练最头疼的问题。模型早期的预测错误率高,伪标签噪声是不可避免的。我试过不同强度的应对方案,从简单到复杂排列:
- 提高置信度阈值,让模型只选最确定的样本。简单有效,但阈值高过0.97之后,参与训练的样本数量会急剧下降,收益反而减少。
- 对每个类的伪标签做一个数量上限,防止模型在几个容易类上疯狂标注。操作上是每个epoch先统计各类通过筛选的样本数,超过上限的直接丢弃,名额留给其他类。
- 在后期用小学习率对模型进行几轮“自动修正”,观察伪标签分布的变化,如果某个类的伪标签数量异常高,就去核查是不是模型在该类上产生了系统性偏见。
经验之谈:伪标签的“选择性”比“数量”更重要。宁可一次只挑500个高质量样本,也不要硬塞5000个“大概对”的样本进去。
4.2 模型坍缩与类别遗忘
训练中途损失不降反升,甚至测试准确率突然跌到接近零,大概率是模型把所有样本都推到了同一类。这种情况我遇到三次,两次是因为强增强的幅度太大,把图像的颜色结构彻底破坏了;一次是因为无标注数据的类别分布严重失衡,模型被带偏了。
排查方法很简单:在训练过程中定期打印无标注分支的伪标签类别直方图,如果某一类的占比超过35%,隔离一个batch看它的增强结果和模型输出,基本就能定位问题。
对付类别遗忘,我建议加一层均匀性约束:每次计算无监督损失时,给伪标签的分布加一个惩罚项,让模型不要把所有样本都归类到一个类别。这一项只花很少的算力,但能保住多类别的均衡。食物分类涉及几十上百个类别,某些菜品之间从颜色上看几乎是一个模子刻的,这类约束尤其管用。
4.3 增强策略的负作用
半监督方法强依赖数据增强,但增强策略选择不适合食物任务时,会适得其反。
我在实验里看到过这类情况:随机旋转90度让模型学到“图像有没有旋转”这个特征,而不是“这是豆腐还是肉”。食物图像的语义很容易被全局几何变换破坏——一块红烧肉转成竖着的,谁还认得出来?所以我在食物任务里把旋转幅度控制在±10度以内,最多加一点小角度的随机仿射,但颜色抖动、对比度变化这些保鲜质感的增强反而可以适当加大。
RandAugment的幅度参数(magnitude)在食物任务上建议从默认的15降低到8~10,实测准确率提高了1到2个点。
4.4 常用问题速查表
| 现象 | 可能原因 | 处理方案 |
|---|---|---|
| 伪标签通过率极低 | 模型置信度不够,训练不充分 | 先多训几个epoch有监督分支,降低阈值 |
| 损失一路下降但准确率不变 | 伪标签全是噪声,模型在“死记硬背” | 检查伪标签类别分布,降低λu |
| 训练开始后不久准确率暴跌 | 模型坍缩到少数类 | 检查强增强幅度,加均匀性约束 |
| 无标注数据强增强和原图差异过大 | 语义信息被破坏 | 关掉过强的几何变换,保留颜色变换 |
| 标注数据过少时训练发散 | 优化器不适应小训练集 | 改用Adam或降低学习率到0.01以下再启动 |
5. 经验补充:食物半监督还能怎么改进
到这里,核心流程已经完整跑通。最后分享三个我后续在这个方向上的扩展思路,每个都是在真实项目中实测过有收益的。
5.1 用类别均衡采样提升伪标签质量
如果标注数据只有每类20张,模型很容易对头部类别产生偏好。我在训练前会计算一下类别频次,在采样器里做重加权——让模型每个epoch平均见过每一类的样本数量大致相同。别看这是一个很小的改动,能直接提升伪标签的类别覆盖率,尤其是那些外观接近的尾部类别。
5.2 引入分类器自适应层
当标注数据特别少的时候,可以尝试把分类器和特征提取器解耦,分开训练。做法是先用无标注数据的伪标签训练分类器,再用交叉熵固定分类器去回传特征提取器。这个办法在一定程度上能缓解伪标签噪声对底层特征的破坏。简单说,给模型装了“两个脑子”各司其职。实验下来,在极端低标注(每类5张)的场景,这个方法比端到端直接训练高出三到四个点。
5.3 混合标注来源的协同训练
真实项目里往往还有一批“脏标签”数据——比如爬下来的菜品图配了粗糙的类别名,或者用户上报时填错。这批数据直接做全监督训练会破坏模型,但丢弃又可惜。我试过的方式是:把有标注数据、无标注数据、脏标签数据三路混合训练,脏标签数据只计算有监督损失,但损失权重拉低到0.1。这样既可以利用它的语义信号,又不会让噪声主导梯度方向。在某个真实餐饮项目里,加了这一路之后准确率又涨了1.8个点。
5.4 训练时长与性价比的权衡
半监督训练比全监督更吃训练时长。原因不难理解:一致性损失需要模型在大量无标注样本上反复比较输出,梯度更新的步数天然更多。我建议在全监督baseline收敛之后,再接入无标注分支。这样模型的初始特征不会太差,伪标签从一开始就相对可靠,能省下不少纯用无标注数据“瞎撞”的训练时间。
我个人在这些项目里最大的体会是:半监督从来不是“免费的午餐”,它把标注成本转移到了训练设计和调参成本上。但做食物图像分类这样的真实场景,数据获取的难度远大于算力获取的难度,这个权衡是值得的。如果你手头刚好有一批没标注的食物图片,不妨按这套流程先跑一轮,拿不准的地方可以随时再细聊。