一、非平衡数据:分类问题的"隐形杀手"

咱们做数据挖掘分类任务时,经常会遇到这样的尴尬:正样本只有100条,负样本却有10000条。这种数据不平衡的情况,就像让一个裁判只看过10场足球比赛就去执法世界杯,结果肯定不靠谱。我在实际项目中就遇到过信用卡欺诈检测的场景,正常交易占比99.9%,欺诈交易只有0.1%,这时候传统分类算法直接"躺平"——把所有样本都预测为正常交易,准确率照样99.9%!

举个Python+Scikit-learn的例子,我们模拟下这种情况:

from sklearn.datasets import make_classification
from collections import Counter

# 生成极度不平衡数据集(1:100)
X, y = make_classification(n_samples=10100, weights=[0.99], flip_y=0, random_state=42)

# 查看类别分布
print("类别分布:", Counter(y))  # 输出:Counter({0: 10000, 1: 100})

# 用普通逻辑回归训练
from sklearn.linear_model import LogisticRegression
model = LogisticRegression().fit(X, y)
print("默认模型的准确率:", model.score(X, y))  # 输出:0.9900(但全是预测为0)

看到没?这个"准确率"完全是个美丽的谎言。这时候我们就需要专门的解决方案了。

二、解决方案一:数据层面的手术刀

2.1 过采样:少数派的逆袭

SMOTE算法就像复印机,但不是简单复制,而是智能生成新样本。比如在医疗诊断中,我们只有少量癌症患者数据,SMOTE会在特征空间中找到相似样本,然后在其连线上生成新样本。

from imblearn.over_sampling import SMOTE

# 应用SMOTE过采样
smote = SMOTE(random_state=42)
X_res, y_res = smote.fit_resample(X, y)

print("SMOTE后分布:", Counter(y_res))  # 输出:Counter({0: 10000, 1: 10000})

# 重新训练模型
balanced_model = LogisticRegression().fit(X_res, y_res)
print("平衡后的准确率:", balanced_model.score(X_res, y_res))  # 注意:这里要用其他评估指标

2.2 欠采样:多数派的瘦身计划

随机欠采样就像抽奖,随机扔掉部分多数类样本。在金融风控中,我们可能保留所有欺诈交易(正样本),然后随机抽取等量的正常交易。

from imblearn.under_sampling import RandomUnderSampler

# 随机欠采样
under_sampler = RandomUnderSampler(random_state=42)
X_under, y_under = under_sampler.fit_resample(X, y)

print("欠采样后分布:", Counter(y_under))  # 输出:Counter({0: 100, 1: 100})

三、解决方案二:算法层面的特调

3.1 代价敏感学习:给错误定价

就像法院对不同罪行量刑不同,我们可以让算法更"害怕"误诊癌症患者。在Python中,class_weight参数就是干这个的:

# 设置类别权重(惩罚误判少数类)
weighted_model = LogisticRegression(class_weight={0:1, 1:100}).fit(X, y)

# 预测概率更能说明问题
probs = weighted_model.predict_proba(X)[:, 1]
print("少数类预测概率示例:", probs[:5])  # 可以看到模型现在更关注少数类

3.2 集成方法:群众的智慧

比如EasyEnsemble,它像多个专家会诊,每个专家只看部分多数类样本:

from imblearn.ensemble import EasyEnsembleClassifier

# 使用10个子模型
eec = EasyEnsembleClassifier(n_estimators=10, random_state=42)
eec.fit(X, y)

# 评估时要注意使用AUC等指标
from sklearn.metrics import roc_auc_score
print("集成模型的AUC:", roc_auc_score(y, eec.predict_proba(X)[:, 1]))

四、解决方案三:评估指标的变革

4.1 抛弃准确率,拥抱新指标

在欺诈检测中,我们更关心:

  • 召回率(Recall):抓到了多少真骗子
  • 精确率(Precision):抓的人里有多少真骗子
  • F1分数:两者的调和平均
  • AUC-ROC:整体区分能力
from sklearn.metrics import classification_report

# 用平衡后的模型做预测
y_pred = balanced_model.predict(X_test)

# 完整的评估报告
print(classification_report(y_test, y_pred, target_names=["正常", "欺诈"]))
"""
输出示例:
              precision  recall  f1-score  support
        正常       0.99      0.98      0.99      2000
        欺诈       0.15      0.25      0.19        20
"""

4.2 阈值调整:改变判决标准

默认0.5的阈值可能不合适,我们可以找最佳阈值:

from sklearn.metrics import precision_recall_curve
import numpy as np

# 获取预测概率
y_scores = balanced_model.predict_proba(X_test)[:, 1]

# 计算各阈值下的指标
precisions, recalls, thresholds = precision_recall_curve(y_test, y_scores)

# 找到使F1最大的阈值
f1_scores = 2 * (precisions * recalls) / (precisions + recalls)
best_threshold = thresholds[np.argmax(f1_scores)]
print("最佳阈值:", best_threshold)  # 可能是0.3等非0.5的值

五、实战中的组合拳

在实际电商用户流失预测项目中,我用了组合方案:

  1. 先用SMOTE生成部分新样本
  2. 再用Tomek Links清洗边界噪声
  3. 最后用XGBoost+类别权重训练
from imblearn.combine import SMOTETomek
from xgboost import XGBClassifier

# 组合采样
smote_tomek = SMOTETomek(random_state=42)
X_resampled, y_resampled = smote_tomek.fit_resample(X_train, y_train)

# XGBoost训练
xgb = XGBClassifier(scale_pos_weight=100, eval_metric='aucpr')
xgb.fit(X_resampled, y_resampled)

# 业务更关注召回率
print("测试集召回率:", recall_score(y_test, xgb.predict(X_test)))

六、技术选型指南

  1. 小数据集(<10k样本):SMOTE + 代价敏感学习
  2. 大数据集:欠采样 + 集成方法
  3. 高维数据:先做PCA再采样
  4. 在线学习:动态调整类别权重

注意事项:

  • SMOTE不适合高维稀疏数据(如文本)
  • 欠采样会丢失信息,要用装袋(Bagging)弥补
  • 代价敏感学习需要业务专家确定权重
  • 评估指标要跟业务方对齐

七、未来发展方向

  1. 深度生成模型(如GAN)用于过采样
  2. 在线学习中的自适应采样策略
  3. 结合因果推断的样本加权方法
  4. 自动化机器学习(AutoML)中的不平衡处理

记住,没有银弹。我在银行反欺诈项目中的最佳方案,换到医疗诊断可能就不好使。关键是要理解业务需求,然后做实验、做实验、再做实验!