自定义Scikit-learn TaxonomyEncoder在ColumnTransformer中遇NotFittedError求助
问题背景
需要处理具有层级结构的数值分类特征(如MCC码、邮政编码、NAICS行业代码):这类特征既不能当作有序特征处理,按纯分类特征(如独热编码)处理又需选择合适的层级粒度。基于此,实现了一个类似TargetEncoder的自定义转换器,用决策树回归器生成编码,通过cross_val_predict获取样本外预测避免过拟合。
原转换器代码如下:
from sklearn.tree import DecisionTreeRegressor from sklearn.base import TransformerMixin, BaseEstimator from sklearn.model_selection import cross_val_predict class TaxonomyEncoder(TransformerMixin, BaseEstimator): def __init__(self, n_leafs=10, cv=3): self.n_leafs = n_leafs self.cv = cv def fit(self, X, y=None): self.tree_ = DecisionTreeRegressor(max_leaf_nodes=self.n_leafs).fit(X,y) return self def transform(self, X): return self.tree_.predict(X).reshape(-1,1) def fit_transform(self, X, y=None): self.tree_ = DecisionTreeRegressor(max_leaf_nodes=self.n_leafs) return cross_val_predict(self.tree_, X, y, cv=self.cv).reshape(-1,1)
单独使用时fit+transform或直接fit_transform均正常,但放入ColumnTransformer后调用fit再transform会抛出错误:
NotFittedError: This DecisionTreeRegressor instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator.
错误原因
原fit_transform方法违反了Scikit-learn的API契约:该方法仅返回样本外预测结果,但将self.tree_赋值为未拟合的模型实例。当ColumnTransformer内部调用fit_transform后,后续transform会尝试使用这个未拟合的模型,从而触发错误;即使直接调用ColumnTransformer.fit,若转换器的状态被意外修改,也会导致类似问题。
同时,原fit_transform没有保证转换器在执行后处于拟合状态,不符合Scikit-learn对转换器的要求:调用fit_transform后必须可以正常执行transform。
解决方案
修改fit_transform方法,先通过父类的fit方法完成模型拟合(保证self.tree_处于可用状态),再返回cross_val_predict生成的样本外预测结果。这样既满足训练阶段用样本外编码避免过拟合的需求,又保证测试阶段transform能正常使用拟合好的模型。
修正后的完整代码:
from sklearn.tree import DecisionTreeRegressor from sklearn.base import TransformerMixin, BaseEstimator from sklearn.model_selection import cross_val_predict class TaxonomyEncoder(TransformerMixin, BaseEstimator): def __init__(self, n_leafs=10, cv=3): self.n_leafs = n_leafs self.cv = cv def fit(self, X, y=None): self.tree_ = DecisionTreeRegressor(max_leaf_nodes=self.n_leafs).fit(X,y) return self def transform(self, X): return self.tree_.predict(X).reshape(-1,1) def fit_transform(self, X, y=None): # 先完成模型拟合,确保transform时模型可用 super().fit(X, y) # 返回样本外的预测结果,避免训练过拟合 return cross_val_predict(self.tree_, X, y, cv=self.cv).reshape(-1,1)
验证测试
使用原复现示例测试:
import pandas as pd df = pd.DataFrame({'mcc':[3000,3500,7339], 'y':[0,0,1]}) # 单独使用正常 te = TaxonomyEncoder().fit(df[['mcc']], df['y']) print(te.transform(df[['mcc']])) # 输出: # array([[0.], # [0.], # [1.]]) print(te.fit_transform(df[['mcc']], df['y'])) # 输出: # array([[0.], # [0.], # [0.]]) # 放入ColumnTransformer中正常运行 from sklearn.compose import ColumnTransformer transformer = ColumnTransformer([('taxonomy', TaxonomyEncoder(), ['mcc'])]) transformer.fit(df[['mcc']], df['y']) print(transformer.transform(df[['mcc']])) # 输出: # array([[0.], # [0.], # [1.]])
内容的提问来源于stack exchange,提问作者Frits

