PyTorch多标签图像分类类别不平衡问题排查与解决
现有方案的实现错误排查
- 学习率策略完全错误:初始学习率设为
1e-4且每轮衰减为原有的1/10,第1轮结束学习率就降到1e-5,第4轮已经到1e-8量级,参数几乎无法更新,这就是你观察到「第1个epoch后损失停滞」的核心原因,和类别不平衡处理无关。常规训练中StepLR衰减策略通常是每5~10个epoch做一次0.1倍衰减,或者直接用余弦退火调度,不要每轮都砍到1/10。 - Focal Loss存在硬逻辑错误:你的实现直接把模型最后一层输出的未归一化logits当成了sigmoid激活后的概率值计算
p_t,logits取值范围是(-∞, +∞),直接计算会产生大量大于1、小于0的无效值,损失计算完全失真。另外你错误继承了单标签多分类场景用的CrossEntropyLoss,多标签场景的Focal Loss应该基于BCEWithLogitsLoss修改,不需要继承CrossEntropyLoss。 - 类别权重设置幅度过大:你按负/正样本比值算的权重给稀有类A给到了10倍以上权重,相当于训练时单个A类正样本的损失权重是负样本的10倍,模型会倾向于尽可能把样本判为正类来压低损失,自然会出现大量假阳性、精确率暴跌的问题。
多标签类别不平衡的可落地方案
- 先修正基础训练配置:把学习率调度调整为每8个epoch做一次0.1倍衰减,或者直接用
CosineAnnealingLR,训练初期先不加任何类别权重、不用Focal Loss,训3~5个epoch看A、C类的真阳性数量是否正常上涨,确认模型能正常收敛再叠加不平衡处理策略,不要一开始就堆所有技巧。 - 软加权替代硬权重:不要直接用负/正样本比值作为
pos_weight,初始值取比值的平方根即可,比如A类原权重10.79开平方后约3.28,D类1.13开平方后约1.06,既给稀有类更高的梯度权重,又不会因为权重差过大导致模型无脑输出正类。训练过程中逐类调权:如果某类验证集召回率低于0.6,就把对应权重加0.20.5;如果精确率低于0.5,就把对应权重减0.20.5,不要硬套统计公式。 - 修正Focal Loss实现:不要用存在bug的自定义实现,基于
nn.BCEWithLogitsLoss修改,先对logits做sigmoid得到概率p再计算p_t,gamma参数固定设为2即可,alpha参数不需要传入你之前算的大权重,统一设为0.25就行——Focal Loss本身通过降低易分样本的权重已经能缓解不平衡,不需要叠加过高的类权重。 - 多标签场景适配采样:不需要对单标签稀有样本做复制过采样(容易过拟合),改用类感知批次采样即可:构造每个训练batch时,保证稀有类(A、C)每个类至少有10~20个正样本出现在当前批次中,只需要调整采样逻辑优先抽取包含稀有类的样本,不需要复制数据,既能保证稀有类的梯度贡献足够,也不会让模型对单标签样本过拟合。
- 单类阈值校准:模型训练完成后不要固定用0.5作为所有类的分类阈值,在验证集上为每个类别单独寻优:从0.1到0.9遍历阈值,选让该类F1分数最高的值作为最终判定阈值。通常稀有类最优阈值会低于0.5,常见类阈值接近0.5,这一步零训练成本,往往能直接把召回、精确率拉到合理区间。
训练排查顺序建议
- 先修改学习率调度,不加任何平衡策略训3个epoch,确认模型能正常收敛、A/C类TP数稳步上涨
- 叠加开平方计算的软pos_weight,训5个epoch,观察两类指标的平衡情况
- 如果加权重后还是存在大量易分样本主导梯度的问题,再换修正后的Focal Loss
- 训练收尾阶段做单类阈值校准,不要同时叠加多种不平衡处理策略,避免梯度方向混乱导致模型不收敛
内容的提问来源于stack exchange,提问作者Wei Feng
相关产品推荐
相关产品推荐

