☰
不平衡数据分类实战:从准确率陷阱到SMOTE与阈值调优
2026/10/22 13:57:13 网站建设 项目流程

我很久以前接过一个信贷风控项目,模型在测试集上的准确率做到了95%,汇报的时候看着挺漂亮,结果业务方看完直接摇头——真正违约的客户,模型几乎一个都没识别出来,被当成正常客户放过去了。那时候我才意识到,自己栽在了一个极度不平衡的数据集上,还用准确率当唯一指标,方向从一开始就偏了。

数据不平衡大概是机器学习实战里最容易被低估的问题。初学者喜欢拿准确率说事,模型跑完一测,99%的准确率,感觉自己已经掌握了模型训练的精髓。但一旦正负样本比例悬殊,比如欺诈交易、罕见病诊断、设备故障预测,这套逻辑立刻失效。你训练出的模型表面很准,本质上相当于一个只会说“多数类”的复读机,业务上一个都排不上用场。

这篇文章想聊的,就是我在处理这类问题时沉淀下来的整套思路:从问题本质、数据层处理、算法层修正,到评估指标、阈值调整,再到一个完整的实战复盘和踩坑记录。适合正在做分类任务的读者参考,尤其适合那些已经发现“准确率很高但业务不认可”的同行,希望能帮你们少走几步弯路。

1. 先从“为什么90%准确率还会翻车”说起——不平衡的本质与代价

1.1 什么程度算“不平衡”,以及它如何被低估

很多人在网上看到“不平衡”三个字,第一反应是:训练集里正负样本八比二,这算不算问题?严格来说,像8:2、7:3这种比例,虽然有些偏斜,多数模型依然能训练出可用的决策边界,不算“严重不平衡”。真正让人头疼的,往往是1:10、1:50甚至1:1000以上这种数量级悬殊的场景。欺诈检测里坏样本占比可能只有0.1%,医疗筛查里某种罕见病的阳性率可能低于万分之几。这种情况下,少数类样本提供的信息量被稀释得极其稀疏,模型很难从寥寥几千甚至几百条正样本里学到稳定的规律。

我第一次做这类任务时犯过一个很傻的错误:看到正负样本比例1:99,刻意把负样本随机丢掉一部分,硬凑成1:1再做训练。结果模型指标提升有限,却在线上暴跌。后来才明白,欠采样不是简单地“丢数据”,丢弃的多数类里同样包含大量有价值的边界信息。数据比例的失衡只是表象,它背后隐藏的是决策边界被多数类“带偏”的问题,这才是真正的核心。

1.2 模型为什么天然偏向多数类:损失函数与决策边界

要理解为什么模型会偏科,需要看模型训练的数学机制。以逻辑回归为例,它的目标是极小化交叉熵损失,每个样本的损失都是同等贡献的。少数类样本少,损失项在总损失中的占比就低;多数类样本多,梯度更新方向自然被它们主导。模型只要把全部样本都预测成多数类,就能把整体损失压得很低——哪怕少数类全都预测错,因为它们在总样本量里占比太小,根本掀不起什么波澜。

决策树、随机森林这类模型也有类似倾向。分裂节点时,纯度增益的计算同样受样本量影响,多数类会更容易占据叶节点。深层一点说,这种偏差不是某一种算法的bug,而是“经验风险最小化”框架下的结构性偏差。只要数据分布不均衡,不调整目标函数,任何标准分类器都会自觉地向多数类倾斜。这也就是为什么单纯换更强的模型,往往很难解决不平衡问题,问题不出在模型容量上,出在优化目标和数据匹配上。

1.3 准确率这个指标是怎么骗人的

说个直观的数学例子。假设10000条样本里只有100条是正样本,坏样本率1%,一个“全部预测为负样本”的傻瓜模型,准确率是99%。如果你只看准确率,会觉得模型天下无敌。但换到业务场景里,这100条坏样本一条都没被抓住,业务损失照旧甚至更大,模型形同虚设。

所以后文我会反复强调一件事:处理不平衡问题,第一原则就是换掉准确率这个指标。更好的替代是精确率Precision、召回率Recall、F1,以及PR曲线、PR-AUC、G-Mean等更关注少数类的指标。指标选错了,后面做的所有调优都可能是在错误的方向上自我安慰。

2. 数据层面三板斧:欠采样、过采样与SMOTE家族的取舍

2.1 随机欠采样与启发式欠采样,各有各的适用面

随机欠采样是所有方法里最简单粗暴的:从多数类里随机抽一部分样本,和少数类凑成均衡的数据集。优点是训练速度快,逻辑直观;缺点是随机丢弃大量多数类样本,很可能丢掉决策边界附近的重要信息,导致模型欠拟合。在实际项目中,这个方法单独用效果通常一般,但可以作为一种快速baseline,用于验证后续复杂方法是否真的有提升。

