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

Деревья решений — это контролируемые алгоритмы. Хотя они не ограничиваются только классификацией и могут использоваться и в задачах регрессии, в этой статье мы рассмотрим только классификационную часть. Регрессия с использованием деревьев решений может стать историей для другого дня.

Статья предоставит вам практический опыт построения модели классификатора дерева решений с использованием scikit-learn и фрагментов кода. Кроме того, он объяснит все ключевые понятия. Итак, приступим без лишних слов.

Примечание. Блокнот, используемый в этом руководстве, можно найти на моем GitHub.

1. Как работают деревья решений?

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

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

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

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

2. Реализация модели с нуля

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

1. Узел — отображает отдельный узел дерева.

2. DecisionTree — для реализации алгоритма

Давайте начнем с класса Node, так как он является строительным блоком для второго класса. Узел хранит не только свое значение, но и отвечает за получение информации, перемещение данных влево и вправо, а также другую информацию о функции. Каждому узлу изначально будет присвоено значение None, и узлы начинаются с корневого узла и заканчиваются конечным узлом.

Давайте сначала напишем код для класса Node.

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

Вот список всех методов, которые мы будем реализовывать в классе:

__init__() — функция-конструктор. Это инициализирует значения наших гиперпараметров min_samples_split и max_depth. Первый используется для определения количества выборок, которое требуется для дальнейшего разделения узла, а последний определяет глубину. дерева решений, которое мы строим. Эти гиперпараметры действуют как условия нарушения рекурсивной функции построения дерева.

_entropy(s) –определяет нечистоту конкретного вектора.

_information_gain(parent, left_child, right_child) чтобы найти прирост информации при разделении узла на дочерние узлы.

_best_split(X, y)самая важная функция; тот, который определяет наилучшие параметры разделения. Он использует входные функции X и целевую переменную y для поиска оптимальных значений.

_build(X, y, depth) — основной метод. Он строит дерево с рекурсивными вызовами узлов разделения до тех пор, пока не будут выполнены условия останова, как мы описали в гиперпараметрах.

fit(X, y) — используется для вызова метода _build() для сохранения обновленного дерева в конструкторе после каждой итерации.

_predict(x) — для прогнозирования тестового набора данных путем обхода всего дерева и преобразования входных данных в выходные.

predict(X) — при наличии матрицы входных признаков X эта функция применяет _predict() для каждой записи в этой матрице.

Вот и все! Это все методы, которые мы будем реализовывать в нашем классе. Давайте начнем кодирование ниже. И, кстати, не пугайтесь кода, так как он не будет легким. Не торопитесь, чтобы понять каждый метод, и после небольшой практики у вас все получится.

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

3. Оценка модели

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

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

Вот краткий обзор набора данных.

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

Давайте сначала добавим имена столбцов, чтобы набор данных имел смысл.

Теперь давайте закодируем данные, а затем выделим целевой класс.

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

И вот, наконец, мы можем перейти к тренировочной части. Мы создадим экземпляр класса, который мы написали ранее, а затем вызовем метод fit(). После этого мы будем использовать метод predict() для вывода прогнозов нашей модели.

Вот как выглядят прогнозы:

Очевидно, что мы не можем ничего оценить только по предсказанным меткам классов. Давайте распечатаем фактические метки, чтобы увидеть, как работает наша модель.

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

4. Сравнение со Scikit-Learn

Теперь давайте быстро сравним со встроенным классом DecisionTreeClassifier из sklearn. Это даст нам представление о том, насколько хороша наша модель, и у нас будет хороший ориентир. Давайте сделаем это быстро.

Оценка 94%, отлично! Таким образом, хотя наша модель не была идеальной, она все же давала хорошие результаты, учитывая тот факт, что она была написана с нуля без использования каких-либо предварительно созданных вспомогательных функций.

5. Заключительные слова

Вот и все! Мы сделали классификатор дерева решений с нуля. От загрузки набора данных до проверки производительности нашей модели каждая часть разработки модели обсуждалась подробно. В итоге мы добились довольно хорошей точности нашего классификатора. Если у вас все еще есть какие-либо вопросы, не стесняйтесь задавать их ниже.