news 2026/10/17 5:11:40

类别不平衡问题实战:从SMOTE采样到评估指标全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
类别不平衡问题实战:从SMOTE采样到评估指标全解析

1. 类别不平衡问题到底在说什么

在真实的数据建模项目里,类别不平衡几乎是一个躲不开的坎儿。我在实际工作中接触过不少刚入门的朋友,他们往往能把准确率跑到95%以上,兴奋地拿着结果过来,结果我一问正样本的召回率,整个人就愣住了——原来那95%是拿"全都预测成多数类"换来的。

类别不平衡,简单说就是分类问题里不同类别的样本数量差异悬殊。比如二分类场景,正样本可能只占1%,甚至千分之一。最典型的例子就是金融风控里的欺诈交易识别:正常交易动辄百万条,欺诈交易可能只有几百条。还有医疗诊断中的罕见病筛查、工业生产里的设备故障预测、电商平台上的恶意评论识别,几乎每个涉及"异常"或者说"少数事件"的场景,都逃不开这个设定。

很多人第一次遇到不平衡数据时,第一反应是"那我多采点少数类的样本不就行了"。这话听起来没问题,但实际上哪有那么多少数类样本给你采?欺诈交易就是欺诈交易,不可能为了让模型好看就去人为制造欺诈。就算能采到一部分,大多数时候也是代价极高、周期极长。所以,这个问题的本质,是在数据分布天然倾斜的前提下,怎么让模型尽可能准确地把少数类找出来——这跟"把准确率刷高"完全是两码事。

我个人的体感是,处理类别不平衡问题,最重要的不是背下哪个库、哪个参数,而是建立起一套完整的判断框架:先搞清楚你的业务到底更在意哪一类错误,再决定用数据层面的方法还是算法层面的方法,最后用对的评估指标去验证你有没有真的解决问题。如果一上来就急着调参,很容易陷入"看着分数很高、业务上完全不能用"的尴尬局面。

这篇文章适合谁看?如果你是刚接触数据建模、正在做分类任务但发现结果不对劲的同学,或者你已经在用一些基础方法处理不平衡数据、但想系统梳理一下思路的从业者,都可以把这篇当作一份接地气的实操笔记来读。里面讲到的所有方法和参数,我都用真实项目验证过,也踩过不少坑,会一并写出来。

2. 处理不平衡问题的三条路线,先选对方向再动手

面对类别不平衡,业界的处理方法大致可以分成三条线:数据层面的采样、算法层面的代价调整、评估层面的指标切换。我第一次接触这个问题的时候,一位前辈跟我说过一句话,我一直记着:"先别急着改数据,先想清楚你的业务要什么。"这句话帮我在很多项目里少走了弯路。

2.1 数据层处理:改变样本分布

数据层面的思路最直观——既然少数类样本太少、多数类样本太多,那就让两边"看起来平衡"一点。常用手段有欠采样、过采样,以及两者的结合。

欠采样是从多数类里随机抽掉一部分,让多数类数量降到和少数类差不多的水平。优点是训练数据量变小,训练速度会快不少;缺点是直接丢弃数据,如果多数类里恰好藏着对分类很重要的信息,模型性能会打折。

过采样则是复制少数类样本,让少数类变多。这个思路简单粗暴,但有个致命缺陷:如果只是单纯复制,模型会在同样的点上反复学习,很容易过拟合。后来出现的SMOTE、ADASYN这类合成采样方法,就是为了解决这个问题——不是复制样本,而是通过在少数类样本之间插值,生成新的"人造样本"。

举一个生活化的类比帮助理解:假设你想教一个孩子认识"猫"和"非猫",你手里有1000张猫的图片,只有10张狗的照片。欠采样的思路是,只留下10张猫的照片,陪10张狗的照片一起学;过采样的思路是,把那10张狗的照片打印100遍,凑够1000张和猫平衡;SMOTE的思路则是,观察那10张狗照片的共同特征,再"造"出一些长相介于两只狗之间的新狗狗照片,让学习素材更丰富。

2.2 算法层处理:让模型偏心

