数据分析中,如何处理数据的不平衡问题? 数据不平衡问题处理指南一、问题本质数据不平衡指分类任务中各类别样本数量差异悬殊例如场景多数类少数类不平衡比信用卡欺诈检测正常交易 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-Score2×P×R / (PR)需要平衡 P 和 RFβ-Score加权调和平均β1 偏召回β1 偏精确PR-AUCPrecision-Recall 曲线下面积不平衡数据首选ROC-AUCTPR-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_aucauc(recall,precision)print(fPR-AUC:{pr_auc:.4f})三、三大处理策略数据不平衡处理 ├── 一、数据层面 — 重采样改变数据分布 │ ├── 欠采样减少多数类 │ └── 过采样增加少数类 ├── 二、算法层面 — 代价敏感学习改变学习策略 │ ├── 类别权重调整 │ └── 代价敏感算法 └── 三、集成层面 — 结合重采样与集成学习 ├── BalancedBagging ├── EasyEnsemble └── RUSBoost策略一数据层面 — 重采样1. 欠采样Undersampling— 减少多数类fromimblearn.under_samplingimportRandomUnderSampler,TomekLinks,NearMiss# 随机欠采样rusRandomUnderSampler(sampling_strategy0.5,random_state42)# 少数:多数 1:2X_res,y_resrus.fit_resample(X_train,y_train)# NearMiss — 保留距少数类最近的多数类样本更智能nmNearMiss(version3,n_neighbors_ver33)X_res,y_resnm.fit_resample(X_train,y_train)# Tomek Links — 只移除与少数类最近且重叠的多数类样本温和欠采样tlTomekLinks(sampling_strategymajority)X_res,y_restl.fit_resample(X_train,y_train)方法优点缺点RandomUnderSampler简单快速丢失多数类信息NearMiss保留边界信息计算距离开销大TomekLinks温和信息损失小降采样幅度有限2. 过采样Oversampling— 增加少数类fromimblearn.over_samplingimportRandomOverSampler,SMOTE,ADASYN,BorderlineSMOTE# 随机过采样简单复制rosRandomOverSampler(sampling_strategy0.5,random_state42)X_res,y_resros.fit_resample(X_train,y_train)# SMOTE — 合成少数类样本最常用fromimblearn.over_samplingimportSMOTE smoteSMOTE(sampling_strategy0.5,k_neighbors5,random_state42)X_res,y_ressmote.fit_resample(X_train,y_train)# BorderlineSMOTE — 只在边界区域合成更精准bsmoteBorderlineSMOTE(kindborderline-1,random_state42)X_res,y_resbsmote.fit_resample(X_train,y_train)# ADASYN — 自适应合成更关注难分类的少数类样本adasynADASYN(sampling_strategy0.5,random_state42)X_res,y_resadasyn.fit_resample(X_train,y_train)SMOTE 原理图解多数类: ○ 少数类: ● 合成样本: ✦ ○ ○ ○ ○ ○ ○ → ○ ✦ ○ ○ ●──● ○ ○ ●──✦──● ○ ○ ○ ✦ ○ ○ ○ ○ ○ 在两个少数类样本连线上随机插值生成新样本方法优点缺点RandomOverSampler简单复制样本 → 过拟合SMOTE合成新样本泛化更好可能生成噪声样本BorderlineSMOTE聚焦决策边界边界样本少时效果受限ADASYN自适应难度对噪声敏感3. 混合采样推荐fromimblearn.combineimportSMOTETomek,SMOTEENN# SMOTE Tomek Links先过采样再清理重叠comboSMOTETomek(sampling_strategy0.5,random_state42)X_res,y_rescombo.fit_resample(X_train,y_train)# SMOTE ENN先过采样再清理噪声更激进combo2SMOTEENN(sampling_strategy0.5,random_state42)X_res,y_rescombo2.fit_resample(X_train,y_train)策略二算法层面 — 代价敏感学习1. 类别权重调整fromsklearn.ensembleimportRandomForestClassifierfromsklearn.linear_modelimportLogisticRegressionfromsklearn.svmimportSVC# 方式1class_weightbalanced — 自动按反比例加权rfRandomForestClassifier(class_weightbalanced,random_state42)lrLogisticRegression(class_weightbalanced,random_state42)# 方式2手动指定权重weights{0:1,1:50}# 少数类权重 50 倍rfRandomForestClassifier(class_weightweights,random_state42)# 方式3XGBoost 的 scale_pos_weightimportxgboostasxgb neg_count(y_train0).sum()pos_count(y_train1).sum()modelxgb.XGBClassifier(scale_pos_weightneg_count/pos_count,# 自动平衡max_depth6,n_estimators200,random_state42)2. 自定义损失函数importxgboostasxgbimportnumpyasnpdefweighted_logloss(y_pred,y_true):少数类漏报惩罚更重y_truey_true.get_label()weightnp.where(y_true1,10.0,1.0)# 少数类权重 10 倍gradweight*(y_pred-y_true)hessweight*y_pred*(1-y_pred)returngrad,hess modelxgb.XGBClassifier(objweighted_logloss,n_estimators200)策略三集成层面 — 专用不平衡集成算法fromimblearn.ensembleimport(BalancedBaggingClassifier,BalancedRandomForestClassifier,EasyEnsembleClassifier,RUSBoostClassifier)# BalancedBagging — 每棵树训练前自动欠采样bbcBalancedBaggingClassifier(estimatorRandomForestClassifier(n_estimators10),n_estimators50,sampling_strategy0.5,random_state42)# BalancedRandomForest — 随机森林的不平衡版本brfBalancedRandomForestClassifier(n_estimators200,sampling_strategyauto,random_state42)# EasyEnsemble — 多组欠采样 多个基分类器 投票eecEasyEnsembleClassifier(n_estimators10,sampling_strategy0.5,random_state42)# RUSBoost — 欠采样 BoostingrusbRUSBoostClassifier(n_estimators200,sampling_strategy0.5,random_state42)四、完整实战流水线fromsklearn.model_selectionimporttrain_test_splitfromsklearn.metricsimportclassification_report,f1_scorefromimblearn.pipelineimportPipeline# 注意用 imblearn 的 Pipelinefromimblearn.combineimportSMOTETomekfromsklearn.ensembleimportRandomForestClassifier# 1. 划分数据重采样只在训练集上做X_train,X_test,y_train,y_testtrain_test_split(X,y,test_size0.2,stratifyy,random_state42# stratify 保证测试集分布一致)# 2. 构建流水线pipelinePipeline([(resampling,SMOTETomek(sampling_strategy0.5,random_state42)),(classifier,RandomForestClassifier(n_estimators200,class_weightbalanced_subsample,max_depth10,random_state42,n_jobs-1))])# 3. 训练pipeline.fit(X_train,y_train)# 4. 评估y_predpipeline.predict(X_test)print(classification_report(y_test,y_pred,target_names[多数类,少数类]))关键原则重采样只在训练集上做测试集保持原始分布五、阈值调整零成本提升不修改模型只调整分类阈值fromsklearn.metricsimportprecision_recall_curve# 获取预测概率y_probamodel.predict_proba(X_test)[:,1]# 默认阈值 0.5 → 降低阈值可提升召回率importnumpyasnp y_pred_custom(y_proba0.3).astype(int)# 降低阈值到 0.3# 自动寻找最优阈值最大化 F1precisions,recalls,thresholdsprecision_recall_curve(y_test,y_proba)f1_scores2*precisions*recalls/(precisionsrecalls1e-8)best_thresholdthresholds[np.argmax(f1_scores)]print(f最优阈值:{best_threshold:.4f})y_pred_optimal(y_probabest_threshold).astype(int)六、策略选择决策树数据不平衡比 ├── 5:1轻度 │ └── class_weightbalanced 阈值调整最简单 ├── 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忽略业务代价根据漏报/误报的业务代价选择指标和阈值只用单一策略组合策略效果最佳重采样 类别权重 阈值调整