迁移学习+半监督学习:智能垃圾分类系统实战解析

发布时间:2026/10/22 14:50:00
迁移学习+半监督学习:智能垃圾分类系统实战解析
1. 项目整体设计与方案选型思路1.1 任务难点垃圾分类不是普通的图像分类垃圾分类这个视觉任务表面看就是一个多分类问题但真正动手做工程落地坑比想象的多得多。我接手的这个“基于迁移学习 半监督学习的智能垃圾分类系统”目标很明确在设备算力有限、人工标注预算紧张的前提下实现一套能对常见生活垃圾进行自动识别的方案。先说清楚任务本身的特殊性。垃圾图像分类和常规物体识别最大的区别在于同类垃圾的外观差异非常大。塑料瓶有矿泉水瓶、饮料瓶、洗衣液瓶形状颜色五花八门纸类有报纸、纸箱、牛奶盒有的皱巴巴有的平整玻璃瓶更是各种颜色都有。而不同类之间反而可能出现视觉相似的情况比如白色塑料盒和白色陶瓷碎片肉眼都容易看错。这意味着系统不能只靠颜色、形状这种浅层特征做判断必须学到更抽象的语义特征。另一个现实约束是数据。标注一张垃圾图像需要人仔细辨认垃圾种类遇到混合垃圾或破损垃圾时标注员之间都容易产生分歧。我项目初期手上只有几千张完成标注的图片数量根本不足以支撑从头训练一个深度卷积网络。而与此同时真正容易获取的反而是大量未标注的垃圾图片——摄像头抓拍、网络公开图库、用户上传的废品照片这些数据不用标注就能积累成庞大的数据池。如何把这部分数据用起来就成了整个项目的一个核心命题。1.2 迁移学习解决“数据少、训练不稳”的问题迁移学习在垃圾识别场景下的价值比在其他标准分类任务上更明显。预训练模型在大规模自然图像数据上已经学会了边缘、纹理、物体部件等通用视觉特征这些特征对垃圾图像同样有效。垃圾图片虽然不是自然风景或动物但它仍然是真实世界的光学成像结果光照、透视、背景杂乱等规律是一致的。我在项目里对比过两条路从零训练一个 MobileNetV3 和加载预训练权重再微调。从零训练的版本在训练集上收敛得非常慢训练 30 个 epoch 后验证集准确率只有不到 60%而且损失曲线波动很大稍微调大学习率就发散。而加载预训练权重的版本只训练 10 个 epoch 验证集就达到 85% 以上。差距如此悬殊的核心原因就是垃圾图像数据量太小模型无法在有限样本中稳定学到通用特征而预训练权重相当于给模型一份“视觉常识底座”后续只需要在它上面微调特定的分类边界即可。这里还想强调一点迁移学习不只是“省时间”它还降低了对数据的苛刻要求。常规分类任务至少需要每类几千张才能训练得稳而垃圾图像数据集通常每类只有几百张此时预训练权重提供的初始化质量直接决定了模型最终能不能收敛到一个不错的局部最优点。1.3 半监督学习解决“标注贵、未标注数据浪费”的问题迁移学习把模型训练拉回了正轨但已标注数据仍然是短板。我手头的已标注数据只有几千张训练集再扩充也有限。而半监督学习正是专门处理这种场景利用大量未标注数据参与训练让模型从数据分布中学习结构信息从而提升泛化能力。半监督学习在这个项目里采用了两种常见策略的组合第一是伪标签Pseudo Label方法。先用已在标注数据上训练好的教师模型对未标注图片进行预测再选择置信度较高的预测结果作为“伪标签”把带有伪标签的未标注数据放进训练集一起训练。这相当于用模型自己的判断力去“自动标注”一批数据。当然伪标签肯定会带噪声所以要通过置信度阈值筛选比如只保留概率大于 0.9 的样本。第二是一致性正则Consistency Regularization。同一张未标注图片做两次不同的随机增强比如一次做色彩抖动随机裁剪另一次做水平翻转随机旋转送入模型后得到的预测应当一致。如果两次预测差异很大说明模型对这个样本不够鲁棒就用一致性损失去约束它。这个方法不依赖标签完全利用图像的语义不变性是半监督学习中非常实用的正则手段。两条路结合起来核心思想就是让模型既从少量高置信度的伪标签中学到判别信息又从大量弱增强样本对的分布一致性中学到鲁棒特征。1.4 方案选型对比为什么不是其他方案我在设计阶段列过一张对比表把可选方案都过了一遍方案优点缺点结论从零训练深度网络完全定制不受预训练分布限制需要上万级标注数据训练周期长数据不足淘汰纯监督迁移学习微调实现简单效果比从零好得多未标注数据完全浪费精度受限作为基线基线迁移学习 伪标签实现成本低能利用未标注数据伪标签噪声影响较大采用配合阈值筛选迁移学习 一致性正则正则效果好不依赖伪标签质量收敛速度较慢训练技巧多采用与伪标签组合迁移学习 半监督全套精度最高利用数据最充分训练复杂度上升需精细调参最终方案最终我选择的是“迁移学习 伪标签 一致性正则”的组合路线。理由是伪标签能直接提升模型的判别能力上限一致性正则负责稳定训练过程两者相互补充。整套方案在单张 GPU 上就能训练不依赖大规模分布式环境落地成本低这也是工程项目的现实要求。2. 数据设计与预处理垃圾图像数据的坑和应对2.1 分类体系的定义四分类还是多分类垃圾分类系统首先要确定分类粒度。我做的是“四分类 子类标签”的两级结构。四分类指可回收垃圾、有害垃圾、厨余垃圾、其他垃圾这是通用垃圾分类的常规框架。子类标签则在可回收类下细分了塑料、纸类、玻璃、金属四个子类目的是在识别阶段给用户更具体的提示比如“可回收垃圾 - 塑料瓶”。这个设计出自一个实际考量四级分类在不少边缘设备上的应用更有价值比如社区垃圾房的自动投放口只需要知道门该往哪个方向开。但四级分类过于粗糙模型训练时类别间的语义间距太大容易出现过拟合小样本的问题。加入子类标签后分类器必须同时预测大类和子类相当于强制模型学习更细粒度的特征这对四分类精度也是有帮助的。损失函数上我用了多任务结构主干网络后面接两个分类头一个大类分类头一个子类分类头。总损失是大类交叉熵损失加子类交叉熵损失的加权和权重分别设为 1.0 和 0.5。这么做的好处是子类任务相当于一个辅助监督信号能引导特征提取层保留更多细节信息。2.2 已标注数据的构成与清洗公开的垃圾图像数据集某通用固体废弃物图像集质量参差不齐。我梳理了一下手头已标注数据的情况总计约 5200 张标注图片四类分布并不均衡可回收垃圾类图片最多约 2200 张厨余垃圾类约 1400 张有害垃圾类最少只有约 600 张其他垃圾类约 1000 张数据清洗方面我主要做了三步第一步是人工抽查。每类随机抽 200 张用肉眼扫一遍剔除那些严重模糊、目标占比过小或明显标注错误的样本。这一步花了大概两天时间看似笨拙但真实数据集的脏样本如果不清理后期伪标签筛选会把错误放大。第二步是尺寸统一。所有图像统一缩放到 224x224 分辨率采用双线性插值不直接拉伸变形而是先按短边等比例缩放再中心裁剪。这样做能保留垃圾目标的主要视觉特征减少形变带来的误导。第三步是重复样本检测。用感知哈希算法计算每张图像的指纹找出和已标注集重复的未标注图像这一类会被单独记录下来绝不能混入测试集否则会造成数据泄漏。2.3 未标注数据池的建设半监督学习需要大规模的未标注数据。我项目里建了一个“某垃圾图像采集池”主要来源包括社区摄像头抓拍的实时垃圾投放画面脱敏后使用网上公开的垃圾分类图片项目合作方提供的废品回收站拍摄照片数据池最终积累了约 25000 张图片。数量看起来不小但要注意的是这些未标注数据并不是直接全量混入训练而是经过两级过滤。第一级是图像质量过滤去掉过暗、过曝、过度模糊的图像第二级是与已标注数据的相似度过滤避免大量重复或近重复样本干扰模型的记忆能力。在预处理上未标注数据和已标注数据保持一致统一缩放到 224x224。增强策略则更激进一些因为半监督训练需要更多样的视角来模拟数据分布。2.4 数据增强策略的细节数据增强是半监督学习成败的关键环节之一。我做增项分两类无监督增强和弱增强。无监督增强用于已标注数据包括随机水平翻转、随机旋转±15度、随机颜色抖动亮度±0.2、对比度±0.2、饱和度±0.2、随机裁剪缩放0.8~1.0倍。这些增强能提高模型的泛化能力防止过拟合。弱增强是半监督一致性正则的核心。两次不同的弱增强——A分支用随机裁剪加水平翻转B分支用色彩抖动加旋转送入同一个模型分别得到预测分布两者应尽可能一致。这里要注意的是增强的强度不能太大如果两次增强差异过大模型学习到的“一致性”反而会偏离真实的语义不变性导致损失难以下降。对于伪标签分支图像也做一次弱增强和已标注数据一起输入模型。实验表明强增强比如 CutOut、RandAugment在半监督训练中不是必须的甚至可能破坏伪标签信号这个我们在后面章节详细展开。3. 模型架构与半监督训练细节3.1 主干的选型ResNet50 还是 MobileNetV3主干网络的选择需要同时考虑准确率和推理速度。我在项目里做了三个主干候选的对比模型参数量预训练来源训练后验证集Top-1准确率CPU单张推理耗时结论ResNet50约25.6MImageNet预训练92.3%约45ms准确率高但部署偏重MobileNetV3-Large约5.4MImageNet预训练90.8%约18ms均衡推荐EfficientNet-B0约5.3MImageNet预训练91.6%约22ms准且轻但训练稍慢最终我选的是 ResNet50 作为主实验模型因为当时需要快速验证半监督算法是否有效ResNet50 结构成熟稳重调参经验丰富。在后续的部署压缩阶段再蒸馏或更换成 MobileNetV3。对于纯线上实时场景建议直接上 EfficientNet-B0 或 MobileNetV3精度损失在 1~1.5 个点换来推理速度翻倍很划算。3.2 迁移学习策略冻结与解冻预训练模型加载后直接全量微调不是最优策略。原因是垃圾图像和 ImageNet 自然图像分布有差异但网络浅层学到的是通用低级特征边缘、颜色块、纹理这些不需要调整太多而深层网络更关注任务专属的语义特征必须重点微调。如果一开始就全量微调小数据集容易导致深层特征被噪声主导泛化能力下降。我的策略分两步第一步冻结前五个卷积层包括预训练权重的 BatchNorm 层只训练最后一个残差块和分类头初始学习率为 0.001训练 15 个 epoch。这一阶段相当于让模型快速适应垃圾分类这个任务更新最顶层的判别边界。第二步解冻倒数第二个残差块以 0.0001 的学习率继续训练 20 个 epoch。此时 BatchNorm 层的参数也会更新但由于数据量小仍要严格控制学习率避免底层特征被大幅改动。这里有一个重要细节冻结层时一定要把 BatchNorm 层也设为 eval 模式或冻结其统计量的更新。否则BatchNorm 在训练模式下会不断用当前 batch 的统计量更新均值和方差而这个 batch 可能只有少量垃圾图像统计量波动很大导致训练极不稳定。我当时因为没处理这个细节前几次训练老是在 40 个 epoch 附近突然出现 loss 爆炸排查时走了不少弯路。3.3 半监督训练的两种关键算法项目里半监督部分采用了两个模块伪标签模块和一致性正则模块。先看伪标签模块的具体流程。教师模型当前训练状态的模型副本使用指数滑动平均更新参数对所有未标注图像做预测得到每个样本的概率分布。只有当最大概率值超过阈值我设置了两个阶段前期阈值 0.75后期 0.85时才把这个样本及其对应类别作为伪标签加入训练集。阈值过低会把大量噪声引入训练阈值过高则自动标注数据量不足。我在训练初期统计过阈值 0.75 时约有 60% 的未标注数据被选中阈值 0.85 时约 35%两个值的取舍需要根据模型的置信度分布来定不能一刀切。一致性正则模块则这样工作对同一个未标注样本进行两次不同的随机弱增强分别得到预测分布 p1 和 p2用均方误差MSE计算两者差异作为损失。这个损失会反传促使模型对增强扰动不敏感。两个模块的关联点在于伪标签分支提供了高置信度的强监督信号一致性正则分支则提供了大量弱监督信号。如果把伪标签分支比作“老师给明确的答案”一致性正则就相当于“反复做相似的题来巩固判断力”。两者配合训练整个半监督流程的效果比单独使用其中任何一边都好。3.4 损失函数与训练超参数损失函数设计如下总损失 分类损失(已标注) λ1 × 分类损失(伪标签样本) λ2 × 一致性损失(MSE)其中 λ1 和 λ2 都不是固定值而是采用 ramp-up 策略在前 10 个 epoch 从 0 线性上升到目标值。这样做的原因是训练初期模型自身能力不足如果过早加上半监督损失会放大模型早期错误的置信度判断反而把训练带偏。我在代码里将 λ1 上限设为 0.8λ2 上限设为 1.5个人经验是这两个值在垃圾图像任务上相对稳健。优化器选用了 SGD Momentum动量 0.9没有用 Adam。原因是半监督训练中动量式更新更稳定配合线性余弦退火学习率调度在同类小数据集上的收敛效果更好。初始学习率 0.001最低学习率降为 0.00001。Batch size 取 64其中已标注数据和未标注数据比例控制在 1:3也就是每批 16 张已标注 48 张未标注。这个比例维持了有监督信号的比重避免模型被大量无标签数据淹没后遗忘判别知识。4. 完整实操流程与关键实现4.1 环境与依赖准备整个项目基于 PyTorch 实现开发环境单卡 GPU某主流平台显存 12GB。依赖库版本如下Python 3.9PyTorch 2.0.1 CUDA 11.7Torchvision 0.15.2OpenCV 4.6.0albumentations 1.3.0ONNX Runtime 1.15.04.2 数据划分与标签处理已标注数据切分为训练集 4500 张、验证集 350 张、测试集 350 张。划分时按大类进行分层抽样保证各类别在三个集合中的比例一致。这里要特别强调分层抽样的重要性如果随机划分可能出现测试集里缺少某一类别的样本测试结果就完全不可信了。未标注数据不参与验证和测试只参与训练阶段的前向预测和损失计算。类别标签映射采用字典结构大类标签 0~3 对应可回收、有害、厨余、其他子类标签 0~4 对应塑料、纸类、玻璃、金属、无子类。4.3 半监督训练主循环代码解析以下是我训练主循环中的关键代码段简化版for epoch in range(epochs): model.train() # 教师模型通过EMA更新 ema_decay min(0.999, 1 - 1 / (epoch 1)) for batch_idx, (labeled_x, labeled_y, unlabeled_x) in enumerate(train_loader): # 有标签数据弱增强 lx_w weak_augment(labeled_x) # 无标签数据两次弱增强 ux_w1 weak_augment(unlabeled_x) ux_w2 weak_augment(unlabeled_x) # 前向 logits_l model(lx_w) logits_u1 model(ux_w1) logits_u2 model(ux_w2) # 一致性正则损失 cons_loss F.mse_loss(torch.softmax(logits_u1, dim1), torch.softmax(logits_u2, dim1)) # 伪标签取u1的预测作为伪标签置信度过滤 prob_u1 torch.softmax(logits_u1, dim1) max_probs, pseudo_label torch.max(prob_u1, dim1) mask (max_probs threshold).float() # 交叉熵损失有标签 伪标签 ce_loss_l F.cross_entropy(logits_l, labeled_y) ce_loss_u F.cross_entropy(logits_u1, pseudo_label, reductionnone) ce_loss_u (ce_loss_u * mask).mean() # 总损失 total_loss ce_loss_l lambda_1 * ce_loss_u lambda_2 * cons_loss optimizer.zero_grad() total_loss.backward() optimizer.step() update_ema(model, ema_model, ema_decay)这段代码里最需要注意的就是 mask 的计算。threshold 如果设得太低伪标签噪声会直接影响损失如果设得太高在训练初期会因选中的伪标签样本太少而导致半监督损失形同虚设。我在实验中发现threshold 在 0.75~0.85 之间是敏感区间需要按照验证集准确率动态调整而不是从一开始就固定不变。4.4 超参数汇总表超参数取值说明输入尺寸224x224通用分类标准初始学习率0.001迁移微调阶段解冻后学习率0.0001深层微调阶段Batch size64有标签:无标签 1:3训练 epoch35含两阶段训练伪标签阈值0.75→0.85线性上升随epoch推进λ1伪标签损失权重0→0.8前10个epoch线性上升λ2一致性损失权重0→1.5前10个epoch线性上升EMA 衰减0.999教师模型更新系数这些超参数并非“最优解”而是基于垃圾图像数据集规模、类别分布和模型结构综合调整后的一个稳定配置。实际使用时建议先跑一个有监督的 baseline再依次加入伪标签和一致性正则每一步都观察验证集精度的变化这样能更精准地定位是哪个部分带来了提升。4.5 训练过程与结果记录我在实验笔记本上记录了完整的训练曲线这里大致描述一下趋势。有监督 baselineResNet50 微调在训练第 10 个 epoch 达到验证集约 86.2%到第 35 个 epoch 在 88% 附近震荡说明模型基本收敛但泛化性能上不去。加入伪标签分支后第 10 个 epoch 验证集约 87.5%第 35 个 epoch 达到 90.1%。提升主要来自后 15 个 epoch因为前期 threshold 较高、选中的伪标签样本少半监督的增益不大而后期模型能力增强后大量高置信度伪标签样本开始发挥作用。再叠加一致性正则后最终验证集达到 91.6%测试集为 91.7%。一致性正则带来的提升虽然只有约 1.5 个点但它显著稳定了训练曲线——在加入该模块后验证集的每轮波动明显变小对模型部署阶段的可控性更有价值。最终我们换用更轻量的 MobileNetV3 做同样流程测试集准确率也有约 90.8%而 CPU 推理单张耗时从 45ms 降到了 18ms。工程上完全够用。5. 常见问题与排查技巧实录5.1 伪标签噪声太大模型被带偏我在第一个实验版本里伪标签阈值设成了 0.6结果训练到第 20 个 epoch 时验证集准确率反而下降到 83%。排查原因阈值过低导致大量错误标签混入训练集模型开始记忆这些错误信号判别能力下降。解决办法把阈值从 0.6 提升到 0.85同时引入“预热期”前 10 个 epoch 不启用伪标签分支只训练有监督部分。预热期结束后再用 EMA 教师模型生成伪标签。这个改动之后训练曲线明显平稳了。另一个辅助技巧是伪标签样本只取每类中最高的若干置信度样本而不是全局置信度排名避免某类样本过多时伪标签分布不均。5.2 训练不收敛或 loss 爆炸这个现象我遇到过三次每次都和冻结模块的 BatchNorm 状态有关。前面提到冻结卷积层时 BatchNorm 必须固定为 eval 模式或冻结对应统计量的更新否则模型本身的归一化统计量会在小 batch 上剧烈波动导致深层特征被无形中破坏。排查步骤建议先在训练脚本里打印每个 epoch 的 BatchNorm running_mean 变化幅度如果波动超过 0.01 就要警惕同时观察 loss 曲线如果 loss 在小幅下降后突然大幅上升优先检查是不是 BatchNorm 或学习率过大。还有一个隐蔽问题混合精度训练AMP在某些 PyTorch 版本中和冻结 BatchNorm 有兼容性问题必要时先关闭 AMP 排查。5.3 类别不均衡导致少数类识别率低有害垃圾只占整体样本的约 12%在初版模型里这类别的召回率仅 74%明显低于其他三类。直接套用加权交叉熵后其他类别的准确率反而下降因为权重过于激进导致模型过度关注少量类破坏了正常类别的学习。最终我用的是类别平衡采样 Focal Loss 的组合。采样时每批数据先按类别均匀抽每个类别至少抽 4 个样本损失函数换成 Focal Lossgamma 取 2.0alpha 按类别频率逆比例设置。这套组合把有害垃圾召回率从 74% 提升到了 87%而其他类别的准确率只损失了不到 0.5 个点是可接受的范围。5.4 数据泄漏未标注集里混入了测试集图片半监督学习的隐患就在这里。未标注数据池里有可能包含与测试集高度相似的图片比如同一时间点摄像头连续拍下的几帧如果这些帧同时进入了未标注训练池和测试集模型在测试时就会变相“看过答案”。我采取的预防措施是依赖第 2 节提到的感知哈希去重对所有未标注数据计算感知哈希与训练集、验证集、测试集图片全部比对汉明距离小于 5 的视为重复直接剔除。这个操作需要放在数据预处理的最前面并且迭代多次。有一次我在新加入的数据池里发现了 18 张和测试集图片非常相似的样本多亏这步检查避免了一次错误的实验结论。5.5 推理部署速度不够快训练阶段的精度再高部署阶段不够快也白搭。我最初的 ResNet50 模型在普通 CPU 上单张推理耗时约 45ms对于社区垃圾投放口这种低时延场景来说偏慢。后续做了三步优化第一步把模型导出为 ONNX 格式精度几乎无损推理耗时降到约 38ms。 第二步用动态量化将模型量化到 INT8推理耗时进一步降到约 20ms但因为垃圾图像颜色细节重要量化后准确率掉了约 1.2 个点。 第三步改用 MobileNetV3 作为部署主干按同样的半监督流程重新训练最终模型准确率约 90.8%CPU 单张推理耗时 18ms完美满足需求。如果追求极致速度还可以考虑转成 TensorRT 引擎在支持 GPU 的硬件上推理耗时能压到 5ms 以内。不过对于大多数嵌入式或工控机场景MobileNetV3 ONNX 已经是性价比最好的组合。5.6 训练时间太长半监督模块拖慢整体迭代半监督训练因为要前向推理未标注数据每轮 epoch 时间比单纯有监督训练长约 40%。我在项目推进过程中采取了“两阶段训练策略”先用有监督数据把模型训练到基本收敛再开启半监督分支继续训练 15 个 epoch。这样既保证了一开始有可靠的 baseline又避免了半监督分支在模型能力不足时浪费训练时间。另外未标注数据的 batch 比例不用每次都和已标注数据完全一样。我在实验里尝试过把未标注数据切分成多个子集每个 epoch 随机选取其中一个子集参与训练。这样每轮的额外计算量降低了但模型仍然能在多个周期内见过全部未标注数据。该策略在不明显掉点的情况下把单 epoch 时间缩短了近 30%。一个小建议先跑稳有监督基线再上半监督最后说点个人体会。做这类“迁移学习 半监督学习”的组合项目最忌讳一上来就把所有模块都堆上。我先做了一版纯迁移学习的 ResNet50 分类器验证集准确率到 86% 后再依次加入伪标签和一致性正则每加一个模块都记录验证集的变化。这样既方便定位问题来源也避免了多个模块同时引入时“出了问题都不知道是哪个环节的问题”的窘境。垃圾分类识别这个领域真正的难点从来不在网络结构上而在数据质量和工程落地。模型再先进面对脏乱差的图像数据、类别不均衡和部署算力限制照样会翻车。迁移学习让我在小数据前提下有了一个可靠的起点半监督学习让我把大量免费未标注数据转化成了精度提升的燃料这套组合非常值得在类似资源受限的视觉项目里推广使用。