比随机欠采样更用心的是启发式欠采样。比如Tomek Links方法,找出那些互为最近邻但属于不同类别的样本对,把它们删掉,能有效清理类别重叠区域的噪声;再比如NearMiss方法,只保留那些离少数类最近的多数类样本,相当于主动保留“最难分类”的近边界信息。实操中,如果多数类样本量大且噪点多,我会先用Tomek Links清洗一遍,再配合其他策略,效果比单纯随机抽好不少。

2.2 随机过采样与SMOTE:从复制到插值的进化

随机过采样的实现也很简单:把少数类样本复制若干份,直到比例不再悬殊。它的主要问题是容易过拟合,因为复制出来的样本只是原样本的重复,模型会把相同样本反复学习,学到的决策边界会非常窄,泛化能力差。

SMOTE的出现就是为了解决这个痛点。它的核心思想不是复制,而是“合成”:对每个少数类样本,在它的k个最近邻(通常是5个)里随机选一个同类邻居,然后在两者连线上随机取一点,生成一个新样本。数学表达近似这样:新样本 = 原样本 + λ × (邻居样本 − 原样本),λ是0到1之间的随机数。这种方式能让模型看到更丰富的少数类分布空间,一定程度上缓解过拟合,是目前使用最广泛的数据层方法。

我实际经验是,SMOTE在正负样本比1:10到1:50之间效果比较明显。如果比例极端到1:100以上,反而要小心,因为少数类样本本来就少,近邻计算可能不够稳定,合成样本的质量也会下降。

2.3 SMOTE的改进变体:Borderline-SMOTE与ADASYN各擅长什么

SMOTE虽然好用,但它对所有少数类样本一视同仁。实际场景中,远离边界的“安全区”样本,以及靠近边界的“危险区”样本,价值完全不同。Borderline-SMOTE的思路是:先识别那些位于边界附近的少数类样本,只在这些样本周围生成新数据,从而强化决策边界附近的密度。这个方法在边界复杂、噪声多的数据集上效果突出。

ADASYN则侧重“难度自适应”。它会根据每个少数类样本周围多数类样本的密集程度,动态决定生成多少合成样本——周围多数类越多,说明这个区域对少数类越“危险”,就多生成一些;周围全是少数类,就少生成。这样生成的新样本更集中在模型难以分类的区域。

选择上,如果追求稳定和通用,我用SMOTE;如果数据边界混乱、异类重叠严重,我偏向Borderline-SMOTE;如果少数类内部结构差异大,ADASYN的表现往往更好。但无论选哪种,都要配合交叉验证去评估,千万别只看训练集上的效果。

3. 数据不够,算法来凑:代价敏感学习与阈值移动

3.1 给少数类“加权重”,本质是修改模型的代价判断

数据层面处理完,接下来该调整算法本身了。代价敏感学习的思路很直接:既然模型固有的偏好是平等对待每个样本,那我们就人为打破这种平等,让少数类样本被分错的代价更高。

工程上最常见的实现方式是class_weight参数。以sklearn的LogisticRegression为例,可以设置class_weight='balanced',模型会自动根据类别频率调整权重,样本量少的类别获得更大的惩罚系数。LightGBM里有scale_pos_weight,XGBoost里也有scale_pos_weight,热门框架基本都有类似参数。千万不要小看这一步,在很多项目里,只调这个参数就能让召回率提升十几个点。

我一度有认知误区,以为class_weight和过采样是“二选一”的关系。实际上两者的作用机制并不完全相同:过采样改变的是训练数据的分布,而class_weight改变的是损失函数里的权重系数。很多场景下两者搭配使用,效果比任何一个单独用都更好。

3.2 阈值移动:最容易被忽略的高性价比操作

关于阈值移动,我想先说一个现象:不少同行处理不平衡数据时,训练阶段用了SMOTE,损失函数加了权重,模型出来的概率却还是直接按0.5来判——这其实是很可惜的浪费。模型输出的概率分布如果偏向少数类方向,默认阈值0.5可能并不对应最佳分类点。

阈值移动的做法分两步:第一步,用原始数据(或经过处理的训练数据)训练模型;第二步,在验证集上遍历所有可能的阈值(比如从0.05到0.95,步长0.05),以你想要的目标指标(F1最大、召回率不低于某个业务线、精确率达标等)为准,找到最优阈值。这个最优阈值往往远低于0.5,比如0.2甚至0.1。

阈值移动之所以重要,是因为它提供了一条不需要重新训练就能调整模型行为的路径。很多时候,数据层和算法层的修改已经把模型调得不错了,阈值移动还能再往上推一截,成本却几乎为零。

3.3 集成策略:Easy Ensemble、BalanceCascade等方法的适用场景

