МАШИННОЕ ОБУЧЕНИЕ

Одна потенциальная причина переобучения, которую я никогда раньше не замечал

Переобучение происходит, когда производительность обучающих данных намного выше, чем производительность данных тестирования. Гиперпараметры по умолчанию в пакетах машинного обучения могут привести к проблеме переобучения.

«Почему моя настроенная модель все еще переоснащена?»

"Вы использовали перекрестную проверку?"

"Конечно да."

«Какую модель вы использовали и какие гиперпараметры настроили?»

«Регрессор случайного леса и я настроили количество деревьев и максимальное количество функций для каждого дерева».

«Мне нужно проверить ваш код».

Это был диалог между мной и моей женой, когда она делала мини-проект.

Честно говоря, судя по ее описанию, я не мог придумать ничего неправильного в этой процедуре. Однако после того, как я проверил ее код построчно, я обнаружил проблему, которая вызвала проблему переобучения, о которой я никогда раньше не думал.

Давайте вместе разберем ее код.

Это блок кода, который настраивал модель в обучающем наборе данных, а также где возникла проблема.

def train_pipeline_rf(X,y):
    # X are factors
    # y is output
    # impute missing X by median
    X_prepared = pre_pipeline.fit_transform(X)
    # set cross-validation
    tscv = TimeSeriesSplit(n_splits=10)
    data_split = tscv.split(X_prepared)
    # hyper-parameter space
    param_grid_RF = {
        'n_estimators' : [10,20,50,100,200,500,1000],
        'max_features' : [0.6,0.8,"auto","sqrt"]
    }
    # build random forest model
    rf_model = RandomForestRegressor(random_state=42,n_jobs=-1)
    # gridsearch for the best hyper-parameter
    gs_rf = GridSearchCV(rf_model, param_grid=param_grid_RF, cv=data_split, scoring='neg_mean_squared_error', n_jobs=-1)
    # fit dataset
    gs_rf.fit(X_prepared, y)
    return gs_rf

Предварительная обработка

pre_pipeline в коде - это конвейер для вменения пропущенных значений и масштабирования признаков, которые являются важными этапами предварительной обработки данных. обработка. Вот как это выглядит:

pre_pipeline = Pipeline([
        ('imputer', SimpleImputer(strategy="median")),
        ('std_scaler', StandardScaler()),
    ])

Здесь она использовала imputer, чтобы заменить значения NA на медианное значение столбца, а масштабатор - стандартный масштабатор, который нормализует столбцы как:

z = (x — u) / s

где u - среднее значение, а s - стандартное отклонение столбца [1]. Некоторые другие импьютеры и скейлеры в sklearn Также можно использовать. Но эта часть не связана с проблемой переобучения.

Установить разделение данных при перекрестной проверке

    # set cross-validation
    tscv = TimeSeriesSplit(n_splits=10)
    data_split = tscv.split(X_prepared)

Здесь она использовала метод, специально разработанный для данных временных рядов, TimeSeriesSplit.

Обычно люди используют k-кратную перекрестную проверку для случайного разделения обучающих данных или стратифицированную k-кратную перекрестную проверку, чтобы сохранить процент выборок для каждого класса [2]. Но эти два метода не подходят для данных временных рядов, потому что они не сохраняют необработанный порядок точек данных.

Этот шаг довольно стандартный и не может быть связан с проблемой переобучения.

Пространство гиперпараметров и определение модели

    # hyper-parameter space
    param_grid_RF = {
        'n_estimators' : [10,20,50,100,200,500,1000],
        'max_features' : [0.6,0.8,"auto","sqrt"]
    }
    # build random forest model
    rf_model = RandomForestRegressor(random_state=42,n_jobs=-1)

Что касается настройки гиперпараметров, следует указать набор кандидатов, которые обычно определяются как формат словаря в пакете sklearn. Имена гиперпараметров взяты из модели, которую нужно построить, например, RandomForestRegressor в коде моей жены.

Так называемый шаг «настройка-гиперпараметр» заключается в выборе наилучшей комбинации гиперпараметров из ваших заданных кандидатов, которая превосходит другие комбинации гиперпараметров в процедуре перекрестной проверки.

Это часть, из-за которой возникла проблема с переоснащением.

Решите проблему переобучения.

Если мы обратимся к странице руководства Random Forest Regressor в sklearn, то увидим длинный список параметров в функции RandomForestRegressor (здесь нет; при необходимости см. веб-страницу) .

