Время прочтения: 5 мин.

Сегодня я расскажу, как с помощью библиотеки 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 сессию 🙂