集成方法在处理不平衡问题时很有一套。Easy Ensemble的思路是:从多数类中做多次随机欠采样,每次得到一个均衡的子数据集,分别训练模型,最后把多个模型的结果集成。这样做既缓解了随机欠采样的信息丢失问题,又保留了欠采样带来的训练速度和类别均衡优势。

BalanceCascade更进一步,它是级联式的:第一轮训练完模型后,把分类正确的多数类样本从数据集中移除,下一轮再训练新模型时只面对那些“还没被解决”的难分样本。每一轮都更聚焦,模型会逐步向边界推进。它的缺点是实现复杂度高,容易过拟合,参数也较难调。

如果项目数据量不大,我会优先尝试Easy Ensemble,或者直接在LightGBM里开启bagging和特征抽样模拟集成效果。数据量极大时,集成策略反而会因为多模型训练带来明显的耗时问题,这时我倾向于直接使用代价敏感学习加阈值移动。

4. 这才是重头戏:不平衡场景下如何选对评估指标

4.1 准确率的盲区,精确率和召回率能补上吗

前面讲了准确率在不平衡场景会失效,但很多人在换指标时,又踩了另一个坑:只知道“要看精确率和召回率”,却不知道这两个指标本身就是互相牵制的。精确率Precision关注的是“你预测的正样本里,有多少是真的”;召回率Recall关注的是“真正的正样本里,你找回来了多少”。两者通常是此消彼长的关系。想提高召回率,往往牺牲精确率;想保住精确率,召回率又会下降。

F1分数就是调和平均两者,试图在它们之间找一个平衡。但在业务层面,精确率和召回率的重要性很少是对半分的。我在信贷风控里会特别看重召回率——漏掉一个坏客户,损失可能是几千上万元;但在某些营销场景,精确率反而更重要——预测错了,请客吃饭的钱打了水漂。业务逻辑不同,指标的侧重就不同,指标选择本质上是业务策略的映射。

4.2 PR曲线和ROC曲线,为什么在不平衡场景下结论会分叉

ROC曲线和PR曲线是两种常用的模型性能可视化工具。ROC曲线横轴是假阳性率FPR,纵轴是真阳性率TPR,它的特点是受类别分布影响相对小。问题在于,当正样本极少时,FPR会显得非常小,曲线看起来非常漂亮,很容易让人误判模型能力很强。

PR曲线的横轴是召回率,纵轴是精确率,它对正样本的表现更敏感。同一个模型,在样本分布变化时,ROC-AUC可能变化很小,但PR-AUC会显著变化。在正负样本极度不平衡时,PR曲线更能反映模型在少数类上的真实表现。这也是为什么一些高规格的比赛和风控场景,最终排名直接看PR-AUC的原因。

我个人的评估习惯是:先用ROC-AUC做快速筛选,模型之间性能接近时,再用PR-AUC和具体的业务指标做精细化比较。这个组合能有效避免只看单一曲线带来的误判。

4.3 业务真实代价如何写进指标

讲到这里,我建议你在做不平衡处理前,先回答一个问题:预测错误的代价具体是多少?一个假阴性(漏判正样本)损失是假阳性(误判负样本)的几倍?如果你能把这个倍率量化,就可以把指标设计得更贴近业务。

比如在信贷场景,拒绝一个好客户,损失的是利息收入;放走一个坏客户,损失的是本金。假设两者的代价比是1:10,那你就可以用带权Fbeta分数,其中beta反映了召回率相对精确率的权重,beta取大于1的值意味着更重视召回率。更进一步,你可以用总代价函数来做阈值选择:总代价 = 漏报数 × 漏报代价 + 误报数 × 误报代价,取总代价最低的阈值点。这样做模型优化的每一步都能直接关联到业务的钱袋子,说服业务方也更容易。

5. 一次完整实战复盘:某信贷申请风险预测项目的不平衡改造

5.1 场景设定与初始基线

为了让你更直观地感受整套流程,我拿之前做过的一个某信贷申请风险预测项目(以下称模拟项目X)来做完整复盘。项目难度在于负样本占比极低,大约10000条申请里只有200条是违约样本,比例约1:50。用户还不是简单的二分类,涉及审核人员后续决策,所以我们还需要模型输出风险等级,而不只是0/1标签。

我的第一步是搭一个最简陋的基线模型,特征先用最基础的年龄、收入、负债率等,模型选LogisticRegression。测试结果是准确率92%,召回率只有3%,几乎等于放弃治疗。这个基线不是说模型能力不行,而是给我们一个参照物,让后面每个操作都能量化出是否有提升。

5.2 完整处理链路:数据划分、SMOTE、加权训练、阈值搜索

这里特别要提醒一个最关键的步骤顺序:一定要先切分训练集和测试集,再对训练集做SMOTE或其他采样操作,坚决不能对测试集做任何处理,也最好不要在切分前对全量数据过采样。否则合成的少数类样本可能同时出现在训练集和测试集中,造成信息泄露,你得到的指标会虚高,线上马上现形。

