我想创建自己的 scikit-learn 转换器,用于编码包含分类的数字特征,例如邮政编码或行业代码(NAICS、MCC 等)。在这些类型的代码中有一个结构:例如MCC 3000-3999 是“旅行和娱乐”,它进一步细分为更细粒度的类别,例如“航空公司”、“汽车租赁”等。我们不能将它们用作序数特征,但如果我们将它们视为纯分类特征(例如,通过 One -Hot-Encoding)我们需要选择在代码结构的哪个级别应用特征编码。
为了解决这个问题,我创建了自己的 scikit-learn 转换器,它是使用决策树的 TargetEncoder 的变体。代码如下所示。重要的是要认识到,在模型训练期间,应使用样本外决策树回归分数来避免过度拟合。因此,我实现了自己的
fit_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):
self.tree_ = DecisionTreeRegressor(max_leaf_nodes=self.n_leafs)
return cross_val_predict(self.tree_, X, y, cv=self.cv).reshape(-1,1)
变压器工作正常,除非在
ColumnTransformer
内使用:
from sklearn.compose import ColumnTransformer
transformer = ColumnTransformer([('taxonomy', TaxonomyEncoder(), ['mcc'])])
transformer.fit(df[['mcc']], df['y'])
transformer.transform(df[['mcc']])
然后我得到决策树尚未拟合的错误:
NotFittedError: This DecisionTreeRegressor instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator.
显然,scikit-learn 做了一些导致此错误的表面检查。请注意,实际上没有理由需要拟合决策树,因为决策树是在
cross_val_predict
函数内重新拟合的。我该如何解决这个问题?
重现错误的完整示例如下所示:
import pandas as pd
df = pd.DataFrame({'mcc':[3000,3500,7339], 'y':[0,0,1]})
te = TaxonomyEncoder().fit(df[['mcc']], df['y'])
te.transform(df[['mcc']])
给出:
array([[0.],
[0.],
[1.]])
并且 fit_transform 给出了预期的结果:
te.fit_transform(df[['mcc']], df['y'])
array([[0.],
[0.],
[0.]])
但是当包装在 ColumnTransformer 中时,事情就会出错:
transformer = ColumnTransformer([('taxonomy', TaxonomyEncoder(), ['mcc'])])
transformer.fit(df[['mcc']], df['y'])
transformer.transform(df[['mcc']])
在您的
fit_transform
中,您需要使树适合整个数据集,以防您稍后调用 transform
。正如所写,您尝试时也会遇到错误
te = TaxonomyEncoder()
te.fit_transform(df[['mcc']], df['y'])
te.transform(df[['mcc']])
修复方法很简单:
def fit_transform(self, X, y=None):
self.fit(X, y)
return cross_val_predict(self.tree_, X, y, cv=self.cv).reshape(-1,1)
ColumnTransformer
有一点怪癖,导致使用它时出错:它实际上总是fit_transform
是它的所有变压器(即使只是拟合列变压器本身),以确定输出是否稀疏从而判断 hstack 是否应该稀疏。