标准CrossEntropyLoss在不平衡数据上效果差,因其对所有样本等权处理,导致少数类梯度小、更新弱,模型偏向多数类,虽验证准确率高但少数类召回率可能低于0.2。

为什么标准 CrossEntropyLoss 在不平衡数据上效果差?
因为 CrossEntropyLoss 对每个样本一视同仁,而少数类样本梯度小、更新弱,模型很快偏向多数类。你看到验证集准确率高,但 recall(尤其对少数类)可能低于 0.2 —— 这不是模型“学不会”,是损失函数没给少数类足够权重。
PyTorch 中实现 Focal Loss 的关键三步
Focal Loss 的核心是降低易分类样本的贡献,聚焦难样本。它不是直接替换 CrossEntropyLoss,而是要在其基础上加 (1 − p_t)^γ 调制因子:
- 先用
F.cross_entropy(..., reduction='none')得到每个样本的原始 loss(不能设'mean',否则无法按样本加权) - 手动计算预测概率
p_t:对正类用torch.softmax(logits, dim=1)[:, 1](二分类),多分类则需按真实标签索引对应类别概率 - 乘上
(1 - p_t) ** gamma,再做mean();gamma建议从2.0起调,太大易导致梯度不稳定
别直接抄网上带 nn.Module 封装的“FocalLoss”类——很多没处理多分类的 p_t 索引逻辑,训练时 loss 突然 NaN 就是这里崩的。
处理多分类 + 不平衡时的标签对齐陷阱
如果你有 5 个类别,但第 0 类只占 0.5%,其他类较均衡,FocalLoss 必须对每个样本按其真实 target 取出对应类别的 softmax 概率,而不是只算最大 logit 的置信度。常见错误写法:probs.max(dim=1)[0] —— 这会把错分样本的“最高错类概率”当 p_t,完全偏离设计初衷。
正确做法是:
# logits: [N, C], targets: [N] probs = torch.softmax(logits, dim=1) p_t = probs[torch.arange(len(targets)), targets] focal_weight = (1 - p_t) ** gamma ce_loss = F.cross_entropy(logits, targets, reduction='none') loss = (focal_weight * ce_loss).mean()
要不要加 alpha 平衡系数?看你的数据倾斜程度
alpha 是类别级缩放因子,比如 alpha = [0.1, 0.9] 给少数类更大权重。但它和 gamma 作用不同:gamma 解决难易样本分布,alpha 解决类别频次分布。两者叠加容易过校正——尤其当少数类本身也包含大量易分样本时。
- 若少数类 recall 持续 gamma=2.0;仍不足,再加
alpha,且用 inverse-class-frequency 计算(如各类样本数倒数归一化) - 避免硬编码
alpha=[0.01, 0.99]:这会让模型拒绝学习多数类中真正的难样本 - 训练中监控 per-class
precision和recall,而不是只盯 overall loss 下降
真正难的不是写对公式,是在 p_t 计算、reduction 控制、alpha 引入时机这三个地方保持清醒——稍一松懈,loss 曲线看着正常,模型其实已经放弃学少数类了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











