
sklearn 1.3 实战3种分层抽样策略解决类别不均衡难题当你的数据集里正样本占比90%负样本只有10%时直接随机划分训练测试集会带来什么后果上周我们团队在金融风控项目中就遇到了这个典型问题——随机划分后测试集中高风险用户仅占5%导致模型评估严重失真。本文将分享如何用sklearn的stratify参数及其进阶技巧解决这一痛点。1. 为什么类别不均衡时需要分层抽样上周三下午团队新来的数据科学家小李兴奋地跑来说我的XGBoost模型在测试集上准确率98%但当我们把模型部署到生产环境后实际捕捉高风险交易的能力却不足60%。这种反差正是随机划分陷阱的典型表现。假设原始数据分布如下类别样本数占比高风险用户1,00010%低风险用户9,00090%使用普通train_test_split后可能出现from sklearn.model_selection import train_test_split # 不指定stratify的随机划分 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.3) print(测试集类别分布, pd.Series(y_test).value_counts(normalizeTrue))输出可能是低风险用户 0.92 高风险用户 0.08这种分布偏移会导致两个严重问题评估指标虚高模型只要全部预测为多数类就能获得虚假的高准确率少数类识别率低模型缺乏足够的少数类样本进行学习解决方案核心保持划分前后类别分布一致。这正是stratify参数的使命# 使用分层抽样 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, stratifyy # 关键参数 ) print(分层后的测试集分布, pd.Series(y_test).value_counts(normalizeTrue))此时输出将保持原始比例低风险用户 0.9 高风险用户 0.12. 三种分层策略深度对比实际项目中我们可能需要更灵活的分层策略。下面通过信贷审批场景的模拟数据演示三种方法2.1 基础分层法按原始比例首先生成模拟数据from sklearn.datasets import make_classification # 生成10000个样本10%正例 X, y make_classification( n_samples10000, weights[0.9], random_state42 )标准分层划分X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42 )验证分布一致性from collections import Counter print(原始分布, Counter(y)) print(训练集分布, Counter(y_train)) print(测试集分布, Counter(y_test))输出结果原始分布 Counter({0: 9000, 1: 1000}) 训练集分布 Counter({0: 7200, 1: 800}) # 800/800010% 测试集分布 Counter({0: 1800, 1: 200}) # 200/200010%2.2 自定义比例分层有时我们需要调整测试集中的少数类比例。比如在医疗诊断场景宁可多检查一些健康人假阳性也不能漏诊病人假阴性。实现方法import numpy as np def custom_stratified_split(X, y, test_size, minority_ratio): # 获取少数类索引 minority_idx np.where(y 1)[0] majority_idx np.where(y 0)[0] # 计算各集样本数 n_test_minority int(len(minority_idx) * test_size * minority_ratio) n_test_majority int(len(majority_idx) * test_size * (1 - minority_ratio)) # 随机选择 test_minority np.random.choice(minority_idx, n_test_minority, replaceFalse) test_majority np.random.choice(majority_idx, n_test_majority, replaceFalse) # 构建测试集 test_idx np.concatenate([test_minority, test_majority]) train_idx np.setdiff1d(np.arange(len(y)), test_idx) return X[train_idx], X[test_idx], y[train_idx], y[test_idx] # 测试集少数类占比提升到20% X_train, X_test, y_train, y_test custom_stratified_split( X, y, test_size0.2, minority_ratio0.2 ) print(自定义分层结果, Counter(y_test))输出自定义分层结果 Counter({0: 1440, 1: 360}) # 360/180020%2.3 多层级分层抽样当存在多个需要保持分布的类别时如用户等级风险标签可以使用GroupShuffleSplitfrom sklearn.model_selection import GroupShuffleSplit # 假设dataframe包含risk和user_level两列 groups df[user_level].astype(str) _ df[risk].astype(str) gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, test_idx next(gss.split(X, y, groupsgroups)) X_train, X_test X[train_idx], X[test_idx] y_train, y_test y[train_idx], y[test_idx]3. 可视化对比三种策略效果让我们用Matplotlib直观展示不同策略的划分结果import matplotlib.pyplot as plt fig, axes plt.subplots(1, 3, figsize(18, 5)) # 原始分布 axes[0].bar([多数类, 少数类], [9000, 1000], color[blue, red]) axes[0].set_title(原始数据分布 (90:10)) # 标准分层 axes[1].bar([多数类, 少数类], [7200, 800], color[lightblue, pink]) axes[1].set_title(标准分层 (训练集)) # 自定义分层 axes[2].bar([多数类, 少数类], [1440, 360], color[lightblue, pink]) axes[2].set_title(自定义分层 (测试集20%)) plt.tight_layout() plt.show()从图中可以清晰看出标准分层严格保持原始比例自定义分层可以人为调整测试集分布多层级分层能保持多个维度的分布一致性4. 何时使用哪种分层策略根据我们在金融、医疗、电商等领域的实战经验总结出以下决策指南场景特征推荐策略原因说明典型案例简单二分类保持原始分布基础分层法确保评估客观性信用卡欺诈检测需要提高少数类测试样本自定义比例分层增强少数类评估可靠性罕见疾病诊断多维度分类变量需保持分布多层级分层抽样避免多维分布偏移用户分群A/B测试时间序列数据按时间顺序划分保持时间连续性股票价格预测超参数调优阶段分层K折交叉验证充分利用数据模型选择与参数优化提示当使用自定义比例时建议在模型评估阶段使用加权指标如f1-score而非单纯准确率最后分享一个实际项目中的教训在电商用户流失预测中我们曾忽略地域分布的分层导致模型在北方省份的预测准确率比南方低15个百分点。后来添加地域维度的分层后区域差异缩小到3%以内。