You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确将自定义类加入Scikit-learn Pipeline?

问题解决:自定义Scikit-learn Transformer报错AttributeError: 'NoneType' object has no attribute 'transform'

错误原因

你遇到的错误是因为自定义category_encoder类的编码器实例在transform阶段为None,说明fit阶段没有正确初始化编码器。核心触发点:

  • 自定义类的属性名与__init__参数名不一致(用encoder作为参数名,却将值存在self.encoder_name中),Scikit-learn的克隆机制可能导致参数传递异常,使得fit中的条件判断不成立。
  • 未处理非预期的编码器名称,当参数不符合时,编码器未被初始化,直接进入transform阶段导致报错。

修复后的代码

修改自定义Transformer类

class category_encoder(BaseEstimator, TransformerMixin):
    def __init__(self, encoder):
        # 保持参数名与属性名一致,符合Scikit-learn规范
        self.encoder = encoder
        # 用下划线标记私有属性,存储实际编码器实例
        self._encoder = None
        
    def fit(self, X, y=None):
        # 根据指定编码器初始化并拟合
        if self.encoder == "ordinalencoder":
            self._encoder = OrdinalEncoder()
            self._encoder.fit(X)
        elif self.encoder == "onehotencoder":
            self._encoder = OneHotEncoder(sparse_output=False, drop='first')
            self._encoder.fit(X)
        else:
            # 对不支持的编码器抛出明确错误
            raise ValueError(f"不支持的编码器类型: {self.encoder}")
        return self
    
    def transform(self, X, y=None):
        # 先检查是否已拟合
        if self._encoder is None:
            raise ValueError("编码器未拟合,请先调用fit()方法")
        
        transformed_data = self._encoder.transform(X)
        # 处理不同编码器的列名问题
        if isinstance(self._encoder, OneHotEncoder):
            columns = self._encoder.get_feature_names_out(X.columns)
        else:
            columns = X.columns
            
        return pd.DataFrame(transformed_data, columns=columns)

完善预处理流程

将未使用的oe_features加入预处理,并修正Pipeline定义:

numeric_features = ["age", "fare"]
ohe_features = ["sex"]
oe_features = ["embarked", "pclass"]

numeric = Pipeline(steps=[
    ("imputer", SimpleImputer()), 
    ("scaler", StandardScaler()), 
    ("transformer", MinMaxScaler())
])

# 分别定义序数编码和独热编码的Pipeline
categoric_oe = Pipeline(steps=[("encoder_oe", category_encoder(encoder="ordinalencoder"))])
categoric_ohe = Pipeline(steps=[("encoder_ohe", category_encoder(encoder="onehotencoder"))])

preprocessor = ColumnTransformer(transformers=[
    ("num", numeric, numeric_features), 
    ("cat_oe", categoric_oe, oe_features),
    ("cat_ohe", categoric_ohe, ohe_features)
])

# 后续GridSearchCV和Pipeline代码保持不变
param_grid = [
    {"classifier": [LogisticRegression()], 
     "classifier__penalty": ["l2"], 
     "classifier__C": np.logspace(0, 4, 10)}, 
    {"classifier": [RandomForestClassifier()],
     "classifier__n_estimators": [10, 100, 1000], 
     "classifier__max_features": [1, 2, 3]}
]

full_pipe = Pipeline(steps=[
    ("preprocessor", preprocessor), 
    ("feature_eliminator", VarianceThreshold()), 
    ("classifier", LogisticRegression())
])

model = GridSearchCV(
    full_pipe, 
    param_grid, 
    cv=5, verbose=0, 
    n_jobs = -1, 
    scoring="accuracy"
)

model.fit(X,y)

额外优化点

  • 简化空值处理代码:drop_index = X[X.isna().any(axis=1)].index,替代原来的冗余写法。
  • 独热编码添加sparse_output=False和drop='first',避免稀疏矩阵并防止多重共线性。

内容的提问来源于stack exchange,提问作者brKdgl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 09:19:49