Привет, Хабр! Меня зовут Никита Морозов, я 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-сессию!
Если есть вопросы, задавайте в комментариях. Будет отлично, если кто-то из читателей сможет поделиться собственными кейсами по теме статьи.