数据不平衡问题处理指南
一、问题本质
数据不平衡指分类任务中各类别样本数量差异悬殊,例如:
| 场景 | 多数类 | 少数类 | 不平衡比 |
|---|---|---|---|
| 信用卡欺诈检测 | 正常交易 99.8% | 欺诈 0.2% | 500:1 |
| 罕见病诊断 | 健康 99.5% | 患病 0.5% | 200:1 |
| 设备故障预测 | 正常 99% | 故障 1% | 100:1 |
| 网络入侵检测 | 正常流量 97% | 攻击 3% | 32:1 |
核心危害:模型被多数类主导,对少数类"视而不见"——准确率 99% 可能毫无意义(全预测为多数类即可达到)。
准确率陷阱: 欺诈检测中,全预测"正常" → 准确率 99.8%,但召回率 0% 真正目标:在保持整体性能的同时,提升对少数类的识别能力二、评估指标先行
处理不平衡前,必须选择正确的评估指标:
| 指标 | 公式/含义 | 适用场景 |
|---|---|---|
| Precision(精确率) | TP / (TP + FP) | 误报代价高(如冤枉好人) |
| Recall(召回率) | TP / (TP + FN) | 漏报代价高(如漏掉欺诈) |
| F1-Score | 2×P×R / (P+R) | 需要平衡 P 和 R |
| Fβ-Score | 加权调和平均 | β>1 偏召回,β<1 偏精确 |
| PR-AUC | Precision-Recall 曲线下面积 | 不平衡数据首选 |
| ROC-AUC | TPR-FPR 曲线下面积 | 不平衡严重时可能过于乐观 |
fromsklearn.metricsimportclassification_report,precision_recall_curve,auc# 不平衡数据必须看的报告print(classification_report(y_test,y_pred,target_names=['多数类','少数类']))# PR-AUC(不平衡数据更可靠)precision,recall,_=precision_recall_curve(y_test,y_proba)pr_auc=auc(recall,precision)print(f"PR-AUC:{pr_auc:.4f}")三、三大处理策略
数据不平衡处理 ├── 一、数据层面 — 重采样(改变数据分布) │ ├── 欠采样(减少多数类) │ └── 过采样(增加少数类) ├── 二、算法层面 — 代价敏感学习(改变学习策略) │ ├── 类别权重调整 │ └── 代价敏感算法 └── 三、集成层面 — 结合重采样与集成学习 ├── BalancedBagging ├── EasyEnsemble └── RUSBoost策略一:数据层面 — 重采样
1. 欠采样(Undersampling)— 减少多数类
fromimblearn.under_samplingimportRandomUnderSampler,TomekLinks,NearMiss# 随机欠采样rus=RandomUnderSampler(sampling_strategy=0.5,random_state=42)# 少数:多数 = 1:2X_res,y_res=rus.fit_resample(X_train,y_train)# NearMiss — 保留距少数类最近的多数类样本(更智能)nm=NearMiss(version=3,n_neighbors_ver3=3)X_res,y_res=nm.fit_resample(X_train,y_train)# Tomek Links — 只移除与少数类最近且重叠的多数类样本(温和欠采样)tl=TomekLinks(sampling_strategy='majority')X_res,y_res=tl.fit_resample(X_train,y_train)| 方法 | 优点 | 缺点 |
|---|---|---|
| RandomUnderSampler | 简单快速 | 丢失多数类信息 |
| NearMiss | 保留边界信息 | 计算距离开销大 |
| TomekLinks | 温和,信息损失小 | 降采样幅度有限 |
2. 过采样(Oversampling)— 增加少数类
fromimblearn.over_samplingimportRandomOverSampler,SMOTE,ADASYN,BorderlineSMOTE# 随机过采样(简单复制)ros=RandomOverSampler(sampling_strategy=0.5,random_state=42)X_res,y_res=ros.fit_resample(X_train,y_train)# SMOTE — 合成少数类样本(最常用)fromimblearn.over_samplingimportSMOTE smote=SMOTE(sampling_strategy=0.5,k_neighbors=5,random_state=42)X_res,y_res=smote.fit_resample(X_train,y_train)# BorderlineSMOTE — 只在边界区域合成(更精准)bsmote=BorderlineSMOTE(kind='borderline-1',random_state=42)X_res,y_res=bsmote.fit_resample(X_train,y_train)# ADASYN — 自适应合成,更关注难分类的少数类样本adasyn=ADASYN(sampling_strategy=0.5,random_state=42)X_res,y_res=adasyn.fit_resample(X_train,y_train)SMOTE 原理图解:
多数类: ○ 少数类: ● 合成样本: ✦ ○ ○ ○ ○ ○ ○ → ○ ✦ ○ ○ ●──● ○ ○ ●──✦──● ○ ○ ○ ✦ ○ ○ ○ ○ ○ 在两个少数类样本连线上随机插值生成新样本| 方法 | 优点 | 缺点 |
|---|---|---|
| RandomOverSampler | 简单 | 复制样本 → 过拟合 |
| SMOTE | 合成新样本,泛化更好 | 可能生成噪声样本 |
| BorderlineSMOTE | 聚焦决策边界 | 边界样本少时效果受限 |
| ADASYN | 自适应难度 | 对噪声敏感 |
3. 混合采样(推荐)
fromimblearn.combineimportSMOTETomek,SMOTEENN# SMOTE + Tomek Links:先过采样再清理重叠combo=SMOTETomek(sampling_strategy=0.5,random_state=42)X_res,y_res=combo.fit_resample(X_train,y_train)# SMOTE + ENN:先过采样再清理噪声(更激进)combo2=SMOTEENN(sampling_strategy=0.5,random_state=42)X_res,y_res=combo2.fit_resample(X_train,y_train)策略二:算法层面 — 代价敏感学习
1. 类别权重调整
fromsklearn.ensembleimportRandomForestClassifierfromsklearn.linear_modelimportLogisticRegressionfromsklearn.svmimportSVC# 方式1:class_weight='balanced' — 自动按反比例加权rf=RandomForestClassifier(class_weight='balanced',random_state=42)lr=LogisticRegression(class_weight='balanced',random_state=42)# 方式2:手动指定权重weights={0:1,1:50}# 少数类权重 50 倍rf=RandomForestClassifier(class_weight=weights,random_state=42)# 方式3:XGBoost 的 scale_pos_weightimportxgboostasxgb neg_count=(y_train==0).sum()pos_count=(y_train==1).sum()model=xgb.XGBClassifier(scale_pos_weight=neg_count/pos_count,# 自动平衡max_depth=6,n_estimators=200,random_state=42)2. 自定义损失函数
importxgboostasxgbimportnumpyasnpdefweighted_logloss(y_pred,y_true):"""少数类漏报惩罚更重"""y_true=y_true.get_label()weight=np.where(y_true==1,10.0,1.0)# 少数类权重 10 倍grad=weight*(y_pred-y_true)hess=weight*y_pred*(1-y_pred)returngrad,hess model=xgb.XGBClassifier(obj=weighted_logloss,n_estimators=200)策略三:集成层面 — 专用不平衡集成算法
fromimblearn.ensembleimport(BalancedBaggingClassifier,BalancedRandomForestClassifier,EasyEnsembleClassifier,RUSBoostClassifier)# BalancedBagging — 每棵树训练前自动欠采样bbc=BalancedBaggingClassifier(estimator=RandomForestClassifier(n_estimators=10),n_estimators=50,sampling_strategy=0.5,random_state=42)# BalancedRandomForest — 随机森林的不平衡版本brf=BalancedRandomForestClassifier(n_estimators=200,sampling_strategy='auto',random_state=42)# EasyEnsemble — 多组欠采样 + 多个基分类器 + 投票eec=EasyEnsembleClassifier(n_estimators=10,sampling_strategy=0.5,random_state=42)# RUSBoost — 欠采样 + Boostingrusb=RUSBoostClassifier(n_estimators=200,sampling_strategy=0.5,random_state=42)四、完整实战流水线
fromsklearn.model_selectionimporttrain_test_splitfromsklearn.metricsimportclassification_report,f1_scorefromimblearn.pipelineimportPipeline# 注意:用 imblearn 的 Pipelinefromimblearn.combineimportSMOTETomekfromsklearn.ensembleimportRandomForestClassifier# 1. 划分数据(重采样只在训练集上做!)X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.2,stratify=y,random_state=42# stratify 保证测试集分布一致)# 2. 构建流水线pipeline=Pipeline([('resampling',SMOTETomek(sampling_strategy=0.5,random_state=42)),('classifier',RandomForestClassifier(n_estimators=200,class_weight='balanced_subsample',max_depth=10,random_state=42,n_jobs=-1))])# 3. 训练pipeline.fit(X_train,y_train)# 4. 评估y_pred=pipeline.predict(X_test)print(classification_report(y_test,y_pred,target_names=['多数类','少数类']))关键原则:重采样只在训练集上做,测试集保持原始分布!
五、阈值调整(零成本提升)
不修改模型,只调整分类阈值:
fromsklearn.metricsimportprecision_recall_curve# 获取预测概率y_proba=model.predict_proba(X_test)[:,1]# 默认阈值 0.5 → 降低阈值可提升召回率importnumpyasnp y_pred_custom=(y_proba>=0.3).astype(int)# 降低阈值到 0.3# 自动寻找最优阈值(最大化 F1)precisions,recalls,thresholds=precision_recall_curve(y_test,y_proba)f1_scores=2*precisions*recalls/(precisions+recalls+1e-8)best_threshold=thresholds[np.argmax(f1_scores)]print(f"最优阈值:{best_threshold:.4f}")y_pred_optimal=(y_proba>=best_threshold).astype(int)六、策略选择决策树
数据不平衡比? ├── < 5:1(轻度) │ └── class_weight='balanced' + 阈值调整(最简单) ├── 5:1 ~ 50:1(中度) │ ├── 样本量充足 → SMOTE / SMOTETomek + 类别权重 │ └── 样本量不足 → BorderlineSMOTE + 集成方法 ├── > 50:1(重度) │ ├── 多数类样本极多 → BalancedRandomForest / EasyEnsemble │ └── 少数类样本极少 → ADASYN + 代价敏感 + 阈值调整 └── 极端不平衡(< 0.1%) └── 异常检测思路(One-Class SVM / Isolation Forest)而非分类七、常见误区与注意事项
| 误区 | 正确做法 |
|---|---|
| 在全量数据上做重采样再划分 | 先划分,再对训练集重采样,测试集保持原始分布 |
| 只看准确率评估模型 | 使用 F1 / PR-AUC / Recall 等不平衡友好指标 |
| 过采样到完全平衡(1:1) | 适度平衡即可(1:2 ~ 1:5),完全平衡易过拟合 |
| SMOTE 在高维数据上效果差 | 高维时先降维再 SMOTE,或用 ADASYN |
| 忽略业务代价 | 根据漏报/误报的业务代价选择指标和阈值 |
| 只用单一策略 | 组合策略效果最佳:重采样 + 类别权重 + 阈值调整 |