具体流程我这样走的:

  1. 按时间顺序切分训练集、验证集、测试集,保证切分后类别分布一致。
  2. 对训练集使用SMOTE,设置采样策略为少数类提升到多数类的30%左右,不要强求1:1。
  3. 模型换用LightGBM,开启scale_pos_weight,值设为负样本数除以正样本数的比值,同时开了早停early stopping,防过拟合。
  4. 在验证集上用网格搜索遍历阈值,范围0.1到0.5,以F1最大为最优阈值,最终确定阈值为0.23。
  5. 测试集上采用阈值0.23判定,同时额外输出风险分数,供人工复核。

最终对比数据如下:

方法准确率召回率精确率F1
基线逻辑回归92%3%11%0.05
SMOTE + 逻辑回归85%42%16%0.23
SMOTE + LightGBM + 权重83%58%24%0.34
加上阈值移动后79%71%28%0.40

准确率确实是下降了,但模型终于能抓住真正的坏客户。在业务上,这就是有效和无效的区别。

5.3 上线前的额外验证与落地效果

光有一个好看的测试集指标还不够,上线前我做了两个额外验证。第一,把预测结果按风险分数分成高、中、低三档,抽查每档的真实违约率,确认排序逻辑合理;第二,设定“高分段直接拒绝、低分段直接通过、中分段人工复核”的三段式策略,避免一个硬阈值带来的业务僵化问题。上线后业务方反馈,每月拦截的违约申请数量显著提升,人工复核的效率和命中率也都在可接受范围内。

这段经历让我对不平衡处理有了一个很深的体会:调模型只是前半场,把结果做成业务方能用、敢用的决策辅助工具,才是后半场。这也是很多社区教程不会教你、但实战中极其重要的能力。

6. 我踩过的坑,和一路沉淀下来的几条经验

6.1 过采样导致的信息泄漏,是最隐蔽的坑

我前面提了一句先切分再采样,这里想展开说说。有一次我图省事,把整个数据集先做了SMOTE,再切分训练集和测试集。结果测试集上所有指标都好得离谱,召回率直接上了0.9。后来发现,训练集里合成的样本和测试集里的原始样本距离非常近,模型几乎是“背答案”。这种情况在线上立刻现原形,性能断崖式下跌。

正确的做法是始终牢记:先划分数据,再处理数据。验证集也要单独处理,不能使用训练集生成的合成样本。这几乎是我见过的所有不平衡实操里出错率最高的环节。熟练之后你会养成肌肉记忆,但新手阶段一定要反复检查自己的数据流水线。

6.2 不做阈值移动,相当于实验只做了一半

还有一类常见情况是:训练时用了SMOTE,模型评分也做了,结果默认以0.5作为阈值切分概率。别忘了,如果模型输出的概率整体被数据重组带偏,0.5很可能不是最优切分点。

我个人现在的习惯是:不管用什么采样方法,拿到验证集后第一件事就是跑一遍阈值搜索,把阈值当成一个超参数来调。这样事后看,往往能额外带来5%到10%的召回率提升,而且几乎不增加任何成本。别让默认阈值替你做决策。

6.3 极端不平衡场景,尝试跳出分类思维

遇到1:1000甚至1:10000的极端不平衡场景,传统分类方法往往变得很鸡肋。因为样本少到连交叉验证都可能不稳定。这时候需要考虑换一个思路:把少数类看作“异常点”,用异常检测来建模,比如孤立森林、一类支持向量机;或者在某些场景里先用无监督聚类找出潜在的高危簇,再人工核查。

另外,如果业务允许,用“先召回、后排序”的两段式策略通常更合适:第一阶段用一个高召回率模型尽可能抓出可疑样本,第二阶段对这部分样本做精细化排序或人工复核。这个思路在数据严重偏斜时,业务弹性比单模型大得多。

6.4 关于衡量投入产出的一点心得

最后分享一个我的体会:数据不平衡处理没有银弹,每一次改进都要用同一个验证集做对照,量化出来到底是召回率提升了还是精确率提升了,这个提升对应的业务成本是什么。与其一上来就堆各种高级方法,不如先跑一个简单的baseline,再结合业务目标逐步加策略。很多项目最终的突破点不是更花哨的算法,而是把阈值和评估指标调准了、把数据泄漏堵住了、把业务逻辑和模型逻辑对齐了。

这一套流程走下来,我面对不平衡数据的思路已经固定为:先看业务代价定指标,再跑基线模型,然后按需做数据层和算法层处理,最后一定做阈值调整和业务验证。每次换数据集,流程不变,变的只是具体参数和取舍。希望这个思路也能在你的项目里派上用场。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询