sklearn的Pipeline是否会将X和y同时传递给后续处理步骤?
问题根源
原生scikit-learn的Pipeline设计上只允许中间转换器修改特征矩阵X,不会向transform方法传入标签y,也不会处理转换器返回的标签值。同时你自定义的transform方法没有设置y的默认值,当sklearn默认调用transform(X)时就会触发缺少参数的TypeError。
解决方案1:使用imbalanced-learn的Pipeline(推荐,改动最小)
imbalanced-learn是sklearn生态的官方扩展库,它的Pipeline原生支持样本筛选、采样类操作,可以正确传递并更新y。
首先安装依赖:
pip install imbalanced-learn
分两步修改代码即可:
- 修正自定义转换器的逻辑和兼容性问题
import numpy as np import pandas as pd from sklearn.base import BaseEstimator, TransformerMixin from sklearn.neighbors import LocalOutlierFactor from sklearn.preprocessing import StandardScaler class OutlierExtractor1(BaseEstimator, TransformerMixin): def __init__(self, novelty=True): self.scaler = StandardScaler() # 开启novelty模式,支持测试集推理 self.lof = LocalOutlierFactor(metric='euclidean', novelty=novelty) self.is_inlier = None def fit(self, X, y=None): # 训练阶段仅用训练集拟合标准化和LOF模型 X_norm = self.scaler.fit_transform(X) self.lof.fit(X_norm) # 记录训练集的内样本索引 self.is_inlier = self.lof.predict(X_norm) == 1 return self def transform(self, X, y=None): X_norm = self.scaler.transform(X) # 只有传入y的时候才做样本过滤(训练阶段) if y is not None: # 训练集用fit阶段预计算的内样本索引 if len(X) == len(self.is_inlier): inlier = self.is_inlier else: # 验证阶段计算当前输入的内样本索引 inlier = self.lof.predict(X_norm) == 1 return X.loc[inlier, :], y.loc[inlier] # 预测阶段没有y,直接返回原X,不过滤样本 return X
- 将sklearn的Pipeline替换为imblearn的Pipeline
from imblearn.pipeline import Pipeline # 仅修改此处导入即可 from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV from sklearn.metrics import fbeta_score, make_scorer space = {'rf__max_depth': [9, 11, 12, 14], 'rf__n_estimators': [80, 90, 100]} pipe = Pipeline([('outliers', OutlierExtractor1()), ('rf', RandomForestClassifier(criterion = 'entropy', min_samples_split = 4, min_samples_leaf = 2, min_impurity_decrease = 0.01, random_state=0))]) ftwo_scorer = make_scorer(fbeta_score, beta=2) search = GridSearchCV(pipe, param_grid = space, scoring = ftwo_scorer, cv = 4, return_train_score = True, verbose = 1) search.fit(X = downsampled, y = target) pd.DataFrame(search.cv_results_)
解决方案2:无额外依赖,封装模型逻辑
如果不想引入第三方库,可以把离群值移除逻辑封装到分类器的fit方法中,不需要修改中间转换器,完全兼容原生sklearn生态:
from sklearn.ensemble import RandomForestClassifier from sklearn.neighbors import LocalOutlierFactor from sklearn.preprocessing import StandardScaler class RFWithOutlierFilter(RandomForestClassifier): def fit(self, X, y, sample_weight=None): # 先做离群值过滤 scaler = StandardScaler() X_norm = scaler.fit_transform(X) lof = LocalOutlierFactor(metric='euclidean') is_inlier = lof.fit_predict(X_norm) == 1 X_filtered = X.loc[is_inlier, :] y_filtered = y.loc[is_inlier] # 再调用原生fit方法 return super().fit(X_filtered, y_filtered, sample_weight=sample_weight) # 后续Pipeline不需要加outliers步骤 pipe = Pipeline([ ('rf', RFWithOutlierFilter(criterion = 'entropy', min_samples_split = 4, min_samples_leaf = 2, min_impurity_decrease = 0.01, random_state=0)) ])
该方案的缺点是离群值过滤逻辑和模型耦合在一起,如果要更换基础模型需要重新封装。
注意事项
- 离群值过滤仅应在训练阶段执行,测试/预测阶段不能删除样本,否则会导致评估指标计算错误,也不符合线上推理的实际场景。
- LocalOutlierFactor默认的
novelty=False模式仅支持对训练集做离群检测,要对新样本做检测必须设置novelty=True。
内容的提问来源于stack exchange,提问作者Ana Carolina Rodrigues
相关产品推荐
相关产品推荐

