Деревья решений в pySpark: от семечка до параметрической оптимизации случайного леса
Сегодня на связи Портнов Клим, участник профессионального сообщества NTA.
В этом посте расскажу о простом для понимания, но в то же время достаточно эффективном алгоритме — дереве решений, а также его расширенной модификацией — случайных лесах решений, и их реализации с помощью PySpark MLib.
Сначала расскажу о подготовке данных для обучения в PySpark MLib и построю наивную модель дерева решений.
Затем буду увеличивать точность модели путем подбора гиперпараметров.
В конце засею целый лес решений, и не забуду про сравнение полученных моделей.
Что такое дерево решений?
Данный раздел предназначен для первопроходцев. Если вы знакомы с данным алгоритмом, то смело переходите к следующей части поста.
Деревья решений
Деревья решений представляют собой семейство алгоритмов, которые естественным образом могут обрабатывать как категориальные, так и числовые функции.
Главные преимущества алгоритма:
- устойчивость к выбросам в данных;
- возможность использования данных разных типов и в разных масштабах без предварительной обработки или нормализации;
- и главное — доступность для понимания.
На самом деле используются одни и те же рассуждения, воплощенные в деревьях решений, неявно в повседневной жизни. Например, серия решений «да/нет», которые приводят к прогнозу будет ли тренировка на улице или нет.
Модель дерева решений сама «придумывает» эти развилки. Чем больше развилок, тем точнее модель будет работать на тренировочных данных, но на тестовых значениях она начнет чаще ошибаться. Необходим некоторый баланс, чтобы избежать этого явления, известного как переобучение.
Случайные леса решений
Деревья решений обобщаются в более мощный алгоритм, называемый случайные леса. Случайные леса объединяют множество деревьев решений, чтобы снизить риск переоснащения и обучения деревьев решений отдельно. Объединение прогнозов уменьшает дисперсию прогнозов, делает результирующую модель более обобщенной и повышает производительность на тестовых данных.
Более подробно об алгоритме случайных деревьев можно почитать тут.
Подготовка данных
Для того, чтобы обучить модель в PySpark MLib надо всегда держать в голове, что предложенный набор данных необходимо перевести в числовой вектор. И неважно, какой у вас набор признаков (числовой, категориальный, смешанный).
Датасет
Для примера возьму общедоступный набор данных Bank Marketing. Данные получены по результатам звонков португальской маркетинговой компании (2008 — 2010 годах) и прекрасно подходят для решения задачи классификации: сделает ли клиент term deposit (срочный вклад) или нет.
Входные данные (features):
- age (numeric);
- job: type of job (categorical: "admin", "unknown", "unemployed", "management", "housemaid", "entrepreneur", "student", "blue-collar", "self-employed", "retired", "technician", "services");
- marital: marital status (categorical: "married", "divorced", "single"; note: "divorced" means divorced or widowed);
- education: (categorical: "unknown", "secondary", "primary", "tertiary");
- default: has credit in default? (binary: "yes", "no");
- balance: average yearly balance, in euros (numeric);
- housing: has housing loan? (binary: "yes", "no");
- loan: has personal loan? (binary: "yes", "no");
- contact: contact communication type (categorical: "unknown", "telephone", "cellular");
- day: last contact day of the month (numeric);
- month: last contact month of year (categorical: "jan", "feb", "mar", ..., "nov", "dec");
- duration: last contact duration, in seconds (numeric);
- campaign: number of contacts performed during this campaign and for this client (numeric, includes last contact);
- pdays: number of days that passed by after the client was last contacted from a previous campaign (numeric, -1 means client was not previously contacted);
- previous: number of contacts performed before this campaign and for this client (numeric);
- poutcome: outcome of the previous marketing campaign (categorical: "unknown", "other", "failure", "success").
Выходные данные (target): y - has the client subscribed a term deposit? (binary: "yes", "no")
С датасетом определился, перехожу к практической части.
Чтение данных
Запускаю spark сессию:
Считываю данные в pyspark.Dataframe():
Для начала нужно избавиться от type string. Нет, не подумайте, что я вырежу все переменные этого типа, хотя было бы славно избавиться от target (шутка). Напоминаю, что target в моем датасете — это столбец «y».
Существует несколько подходов для перехода от string к integer, я рассмотрю парочку.
Первый: используем udf функции
Использование таких функций позволяет работать с sparkDataframe, как с pandasDataframe, но без использования toPandas(), что позволяет сэкономить кучу времени и нервных клеток.
Второй: OneHotEncoder или преобразование в бинарные вектора
Для последовательного преобразования данных, а затем и для обучения модели буду использовать pipeline. Я формирую некое расписание в списке stages, а затем pipeline их последовательно исполняет.
Преобразую все категориальные признаки в бинарные вектора:
Соберу все признаки в один вектор, необходимые для обучения моделей в PySpark MLib.
Посмотрю, что произойдет с данными после преобразования. Я создаю контейнер pipeline, но не добавляю в него никакую модель. Метод fit преобразует данные полученные после применений udf функций.
Для работы модели повторять эту часть необязательно, она наглядно показывает, как преобразуются данные.
Все категориальные признаки превратятся в sparse вектора. (Например, job → jobclassVec).
Конечный вектор признаков (features) → sparse vector с размерностью 40.
Делим данные на train и test
Наше первое дерево
После подготовки данных перехожу к построению модели.
Создание модели классификатора:
Создание контейнера pipeline и тренировка модели:
Оценю качество модели.
MulticlassClassificationEvaluator позволяет определять различные метрики модели. Я буду оценивать точность модели (accuracy).
Оптимизация деревьев решений
Повысить метрику можно изменив её гиперпараметры. Для этого я выясню, какие гиперпараметры есть у дерева решений, а также рассмотрю инструмент для их автоматического перебора.
Гиперпараметры дерева решений
Все гиперпараметры дерева решений можно посмотреть тут.
Я рассмотрю наиболее важные гиперпараметры:
- Максимальная глубина (maxDepth) — это максимальное количество связанных решений, которые классификатор примет для классификации примера. Это необходимо, чтобы избежать переобучения.
- Максимальное количество бинов (развилок) в дереве (maxBins).
- Мера примеси (impurity) — хорошие правила делят целевые значения обучающих данных на относительно однородные или «чистые» подмножества. Выбор наилучшего правила означает минимизацию нечистоты двух подмножеств, которые оно вызывает. В основном используются две меры примеси: gini и entropy.
- Минимальный информационный прирост (minInfoGain) — это гиперпараметр, который определяет минимальный информационный прирост или уменьшение примесей для правил принятия решений‑кандидатов.
Реализация
Для начала необходимо создать стандартный контейнер аналогично разделу «Наше первое дерево»:
Затем в ParamGridBuilder определю возможные варианты интересующих меня гиперпараметров:
Далее передам в TrainValidationSplit правила построения модели (pipeline_with_optimization), метрику сравнения моделей (multiclassEval), возможные варианты гиперпараметров (paramGrid_with_optimization) и соотношение, на которое разобьётся train (trainRatio), т. е. dataset train во время обучения поделится на две выборки: на одной модели будут обучаться, а с помощью второй модели будут сравниваться между собой.
Есть возможность посмотреть результаты работы каждой модели, также их гиперпараметры.
С помощью метода bestModel «вытаскиваю» лучшую модель из набора.
Случайные леса решений
Было бы здорово иметь не одно дерево, а много деревьев, каждое из которых дает разумные, но разные и независимые оценки правильного целевого значения. Их коллективный средний прогноз должен быть близок к истинному ответу больше, чем прогноз любого отдельного дерева. Это есть алгоритм случайного леса.
Предсказание случайного леса — это просто средневзвешенное значение предсказаний деревьев. Для категориальной цели это может быть большинство голосов или наиболее вероятное значение, основанное на среднем значении вероятностей, полученных деревьями. Случайные леса, как и деревья решений, также поддерживают регрессию, и прогноз леса в этом случае представляет собой среднее число, предсказанное каждым деревом.
Хотя случайные леса являются более мощным и сложным методом классификации, хорошая новость заключается в том, что разработка модели практически ничем не отличается от разработки дерева.
Первым делом заменим в stages модель дерева на модель случайного леса:
Добавлю еще один гиперпараметр: numTrees – количество деревьев в лесе:
Аналогично предыдущему разделу тренирую модели и извлекаю наилучшую:
Сравнение моделей
Перед сравнением моделей создам модель randomizer, которая случайно будет выбирать класс. Буду считать, что данная модель обладает наихудшей точностью.
Вывод
Для повышения метрик дерева решений в PySpark можно использовать параметрическую оптимизацию. Следует обратить внимание на то, что каждая модель требует индивидуального подбора гиперепараметров.
Случайный лес в основном показывает более точные результаты, но требует большего количества ресурсов.
P. S. На написании поста меня вдохновила книга издательства O'Reilly «Advanced Analytics with PySpark».