如何使用 sklearn 管道跟踪 catboost 的分类索引
Posted
技术标签:
【中文标题】如何使用 sklearn 管道跟踪 catboost 的分类索引【英文标题】:How to track categorical indices for catboost with sklearn pipeline 【发布时间】:2019-11-06 14:34:17 【问题描述】:我想在 sklearn 管道中跟踪分类特征索引,以便将它们提供给 CatBoostClassifier。
我从管道的 fit() 之前的一组分类特征开始。 管道本身会改变数据的结构并在特征选择步骤中移除特征。
我如何预先知道哪些分类特征将被删除或添加到管道中? 当我调用 fit() 方法时,我需要知道更新的列表索引。 问题是,我的数据集在转换后可能会发生变化。
这是我的数据框的示例:
data = pd.DataFrame('pet': ['cat', 'dog', 'dog', 'fish', np.nan, 'dog', 'cat', 'fish'],
'children': [4., 6, 3, np.nan, 2, 3, 5, 4],
'salary': [90., 24, np.nan, 27, 32, 59, 36, 27],
'gender': ['male', 'male', 'male', 'male', 'male', 'male', 'male', 'male'],
'happy': [0, 1, 1, 0, 1, 1, 0, 0])
categorical_features = ['pet', 'gender']
numerical_features = ['children', 'salary']
target = 'happy'
print(data)
pet children salary gender happy
0 cat 4.0 90.0 male 0
1 dog 6.0 24.0 male 1
2 dog 3.0 NaN male 1
3 fish NaN 27.0 male 0
4 NaN 2.0 32.0 male 1
5 dog 3.0 59.0 male 1
6 cat 5.0 36.0 male 0
7 fish 4.0 27.0 male 0
现在我想运行一个包含多个步骤的管道。 其中一个步骤是 VarianceThreshold(),在我的例子中,这将导致从数据框中删除“性别”。
X, y = data.drop(columns=[target]), data[target]
pipeline = Pipeline(steps=[
(
'preprocessing',
ColumnTransformer(transformers=[
(
'categoricals',
Pipeline(steps=[
('fillna_with_frequent', SimpleImputer(strategy='most_frequent')),
('ordinal_encoder', OrdinalEncoder())
]),
categorical_features
),
(
'numericals',
Pipeline(steps=[
('fillna_with_mean', SimpleImputer(strategy='mean'))
]),
numerical_features
)
])
),
(
'feature_selection',
VarianceThreshold()
),
(
'estimator',
CatBoostClassifier()
)
])
现在,当我尝试获取 CatBoost 的分类特征索引列表时,我无法判断“性别”不再是我的数据框的一部分。
cat_features = [data.columns.get_loc(col) for col in categorical_features]
print(cat_features)
[0, 3]
索引 0、3 是错误的,因为在 VarianceThreshold 之后,特征 3(性别)将被移除。
pipeline.fit(X, y, estimator__cat_features=cat_features)
---------------------------------------------------------------------------
CatBoostError Traceback (most recent call last)
<ipython-input-230-527766a70b4d> in <module>
----> 1 pipeline.fit(X, y, estimator__cat_features=cat_features)
~/anaconda3/lib/python3.7/site-packages/sklearn/pipeline.py in fit(self, X, y, **fit_params)
265 Xt, fit_params = self._fit(X, y, **fit_params)
266 if self._final_estimator is not None:
--> 267 self._final_estimator.fit(Xt, y, **fit_params)
268 return self
269
~/anaconda3/lib/python3.7/site-packages/catboost/core.py in fit(self, X, y, cat_features, sample_weight, baseline, use_best_model, eval_set, verbose, logging_level, plot, column_description, verbose_eval, metric_period, silent, early_stopping_rounds, save_snapshot, snapshot_file, snapshot_interval, init_model)
2801 self._fit(X, y, cat_features, None, sample_weight, None, None, None, None, baseline, use_best_model,
2802 eval_set, verbose, logging_level, plot, column_description, verbose_eval, metric_period,
-> 2803 silent, early_stopping_rounds, save_snapshot, snapshot_file, snapshot_interval, init_model)
2804 return self
2805
~/anaconda3/lib/python3.7/site-packages/catboost/core.py in _fit(self, X, y, cat_features, pairs, sample_weight, group_id, group_weight, subgroup_id, pairs_weight, baseline, use_best_model, eval_set, verbose, logging_level, plot, column_description, verbose_eval, metric_period, silent, early_stopping_rounds, save_snapshot, snapshot_file, snapshot_interval, init_model)
1231 _check_train_params(params)
1232
-> 1233 train_pool = _build_train_pool(X, y, cat_features, pairs, sample_weight, group_id, group_weight, subgroup_id, pairs_weight, baseline, column_description)
1234 if train_pool.is_empty_:
1235 raise CatBoostError("X is empty.")
~/anaconda3/lib/python3.7/site-packages/catboost/core.py in _build_train_pool(X, y, cat_features, pairs, sample_weight, group_id, group_weight, subgroup_id, pairs_weight, baseline, column_description)
689 raise CatBoostError("y has not initialized in fit(): X is not catboost.Pool object, y must be not None in fit().")
690 train_pool = Pool(X, y, cat_features=cat_features, pairs=pairs, weight=sample_weight, group_id=group_id,
--> 691 group_weight=group_weight, subgroup_id=subgroup_id, pairs_weight=pairs_weight, baseline=baseline)
692 return train_pool
693
~/anaconda3/lib/python3.7/site-packages/catboost/core.py in __init__(self, data, label, cat_features, column_description, pairs, delimiter, has_header, weight, group_id, group_weight, subgroup_id, pairs_weight, baseline, feature_names, thread_count)
318 )
319
--> 320 self._init(data, label, cat_features, pairs, weight, group_id, group_weight, subgroup_id, pairs_weight, baseline, feature_names)
321 super(Pool, self).__init__()
322
~/anaconda3/lib/python3.7/site-packages/catboost/core.py in _init(self, data, label, cat_features, pairs, weight, group_id, group_weight, subgroup_id, pairs_weight, baseline, feature_names)
638 cat_features = _get_cat_features_indices(cat_features, feature_names)
639 self._check_cf_type(cat_features)
--> 640 self._check_cf_value(cat_features, features_count)
641 if pairs is not None:
642 self._check_pairs_type(pairs)
~/anaconda3/lib/python3.7/site-packages/catboost/core.py in _check_cf_value(self, cat_features, features_count)
360 raise CatBoostError("Invalid cat_features[] = value type=: must be int().".format(indx, feature, type(feature)))
361 if feature >= features_count:
--> 362 raise CatBoostError("Invalid cat_features[] = value: must be < .".format(indx, feature, features_count))
363
364 def _check_pairs_type(self, pairs):
CatBoostError: Invalid cat_features[1] = 3 value: must be < 3.
我希望 cat_features 为 [0],但实际输出为 [0, 3]。
【问题讨论】:
【参考方案1】:问题不在于 catboost,而在于您的 ColumnTransformer
的工作方式。 columnTransfomer 按照您的转换操作顺序重建输入 df 转换后
【讨论】:
【参考方案2】:这里的根本问题是转换器不遵循预定义的输出模式,这意味着您可以将 1 列转换为 3(分类列)。
因此,您需要跟踪自己生成的功能数量。
我对此的解决方案是以我提前知道哪些索引对应于最后一步(Catboost 估计器)的分类列的方式组织管道。通常,我会将所有与分类相关的操作隔离并包装在单个转换器中(您也可以在其中进行子转换),并且我会跟踪它将输出多少列。至关重要的是;将此转换器设置为管道中的第一个转换器。这将保证我的第一个 X 索引是分类的,我可以在最后将这个索引列表传递给你的 catboost cat_features
参数。
【讨论】:
【参考方案3】:您收到错误的原因是您当前的 cat_features 源自您的 non_transformed 数据集。为了解决这个问题,您必须在数据集转换后派生 cat_features。 这就是我跟踪我的方式:我将转换器拟合到数据集,检索数据集并将其转换为 pandas 数据框,然后检索分类索引
column_transform = ColumnTransformer([('n', MinMaxScaler(), numerical_idx)], remainder='passthrough')
scaled_X = column_transform.fit_transform(X)
new_df = pd.DataFrame(scaled_X)
new_df = new_df.infer_objects() # converts the datatype to their most accurate datatype
cat_features_new = [new_df.columns.get_loc(col) for col in new_df.select_dtypes(include=['object', 'bool']).columns]
【讨论】:
【参考方案4】:您可以尝试将 cat_features 传递给 CatBoostClassifier 初始化函数。
【讨论】:
您能详细说明一下吗?我看不出它对谁有帮助。问题仍然存在:我不知道在初始化 CatBoostClassifier 时会从我的数据框中删除“性别”。 有什么想法吗? @安娜以上是关于如何使用 sklearn 管道跟踪 catboost 的分类索引的主要内容,如果未能解决你的问题,请参考以下文章
mlflow 如何使用自定义转换器保存 sklearn 管道?
如何使用 sklearn 管道缩放 Keras 自动编码器模型的目标值?