Вопрос или проблема
Почему метод (пользовательский) .fit() преобразует pd.DataFrame X в numpy.ndarray?
import pandas as pd
import numpy as np
from sklearn.pipeline import make_pipeline
from sklearn.compose import TransformedTargetRegressor, make_column_transformer
from xgboost import XGBRegressor
class XGBRPipeline(XGBRegressor):
def __init__(self, preprocessing, cardinality=10, **xgboost_params):
super().__init__(**xgboost_params)
self.preprocessing = preprocessing
self.cardinality = cardinality
self.pipeline = None
def _get_num_cols(self, df):
return [f for f in df.columns if pd.api.types.is_numeric_dtype(df[f])]
def _get_cat_cols(self, df):
return [f for f in df.columns if df[f].dtype.name == 'category']
def _get_ord_cols(self, df):
cat_cols = self._get_cat_cols(df)
return [f for f in cat_cols if df[f].cat.ordered]
def _get_non_ord_cols(self, df):
cat_cols = self._get_cat_cols(df)
return [f for f in cat_cols if not df[f].cat.ordered]
def _get_llc_cols(self, df, cardinality):
non_ord_cols = self._get_non_ord_cols(df)
return [f for f in non_ord_cols if len(df[f].cat.categories) <= cardinality]
def _get_hcc_cols(self, df, cardinality):
non_ordinal_cols = self._get_non_ord_cols(df)
return [f for f in non_ord_cols if len(df[f].cat.categories) > cardinality]
def fit(self, X, y=None, **fit_params):
preprocessing = make_column_transformer(
(self.preprocessing.get('numeric'), self._get_num_cols(X)),
(self.preprocessing.get('ordinal'), self._get_ord_cols(X)),
(self.preprocessing.get('cardinal'), self._get_llc_cols(X, self.cardinality))
)
ttr = TransformedTargetRegressor(
regressor=self,
func=np.log1p,
inverse_func=np.expm1,
check_inverse=False
)
self.pipeline = make_pipeline(preprocessing, ttr)
self.pipeline.fit(X, y, **fit_params)
return self
def transform(self, X, **transform_params):
return self.pipeline.transform(X, **transform_params)
xgbr = XGBRPipeline(
preprocessing=preprocessing_xgbr,
cardinality=200,
n_estimators=100,
learning_rate=0.1
)
print(type(train))
xgbr.fit(X=train.drop(columns=[target]), y=train[target]))
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Input In [67], in ()
53 xgbr = XGBRPipeline(preprocessing=preprocessing_xgbr, cardinality=200, n_estimators=100, learning_rate=0.1)
54 print(type(train))
---> 55 xgbr.fit(X=train.drop(columns=[target]), y=train[target])
Input In [67], in XGBRPipeline.fit(self, X, y, **fit_params)
39 ttr = TransformedTargetRegressor(
40 regressor=self,
41 func=np.log1p,
42 inverse_func=np.expm1,
43 check_inverse=False
44 )
46 self.pipeline = make_pipeline(preprocessing, ttr)
---> 47 self.pipeline.fit(X, y, **fit_params)
48 return self
File ~\AppData\Local\Programs\Python\Python310\lib\site-packages\sklearn\pipeline.py:405, in Pipeline.fit(self, X, y, **fit_params)
403 if self._final_estimator != "passthrough":
404 fit_params_last_step = fit_params_steps[self.steps[-1][0]]
--> 405 self._final_estimator.fit(Xt, y, **fit_params_last_step)
407 return self
File ~\AppData\Local\Programs\Python\Python310\lib\site-packages\sklearn\compose\_target.py:262, in TransformedTargetRegressor.fit(self, X, y, **fit_params)
259 else:
260 self.regressor_ = clone(self.regressor)
--> 262 self.regressor_.fit(X, y_trans, **fit_params)
264 if hasattr(self.regressor_, "feature_names_in_"):
265 self.feature_names_in_ = self.regressor_.feature_names_in_
Input In [67], in XGBRPipeline.fit(self, X, y, **fit_params)
32 def fit(self, X, y=None, **fit_params):
33 preprocessing = make_column_transformer(
---> 34 (self.preprocessing.get('numeric'), self._get_num_cols(X)),
35 (self.preprocessing.get('ordinal'), self._get_ord_cols(X)),
36 (self.preprocessing.get('cardinal'), self._get_llc_cols(X, self.cardinality))
37 )
39 ttr = TransformedTargetRegressor(
40 regressor=self,
41 func=np.log1p,
42 inverse_func=np.expm1,
43 check_inverse=False
44 )
46 self.pipeline = make_pipeline(preprocessing, ttr)
Input In [67], in XGBRPipeline._get_num_cols(self, df)
10 def _get_num_cols(self, df):
---> 11 return [f for f in df.columns if pd.api.types.is_numeric_dtype(df[f])]
AttributeError: 'numpy.ndarray' object has no attribute 'columns'
|
Это принцип проектирования sklearn… Я думаю, он начался с numpy -> только numpy, и когда появился df, они работали над тем, чтобы сначала принять формат, сохранив выходные данные numpy для согласованности. В настоящее время ведется работа над тем, чтобы sklearn мог выводить датафреймы, когда предоставляются датафреймы. Но библиотека большая, и это требует много утомительной работы.
Ответ или решение
Метод .fit()
в вашем кастомном классе XGBRPipeline
трансформирует входной параметр X
из типа pd.DataFrame
в np.ndarray
по нескольким причинам, которые связаны с проектированием библиотеки Scikit-learn и ее интеграцией с библиотекой NumPy. Далее, я подробно разложу на составляющие этот процесс.
F (Факт)
Метод .fit()
в данном классе используется для обучения модели на основе предоставленных входных данных. В процессе работы с X
необходимо выполнять определенные преобразования, включая:
-
Упрощение обработки данных: NumPy обеспечивает легкость и производительность работы с многомерными массивами, что делает его предпочтительным форматом для математических и статистических операций, необходимых в машинном обучении.
-
Совместимость с библиотеками: Существует широкая практика в экосистеме Python, где многие библиотеки (например, Scikit-learn) ожидают, что входные данные будут представлены в виде массивов NumPy. Это решение принято для обеспечения однородности и производительности.
O (Объяснение)
Когда метод .fit()
вызывается, данный ваш класс начинает обрабатывать датафрейм pd.DataFrame
, переходя к этапе подготовки данных, в котором он использует make_column_transformer
. На этом шаге, в зависимости от специфики обработки и используемых трансформеров, библиотека может использовать NumPy для выполнения различных математических расчетов.
-
Конвертация данных: Scikit-learn самостоятельно гарантирует, что входные данные преобразуются в формат, оптимальный для работы, в данном случае — это чаще всего
np.ndarray
. При этом, если входные данные представлены в виде DataFrame, метод автоматически конвертирует их в массив NumPy. -
Производительность: NumPy был разработан для эффективного выполнения операций над массивами. При использовании массивов NumPy вместо DataFrame значительно увеличивается производительность при обработке массивных данных.
R (Результат)
В вашем коде, когда происходит вызов self.pipeline.fit(X, y, **fit_params)
, интерфейс ожидает, что X
будет массивом NumPy. Однако, если X
остается DataFrame, попытка обращения к X.columns
(как в методе _get_num_cols
) приводит к AttributeError
, так как NumPy-массивы не имеют атрибута columns
. Это подчеркивает важность факта, что Scikit-learn и, в частности, ваш класс, рассчитаны на использование массивов NumPy для выполнения расчетов.
S (Ситуация)
Таким образом, применение метода .fit()
в контексте вашего кастомного класса является достаточно стандартной практикой в Python для обработки данных в машинном обучении. Разработчики библиотек стремятся к упрощению интерфейсов и реализации алгоритмов, что и приводит к необходимости преобразования DataFrame в массивы NumPy. Это соответствует философии Scikit-learn — быть максимально совместимыми с NumPy, обеспечивая высокую производительность.
T (Точка зрения)
Считаю, что поддержка формата NumPy как основного способа для работы с данными в методах Scikit-learn является оправданной и целесообразной. Несмотря на то что в сообществе машинного обучения наблюдается растущий интерес к DataFrame как формату для хранения данных, конвертация в массивы NumPy гарантирует более быструю обработку и совместимость с большинством существующих библиотек.
Заключение
Таким образом, метод .fit()
изменяет входные данные X
из типа pd.DataFrame
на np.ndarray
, что обуславливается целым рядом факторов, включая оптимизацию производительности, совместимость с библиотеками и стандартные практики, которые формируют современные подходы в разработке алгоритмов машинного обучения. Это решение является не капризом разработчиков, а необходимостью, вытекающей из структуры и философии самой библиотеки Scikit-learn.