机器学习中不平衡数据问题的解决方案与实践
2026/4/23 9:14:17 网站建设 项目流程

1. 不平衡数据问题的本质与挑战

当我们在处理分类问题时,经常会遇到某些类别的样本数量远多于其他类别的情况。比如在信用卡欺诈检测中,正常交易可能占总样本的99.9%,而欺诈交易仅占0.1%。这种数据分布极端不均衡的情况,我们称之为"类别不平衡问题"。

传统机器学习算法在这种场景下会表现出明显的局限性。以准确率为例,一个简单的"总是预测多数类"的模型在欺诈检测中就能达到99.9%的准确率,但这显然毫无实际价值。更糟糕的是,少数类样本往往才是我们真正关心的(如欺诈病例、设备故障等)。

我在金融风控项目中曾遇到一个典型案例:原始数据中正负样本比例达到10000:1,直接训练的模型对所有样本都预测为负类,AUC值只有0.5。经过调整后,模型成功捕捉到了85%的真实欺诈案例,这就是处理不平衡数据的价值所在。

2. 数据层面的解决方法

2.1 过采样技术:SMOTE及其变种

SMOTE(Synthetic Minority Over-sampling Technique)是最经典的过采样方法之一。它的核心思想不是简单复制少数类样本,而是在特征空间中合成新的样本。

具体实现步骤:

  1. 对于少数类中的每个样本x,找到其k个最近邻(通常k=5)
  2. 随机选择其中一个近邻x'
  3. 在x和x'连线上随机选取一点作为新样本
  4. 重复上述过程直到达到需要的样本量

Python实现示例:

from imblearn.over_sampling import SMOTE sm = SMOTE(sampling_strategy='auto', k_neighbors=5) X_res, y_res = sm.fit_resample(X_train, y_train)

注意事项:SMOTE在高维数据上效果可能下降,且可能生成不合理的样本。改进版本如Borderline-SMOTE、ADASYN针对这些问题进行了优化。

2.2 欠采样技术:NearMiss与Tomek Links

欠采样通过减少多数类样本来平衡数据集。常见的NearMiss方法有三种变体:

  • NearMiss-1:选择与少数类样本平均距离最近的多数类样本
  • NearMiss-2:选择与少数类样本平均距离最远的多数类样本
  • NearMiss-3:为每个少数类样本保留指定数量的最近多数类样本

代码实现:

from imblearn.under_sampling import NearMiss nm = NearMiss(version=3) X_res, y_res = nm.fit_resample(X_train, y_train)

Tomek Links则识别并删除边界上"模棱两可"的多数类样本:

from imblearn.under_sampling import TomekLinks tl = TomekLinks() X_res, y_res = tl.fit_resample(X_train, y_train)

经验分享:欠采样会丢失信息,适合数据量极大的场景。建议先保留所有少数类样本,再对多数类进行欠采样。

3. 算法层面的解决方案

3.1 代价敏感学习

通过为不同类别的误分类赋予不同代价,使算法更关注少数类。以逻辑回归为例,我们可以调整类别权重:

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

对于自定义权重:

class_weights = {0: 1, 1: 10} # 少数类权重设为10倍 model = LogisticRegression(class_weight=class_weights)

3.2 集成学习方法

3.2.1 EasyEnsemble

通过多次对多数类下采样并集成多个模型:

from imblearn.ensemble import EasyEnsembleClassifier eec = EasyEnsembleClassifier(n_estimators=10) eec.fit(X_train, y_train)
3.2.2 BalancedRandomForest

在每棵决策树的构建过程中进行欠采样:

from imblearn.ensemble import BalancedRandomForestClassifier brf = BalancedRandomForestClassifier(n_estimators=100) brf.fit(X_train, y_train)

实测发现:在信用卡欺诈数据集上,BalancedRandomForest的F1-score比普通随机森林高出30%。

4. 评估指标的选择

在不平衡数据场景下,准确率完全不可靠。应该使用以下指标:

