Привет, Хабр! Меня зовут Никита Морозов, я Data Scientist в Сбере. Сегодня поговорим о том, как при помощи библиотеки ML Tuning осуществить подбор гиперпараметров модели GBTRegressor в PySpark. Зачем всё это нужно? Дело в том, что они используются в машинном обучении для управления процессом обучения модели. Соответственно, подбор оптимальных гиперпараметров — критически важный этап в построении ML‑моделей. Это даёт возможность не только повысить точность, но и бороться с переобучением.

Привычный тюнинг параметров в Python для моделей машинного обучения представляет собой множество техник и способов, например GridSearch, RandomSearch, HyperOpt, Optuna. Но бывают случаи, когда предобработка данных занимает слишком много времени или же объём данных слишком велик, чтобы уместиться в оперативную память одной машины. Для этого на помощь приходит Spark. Подробности — под катом.

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

Сразу приступим к основной задаче — рассмотрим, как в Spark работать с моделями машинного обучения на примере GBTRegressor, а главное, как подбирать гиперпараметры. Делать я это буду на всем известном датасете Boston. Датасет содержит информацию об объектах недвижимости в Бостоне. Его можно скачать из библиотеки sklearn. Объём датасета — (506 X 14).

GBTRegressor — модель градиентного бустинга для задачи регрессии.

Начну с импорта необходимых библиотек:

from pyspark.ml.feature import VectorAssembler
from  pyspark.ml.regression import GBTRegressor
from pyspark.ml.evaluation import RegressionEvaluator
from pyspark.ml.tuning import CrossValidator, ParamGridBuilder

Загружу датасет из sklearn и запишу его в Spark DataFrame:

from sklearn.datasets import load_boston
import pandas as pd
boston = load_boston()
boston_df = pd.DataFrame(boston.data, columns = boston.feature_names)
boston_df['TARGET'] = boston.target
boston_df = spark.createDataFrame(boston_df)

Посмотрю на данные:

Теперь самое время разделить данные на train и test:

train, test = boston_df.randomSplit([0.8, 0.2], seed = 12345)

Преобразую признаки в единый вектор, а также преобразую датафрейм:

vec = VectorAssembler(inputCols=['CRIM', "ZN", "INDUS", "CHAS", 
                                 "NOX", "RM", "AGE", "DIS",
                                 "RAD", "TAX","PTRATIO", "B", "LSTAT"],
                      outputCol='FEATURES')

vector_feature_train = vec.transform(train)
vector_feature_test = vec.transform(test)

Инициализирую модель GBTRegressor, явно указав, какая колонка будет отвечать за признаки, а какую колонку будем предсказывать:

gbt = GBTRegressor(featuresCol='FEATURES', labelCol='TARGET')

Объявлю Evaluator для оценки модели, в качестве метрики выбрав MAE (по умолчанию стоит RMSE):

evaluator = RegressionEvaluator(predictionCol='prediction',
                                labelCol='TARGET',
                                metricName = 'mae')

Прежде чем приступать к подбору гиперпараметров, посмотрим на точность модели с дефолтными настройками:

gbt_model = gbt.fit(vector_feature_train)
pred = gbt_model.transform(vector_feature_test)
mae = evaluator. evaluate(pred)
print(mae)

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

Объявлю сетку параметров, которые требуется подобрать, где MaxDepth — это максимальная глубина дерева, в MaxIter — количество деревьев:

paramGrid = ParamGridBuilder() \
    .addGrid(gbt.maxIter, [10, 20, 30])\
    .addGrid(gbt.maxDepth, [3, 4, 5])\
    .build()

В GBTRegressor есть и другие параметры, например featureSubsetStrategy — стратегия подбора подмножества признаков, но здесь я рассматриваю сам механизм подбора параметров. С самими параметрами можно ознакомиться в официальной документации алгоритма GBTRegressor для Spark.

Значения параметров для подбора можно расширить, скажем, рассмотреть количество итераций не строго 10 или 30, а указать диапазон — [10, 11, 12…30]. Тем самым, возможно, повышаем точность итоговой модели. Но чем больше количество значений перебираемых параметров, тем больше времени займёт процесс самого перебора, особенно на большом объёме данных. Поэтому с количеством параметров и значениями для этих параметров нужно быть осторожным.

Объявлю CrossValidator, в котором укажу алгоритм, сетку параметров, способ оценивания алгоритма и количество фолдов:

crossval = CrossValidator(estimator=gbt,
                          estimatorParamMaps=paramGrid,
                          evaluator=evaluator,
                          numFolds=3)

Запустим кросс-валидацию и выведем среднюю метрику на каждую комбинацию параметров:

cvModel = crossval.fit(vector_feature_train)
cvModel.avgMetrics

Также очень важно посмотреть, при каких параметрах модели была получена лучшая метрика:

cvModel.bestModel.extractParamMap()

Отсюда можно увидеть, что MaxDepth = 4, а MaxIter = 30.

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

Теперь проверим точность при полученных гиперпараметрах на тестовой выборке:

gbt = GBTRegressor(featuresCol='FEATURES', labelCol='TARGET', maxDepth=4, maxIter=30)
gbt_model = gbt.fit(vector_feature_train)
pred = gbt_model.transform(vector_feature_test)
mae = evaluator. evaluate(pred)
print(mae)

Всё отлично, метрику удалось улучшить.

Что ещё?

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

И последнее — не забудьте остановить spark-сессию!

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

Комментарии (0)