Однако в приведенном выше коде только n_estimators и max_features были переданы функции во время процесса настройки, что в результате все остальные параметры приняли значения по умолчанию.

Один из параметров функции называется max_depth, который представляет собой максимальную глубину дерева в вашей модели случайного леса. Значение по умолчанию - Нет, что означает, что дерево решений будет доходить до такой степени, что каждый выход чистый или все листья имеют не более m выборок, где m определяется как min_samples_split ( по умолчанию = 2).

Этот параметр значительно повысил сложность обученной модели. И на основе компромисса между смещением и дисперсией модель, обученная в приведенном выше коде, имела низкое смещение и высокую дисперсию. Конечно, в конечном итоге это привело к проблеме переобучения.

Я решил проблему, добавив max_depth в пространство гиперпараметров.

    # hyper-parameter space
    param_grid_RF = {
        'n_estimators' : [10,20,50,100,200,500,1000],
        'max_features' : [0.6,0.8,"auto","sqrt"],
        'max_depth' : [4,5,6]
    }

На самом деле есть некоторые другие параметры, которые могут повлиять на сложность модели в RandomForestRegressor, но не рекомендуется помещать их все в пространство гиперпараметров.

Дополнительный гиперпараметр в приведенном выше коде уже увеличил стоимость вычислений в три раза. Таким образом, не стоит увеличивать время работы в геометрической прогрессии, чтобы просто настроить больше параметров.

Вместо этого вы можете установить некоторые из них при запуске модели следующим образом.

# hyper-parameter space
    param_grid_RF = {
        'n_estimators' : [10,20,50,100,200,500,1000]
    }
# build random forest model
    rf_model = RandomForestRegressor(random_state=42,n_jobs=-1,max_features=0.6,max_depth=5)

В приведенном выше коде единственный настроенный гиперпараметр - это n_estimators и max_features,, а также max_depth, фиксируются в процессе обучения. Настроенные или фиксированные параметры не обязательно должны быть такими.

Обоснование настройки состоит в том, чтобы снизить вычислительные затраты, когда вы уже имеете некоторое представление о выборе некоторых гиперпараметров.

Да, проблема моей жены решена. Однако, честно говоря, я не мог найти его , пока не просмотрел все параметры по умолчанию в пакете. Вот почему я хочу поделиться с вами этим опытом, чтобы вы могли более внимательно относиться к параметрам по умолчанию в процессе обучения модели машинного обучения.

Переоснащение - одна из наиболее распространенных проблем в науке о данных, которая в основном возникает из-за высокой сложности модели и отсутствие точек данных.

Чтобы этого избежать, лучше полностью контролировать используемые пакеты.

Надеюсь, этот совет поможет вам.

Использованная литература:

  1. Https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html#sklearn.preprocessing.StandardScaler
  2. Https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedKFold.html
  3. Https://en.wikipedia.org/wiki/Random_forest

Исправления:

Я хотел бы поблагодарить Антонио Карлоса за указание на эту проблему в моем исходном посте. Он также предложил хороший пост Предварительная обработка данных с конвейером для предотвращения утечки данных во время перекрестной проверки. к теме. Я действительно ценю это.

Это связано с частью конвейера предварительной обработки. Этого следовало делать не на всем наборе обучающих данных, а на каждой итерации этапа перекрестной проверки, потому что общая нормализация обучающих данных вызовет утечку между обучающим и проверочным набором данных на этапе CV.

Итак, я поместил исправленный код с соответствующей ревизией, как показано ниже:

def train_pipeline_rf(X,y):
    # X are factors
    # y is output
    # set cross-validation
    tscv = TimeSeriesSplit(n_splits=10)
    data_split = tscv.split(X)
    
    # build a pipeline of pre-processing and random forest model
    my_pipe = Pipeline([
        ('imputer', SimpleImputer(strategy="median")), 
        ('std_scaler', StandardScaler()),
        ('rf_model', RandomForestRegressor(random_state=42,n_jobs=-1))
    ])
    # hyper-parameter space
    param_grid_RF = {
        'rf_model__n_estimators' : [10,20,50,100,200,500,1000],
        'rf_model__max_features' : [0.6,0.8,"auto","sqrt"],
        'rf_model__max_depth' : [4,5,6]
    }
    # gridsearch for the best hyper-parameter within the pipeline.
    gs_rf = GridSearchCV(my_pipe, param_grid=param_grid_RF, cv=data_split, scoring='neg_mean_squared_error', n_jobs=-1)
    # fit dataset
    gs_rf.fit(X, y)
    return gs_rf