弱监督语义分割新范式:单阶段端到端训练与优化实战
简介一份PDF格式的学术论文聚焦单阶段弱监督语义分割方法面向研究语义分割、弱监督学习及低标注成本场景的计算机视觉学习者与从业者。针对传统多阶段WSSS依赖图像级标签生成CAM、计算复杂且泛化受限的问题该研究提出利用点标注在线生成可靠伪标签无需预训练主干、分类任务或单独细化模块可从头训练。论文在PascalVOC 2012上取得SOTA水平并在CRAID、CityPersons、IAD等真实数据集上优于其他主流方法对自动驾驶、智能监控等领域具有参考价值。资源为单一PDF文件大小约22.25MB包含完整的摘要、引言、方法描述及实验对比读者可直接阅读原文学到具体的技术思路与实验设置。目前已有180人学习下载适合希望快速了解单阶段WSSS前沿方案并复现实验的进阶研究人员。1. 单阶段弱监督语义分割在解决什么问题主流弱监督语义分割方法通常采用两阶段范式先基于图像级标签训练分类网络从中提取类激活图Class Activation Map, CAM再对 CAM 进行密集CRF后处理或区域生长等操作生成伪像素级标签最后用这些伪标签训练一个独立的语义分割网络。这个过程存在一个明显的结构性问题——CAM 的分辨率受限于分类网络最后的全局池化层很多边缘细节在池化过程中已经丢失后期无论怎么细化都是在修复先天缺陷。单阶段方法的思路则是把生成伪标签和学习分割合并到同一个网络里用可微的方式边生成边学习让分割网络在训练过程中主动参与伪标签的修正而不是被动接受一个静态结果。单阶段不是简单地把两阶段网络拼在一起。它真正的难点在于如果整个流程全部可微梯度会通过伪标签一路回传到分割分支训练不稳定如果伪标签完全不可微又失去了联合优化的意义。所以主流单阶段方法会引入一些折中机制比如用梯度的 stop-gradient 操作截断反传路径或者通过在线更新伪标签让分割分支被柔和地引导。本文将围绕这种范式的网络结构、学习策略、训练参数和排错经验展开适合已经跑通过两阶段流程、目前希望简化管线并提升端到端性能的研究者或算法工程师。2. 网络结构与损失函数单阶段模型的两个核心组件2.1 从两阶段到单阶段的关键结构差异在两阶段方法中分类网络通常是带有全局平均池化的 ResNet和分割网络通常是 DeepLabV2 或类似结构是两套独立参数。单阶段方法省掉了中间产物落盘的环节但也会因此失去用静态伪标签稳定训练这个保障需要在结构层面做针对性设计。我不敢说业界存在标准模板但大多数单阶段方法会包含三个关键结构件共享骨干网络通常还是 ResNet但不再是只用于分类或只用于分割两侧的任务会同时更新骨干参数。一个轻量的分类分支挂在骨干的深层特征之后用全局池化1x1卷积输出类别得分用于计算图像级分类损失。一个分割解码器从骨干的中高层特征做上采样恢复分辨率输出像素级类别概率图用于计算分割损失。这三者在结构上并不复杂真正的关键在于如何在反向传播中处理分割预测结果影响分类特征这条路径。以常见的在线伪标签思路为例分割分支输出的概率图会被用来做类别特征聚合即计算各类别的特征原型再用相似度增强分类预测。这个过程如果直接端到端可微分类分支在训练初期的输出还不稳定会给分割分支引入很强的噪声稳妥的做法是将伪标签的生成部分做 stop-gradient让分割分支的输出以常数的方式参与后续计算避免梯度经伪标签路径回传。import torch import torch.nn.functional as F def online_pseudo_label(seg_logit, feature, labels_present, tau0.5): seg_logit: [B, C, H, W] 分割网络的logits feature: [B, D, H, W] 骨干网络的中间特征 labels_present: [B, C] 图像级标签多标签0/1表示类别是否存在 prob torch.sigmoid(seg_logit.detach()) # detach使其不回传梯度 B, C, H, W prob.shape pseudo torch.zeros_like(prob) for b in range(B): # 找出当前图像中出现的目标类别 present_cls torch.nonzero(labels_present[b]).squeeze(1) for cls in present_cls: # 阈值筛选高置信度像素作为该类别代表性特征 mask prob[b, cls] tau if mask.sum() 10: continue # 计算该类别的特征原型类别中心 cls_feat feature[b, :, mask].mean(dim1) # 用余弦相似度重新加权特征增强类别可分性 norm_feat F.normalize(feature[b], dim0) norm_cls F.normalize(cls_feat, dim0) sim torch.einsum(dhw,d-hw, norm_feat, norm_cls) sim torch.clamp(sim, min0) pseudo[b, cls] torch.where(mask, sim, torch.zeros_like(sim)) # 将伪标签作为分类分支的辅助特征 # 这里不需要回传梯度所以pseudo不参与梯度计算 return pseudo.detach()这段代码模拟了分割分支和分类分支之间的信息流交互方式。detach()是关键操作它将分割分支的输出转化为一个静态的引导信号labels_present保证只在当前图像确实包含该类别时进行特征聚合避免把背景像素错认成前景类别。在实际部署中这种原型聚合的计算量并不大只对出现在当前图像中的类别计算瓶颈仍然在分割解码器本身。结构层面需要注意的另一件事是分类分支的感受野天然大于分割分支因为经过全局池化两个分支对同一骨干会有不同的梯度偏好需要控制两个分支的损失权重分配。2.2 感知对比损失解决前景与背景特征分不开的问题单阶段方法还有一个容易被忽略但实际非常关键的问题分割分支在早期训练阶段对前景和背景的区分度很差尤其是在目标边缘区域。单纯依赖图像级标签的信息量不足模型可能收敛到把所有接近目标的像素都预测为前景或者反过来混淆类别。这个问题靠交叉熵本身无法暴露因为背景像素在图像级标签中根本不出现。常见做法是引入对比学习的思想。具体到语义分割对比学习的粒度需要细到像素级而不是像图像分类那样做整个图的嵌入。对每个像素如果它属于同一语义类别它们的特征应该在嵌入空间中接近不同类别的像素特征应该互相推远。def pixel_contrastive_loss(features, probe_mask, pos_mask, neg_mask, temperature0.1): features: [N, D] 归一化后的像素级特征 probe_mask: [N] 作为锚点的像素索引通常选择高置信度像素 pos_mask: [N, N] 正样本对掩码 neg_mask: [N, N] 负样本对掩码 # 特征的余弦相似度 sim torch.matmul(features, features.T) / temperature # [N, N] loss 0.0 count 0 for i in torch.nonzero(probe_mask).squeeze(1): # InfoNCE形式log(exp(sim_pos) / sum(exp(sim_all))) pos_sim sim[i][pos_mask[i]] all_sim sim[i][neg_mask[i] | pos_mask[i]] logits torch.cat([pos_sim, all_sim], dim0) # 正样本对自身作为分类目标 labels torch.zeros(len(pos_sim), dtypetorch.long, devicesim.device) loss_i F.cross_entropy(logits.unsqueeze(0), labels.unsqueeze(0)) loss loss_i count 1 return loss / max(count, 1)这段对比损失的关键在于pos_mask的构造而不是损失函数本身。实际情况中正样本对来自当前图像中同一类别的像素负样本对来自不同类别的像素。但如果直接在整张图上做对比计算量是 O(N²)对于一张 512×512 的特征图来说完全不现实。工程上通常会下采样特征分辨率例如只保留 64×64并且只选择置信度较高的像素参与计算否则噪声像素会把损失值拉得很高且训练信号紊乱。从实践经验来看对比损失对单阶段弱监督分割的价值主要体现在两点一是减少了因为 CAM 定位不准确而导致的错误放大二是对类别间特征混淆有明显缓解。但在调参时切忌权重设置过大否则模型会为了满足对比约束而牺牲像素级分类精度。2.3 类别级均衡图像级标签中的天然缺陷图像级标签是与或逻辑只要图像里存在某个类别就标记为1否则为0。这种标注形式决定了单阶段模型无法精确知道每个类别在图像中的尺度占比从而导致两个很严重的问题小物体类别容易丢失大物体类别会主导特征统计。常见的对策是类别重加权。在计算分类损失时不是直接使用二分类交叉熵而是根据当前 batch 中出现的类别频率做反比加权。另一个更精细的做法是结合类别出现的图像数量与预估分割面积两个维度做动态调整——因为图像级标签只能反映前者后者需要靠分割分支当前的输出来估计。def class_balanced_bce(cls_logit, labels_present, freq_in_batch, alpha0.5): cls_logit: [B, C] 图像级分类logits labels_present: [B, C] 图像级多标签 freq_in_batch: [C] 各类别在当前batch中出现的频率 # 频率反比加权出现少的类别权重高 freq_weight 1.0 / torch.sqrt(freq_in_batch 1e-6) freq_weight freq_weight / freq_weight.mean() # alpha控制分类损失对分割损失的相对重要程度 bce F.binary_cross_entropy_with_logits( cls_logit, labels_present.float(), pos_weightfreq_weight ) return alpha * bce这里的freq_weight做了一次归一化目的是让权重的均值保持在1附近这样就不会因为调整权重而整体改变损失尺度。pos_weight在 PyTorch 中是专门给正样本类别加权的语义上对多标签分类比较合适。类别均衡策略选择时有一个细节值得注意如果某个类别在训练集中出现频率极低例如汽车在某个特定数据子集中很少出现频率反比加权会把该类别的权重推到很高的位置导致模型为了少数类别牺牲多数类别的分类精度。稳妥的做法是设置权重上限比如不超过平均权重的3倍。3. 训练策略与超参数两阶段和单阶段的显著差异点3.1 训练流程和阶段划分单阶段模型虽然端到端但直接从头训练往往收敛很慢。主流做法是分阶段抬起先冻结分割分支只训练骨干分类分支让分类分支先稳定下来这是伪标签质量的基础等分类准确率大致收敛后再解冻分割分支让分割损失开始指导骨干特征的细粒度化。这个预热阶段通常需要 5~10 个 epoch取决于数据集大小和骨干网络的预训练状态。解冻之后的训练也不能把分类损失直接撤掉。分类分支还承担着逆传播监督的作用——分割分支有时候会钻空子连续多轮输出类间不互斥的错误分割图分类损失能持续为骨干提供全局语义约束避免分割分支的局部最优。3.2 关键超参数设置及其敏感性分析单阶段方法的超参数敏感性比两阶段高。最容易出问题的几个学习率策略常用的是 poly 学习率衰减指数在 0.9 到 1.0 之间。但如果分割分支用的是带空洞卷积的主干比如 ResNet-50 的 last block 使用 dilation2批量归一化的统计量更新会比普通训练更敏感学习率过大会导致中间特征剧烈抖动伪标签也随之不稳定。建议初始学习率从 0.01 起步批大小16必要时降到 0.007。损失权重分类损失、分割损失、对比损失三者的比例关系直接影响模型行为。参考实验中常用的分配方式是 1:0.7:0.1分类:分割:对比但这不是固定的。如果数据集中类别数多20类以上对比损失的权重要调低否则对比的负样本对数量太多模型很难同时满足分类约束和特征分离约束。伪标签阈值tau在 2.1 节的代码中tau决定了哪些像素可以参与特征原型计算。阈值太高会让原型只覆盖目标中心区域缺乏边缘信息阈值太低会把大量背景像素引入原型类别中心被污染。从实践经验看在 PASCAL VOC 上 0.4~0.6 是合理区间但在遥感或医学影像数据上需要重新根据类别像素分布校准。3.3 弱监督长尾问题与数据增强策略单阶段弱监督方法的长尾问题比全监督方法更严重。原因在于图像级标签无法提供像素尺度的类别计数因此模型对出现频次低的类别天然缺乏像素级的判别经验。更麻烦的是当低频类别和高频类别同时出现在一张图像中时伪标签倾向于把重叠区域分配给高频类别。应对手段有两类可以在训练中同时使用类别裁剪增强训练时按照类别频率分布做加权采样让低频类别有更多出场机会。具体到数据增强对含低频类别的图像做多次随机裁剪让低频类别在裁剪后占据更大画幅比例。伪标签置信度校准对不同类别采用不同的阈值选择高置信度像素。频率低的类别阈值可以适当放宽因为其正样本本身就少只要不引入致命噪声多一些候选像素对特征学习有利。另外在数据增强方面还有一点容易踩坑随机擦除和 CutOut 这类区域丢弃增强在弱监督场景下要慎用。分类分支在图像级标注下已经只有弱监督信号如果目标区域再被随机擦除模型很可能无法识别出该类别。3.4 提升模型性能的进阶技巧能量函数与显著图平滑针对弱监督分割常见的伪标签边缘锯齿化问题有一个有效技巧是引入能量函数约束。能量函数的基本思想是相邻像素如果特征接近它们的类别预测应该也接近。这个约束在分割解码器的输出上做平滑化可以有效减小伪标签中的椒盐噪声。def energy_smooth_loss(seg_prob, feature, neighbor_range1): seg_prob: [B, C, H, W] 分割概率 feature: [B, D, H, W] 骨干特征浅层 B, C, H, W seg_prob.shape # 水平方向的相邻像素对 feat_h feature[:, :, :, :-1] feat_h_next feature[:, :, :, 1:] prob_h seg_prob[:, :, :, :-1] prob_h_next seg_prob[:, :, :, 1:] # 特征距离作为权重距离小权重高约束更紧 feat_dist F.pairwise_distance(feat_h, feat_h_next, p2) feat_weight torch.exp(-feat_dist / feat_dist.mean(dim(1,2,3), keepdimTrue)) # 相邻像素类概率差 prob_diff torch.abs(prob_h - prob_h_next).mean(dim1) loss_h (feat_weight * prob_diff).mean() return loss_h能量平滑损失在训练初期可以开得较大权重0.1左右帮助模型输出空间连贯的分割图训练后期建议逐步减小权重至0.01给模型保留一定的边界锐利度。这个技巧对高分辨率输入尤其有效因为在高分辨率下分割图边缘的不连续性会更加明显。4. 语义分割场景下的实验验证与分析4.1 评估指标的选取与代码实现单阶段弱监督语义分割的评估指标与全监督基本一致最常用的是 mIoUmean Intersection over Union。mIoU 对类别不均衡很敏感恰好能反映出弱监督方法在少数类别上的不足。除 mIoU 外还建议同时观察每类的 IoUclass-wise IoU和 pixel accuracyPA因为 mIoU 相同的情况下不同方法的类别间表现差异可能很大。def compute_iou_per_class(pred, target, num_classes): pred: [H, W] 预测的类别索引 target: [H, W] 真实类别索引0为背景 ious {} for cls in range(num_classes): pred_mask (pred cls) target_mask (target cls) # 处理两者都为空的类别 if pred_mask.sum() 0 and target_mask.sum() 0: ious[cls] 1.0 continue inter (pred_mask target_mask).sum().float() union (pred_mask | target_mask).sum().float() ious[cls] (inter / union).item() if union 0 else 0.0 return ious这个实现里需要特别关注类别全为空时的处理逻辑。部分论文在计算时会跳过预测和真值都不存在的类别这样 mIoU 会偏高但也有一部分实现会把这种情况记为1导致结果虚高。在复现时需要查看官方评估脚本的实现方式否则可能会导致论文对比时的误判。4.2 消融实验设计与关键观察单阶段方法进行消融实验时有几个核心变量需要逐步验证分类分支是否参与分割分支的伪标签生成对比损失是加在骨干深层特征上还是分割输出上能量平滑损失的权重调整从我做过类似实验的经验看第一个变量的影响是最大的。如果分类分支完全不参与伪标签生成即分割分支只看图像级标签做监督性能会退回到跟朴素单阶段网络差不多通常比完整版本下降4~6个 mIoU 点。对比损失加在骨干深层特征上比加在分割输出上更有效因为骨干层的特征更具备全局语义性而分割输出层的特征已经有很强的定位导向再做特征分离约束容易和分割损失产生梯度冲突。需要注意的是消融实验的结论在不同骨干网络和数据规模下会发生迁移。在 PASCAL VOC 上有效的组合在 COCO 或 Cityscapes 上可能不成立主要原因是这些数据集的类别内差异、目标尺度分布和标注噪声水平差异很大。4.3 训练收敛行为分析与瓶颈判定单阶段模型在训练过程中比较容易观察到几个典型现象一是在解冻分割分支后的前几个 epochmIoU 可能出现多次震荡下降再上升的情况二是分类损失已经收敛但分割损失还在持续下降这通常是正常现象代表模型在利用分类分支的信号细化边缘三是如果分割损失在某个阶段开始不降反升而分类损失不动大概率是伪标签中出现了系统性错误的类别混淆。通过可视化分割输出的中间结果可以高效定位瓶颈如果分割图整体轮廓正确但边缘粗糙问题在特征分辨率不足或能量平滑约束过弱如果分割图整体混乱但分类分支稳定问题大概率在伪标签生成策略或者对比损失的锚点选择上。4.4 测试时增强对性能的影响测试时增强Test-Time Augmentation, TTA在弱监督语义分割中也很有效。由于训练过程中伪标签存在噪声模型对目标的尺度、翻转方向不敏感是训练不充分的表现。在测试阶段使用8种变换4种翻转2种尺度做平均通常可以将 mIoU 提升1到2个百分点。如果模型本身已经在弱监督下表现出较稳定的边缘预测TTA 的收益会变小说明模型已经学会了尺度不变的表示。5. 单阶段模型的推理效率与部署细节5.1 推理时如何去掉训练特定模块单阶段模型在训练时引入了伪标签生成、特征原型聚合、图像级分类辅助等模块但在推理阶段这些模块全部可以移除只保留骨干分割解码器。这意味着部署时的计算量其实不大——和一个紧凑的语义分割网络相当。需要在导出模型时注意确保把训练阶段的detach操作、原型聚合逻辑以及分类分支全部从推理图中剪掉否则推理不仅变慢还可能因为分配了不必要的显存导致部署失败。class SingleStageInference(nn.Module): def __init__(self, backbone, seg_decoder): super().__init__() self.backbone backbone self.seg_decoder seg_decoder def forward(self, x): # 只需骨干和分割解码器分类分支和原型聚合已剔除 feats self.backbone(x) seg self.seg_decoder(feats[-1]) return torch.argmax(seg, dim1, keepdimTrue)导出时还需要确认分割解码器的输出是否需要做 softmax一般torch.argmax在 logits 上直接取最大值索引即可不需要过非线性激活省去一层不必要的计算。5.2 动态输入尺寸的兼容单阶段模型在训练时常用固定分辨率比如 512×512但推理时输入的图像尺寸可能是任意的。分割解码器通常包含几次上采样/下采样操作如果使用了全局池化或固定尺寸的AdaptiveAvgPool模型对输入尺寸不敏感。但如果解码器里用了固定的线性层或固定尺寸的插值目标推理时的输入必须严格对齐训练尺寸。经验做法是使用维度无关的归一化和全局平均池化输出1x1在最后的upsample层用size输入尺寸的动态目标。这样无论输入是什么分辨率模型都能正确输出。5.3 浮点量化与边缘部署的适配性弱监督训练出来的模型在量化后精度损失通常比全监督模型更大。原因是弱监督模型的预测结果更依赖特征之间的细微差异量化会把特征分布的尾部信息截断。在做 8-bit 量化时需要注意优先使用 TensorRT 的 INT8 量化配合 KL 散度校准算法对弱监督模型更友好校准数据集要覆盖所有类别尤其要保证低频率类别在校准数据中出现否则量化后低频类别的误区率会显著提高输出端不要使用 argmax 再量化应该在量化前后保持 softmax 输出的概率分布形态便于后处理时做置信度过滤如果部署目标是端侧设备还可以考虑将分割输出的分辨率降低到输入的四分之一再用最近邻插值恢复原图这一招在很多实时分割场景中是标准的后处理方式视觉差异不大但对算力要求大幅降低。6. 实验细节的深层打磨基于 mIoU 的类别间敏感性分析对弱监督语义分割模型来说mIoU 只能给出总体水平真正需要观察的是类别间的差异。有些类别比如 PASCAL VOC 中的椅子天然具有镂空结构和复杂边缘模型在这类目标上的表现往往远低于平均水平。我在分析结果时习惯做两类细致检查第一分类别统计 IoU 与分割面积的散点图。这个图能直观暴露类别越小、IoU 越低的负相关规律。如果出现某些小类别 IoU 异常低而同类其他模型的表现远好于此这就说明模型对这类别的特征学习存在盲点需要针对性增加该类别的伪标签生成力度。第二针对特定类别做显著性错误可视化。将预测错误区域的像素叠加到原图上观察错误像素是聚在目标中心还是分散在边缘。聚在中心说明该类别内部的纹理变化大模型没有学到类别一致性特征需要增强对比损失的锚点选择分散在边缘说明定位能力不足需要增强能量平滑或提升特征分辨率。这些分析对判断模型瓶颈非常有帮助比单纯调参要直观得多。复现单阶段弱监督分割的实验时几个细节会显著影响最终结果伪标签阈值不要全局固定最好按训练轮数做动态调整让后阶段的阈值略高于前阶段原型的特征提取要从骨干网络的第二层或第三层特征取而不是最后一层因为最后一层的语义信息强但空间信息弱分类分支的优化器参数可以与分割分支分离分别控制学习率通常会取得更好的效果。这些操作本身并不改变网络结构但确实能拉开 mIoU 的差距。单阶段弱监督方案在训练管线简洁性和最终精度之间权衡得很好特别适合那些既不能满足像素级标注成本、又希望得到较清晰分割边界的应用场景。本文还有配套的精品资源点击获取