Узнайте, как использовать поиск по сетке для оптимизации гиперпараметров

Поздравляем! Ваша статья в прямом эфире в нашем издании. Подумайте о том, чтобы представить больше статей. Не забудьте подписаться на нас в https://blog.devops.dev/ и в Twitter (https://twitter.com/devops_blog). Гиперпараметры в моделях машинного обучения — это параметры, которые не извлекаются из обучения. данные, но устанавливаются перед тренировкой. Эти параметры влияют на поведение модели во время обучения и могут оказать существенное влияние на производительность модели и ее способность обобщать новые данные.

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

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

Я буду использовать набор данных о ценах на ноутбуки от Kaggle, чтобы продемонстрировать, как использовать поиск по сетке в Python. Получить данные можно здесь.

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

import pandas as pd
import xgboost as xgb
import numpy as np
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer

df = pd.read_csv("laptopPrice.csv")
# there are some duplicates in the dataset
df = df.drop_duplicates()
df.reset_index(drop=True,inplace=True)

# some replacements

df['ram_gb'] = df['ram_gb'].str.replace(' GB', '').astype(int)
df['ssd'] = df['ssd'].str.replace(' GB', '').astype(int)
df['hdd'] = df['hdd'].str.replace(' GB', '').astype(int)
df['graphic_card_gb'] = df['graphic_card_gb'].str.replace(' GB', '').astype(int)
df['os_bit'] = df['os_bit'].str.replace('-bit', '').astype(int)
df["rating"] = df["rating"].replace({"1 star": "1 stars"})
df['rating'] = df['rating'].str.replace(' stars', '').astype(int)

mapping = {'ThinNlight': 1, 'Casual': 2, 'Gaming': 3}
df["weight"] = df["weight"].replace(mapping)

mapping = {'No warranty': 0, '1 year':1, '2 years':2, '3 years':3}
df["warranty"] = df["warranty"].replace(mapping)

mapping = {'No': 0, 'Yes': 1}
df["Touchscreen"] = df["Touchscreen"].replace(mapping)

mapping = {'No': 0, 'Yes': 1}
df["msoffice"] = df["msoffice"].replace(mapping)

df.head(10)

num_features = [feature for feature in df.columns if df[feature].dtype != 'object' and feature != "Price"]
cat_features = [feature for feature in df.columns if df[feature].dtype == 'object']
print("Numerical features: ", num_features)
print("Categorical featues:", cat_features)

"""
Numerical features:  ['ram_gb', 'ssd', 'hdd', 'os_bit', 'graphic_card_gb', 'weight', 'warranty', 'Touchscreen', 'msoffice', 'rating', 'Number of Ratings', 'Number of Reviews']
Categorical featues: ['brand', 'processor_brand', 'processor_name', 'processor_gnrtn', 'ram_type', 'os']
"""
# feature transformation

cat_transformer = OneHotEncoder(handle_unknown='ignore')
num_transformer = StandardScaler()

preprocessor = ColumnTransformer(
    transformers=[
        ('cat', cat_transformer, cat_features),
        ('num', num_transformer, num_features)
    ])

X = preprocessor.fit_transform(df)
y = df["Price"].values.reshape(-1,1)

print(f"X: {X.shape}, y: {y.shape}")
# X: (802, 51), y: (802, 1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, shuffle=True)

Хорошо, теперь мы предварительно обработали как входные, так и целевые данные, и они готовы к использованию в модели.

Давайте обучим нашу базовую модель. Я буду использовать XGBoost с его гиперпараметрами по умолчанию.

model = xgb.XGBRegressor()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
rmse_train = np.sqrt(mean_squared_error(y_train, model.predict(X_train)))
rmse_test = np.sqrt(mean_squared_error(y_test, y_pred))
print(f"Train RMSE: {rmse_train}, Test RMSE: {rmse_test}")
# Train RMSE: 2079.484814769608, Test RMSE: 19711.16864024852

Наша модель выглядит сильно переобученной. Поэтому теперь мы включим Grid Search для решения этой проблемы.

from sklearn.model_selection import GridSearchCV

Теперь давайте подробнее рассмотрим класс GridSearchCV. Параметры, которые он принимает, следующие:

  • estimator — это модель, которая будет использоваться для обучения.
  • param_grid указывает пространство гиперпараметров для поиска. Это должен быть словарь или список словарей, где каждый словарь содержит набор гиперпараметров, которые нужно попробовать.
  • scoring — это показатель, используемый для оценки производительности модели. Он может принимать множество различных форм, включая строки, вызываемые функции и словари с несколькими метриками. Показатели классификации: точность, прецизионность, полнота, f1. Показатели регрессии: neg_mean_squared_error, r2. Показатели кластеризации: Adjust_rand_score, silhoutte_score. Это были самые популярные, заходите сюда, чтобы посмотреть весь список.
  • n_jobs указывает количество ядер ЦП, используемых для распараллеливания вычислений. Значение -1 указывает, что должны использоваться все доступные ядра.
  • refit указывает, следует ли переоборудовать наилучшую оценку для всего набора данных, используя лучшие гиперпараметры, найденные во время поиска. По умолчанию для refit установлено значение True, что означает, что после завершения поиска в сетке объект GridSearchCV автоматически подгонит лучшую оценку для всего набора данных, используя лучшие найденные гиперпараметры.
  • cv указывает стратегию разделения перекрестной проверки. Это может быть целочисленное значение, указывающее количество сгибов, или генератор перекрестной проверки, который можно использовать для определения более продвинутых стратегий перекрестной проверки.
  • verbose управляет подробностью вывода во время поиска.
  • pre_dispatch используется для управления количеством заданий, которые запускаются параллельно во время поиска по сетке. Он принимает целочисленное значение, указывающее максимальное количество заданий, которые могут быть запущены в любой момент времени. Например, если pre_dispatch=2, то одновременно будет запущено не более 2-х заданий.
  • error_score используется для указания того, какую оценку следует присвоить комбинации гиперпараметров, если она не может завершить процесс подбора. В процессе поиска по сетке алгоритм GridSearchCV обучает и оценивает модель для каждой комбинации гиперпараметров. Однако иногда модель может не соответствовать или не набирать баллы из-за таких причин, как нехватка памяти или численная нестабильность. В таких случаях алгоритму GridSearchCV необходимо присвоить оценку неудачной комбинации гиперпараметров, чтобы продолжить поиск.
  • return_train_score указывает, следует ли включать результаты обучения в выходные данные.
