Я пытаюсь построить конвейер для предварительной обработки данных для моей модели XGBoost. Данные содержат NaN и должны быть масштабированы. Это соответствующий код:
xgb_pipe = Pipeline(steps=[
('imputer', SimpleImputer(strategy='mean')),
('scaler', preprocessing.StandardScaler()),
('regressor', xgboost.XGBRegressor(n_estimators=100, eta=0.1, objective = "reg:squarederror"))])
xgb_pipe.fit(train_x.values, train_y.values,
regressor__early_stopping_rounds=20,
regressor__eval_metric = "rmse",
regressor__eval_set = [[train_x.values, train_y.values],[test_x.values, test_y.values]])
Потери сразу увеличиваются, и обучение останавливается после 20 итераций.
Если я уберу импьютер и скейлер из конвейера, он будет работать и обучаться полные 100 итераций. Если я предварительно обрабатываю данные вручную, они также работают, как и предполагалось, поэтому я знаю, что проблема не в данных. Что мне не хватает?
@user1808924 user1808924 Если я удалю только эту строку, я получу ошибку IndexError: list index out of range. Если я удаляю все три параметра регрессора для ранней остановки, он тренируется в течение всего времени (и потери улучшаются, как и предполагалось). Каков был бы правильный способ реализовать раннюю остановку?
Обратите внимание, что деревья (с усилением градиента) не заботятся о масштабе входных данных, поэтому StandardScaler здесь строго не требуется. И xgboost будет обрабатывать отсутствующие значения (но если вы этого не хотите, то вменение вызовет разницу.)
Проблема в том, что предварительная обработка не применяется к вашим наборам eval, поэтому модель работает на них довольно плохо, и ранняя остановка срабатывает очень рано.
Я не уверен, что есть простой способ сделать это, к сожалению, чтобы все было в одном конвейере. Вам необходимо применить этапы предварительной обработки конвейера к наборам eval, поэтому их необходимо настроить перед установкой этого параметра.
Как два объекта это не проблема:
preproc = Pipeline(steps=[
('imputer', SimpleImputer(strategy='mean')),
('scaler', preprocessing.StandardScaler()),
])
reg = xgboost.XGBRegressor(n_estimators=100, eta=0.1, objective = "reg:squarederror")
train_x_preproc = preproc.fit_transform(train_x.values, train_y.values)
test_x_preproc = preproc.transform(test_x)
reg.fit(train_x.values, train_y.values,
regressor__early_stopping_rounds=20,
regressor__eval_metric = "rmse",
regressor__eval_set = [[train_x_preproc, train_y.values], [test_x_preproc, test_y.values]],
)
После подгонки вы можете объединить эти уже подобранные оценки в конвейер (конвейеры не клонируют свои оценки) для предсказания, если хотите.
Есть много способов сделать это, но наследование от Pipeline означает, что вы можете инициализировать так же, как вы делаете текущую настройку, и мы просто предполагаем, что последний шаг — это модель xgboost, а остальные — предварительная обработка, которую необходимо применить к наборы eval, а также наборы для подгонки и прогнозирования. Думаю, все остальное можно оставить унаследованным от Pipeline методам?
class PreprocEarlyStoppingXGB(Pipeline):
def fit(self, X, y, eval_set):
preproc = self.steps[:-1]
X_preproc = preproc.fit_transform(X, y)
eval_preproc = []
for eval in eval_set:
eval_preproc.append([preproc.transform(eval[0]), eval[1]])
self.steps[-1].fit(X_preproc, y, eval_set=eval_preproc)
return self
Что касается вашего варианта использования из комментариев, что происходит, когда вы выполняете перекрестную проверку с этим объектом? На каждой тренировочной складке установлены этапы предварительной обработки. Затем они применяются к обучающей выборке и ко всем оценочным наборам (всему обучающему набору, а также внешнему тестовому набору) и, наконец, при подсчете тестовой выборки. Модель xgboost тренируется на предварительно обработанной обучающей выборке и отслеживает результаты всего обучающего набора и внешнего тестового набора (оба были предварительно обработаны), причем последний используется для ранней остановки.
В этом есть смысл. Как это будет сочетаться с перекрестной проверкой kfold? Я реализовал конвейер, чтобы иметь простую оценку gridsearch/kfold, где масштабирование/вменение настраивается отдельно для каждого из 5 разбиений train/eval, чтобы избежать утечек данных. Следующим шагом должно было быть что-то вроде: CV = GridSearchCV(xgb_pipe, param_grid)
@Jonas Да, это главный недостаток отсутствия всего этого в одном объекте. Я добавил пользовательский оценщик, который, как мне кажется, должен выполнять эту работу; может быть что-то и можно сделать без этого, но я этого не вижу.
Тогда это должно быть связано с вашим regressor__eval_set параметром соответствия. Для начала оставьте это и посмотрите, работает ли XGBoost 100 итераций или нет.