数据改完之后,还可以从算法层面入手。核心思想是让模型在训练时"重视"少数类——具体来说,通过调整损失函数中不同类别的权重,让少数类分错的代价更大。

最直观的实现方式就是设置类别权重。比如在逻辑回归里,把少数类的权重设成多数类的十倍,模型在优化时就会为了压低少数类的错分损失,而主动把决策边界往多数类方向推,从而"捞"回更多少数类样本。这个操作在很多现成算法里都有内置参数,后面我会专门演示。

这种方式的优势是不改变数据分布、不丢数据,而且实现成本极低。但需要注意,权重设多少不是拍脑袋定的,它和你的业务代价直接相关——如果你觉得"漏掉一个欺诈样本"和"误判一个正常交易"相比,前者严重十倍,那权重就应该往十比一的方向去调。

2.3 评估指标层处理:换一把尺子

这一条路经常被新手忽略,但恰恰是判断前两条路有没有见效的关键。在类别不平衡的场景下,准确率是最大的欺骗者。如果99%的样本是多数类,你什么都不做,把全部样本预测成多数类,准确率也有99%。但这个模型在业务上毫无价值,因为它一个少数类都找不出来。

所以做不平衡分类,一定要把注意力放到精确率(Precision)、召回率(Recall)、F1分数、PR曲线、AUC-PR这些指标上。精确率回答的是"你预测成少数类的里面,有多少真的对了";召回率回答的是"真正的少数类里面,你找回来了多少"。这两个指标天然就是一对矛盾和平衡。

我在实际项目中总结出来的一个经验是:先用业务视角定出"召回率至少要达到多少"这个底线,然后再去追求精确率的最大化。这样做的好处是,你每次调参都有一个明确的方向,而不是漫无目的地试来试去。

3. 数据采样实操:从随机采样到SMOTE,完整步骤与参数细节

在确定了要用数据层方法之后,第一个实操问题就是:怎么采样,用什么采样方法,参数怎么调。这一节我把我用过的几种主流方法完整过一遍,包括代码思路和注意事项。

3.1 随机过采样与随机欠采样

随机过采样实现起来最简单——用现成工具库,直接把少数类样本重复采样到目标比例。随机欠采样则是从多数类中随机抽取,直到达到目标数量。

以一款常用的机器学习工具库为例,核心代码大概长这样:

from imblearn.under_sampling import RandomUnderSampler from imblearn.over_sampling import RandomOverSampler # 随机欠采样:让多数类降到和少数类一样多 rus = RandomUnderSampler(sampling_strategy='auto', random_state=42) X_resampled, y_resampled = rus.fit_resample(X_train, y_train) # 随机过采样:让少数类升到和多数类一样多 ros = RandomOverSampler(sampling_strategy='auto', random_state=42) X_resampled, y_resampled = ros.fit_resample(X_train, y_train)

这两个方法虽然基础,但在某些场景下依然有奇效。我曾经在一个文本分类任务里试过欠采样,虽然数据量从两万降到了三千,但模型的F1分数反而提升了两个百分点——因为多数类里有大量重复度极高的模板化文本,丢掉一部分完全不影响模型学习核心特征。所以说,不要因为方法简单就看不上它,具体效果跟数据质量关系很大。

不过随机过采样的问题在真实项目里很容易暴露:训练集上表现很好,一上验证集就崩。原因就是模型把少数类的"重复副本"背了下来,而不是学会了对少数类的泛化识别。这时候你会发现,过采样提升的更多是训练集分数,而不是真实泛化能力。

3.2 SMOTE的原理与代码实现

SMOTE(Synthetic Minority Over-sampling Technique)的出现在当时算是解决了一个大痛点。它的核心逻辑是:随机挑选一个少数类样本,找到它的K个近邻(默认通常是5个),然后在样本点和近邻点的连线上随机取一点,作为新样本。插值的好处是,新样本不是已有样本的简单复制,而是"长得既像A又像邻居B"的全新样本,能够在一定程度上缓解过拟合。

代码示例:

from imblearn.over_sampling import SMOTE smote = SMOTE(sampling_strategy='auto', k_neighbors=5, random_state=42) X_resampled, y_resampled = smote.fit_resample(X_train, y_train)

这个k_neighbors参数值得说说。它控制的是用来插值的近邻数量。如果设得太小(比如1),生成的新样本会非常局限在局部区域,多样性不够;如果设得太大(比如10以上),又可能把离得很远的"远房亲戚"也算进来,生成的样本反而偏离真实的少数类分布。我通常的做法是先跑一个简单的网格搜索,看看k_neighbors在3到10之间哪个值配合模型效果最好,而不是一直用默认值。

另一个需要细抠的参数是sampling_strategy。默认'auto'意味着会把少数类采样到和多数类一样多,但很多场景下并不需要做到1:1。比如正样本只占2%的场景,你硬生生把正样本扩充到和负样本一样多,不仅训练时间翻倍,还可能引入大量质量存疑的合成样本。我一般会先试sampling_strategy=0.3到0.5这个区间,也就是让少数类占多数类的30%到50%,很多时候效果反而比1:1更好。

3.3 降采样与合成的组合拳

在真实项目里,纯用SMOTE有时候效果并不理想,尤其是当多数类样本本身就有几十万甚至上百万条的时候。训练数据太庞大,SMOTE再一扩,直接内存爆炸都不是开玩笑的。

我常用的组合方案是:先对多数类做一次欠采样,把数据量降到一个可控范围,再用SMOTE对少数类做合成扩充。这一步可以先用工具库里的Pipeline模块把两步串起来,干净又不容易出错:

from imblearn.pipeline import Pipeline from imblearn.under_sampling import RandomUnderSampler from imblearn.over_sampling import SMOTE pipeline = Pipeline([ ('under', RandomUnderSampler(sampling_strategy=0.5, random_state=42)), ('over', SMOTE(sampling_strategy=1.0, k_neighbors=5, random_state=42)), ('model', LogisticRegression(class_weight='balanced')) ])

这样组合的好处在于:欠采样控制了数据总量,SMOTE补足了少数类比例,最后的模型再用类别权重做兜底,等于三层保险。我有个项目就是靠这个组合,把欺诈识别的召回率从62%一路拉到了88%,代价仅仅是精确率下降了4个百分点,业务上完全可以接受。

这里有一个特别重要的提醒:采样操作只能在训练集上做,验证集和测试集绝对不能碰。很多新手一开始不懂,拿全量数据做了SMOTE,然后切分训练集验证集,结果验证集里全是合成样本的"亲戚",评估结果虚高得吓人。一旦上真实数据就原形毕露。正确做法是先切分,再只对训练集做采样。这一步的代码顺序,比任何调参都关键。

4. 算法侧招数实操:类别权重、阈值移动与集成方法

数据层面的处理做了,接下来算法层面的配合能让效果再上一个台阶。尤其是当你不想动数据分布、或者数据量实在太小连SMOTE都合成不出好样本的时候,算法侧的方法就是主力。

4.1 class_weight的设定逻辑

很多常用分类器都支持class_weight参数,这是一个从算法层面调节不平衡的入口。以逻辑回归为例:

from sklearn.linear_model import LogisticRegression model = LogisticRegression(class_weight='balanced', max_iter=1000) model.fit(X_train, y_train)

balanced是一种自动模式,它会根据类别频率自动计算权重,计算公式大致是:总样本数 / (类别数 * 该类别样本数)。所以少数类的权重天然就大。如果自动计算出来的权重不够用,你还可以手动指定:

weights = {0: 1.0, 1: 10.0} model = LogisticRegression(class_weight=weights)

手动指定好就好在可控。比方说你的业务明确认为"漏掉一个欺诈样本"的代价是"误杀一个正常样本"的20倍,那你直接把权重设成20:1,让模型去匹配这个风险偏好。这里有个实操中容易误解的点:class_weight改变的是损失函数里不同样本的惩罚系数,而不是简单地对样本做加权复制,所以它不会像过采样那样增加训练时间。

用随机森林、梯度提升树这类模型时,也都有对应的class_weight参数(比如随机森林的class_weight、某些梯度提升框架的scale_pos_weight),设置思路完全一致。