param_grid = {
    'learning_rate': [0.01, 0.1],
    'n_estimators': [100, 500],
    'max_depth': [3, 5],
    'colsample_bytree': [0.5, 0.9],
    'gamma': [0, 0.1, 0.5],
    'reg_alpha': [0, 1, 10],
    'reg_lambda': [0, 1, 10],
}

xgb = xgb.XGBRegressor(random_state=1)
grid_search = GridSearchCV(xgb, param_grid=param_grid, cv=5, n_jobs=-1, verbose=1, 
                           scoring="neg_root_mean_squared_error", )

grid_search.fit(X_train, y_train)
#Fitting 5 folds for each of 432 candidates, totaling 2160 fits

GridSearchCV выполняет 5-кратную перекрестную проверку (т. е. разбивает данные на 5 частей и обучает модель 5 раз, каждый раз используя другую часть в качестве набора проверки) для каждой из 432 различных комбинаций гиперпараметров в пространстве поиска. В результате получается 2160 подгонок (т. е. обучение модели и оценка ее производительности 2160 раз).

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

  • best_estimator_ возвращает оценщик, который был выбран как лучший среди всех кандидатов на основе указанной метрики оценки.
  • best_score_ возвращает средний балл перекрестной проверки, полученный лучшим оценщиком тестовых данных.
  • best_params_ возвращает словарь гиперпараметров, которые дали наилучший результат.
  • cv_results_ возвращает словарь, содержащий подробную информацию о производительности каждой комбинации гиперпараметров, включая среднее значение и стандартное отклонение показателей перекрестной проверки, время, необходимое для подгонки и оценки каждой модели, а также значения гиперпараметров для каждой модели.
  • best_index_ возвращает индекс лучшей комбинации гиперпараметров в словаре cv_results_.
  • scorer_ представляет функцию оценки, используемую для оценки производительности моделей во время поиска по сетке.
  • n_splits_ представляет количество кратностей, использованных в процедуре перекрестной проверки во время поиска по сетке.
  • refit_time_ представляет время, которое потребовалось для переоснащения наилучшей оценки для всего набора данных.
  • multimetric_ указывает, использовались ли во время поиска по сетке несколько показателей оценки.
  • classes_ возвращает уникальные метки класса в целевой переменной.
  • n_features_in_ возвращает количество объектов во входных данных.
  • feature_names_in_ — это имена функций, видимых во время подгонки.
print("Best estimator: ", grid_search.best_estimator_)

"""
Best estimator:  XGBRegressor(base_score=0.5, booster='gbtree', callbacks=None,
             colsample_bylevel=1, colsample_bynode=1, colsample_bytree=0.5,
             early_stopping_rounds=None, enable_categorical=False,
             eval_metric=None, gamma=0, gpu_id=-1, grow_policy='depthwise',
             importance_type=None, interaction_constraints='',
             learning_rate=0.1, max_bin=256, max_cat_to_onehot=4,
             max_delta_step=0, max_depth=5, max_leaves=0, min_child_weight=1,
             missing=nan, monotone_constraints='()', n_estimators=100, n_jobs=0,
             num_parallel_tree=1, predictor='auto', random_state=1, reg_alpha=0,
             reg_lambda=0, ...)
"""
print("Best score: ", grid_search.best_score_)
print("Best hyperparameters: ", grid_search.best_params_)

"""
Best score:  -23298.387344638286
Best hyperparameters:  {'colsample_bytree': 0.5, 'gamma': 0, 'learning_rate': 0.1, 'max_depth': 5, 'n_estimators': 100, 'reg_alpha': 0, 'reg_lambda': 0}
"""
results_df = pd.DataFrame(grid_search.cv_results_)
results_df.head()

print("Best index: ", grid_search.best_index_)
print("Best scorer: ", grid_search.scorer_)
print("Best n splits: ", grid_search.n_splits_)
print("Best refit time: ", grid_search.refit_time_)
print("Best multi metric: ", grid_search.multimetric_)
print("Best n features: ", grid_search.n_features_in_)

"""
Best index:  54
Best scorer:  make_scorer(mean_squared_error, greater_is_better=False, squared=False)
Best n splits:  5
Best refit time:  0.055130958557128906
Best multi metric:  False
Best n features:  51
"""

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

best_model = grid_search.best_estimator_
best_model.fit(X_train, y_train)
y_pred = best_model.predict(X_test)
rmse_train = np.sqrt(mean_squared_error(y_train, best_model.predict(X_train)))
rmse_test = np.sqrt(mean_squared_error(y_test, y_pred))
print(f"Train RMSE: {rmse_train}, Test RMSE: {rmse_test}")

"""
Train RMSE: 6502.070891973686, Test RMSE: 17419.48195947506
"""

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

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

Читать далее











Источники



https://www.kaggle.com/datasets/anubhavgoyal10/laptop-prices-dataset/code

https://community.alteryx.com/t5/Data-Science/Hyperparameter-Tuning-Black-Magic/ba-p/449289

https://scikit-learn.org/stable/modules/model_evaluation.html#скоринг-параметр