使用imblearn Pipeline替代sklearn Pipeline时文本特征消失的解决方法
问题根因
文本特征丢失是三个逻辑问题叠加导致的,和imblearn本身兼容性无关:
- 嵌套的文本处理子Pipeline用了sklearn原生版本,内部自定义的
FunctionTransformer没配置特征名透传规则,imblearn Pipeline在采样前做特征元数据校验时,识别不到CountVectorizer动态生成的高维文本特征,会默认过滤掉没有合法元数据的特征块。 - 你给
SMOTENC传的分类特征索引是硬编码的[4,5,6,7],但ColumnTransformer的输出顺序是先拼分类特征、再拼文本特征,且OneHotEncoder会把每个分类特征展开成多列(比如3取值的特征会展开成2-3列),4个原始分类特征最终输出的列数远大于4,硬编码的索引会直接指到文本特征块,甚至在索引越界时直接截断后面的文本特征。 - 你提前在文本子Pipeline里把TF-IDF的稀疏输出转成了稠密数组,旧版本imblearn处理高维稠密输入时,会因为元数据长度和数组实际列数不匹配,丢弃长度校验不通过的特征块。
修正方案
按以下规则调整代码即可解决问题:
- 嵌套的文本处理子Pipeline也换成imblearn的Pipeline,保证全链路元数据传递逻辑一致
- 给自定义
FunctionTransformer补充特征名透传配置,让上层转换器能识别动态生成的文本特征 - 不要硬编码SMOTENC的分类特征索引,等预处理层拟合后自动计算分类特征的位置
- 把稀疏转稠密的步骤移到SMOTENC采样之后,避免采样阶段处理高维稠密数组触发校验错误
- 预处理阶段保持稀疏矩阵输出,减少内存占用
修正后的可运行代码如下:
from imblearn.pipeline import Pipeline from sklearn.preprocessing import OneHotEncoder, FunctionTransformer from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer from sklearn.compose import ColumnTransformer from sklearn.linear_model import SGDClassifier from imblearn.over_sampling import SMOTENC import numpy as np vectorizer_params = dict(ngram_range=(1, 2), min_df=200, max_df=0.8) categorical_features = ['F1','F2','F3','F4'] # sklearn版本低于1.2时,把sparse_output参数改为sparse=False categorical_transformer = OneHotEncoder(handle_unknown="ignore", sparse_output=False) textual_feature = ['F5'] # 嵌套子Pipeline统一使用imblearn的Pipeline text_transformer = Pipeline( steps=[ # 配置特征名透传规则 ("squeez", FunctionTransformer( lambda x: x.squeeze(), feature_names_out='one-to-one' )), ("vect", CountVectorizer(**vectorizer_params)), ("tfidf", TfidfTransformer()), # 移除此处转稠密数组的步骤,采样完成后再做转换 ] ) preprocessor = ColumnTransformer( transformers=[ ("cat", categorical_transformer, categorical_features), ("txt", text_transformer, textual_feature), ], sparse_threshold=1.0 # 预处理阶段保持稀疏输出,降低内存占用 ) # 动态计算分类特征索引,禁止硬编码 # 实际运行时将X_train替换为你的训练集特征即可 # base_prep = Pipeline([("preprocessor", preprocessor)]) # base_prep.fit(X_train, y_train) # cat_col_count = base_prep.named_steps['preprocessor'].named_transformers_['cat'].get_feature_names_out().shape[0] # cat_indices = list(range(cat_col_count)) # 调试阶段可临时替换为上面动态计算得到的实际索引值 cat_indices = list(range(4)) sgd_log_pipeline = Pipeline( [ ("preprocessor", preprocessor), ('smote', SMOTENC(random_state=11, categorical_features=cat_indices)), # 采样完成后再转稠密数组,适配分类器输入要求 ("toarray", FunctionTransformer( lambda x: x.toarray() if hasattr(x, 'toarray') else x, accept_sparse=True )), ("clf", SGDClassifier()), ] )
注意事项
- 绝对不要硬编码
categorical_features的索引:OneHotEncoder输出的列数由每个分类特征的不同取值数量决定,4个原始分类特征最终展开的列数基本不可能刚好是4,硬编码索引必然导致SMOTENC处理逻辑出错。 - 运行前检查
F5文本列的格式,确保列内所有值都是字符串类型,不存在空值被转成浮点数的情况,否则squeeze步骤会把数据转成通用对象数组,导致CountVectorizer报错。 - 如果内存足够,可以把
sparse_threshold设为0,让ColumnTransformer直接输出稠密数组,但会显著增加内存占用,不推荐在文本特征维度较高时使用。
内容的提问来源于stack exchange,提问作者Ahmad Abdel-Hafez
相关产品推荐
相关产品推荐