4.2 阈值移动:一个经常被低估的技巧

很多模型输出的是一个概率值,默认情况下我们把大于0.5的判为少数类。但在不平衡场景下,0.5这个阈值往往不是你真正想要的。

举个例子:模型在训练后对少数类给出的概率普遍在0.1到0.4之间,因为少数类样本太少,模型学到的整体倾向就是"不太像少数类"。这时候如果你死守着0.5的阈值,那召回率就会非常难看。解法是:把判定阈值往下调,比如调到0.2,让更多概率值超过0.2的样本被判成少数类。

实操方法很简单,模型预测时别用predict(),改用predict_proba(),拿到少数类的概率后自己设阈值:

prob = model.predict_proba(X_val)[:, 1] threshold = 0.2 y_pred = (prob >= threshold).astype(int)

那么阈值怎么选?我一般会在验证集上把0.05到0.5之间的值都扫一遍,画出召回率和精确率的变化曲线,找到同时满足业务要求的点。这本质上是在精确率和召回率之间做权衡——阈值越低,召回来的少数类越多,但误判也越多。你不需要追求最好看的评估指标,你要追求的是最符合业务代价的那个点。

4.3 集成方法:让多个模型投票

单独一个模型折腾完了,还可以用集成的方式进一步提升。常见的做法是把多数类切成好几份,每份分别和少数类组合训练一个模型,最后把所有模型的预测结果综合起来。

这种做法在业界被称为"平衡级联"或者类似的思路。实际操作中,我比较常用的是配合某种梯度提升框架,里面内置了一些处理不平衡的机制,比如训练时对少数类做加权、或者在每轮迭代中重点关注之前分错的少数类样本。这些内置机制用起来比自己从头搭要省心得多,但原理还是同一套:对少数类的错分施加更大的代价。

我自己的习惯是:先用一个带class_weight的基线模型跑通流程,确认评估指标没问题,再上集成模型或者更复杂的采样组合。这样每一步的提升你心里都有数,不会出现"模型换了又换、分数忽高忽低"的混乱状况。

5. 评估指标的选择与读法:别被准确率骗了

处理类别不平衡问题,最要命的一件事就是"努力了半天,结果用的尺子是错的"。很多时候模型其实已经进步了,但因为你盯着准确率看,看到的结果是"没变化",就误以为方法无效。反过来,也可能方法其实没效果,但准确率稍微动了一下,你就以为成了。

5.1 为什么准确率会失效

假设一个数据集里1%是少数类。你做了一个模型,把所有样本全预测成多数类,准确率是99%。这个模型什么也没学到,但它准确率很高。如果你接着用准确率来指导调参,你会陷入一个怪圈:无论怎么调,准确率都在99%附近波动,几乎没有任何信息量。

更麻烦的是,有些模型为了压低损失,会把决策边界调的非常保守,结果少数类全被吞掉,准确率还是很高。这一类"虚假繁荣"的结果,只有在回到业务现场时才会暴露得淋漓尽致——你需要找的少数类一个都找不出来。

5.2 精确率、召回率与F1的正确读法

这三个指标是针对"少数类"来算的,它们共同构成了一套更完整的评价语言。

  • 精确率(Precision):预测为少数类的样本中,真的是少数类的比例。高精确率意味着你"抓得准",误报少。
  • 召回率(Recall):真实少数类中,被成功预测出来的比例。高召回率意味着你"抓得全",漏网少。
  • F1分数:精确率和召回率的调和平均,用来在两者之间找一个综合平衡点。

举一个实际业务例子。做故障预测的时候,如果精确率很高但召回率很低,说明你预测出来的故障基本都是对的,但真故障有一大半没预判到;如果召回率高但精确率低,说明故障基本都被你逮住了,但同时也误报了大量正常设备,工程师每天都在处理假警报,也很崩溃。

所以这里没有"哪个指标最好"的标准答案,只有"哪个指标更适合你的业务"。你需要做的是:先想清楚业务方更能接受哪种错误,然后据此确定优化目标。

