Python分类前如何平衡类别?求助类不平衡处理方法与库
嘿,我太懂你这种挫败感了——类别不平衡绝对是分类任务里最磨人的问题之一,哪怕用了StratifiedKFold保证每折的类别分布,结果还是拉胯的情况太常见了。别担心,咱们从数据处理、算法调整、评估指标三个维度来拆解解决方案,都是实战里能用的硬货:
你之前没找到合适的库?那imblearn绝对是你的救星——它是专门针对不平衡数据集的工具库,和sklearn完美兼容。注意:所有重采样操作只能在训练集上做,绝对不能碰测试集! 不然会严重数据泄露。
过采样(给少数类“凑数”):最常用的是SMOTE,它会在少数类样本的特征空间里生成新的合成样本,避免简单重复带来的过拟合。
示例代码:from imblearn.over_sampling import SMOTE # 只在训练集上应用 X_train_resampled, y_train_resampled = SMOTE(random_state=42).fit_resample(X_train, y_train)如果担心边界样本干扰,可以试试
SMOTETomek(结合SMOTE和Tomek Links移除噪声样本)。欠采样(给多数类“瘦身”):当数据集特别大的时候,欠采样效率更高,比如
RandomUnderSampler随机去掉多数类样本,或者NearMiss保留和少数类更相关的多数类样本。
示例代码:from imblearn.under_sampling import RandomUnderSampler X_train_resampled, y_train_resampled = RandomUnderSampler(random_state=42).fit_resample(X_train, y_train)混合采样:比如
SMOTEENN,先过采样少数类,再用ENN算法移除那些被误分类的样本,兼顾数量平衡和样本质量。
不想改数据的话,直接给分类器加类别权重或者用专门针对不平衡数据的模型:
给sklearn分类器加class_weight:大部分sklearn的分类器都支持
class_weight参数,比如'balanced'会自动根据类别比例调整权重,让模型更关注少数类。
示例:from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier # 逻辑回归 lr = LogisticRegression(class_weight='balanced', random_state=42) # 随机森林,还可以用balanced_subsample在每棵树的采样阶段调整权重 rf = RandomForestClassifier(class_weight='balanced_subsample', random_state=42)用树模型的专属参数:XGBoost、LightGBM、CatBoost这些进阶树模型都有针对不平衡数据的参数:
- XGBoost:
scale_pos_weight = 多数类样本数 / 少数类样本数 - LightGBM:
is_unbalance=True或者scale_pos_weight - CatBoost:
auto_class_weights='Balanced'
- XGBoost:
类别不平衡时,准确率完全是个“骗人”的指标——比如99%的样本是多数类,模型全猜多数类也能有99%的准确率,但完全没抓到少数类。你应该用这些指标:
- F1分数:精确率和召回率的调和平均,兼顾查准和查全
- 召回率(Recall):衡量少数类被正确识别的比例,对不平衡任务尤为重要
- PR-AUC:精确率-召回率曲线下的面积,比ROC-AUC更能反映不平衡数据下的模型性能
- sklearn里可以用
classification_report一键输出这些指标:from sklearn.metrics import classification_report print(classification_report(y_test, y_pred))
把重采样和交叉验证结合起来,避免数据泄露:
from sklearn.model_selection import StratifiedKFold from imblearn.over_sampling import SMOTE from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import f1_score # 初始化分层交叉验证 skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) smote = SMOTE(random_state=42) # 循环每一轮交叉验证 for train_idx, test_idx in skf.split(X, y): X_train, X_test = X.iloc[train_idx], X.iloc[test_idx] y_train, y_test = y.iloc[train_idx], y.iloc[test_idx] # 只对训练集做过采样 X_train_resampled, y_train_resampled = smote.fit_resample(X_train, y_train) # 训练带权重的模型 model = RandomForestClassifier(class_weight='balanced', random_state=42) model.fit(X_train_resampled, y_train_resampled) # 用F1分数评估 y_pred = model.predict(X_test) print(f"当前折F1分数: {f1_score(y_test, y_pred):.4f}")
先从调整评估指标开始,看看模型的真实表现,再尝试数据重采样或者调整模型权重,应该能看到明显的提升!
内容的提问来源于stack exchange,提问作者dblanco

