如何在Scikit-Learn Pipeline中正确集成SMOTE处理类别不平衡
SMOTE接入Scikit-Learn生态Pipeline的正确方式
核心规则(必守,否则结果无效)
- 禁止在
train_test_split之前应用SMOTE:否则测试集样本会混入训练采样过程,造成严重数据泄露,验证结果完全失真。 - 不要使用Scikit-Learn原生
Pipeline承载SMOTE:SMOTE是同时修改特征矩阵X和标签y的重采样组件,sklearn原生Pipeline仅支持前置步骤输出单份特征矩阵,无法兼容重采样逻辑,需要使用imbalanced-learn库提供的Pipeline。该Pipeline会自动控制SMOTE仅在训练fit阶段对训练集生效,预测阶段不会对测试集做任何采样处理,从机制上规避泄露。 - 禁止在预处理步骤之前插入SMOTE:SMOTE基于样本距离生成少数类新样本,未填充的缺失值、未编码的分类特征、未缩放的数值特征都会导致距离计算完全失效,生成的样本无实际意义。
正确的Pipeline步骤顺序:列预处理器(缺失值填充/分类编码/数值缩放) → SMOTE过采样 → 分类模型
现有代码的其他问题
- 任务类型错配:当前是欺诈检测二分类任务,模型列表中混入了
RandomForestRegressor回归模型,不适用于分类场景。 - 评估指标错配:
r2_score是回归任务专属指标,不适合分类任务,尤其类别不平衡场景下,应该优先使用F1、Recall、ROC-AUC这类对少数类敏感的分类指标。 - 数据集拆分未做分层:原拆分逻辑没有保证训练、测试集的类别比例与原数据集一致,容易引入额外偏差。
修正后可运行代码
首先安装必要依赖:pip install imbalanced-learn
from collections import Counter from sklearn.model_selection import train_test_split from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler, OrdinalEncoder from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from imblearn.over_sampling import SMOTE # 导入imblearn的Pipeline from imblearn.pipeline import Pipeline as ImbPipeline from sklearn.ensemble import RandomForestClassifier from sklearn.linear_model import LogisticRegression from sklearn.tree import DecisionTreeClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn.svm import LinearSVC from sklearn.metrics import f1_score, recall_score, roc_auc_score df_n = df[['user_id','signup_day', 'signup_month', 'signup_year', 'purchase_day', 'purchase_month', 'purchase_year','purchase_value', 'source','browser','sex','age', 'is_fraud']] X = df_n.drop(['is_fraud'], axis=1) y = df_n.is_fraud # 拆分时增加stratify参数,保证训练测试集类别分布一致 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) print(Counter(y_train)) # 输出Counter({0: 95844, 1: 9934}) numeric_transformer = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='mean')), ('scaler', StandardScaler()) ]) categorical_transformer = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='constant', fill_value='missing')), ('encoder', OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=-1)) ]) numeric_features = ['user_id','signup_day', 'signup_month', 'signup_year', 'purchase_day', 'purchase_month', 'purchase_year','purchase_value', 'age'] categorical_features = ['source', 'browser', 'sex'] preprocessor = ColumnTransformer( transformers=[ ('numeric', numeric_transformer, numeric_features), ('categorical', categorical_transformer, categorical_features) ]) # 替换为全部分类模型 classifiers = [ RandomForestClassifier(random_state=42, n_jobs=-1), LogisticRegression(max_iter=1000, random_state=42, n_jobs=-1), DecisionTreeClassifier(random_state=42), KNeighborsClassifier(n_jobs=-1), LinearSVC(random_state=42, dual=False) ] for clf in classifiers: # 用imblearn的Pipeline构建流程,SMOTE插入在预处理器之后、分类器之前 pipeline = ImbPipeline(steps = [ ('preprocessor', preprocessor), ('smote', SMOTE(random_state=42)), ('classifier', clf) ]) model = pipeline.fit(X_train, y_train) predictions = model.predict(X_test) print(clf) # 重点关注少数类(欺诈标签=1)的识别效果 print(f"F1 Score: {f1_score(y_test, predictions, pos_label=1):.4f}") print(f"Fraud Class Recall: {recall_score(y_test, predictions, pos_label=1):.4f}") # 支持概率输出的模型计算ROC AUC if hasattr(clf, "predict_proba"): proba = model.predict_proba(X_test)[:,1] print(f"ROC AUC: {roc_auc_score(y_test, proba):.4f}") print("-"*60)
额外优化建议
- 如果普通SMOTE效果不理想,可以尝试SMOTE的变体如BorderlineSMOTE、SVMSMOTE,这类方法对边界少数类样本的生成质量更高,更适配欺诈检测这类正负类边界模糊的场景。
- 可以搭配模型的
class_weight='balanced'参数使用,从算法层面进一步提升对少数类的权重,多数场景下效果会优于单独使用SMOTE。 user_id属于用户唯一标识,不具备泛化预测价值,建模时可以考虑从特征列表中移除,避免模型学到无意义的ID对应规律。
内容的提问来源于stack exchange,提问作者Anastasia_data
相关产品推荐
相关产品推荐