5.3 PR曲线与ROC曲线的选型

如果把阈值从0到1之间连续移动,可以画出两条曲线:ROC曲线和PR曲线。

ROC曲线的横轴是假正率,纵轴是真正率(召回率)。它的优势是当正负样本比例变化时,曲线形状相对稳定——这一点在某些场景下是优点,但在不平衡场景下恰恰是缺点:它会让你觉得模型还不错,就算你连一个少数类都没找出来,ROC曲线也可能看起来很平滑。

PR曲线则完全不同。它的横轴是召回率,纵轴是精确率。在类别极不平衡的时候,PR曲线能够更灵敏地反映出少数类性能的微小变化。两条曲线在同一个模型上的表现可能差距很大,很多项目里出现过"ROC-AUC高达0.95,PR-AUC却只有0.2"的反差。所以我的建议是:做不平衡分类,优先看PR曲线以及对应的AUC-PR值,ROC可以辅助参考,但别只依赖它。

实操中我还习惯做一个"基线对比":算一下不做任何处理、全部预测成多数类时的精确率、召回率和F1,然后以这个为起点,看每一步处理带来了多少提升。这样你就能清楚地知道,你的努力究竟有没有让模型真正开始"看见"少数类。

6. 我踩过的坑和排查技巧

操作层面的方法讲得差不多了,这一节我想把真实项目里反复出现的几个大坑单独拎出来说一说。这些坑我几乎都在项目里亲自踩过,有些坑踩一次就记住了,有些坑换了场景又踩一次。

6.1 采样之前切分数据,顺序千万别搞错

这个坑我在前面提了一嘴,但值得展开讲。有一次我在做一个营销响应预测项目,数据量大概20万条,正样本占比只有3%。当时的流程是先对全量数据做了SMOTE,再切训练验证集。结果验证集上F1分数高得离谱,我一度以为找到了一个无敌的方案。后来把流程改成先切分再SMOTE,分数立刻掉下去一大截。原因很简单:SMOTE生成的新样本是建立在原始样本位置上的,切分之后,大量合成样本的"近邻"跑到了验证集里,导致验证集和训练集严重重叠,评估结果虚高。

这算是数据建模里最基础的一点,但因为报错不会直接弹出来,很多人根本意识不到。建议在代码注释里明确写上"SMOTE只放训练集内",防止自己或者协作的同事哪天手滑。

6.2 SMOTE生成样本的噪声问题

SMOTE不是万能的。当少数类样本本身就存在噪声的时候,SMOTE会在噪声的基础上继续插值,把噪声"发扬光大",生成一堆分布在奇怪区域的新样本。这会让模型的决策边界变得扭曲。

排查方法很简单:把SMOTE生成的新样本投影到二维或者三维空间,肉眼扫一眼分布。如果发现大量新样本和多数类样本混在一起、甚至跑到了多数类的"地盘"里,那就说明少数类原始样本质量可能有问题,或者k_neighbors设得太大。这时候我更倾向于先做一轮简单的数据清洗,再考虑要不要用SMOTE,或者把k值调小一点,减少跨区域插值的概率。

6.3 极端不平衡下的数据量焦虑

有些场景下少数类占比不是1%,而是万分之一。这种极端情况下,SMOTE能发挥的作用就非常有限了——因为少数类本身就只有几十个样本,插值也插不出足够多样化的新样本,模型很难学到稳定的规律。

我的经验是,面对极端不平衡,优先考虑两个方向:一是换用更擅长从少量样本里学习的模型(比如一些基于规则或者集成的算法,而不是深度神经网络);二是考虑用异常检测的思路,把这当做一个"找离群点"的问题而不是"分类少数类"的问题。这两个思路在很多极端不平衡项目中反而比裸上分类模型更有效。

6.4 设置随机种子,保证结果可复现

最后一条小建议:任何涉及随机采样的操作,都务必设置random_state。我之前在做一个项目时,同一套代码在不同时间跑出两个完全不同的结果,排查了整整半天,最后发现是SMOTE和欠采样的随机种子没固定。数据建模本来就有很多随机因素,不把种子固定住,你连"这次调参到底有没有效果"都判断不了。