指标公式适用场景
精确率TP/(TP+FP)关注假阳性代价(如垃圾邮件分类)
召回率TP/(TP+FN)关注漏检代价(如疾病诊断)
F1-score2*(Precision*Recall)/(Precision+Recall)平衡精确率和召回率
AUC-ROCROC曲线下面积整体分类性能评估
PR曲线精确率-召回率曲线极度不平衡数据更适用

Python实现示例:

from sklearn.metrics import classification_report print(classification_report(y_test, y_pred, target_names=['多数类', '少数类']))

5. 实战案例:信用卡欺诈检测

5.1 数据准备

使用Kaggle信用卡欺诈数据集:

import pandas as pd from sklearn.model_selection import train_test_split data = pd.read_csv('creditcard.csv') X = data.drop('Class', axis=1) y = data['Class'] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, stratify=y, random_state=42)

5.2 模型训练与比较

我们比较三种处理方案:

  1. 原始数据 + 普通逻辑回归
  2. SMOTE过采样 + 随机森林
  3. BalancedRandomForest
from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from imblearn.pipeline import make_pipeline # 方案1 lr = LogisticRegression(max_iter=1000) lr.fit(X_train, y_train) # 方案2 pipeline = make_pipeline( SMOTE(), RandomForestClassifier() ) pipeline.fit(X_train, y_train) # 方案3 brf = BalancedRandomForestClassifier() brf.fit(X_train, y_train)

5.3 结果对比

方法准确率召回率(少数类)F1-score(少数类)AUC
原始LR0.9990.610.720.805
SMOTE+RF0.9980.780.850.972
BalancedRF0.9970.820.870.981

关键发现:虽然准确率略有下降,但少数类的召回率和F1-score显著提升,这正是我们需要的。

6. 进阶技巧与注意事项

6.1 组合采样技术

SMOTE与Tomek Links的组合往往能取得更好效果:

from imblearn.combine import SMOTETomek smt = SMOTETomek() X_res, y_res = smt.fit_resample(X_train, y_train)

6.2 阈值调整

默认0.5的分类阈值可能不是最优的。我们可以通过PR曲线找到最佳阈值:

from sklearn.metrics import precision_recall_curve probs = model.predict_proba(X_test)[:, 1] precisions, recalls, thresholds = precision_recall_curve(y_test, probs) # 找到使F1-score最大的阈值 f1_scores = 2 * (precisions * recalls) / (precisions + recalls) best_threshold = thresholds[np.argmax(f1_scores)]

6.3 常见陷阱

  1. 数据泄露:在交叉验证前进行采样会导致数据泄露,应该只在训练折叠内进行采样
  2. 过度拟合少数类:SMOTE可能生成不现实的样本,导致模型过拟合
  3. 评估不当:在测试集上应用采样方法会扭曲真实性能评估

正确做法示例:

from sklearn.model_selection import cross_val_score from imblearn.pipeline import make_pipeline pipeline = make_pipeline( SMOTE(), LogisticRegression() ) scores = cross_val_score(pipeline, X, y, scoring='f1')

7. 工具与资源推荐

7.1 Python库

  • imbalanced-learn:专为解决不平衡问题而设计
  • scikit-learn:提供基本的类别权重设置
  • xgboost/lighgbm:内置处理不平衡数据的参数

7.2 实用代码片段

类别权重自动计算:

from sklearn.utils.class_weight import compute_class_weight classes = np.unique(y_train) weights = compute_class_weight('balanced', classes=classes, y=y_train) class_weights = dict(zip(classes, weights))

7.3 可视化工具

绘制类别分布:

import seaborn as sns sns.countplot(x=y_train) plt.title('Class Distribution') plt.show()

绘制PR曲线:

from sklearn.metrics import plot_precision_recall_curve disp = plot_precision_recall_curve(model, X_test, y_test) disp.ax_.set_title('Precision-Recall Curve')

在实际项目中,我通常会尝试3-4种不同的方法,然后选择在验证集上表现最好的方案。记住没有放之四海而皆准的解决方案,关键是根据具体业务需求和数据特点选择合适的方法组合。

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

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

立即咨询