资讯动态

正负样本不均衡困境:AUC评估与Focal Loss实战指南

发布时间:2026/10/19 8:27:23 来源:尧图企业网站定制
正负样本不均衡是分类模型迭代中最容易被低估的问题我最早是在一个风控类的模拟项目里被现实狠狠教育了一顿。当时模型准确率很高但用AUC一测才发现排序质量很差后来换了Focal Loss做训练损失才把少数类召回拉上来。所以今天就把我踩过的坑和最终固定下来的做法拆开聊聊也希望你能少绕几段弯路。1. 正负样本不均衡它到底在制造什么麻烦1.1 先看一个跑偏的模型长什么样做分类任务的人大概率都见过这种场面训练数据里负样本有90万条正样本只有900条比例1000比1。模型跑完之后测试集accuracy有98%效果看起来好得吓人。但把它放到真实业务里它很快就原形毕露——遇到真正需要抓出来的那1%对象几乎一个都抓不到。这种“准确率虚高、实际一无是处”的情况我最早在一个风控类的模拟项目里踩过。当时的数据分布是绝大多数样本是正常用户欺诈用户只占极少数。模型训练时把所有样本都预测成正常类就能把loss压得很低因为错误代价主要集中在少数类错分上多数类错分几乎不产生惩罚。于是模型学会了“躺平”不区分样本直接输出多数类。这类问题就是典型的不均衡问题。所以当我看到有人用accuracy去衡量这种模型时第一反应是先看看正负样本比例再说。核心问题是模型不是学不到少数类特征而是少数类的“存在感”太弱。在梯度更新时多数类样本提供的梯度会反复占据主导正类样本被淹没在几十万个反例里即使模型偶尔能学到一些正类特征也会被后续的梯度冲掉。这不只是数据工程问题更是模型训练机制的问题。1.2 不均衡从哪来为什么不能直接忽略正负样本不均衡在真实世界里是常态不均衡往往来自业务本身的天然稀疏性。比如点击率预估用户点击的商品占比通常不到百分之一比如反欺诈真实欺诈交易可能只有万分之几比如医疗筛查患病样本和健康人群的比例可能达到1比10000。所以不均衡不是数据采集出了问题而是业务本身就对“某个事件”存在天然的偏向。如果把这种数据直接喂给常规的分类模型会出现四个连锁问题模型预测偏向多数类少数类召回率极低。这不是模型笨而是它在优化全局损失少数类贡献太小。accuracy不可信甚至具有误导性。98%的准确率可能只是把所有样本都判成了负类。概率输出校准差。模型对少数类样本给出的概率普遍偏低导致排序和阈值切分都受影响。少数类的梯度信号太弱训练后期容易过拟合到噪声上表现为验证集评估波动大。很多人最开始觉得“把负样本随机砍一砍”就能解决但这种方法需要谨慎使用。欠采样会让模型失去大量有效信息尤其是当多数类本身也带有复杂模式时方差会变大。过采样则要小心过拟合——正样本被反复复制模型容易把少数类样本背下来。真正靠谱的做法是数据层面、模型层面、评估层面一起动手而不是只靠某一个技巧。1.3 模型内部发生了什么梯度被多数类带跑要理解为什么正负样本不均衡会让模型“学歪”可以回到损失函数。以二分类交叉熵为例每个样本的损失是L -y * log(p) - (1 - y) * log(1 - p)其中p是模型预测为正类的概率。如果负样本有1000条、正样本只有10条那么累计损失里负样本贡献了绝大部分梯度。模型发现只要把p压低就能让负样本的log(1-p)变小整体loss下降得最快。至于那10条正样本它们对总loss的贡献占比太小“不值得”模型去调整。于是模型在训练中会逐渐学习到一个策略尽量输出很小的正类概率。这种策略在全局loss上确实是“最优”的但对我们关心的少数类预测毫无帮助。这也是为什么单纯从loss上盯着看很难发现模型出了问题——因为loss一直在降看起来一切正常直到评估阶段才翻车。理解了这层机制就容易明白为什么需要在评估方式和损失函数上做文章了。2. 用AUC评估模型为什么它比准确率靠谱2.1 ROC曲线和AUC到底是啥AUC的全称是Area Under the ROC Curve也就是ROC曲线下方的面积。ROC曲线由两个基础指标构成真正例率TPR也叫召回率假正例率FPR。TPR表示真实正样本里有多少被找出来了FPR表示真实负样本里有多少被误判成了正样本。把不同阈值下这两个率画到二维平面横轴是FPR纵轴是TPR连成的曲线就是ROC。随机猜测的模型对应的是从左下角到右上角的对角线AUC大概是0.5。如果一个模型AUC是0.9意思是随机从正样本里抽一个再从负样本里抽一个模型给正样本打高分的概率是90%。AUC衡量的是模型把正样本排在负样本前面的能力而不是预测绝对概率值的准确性。这是理解AUC最关键的切入点。AUC还有一个特性它只关心排序不关心概率的具体数值。模型把所有预测概率乘以10倍、加上一个常数只要排序顺序不变AUC就不变。所以AUC对“概率校准”这件事是无感的。它非常适合用来做模型比较因为不同训练阶段、不同模型的输出范围可能不一致用accuracy无法公平对比但AUC可以。2.2 AUC为什么对不均衡相对鲁棒在正负样本比例悬殊的情况下accuracy会因为“全都预测为多数类”而虚高ROC曲线却不会。因为TPR只关注正样本中被覆盖的比例FPR关注负样本中被误报的比例这两个指标都是按类别独立计算的不直接受正负样本绝对数量差异的影响。换句话说ROC空间里正负样本比例不会改变曲线的形状逻辑。举个例子假设测试集有100个正样本、1000个负样本。一个模型把100个正样本全部排在最前面同时把前900个负样本排在最后面那么它的TPR在某个阈值下是100%FPR是0%AUC接近1。如果另一个模型只会全输出负类它的TPR在阈值很低时是0%在阈值很高时也是0%AUC只有0.5。所以AUC能从“排序质量”这个角度把两类模型区分开而accuracy对这两种模型的判断很可能是第一个模型100%准确率第二个模型90.9%准确率差距会被缩小。实际操作中AUC还有一个好处评估时不需要提前设定阈值。对于不均衡业务阈值选多少本身就是一个很麻烦的问题。AUC把阈值选择的环节后置了让我们可以先评估“模型有没有把正样本排到前面”的能力。这也是它在不均衡场景里能成为评估首选的核心原因。2.3 只看AUC也有坑阈值、业务代价虽然AUC对不均衡相对友好但只盯AUC也会踩坑。AUC是排序指标它不关心模型输出的概率是否绝对值可靠。同一个AUC 0.9可能对应的是一组概率全在0.2到0.3之间的输出也可能对应一组0.01到0.99的输出。前者如果直接用一个默认的0.5阈值去切会发现根本切不出正样本召回率极低后者则一切就灵。所以在不均衡场景里我一般会同时看三样东西AUC、PR曲线、业务代价矩阵。PR曲线以精确率Precision为纵轴、召回率Recall为横轴它更关注少数类预测出来的质量。AUC在负样本特别多时会显得“数值很高”因为FPR分母很大即使误报了一堆正类FPR也可能不高PR曲线则没有这个问题。因此当负样本远多于正样本时PR曲线往往更能反映少数类的真实表现。另外一个常被忽略的坑是AUC对样本分布敏感。测试集如果和训练集分布不一致比如正样本采样方式不同AUC会失真。所以用AUC做模型评估最好在固定分布、固定采样策略下进行横向比较不要动不动拿不同时间窗口的数据去比。从这些细节能看出来AUC是评估工具不是万能指标模型选择要综合多个维度来看。3. Focal Loss让模型把注意力放到“难啃的硬骨头”上3.1 先从交叉熵说起为什么它容易躺平前面已经提到二分类交叉熵的损失对每个样本是L -y*log(p) - (1-y)*log(1-p)。在不均衡场景下多数类样本数量多它们每个样本即使已经被分类得很“自信”损失很小但数量堆起来之后对梯度的贡献依然很大。多数类的“简单样本”成了梯度大户少数类的“困难样本”反而被淹没。这会造成一个结果模型一直在优化那些已经学得很好的负类样本而对正类样本的优化动力不足。Focal Loss的提出者最早在目标检测任务里遇到类似问题一张图片中的背景框数量巨大而目标框少训练时背景框产生的梯度主导了模型更新。于是他们设计了一个新的损失函数核心思想是让模型少关注那些“已经被分类得很好的样本”多关注那些“还不确定、分错的样本”。这个思想放到正负样本不均衡的一般分类问题里同样适用。Focal Loss本质上是对交叉熵的一种重构。它给每个样本的损失加了一个调制系数让简单样本的权重下降困难样本的权重相对上升。这样即使多数类样本数量很多如果它们大多是简单样本整体贡献也会被压下去。少数类里那些难以区分的样本会获得更多的发言权。3.2 Focal Loss的公式拆解α和γ分别管什么Focal Loss最常见的定义是FL -α * (1 - p)^γ * log(p)对正样本y1对负样本则通常写成FL -(1-α) * p^γ * log(1-p)为了简洁也有人写成统一形式FL -α * (1 - p_t)^γ * log(p_t)其中p_t是模型对正确类别的预测概率。如果样本真实类别是1p_t就是p如果真实类别是0p_t就是1-p。这样一个公式就能覆盖正负样本两种情况。这里有两个核心超参数。第一个是α负责处理类别不均衡的数量比例它相当于给少数类样本一个额外的权重。比如正负样本比例1比1000α可以设置成0.75甚至0.9意思是模型更重视正类。第二个是γ负责处理难易样本的权重分配它的作用更精妙。当γ0时Focal Loss退化成带类别权重的交叉熵当γ增大时(1-p_t)^γ会让那些p_t接近1的简单样本权重迅速衰减而p_t接近0的困难样本权重基本不衰减。我举一个直观的例子。假设γ2一个正样本的真实标签是1模型给它预测概率p0.9那调制系数是(1-0.9)^20.01这个样本对loss的贡献被压到原来的1%。而另一个正样本模型预测概率p0.6调制系数是(1-0.6)^20.16权重相对保留较多。模型自然会把优化重心放到p0.6这类难分样本上。这个机制对正负样本不均衡场景很有价值因为很多负样本其实非常容易分类它们的loss被压缩后就不会反复霸占梯度了。3.3 PyTorch实现与训练接入这里给出一个比较实用的PyTorch实现支持二分类和多分类方便直接改造成自己的损失函数模块。为了简洁我假设输入是logits并且类别标签从0开始。import torch import torch.nn as nn import torch.nn.functional as F class FocalLoss(nn.Module): def __init__(self, alphaNone, gamma2.0, reductionmean): super().__init__() self.gamma gamma self.reduction reduction # alpha可以传入一个标量也可以传入按类别权重数组 self.alpha alpha def forward(self, logits, targets): ce_loss F.cross_entropy(logits, targets, reductionnone) # 得到每个样本被预测为正确类别的概率 p_t pt torch.exp(-ce_loss) # 等价于 softmax(logits) 在目标类上的概率 # 调制系数 focal_weight (1 - pt) ** self.gamma if self.alpha is not None: # alpha如果是一个数组需要按目标类别索引 if isinstance(self.alpha, (list, tuple, torch.Tensor)): alpha_t torch.tensor(self.alpha, devicelogits.device)[targets] else: alpha_t self.alpha focal_weight alpha_t * focal_weight loss focal_weight * ce_loss if self.reduction mean: return loss.mean() elif self.reduction sum: return loss.sum() else: return loss这段代码里pt的计算方式很巧妙交叉熵在不reduce时得到的是每个样本的CE loss而exp(-CE)正好就是该样本被模型分配到正确类别的概率p_t。因此不需要单独拿softmax再gather既简洁又不容易出错。很多人在尝试Focal Loss时踩过“pt算错”的坑用这种写法可以规避。在训练时把模型输出和标签喂给FocalLoss即可。建议先把原来的CrossEntropyLoss换成FocalLossα设成训练集负样本占比的倒数再归一化γ可以先设2。训练过程记得每个epoch在验证集上算AUC而不是只看loss。这里给出一个训练循环的骨架model YourModel() criterion FocalLoss(alpha0.75, gamma2.0) optimizer torch.optim.Adam(model.parameters(), lr1e-4) for epoch in range(args.epochs): model.train() for x, y in train_loader: optimizer.zero_grad() logits model(x) loss criterion(logits, y) loss.backward() optimizer.step() model.eval() valid_scores [] valid_labels [] with torch.no_grad(): for x, y in valid_loader: logits model(x) prob torch.softmax(logits, dim1)[:, 1] valid_scores.append(prob.cpu()) valid_labels.append(y.cpu()) # 拼接后调用 sklearn.metrics.roc_auc_score 计算AUC这里面有个细节α可以按训练集里负样本占比设置比如负样本占比0.99时α取0.75到0.9左右不一定非要严格等于1减正样本占比。实际操作中我习惯先固定γ2把α从0.5往0.9方向扫再看验证AUC和PR曲线变化而不是一次调两个参数。3.4 多分类和业务场景适配Focal Loss不仅能用在二分类。多分类场景下只需在代码里把alpha传成每个类别的权重数组就可以。例如在某个多分类故障识别项目里类别0有10万条样本类别1有1万条类别2只有1000条那么alpha可以设置成0.1、0.3、0.6这类递增的权重。要注意alpha要跟着数据分布走类别越少权重越大但不能简单让少数类权重高到“扭曲”模型否则会放大噪声样本的影响。业务场景适配时要考虑“难易”的定义。在Focal Loss里“难”的定义是模型预测概率远离正确类别。这个定义在绝大多数分类任务里是合理的。但有些场景里我们想让模型特别关注特定类别比如医疗筛查里不想漏掉患病样本此时除了调高α还可以辅以阈值移动、定制评估指标把模型输出概率当作排序值来用而不是直接当作最终决策。Focal Loss能解决“注意力分配”问题但决定“哪些样本不能放过”这种业务逻辑还要靠数据标注和评估规则去约束。4. 实战调参与避坑从数据到训练的一整套做法4.1 在动Focal Loss之前先试着解决数据层面问题Focal Loss不是银弹它再厉害也架不住数据本身太烂。我在实际项目里有一个固定的处理顺序第一步先看能不能通过数据清洗、特征工程和采样策略把不均衡问题缓解一部分第二步再引入Focal Loss或样本权重。如果数据里正样本只有几十条任何损失函数都很难救回来。数据层面常见的手段有几种。欠采样会在多数类里随机抽样让正负样本比例接近优点是训练速度快、内存占用低缺点是可能丢掉多数类的关键信息。过采样则复制少数类样本比如SMOTE会在少数类样本之间线性插值生成新样本它能平衡比例但要小心生成样本和真实样本混合后带来的过拟合风险。更稳健一点的做法是先用集成学习方式把多数类切成多个子集每个子集和全部少数类训练一个模型再用bagging把多个模型融合。这种方式对非常不平衡的数据往往比单模型更稳。我见过一个模拟项目正负样本比例1比5000一开始直接上Focal LossAUC只有0.72。后来先用聚类对多数类进行下采样保留有代表性的负样本再配合Focal LossAUC提升到0.85。这说明数据层面的处理是地基Focal Loss是上层建筑两者配合而不是互相替代才有稳定的提升。4.2 Focal Loss 参数怎么调调参是多数人最容易纠结的部分。我的建议是不要同时调两个超参会分不清是α还是γ贡献了提升。先把γ固定在2接下来只扫α从0.5到0.9步长0.1每个值在固定验证集上记录AUC、精确率、召回率。等α确认了再固定α扫γγ范围可以从1.0到3.0。如果γ太大比如超过3会发现训练loss非常难降甚至梯度震荡这是因为困难样本的权重被抬得太高模型一开始就被少数极端样本带着走。另外Focal Loss对学习率更敏感。交叉熵训练时学习率1e-3可能没问题换成Focal Loss后同样学习率可能让训练不稳定。因为Focal Loss整体数值量级比交叉熵小梯度更新幅度也会变化。我通常会先把学习率降到原来的二分之一或三分之一然后看loss曲线的下降速度。如果前几个batch loss不降反升大概率是学习率太大或者γ太大先降学习率再降γ。还有一个很容易忽略的点类别权重α不要和Focal Loss叠加得太猛。有些人先给每个样本乘一个class_weight又用Focal Loss还把γ设得很大最后训练集loss居高不下。简单样本被压太狠模型反而学不到稳定的类别边界。正确做法是要么用class weight要么用Focal Loss里的α不要同时上两套强权重。真需要叠也要把数值调温和一点。4.3 常见问题和排查技巧实录这里整理了一份我在实际调Focal Loss时遇到的典型问题速查表可以对着排查。现象可能原因操作建议训练loss不降γ太大、学习率太高、模型本身欠拟合降低γ到1或2降低学习率确认特征和数据是否有效验证AUC低于0.5标签错乱、训练验证数据泄漏、正负样本定义混乱先检查标签对齐重新切分数据确保验证集分布与线上一致预测概率普遍接近0模型输出不是概率校准值Focal Loss让数值分布偏移不要直接拿概率当置信度用温度缩放或Platt缩放校准后再定阈值正样本比例极低AUC仍上不去数据量太少噪声大Focal Loss无法拯救信息量缺失回到数据层面尝试迁移学习、半监督或补充标注数据加了alpha后训练震荡明显alpha过大模型被少数极端样本带偏降低alpha配合早停和梯度裁剪我碰到过一个比较隐蔽的问题训练时用了Focal Loss验证AUC很高但线上效果很差。排查后发现验证集里的正样本被重复采样过导致验证集分布已经失真。所以验证AUC再高也不能代表真实场景。解决办法是单独保留一份“纯净”的验证集正负样本比例和线上保持一致并且不做任何过采样。只有这样的验证AUC才有参考价值。还有一个经验当类别不均衡很严重时除了看AUC要额外关注PR曲线下的面积。有时候AUC提升0.02但PR-AUC提升0.1说明模型对少数类实际更有用了。有时候AUC提升了PR-AUC反而下降说明模型只是把容易的负样本排得更靠后但对少数类的精准区分没有帮助。所以我在项目复盘里会同时记录AUC和PR-AUC两个数字避免被单一指标误导。4.4 从离线到在线阈值选择与效果验收评估和训练都搞定之后还差最后一步怎么把模型输出变成业务决策。这时候需要选定阈值或者设计一套线上打分规则。对不均衡问题来说阈值不能默认用0.5因为模型输出的概率分布通常会整体偏移。比如用Focal Loss训练出的模型少数类概率可能普遍低于0.5但排序依然很好。这时可以去验证集上遍历阈值找到精确率和召回率平衡点或者根据业务代价函数计算期望收益最大化对应的阈值。在线效果验收也不能只看指标是不是变好还要看坏样本覆盖收益、误伤成本这些业务指标。某个风控项目里离线AUC提升了0.03但线上坏账率没有下降最后发现是阈值选错了旧模型阈值0.5对应的召回率很低新模型适合阈值0.3但线上还沿用0.5把提升卡在门外。调整阈值之后效果才真正体现出来。这种“最后一公里”的问题很容易被忽视但它往往是项目成败的关键。我个人在做这类项目时习惯把AUC、PR-AUC、阈值、成本收益四个维度一起固化到模型评测脚本里。每次迭代模型先跑这四个指标的对比再决定是否上线。这个习惯帮我避免了很多次“指标好看但业务不买账”的尴尬。5. 进阶补充Focal Loss之外不均衡问题还能怎么打5.1 常见损失函数的选型对比Focal Loss确实好用但也不是唯一选择。在具体项目里我还用过带权重交叉熵、Dice Loss以及梯度调和损失。它们的适用场景不太一样先列一个表方便大家选型。损失函数核心机制适用场景普通交叉熵每个样本权重一致数据相对均衡或者类别不均衡但特征区分度很高带类别权重的交叉熵按类别比例放大少数类loss类别不均衡明确但难易样本差异不大Focal Loss调制难易样本权重兼顾类别权重类别极不均衡且存在大量容易分类的多数类样本Dice Loss / Focal Dice按区域重叠度计算损失分割任务、小目标检测、像素级类别不均衡梯度调和损失按梯度密度给样本加权样本噪声较大希望通过梯度分布自动调节权重选型时我看两步第一是少数类是不是“容易被学”第二是多数类是不是“噪音很大”。如果多数类本身很干净用带权重的交叉熵就够如果多数类里有大量和少数类边界模糊的样本Focal Loss会更合适如果任务本身就是像素级预测比如图像分割Dice类损失往往更贴近评估指标。5.2 组合策略的思路Focal Loss和其他策略组合时顺序很重要。我比较推荐“先修数据再调损失最后定阈值”的路线。数据层面可以先做聚类欠采样把多数类里和少数类高度相似的簇保留下来减少无意义样本模型层面再引入Focal Loss让难分样本获得更多权重最后在验证集上做阈值搜索找到业务收益最高的切分点。组合使用时要格外小心过拟合。比如先对少数类做过采样又用大γ的Focal Loss模型会拼命去拟合被重复生成的少数类样本训练集指标可以做到很好看验证集AUC反而会掉。我通常会在组合前先观察正样本之间的相似度如果相似度很高就少用SMOTE多依赖Focal Loss本身如果正样本来源多样则可以适当用合成样本增强特征空间覆盖。还有一个思路是集成。把训练数据切成多个子集每个子集使用不同的多数类采样比例训练多个模型最后对预测概率做平均或加权投票。这个策略和Focal Loss可以兼容每个基模型都使用Focal Loss最终集成后的概率排序往往比单模型稳定。缺点是训练成本成倍增加但考虑到不均衡项目的试错成本多花点算力通常是值得的。5.3 一套可复用的实验评估模板最后分享一套我固定用的实验评估模板你可以直接搬到自己的项目里。每次实验都要记录数据切分方式、正负样本比例、特征版本、模型结构、损失函数类型、α和γ的取值、学习率、验证AUC、PR-AUC、最优阈值、精确率、召回率、以及业务收益估算。没有这些记录后面调参和复盘都会变成玄学。在代码层面train和valid切分时保持stratified策略确保验证集里正样本比例和真实场景接近训练过程中至少保存三类信息每个epoch的训练loss、验证AUC、验证PR-AUC。不要只保存最后一个epoch的结果否则没法回溯最佳模型。模型选择时我一般不看最后一轮权重而是选择验证AUC最高的那一个epoch再做一次早停。这个模板看起来简单但真能坚持执行的团队不多。很多时候大家只盯着一个acc或AUC看忽略了阈值和代价矩阵导致模型迭代评判标准前后不一致。把模板固定下来之后实验对比的效率会提升一大截线上出问题也能快速定位是数据、模型还是阈值的问题。我在实际项目中最大的感受是AUC和Focal Loss不是万能药但它们确实解决了我最头疼的两件事用对尺子看清模型以及把训练火力对准真正难分的少数类。如果只记住一个原则那就是任何时候都先想清楚要评估什么再去谈用什么损失函数。顺序对了结果通常不会太差。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