6.5 常见问题速查表

为了让你在实操时能快速定位问题,我整理了一份速查表:

现象可能原因排查与解决
准确率很高但少数类完全找不出评估指标选错,模型被多数类主导改用精确率、召回率、F1、PR-AUC,并检查决策阈值
验证集分数虚高,上线后大跌采样操作在切分之前做了先切分训练集/验证集,再对训练集做采样
SMOTE后模型严重过拟合k_neighbors太大或合成样本质量差调小k值,先清洗数据再合成,或者改用边界采样变体
训练时间过长过采样后数据量爆炸先欠采样控制总量,再用SMOTE;或直接用类别权重
模型输出概率整体偏低少数类比例太低,模型倾向保守尝试降低分类阈值,用PR曲线辅助找最优阈值点

我个人在实际操作中最深刻的体会是:这个领域没有银弹,不存在一个"只要调了这个参数,不平衡问题就自动解决"的方法。它更像是配药——数据采样、算法权重、评估指标、阈值移动,每一味药都起一点作用,而你需要根据具体数据的情况,把它们按合理的比例组合起来。多试几组,记录结果,你会慢慢练出一种直觉,看到数据分布就知道该往哪个方向使劲。

最后再补一个小技巧:每次实验都顺手把数据集分布情况、采样方式、模型参数、关键指标完整记录下来。等攒过几次不同场景的项目之后再看,这些记录比任何教程都值钱,它们是你自己用真金白银踩出来的经验库。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 5:11:38

基于兰姆波的数据驱动结构健康监测:从原理到Matlab实现

很多搞结构强度的人,第一次接触结构健康监测(SHM)时,总会先问一句:这玩意和传统的无损检测到底差在哪?传统NDT(无损检测)是拿着超声/射线设备去做"定期体检",结…

作者头像 李华
网站建设 2026/10/17 5:11:06

Flutter网络层封装实践:用Dio拦截器实现自动加载框与并发控制

很多人刚接触Flutter的时候,第一个困惑不是Widget怎么摆,而是网络请求怎么写。用自带的HttpClient写两下就烦了,于是换到Dio,先是每个页面里new一个Dio实例,然后在请求前手动show一个loading弹窗,请求结束后…

作者头像 李华
网站建设 2026/10/17 5:10:58

浏览器桌面通知Notification API实战:权限、封装与用户体验

1. 桌面通知弹窗到底是个什么东西第一次接触 Notification API 是在做一个后台管理系统的时候。当时的需求很朴素:用户提交了一个耗时任务,比如批量导出数据、生成报表,处理可能要跑十几秒甚至几分钟。如果用户切到别的标签页去干别的事了&am…

作者头像 李华
网站建设 2026/10/17 5:10:08

C# WinForms接口测试工具:生产级API调试与自动化验证

简介:这是一款基于C#开发的轻量级Windows桌面接口测试工具,面向.NET初学者、后端开发者及API调试人员,解决日常HTTP接口快速验证与调试需求。工具采用WinForm框架构建图形界面,支持GET、POST、PUT、DELETE四大标准请求方法&#x…

作者头像 李华
网站建设 2026/10/17 5:10:04

基于数据驱动与兰姆波的结构健康监测损伤识别方法

1. 项目背景与核心思路结构健康监测(SHM)这个方向,说直白点就是给飞行器、桥梁这类大件装备装上一套“神经系统”,让它们能随时向维护人员汇报自己哪儿不舒服。尤其对于航空航天结构,疲劳裂纹、冲击损伤、连接松动这些问题,早期发…

作者头像 李华
网站建设 2026/10/17 5:09:44

神经网络稀疏性实战指南:从原理到端侧落地

1. 项目概述:为什么“神经网络稀疏性”正在成为模型落地的分水岭最近三个月,我在给某高校实验室做模型轻量化咨询时,连续遇到三类典型问题:边缘设备上部署ResNet-50推理延迟超300ms、医疗影像分割模型在Jetson Nano上显存溢出、某…

作者头像 李华