一、非平衡数据:分类问题的"隐形杀手"
咱们做数据挖掘分类任务时,经常会遇到这样的尴尬:正样本只有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的值
五、实战中的组合拳
在实际电商用户流失预测项目中,我用了组合方案:
- 先用SMOTE生成部分新样本
- 再用Tomek Links清洗边界噪声
- 最后用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)))
六、技术选型指南
- 小数据集(<10k样本):SMOTE + 代价敏感学习
- 大数据集:欠采样 + 集成方法
- 高维数据:先做PCA再采样
- 在线学习:动态调整类别权重
注意事项:
- SMOTE不适合高维稀疏数据(如文本)
- 欠采样会丢失信息,要用装袋(Bagging)弥补
- 代价敏感学习需要业务专家确定权重
- 评估指标要跟业务方对齐
七、未来发展方向
- 深度生成模型(如GAN)用于过采样
- 在线学习中的自适应采样策略
- 结合因果推断的样本加权方法
- 自动化机器学习(AutoML)中的不平衡处理
记住,没有银弹。我在银行反欺诈项目中的最佳方案,换到医疗诊断可能就不好使。关键是要理解业务需求,然后做实验、做实验、再做实验!
评论