Использование NumPy в Python

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

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

Что такое дерево решений?

Проще говоря, дерево решений - это, по сути, двоичное дерево. Чтобы объяснить, как работает дерево решений, полезно иметь гипотетическое (полностью составленное !!) дерево для ссылки:

Хотите прочитать эту историю позже? Сохраните в Журнале.

Пока в нижней части дерева не будет достигнут прогноз или «лист», каждый узел можно рассматривать как оператор if. В корневом узле (прямоугольник наверху дерева) алгоритм гипотетического дерева определил, что идеальной характеристикой является вес, а порог - 300 фунтов. Другими словами, человек, вес которого не превышает 300 фунтов, был наиболее идеальным показателем для определения его диабетического статуса. У тех, кто весит не менее 300 фунтов, дерево раскалывается в возрасте 60 лет. Затем он не нашел другого идеального разделения значений характеристик. Гипотетическое дерево показало, что люди, которые, как считалось, весили не менее 300 фунтов И не менее 60 лет, в большинстве своем страдали диабетом. Таким образом, модель может предсказать, что у человека диабет, если он весит 350 фунтов и ему 65 лет. Если спуститься по другому маршруту дерева, то у человека, который весит 200 фунтов и тренируется 1 час в неделю, будет прогноз, что он не болен диабетом. Идея состоит в том, что теперь мы можем ввести ожидаемые характеристики любого человека (вес, возраст, физические нагрузки, семейный анамнез), а обученное дерево классификации выведет их прогнозируемый статус диабета.

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

Расчет идеальных разбиений

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

n - количество выборок, а Pk² - доля наблюдений класса k.

Чтобы увидеть, как это работает на практике, давайте подумаем о примеси Джини в терминах гипотетического дерева примеров:

  • Примесь Джини для всех данных о тренировках, если предположить, что люди с диабетом и без диабета разделены на 50/50, будет 0,5. Другими словами, прогнозируя в соответствии с гипотетически измеренным распределением диабетического статуса, мы будем ошибаться в 50% случаев.
  • Если бы вместо этого было деление 75/25, то примесь Джини составила бы 0,375, и мы ожидали бы ошибиться в 37,5% случаев.

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

Это вычисляется для корневого узла (всех данных), а затем для каждого последующего подмножества данных, созданного каждым разбиением. Разделение продолжается до тех пор, пока не будет достигнута максимальная глубина или если примесь Джини дочерних узлов не меньше, чем у их родительского узла.

Применение классификаторов дерева решений

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

Преимущества включают:

  • Легко интерпретируемый
  • Отсутствие необходимости в предварительной обработке данных или разработке функций
  • Можно сделать более точным (с меньшим количеством перегибов), используя технику ансамбля для создания случайного леса.

К недостаткам можно отнести:

  • Восприимчив к ограничениям времени и памяти
  • Чувствителен к изменениям в тренировочных данных
  • Легко переборщить без правильной обрезки.

Реализация дерева решений

Класс узла

Во-первых, нам нужно создать класс Node, чтобы мы могли хранить информацию о любых разбиениях:

  • Прогнозируемый класс
  • Указатель возможностей
  • Порог

Мы также хотим знать, есть ли у конкретного узла дочерние узлы и что это за дочерние узлы:

  • Левый
  • Верно

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

  • Левая ветвь
  • Правая ветвь
  • Глубина

Класс классификатора дерева решений

Затем мы создаем DecisionTreeClassifier класс. На данный момент ему нужен только атрибут для максимальной глубины дерева и атрибут для самого дерева (корневой узел). Позже нам нужно будет добавить методы, чтобы мы могли соответствовать обучающим данным и прогнозировать новые данные.

Подходящий метод

Метод fit() класса Decision Tree Classifier довольно сложен, и я разбил его на несколько вспомогательных методов. Сам метод должен сохранять атрибуты для количества классов и функций во всем наборе данных, а также сохранять дерево после его создания. Он также ожидает X и y для функций и классов обучающих данных.

Найти метод разделения

Прежде чем переходить к grow_tree() методу, важно взглянуть на другой вспомогательный метод, который он использует, - метод find_split(). Метод поиска разбиения используется для поиска идеального признака и соответствующего значения признака для разбиения (идеальный столбец и идеальный порог). Это сделано для минимизации примеси Джини дочерних узлов. Это также самый сложный метод в этой статье, поэтому я разделю его на две части.

Найдите разделение, часть 1

  • Создайте экземпляр идеального столбца и идеальных пороговых переменных как Нет. Это переменные, возвращаемые find_split()
  • Соберите количество наблюдений и подсчитайте каждое уникальное значение класса, чтобы вычислить примесь Джини текущего узла
  • Убедитесь, что есть как минимум 2 наблюдения, чтобы можно было разделить, иначе верните
  • Вычислить примесь Джини текущего узла

* Все вызовы метода reshape используются для облегчения будущих операций с использованием NumPy *

Найдите разделение, часть 2

  • Прокрутите столбцы функций X
  • Объедините данные X (один столбец) и y в одну матрицу и отсортируйте их по значению X
  • Снова разделите их. Значения X теперь являются всеми возможными порогами, а значения y - наблюдаемым классом каждой строки данных.
  • Отслеживайте, сколько наблюдений каждого класса находится в левом и правом узле, используя num_left и num_right
  • Прокрутите индекс каждого наблюдения в данных
  • Найдите примесь Джини дочерних узлов для каждого потенциального разбиения и проверьте, меньше ли она примеси Джини родительского узла. Если это так, сохраните его как новый best_gini и обновите ideal_col и ideal_threshold.

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

Метод выращивания дерева

Метод выращивания дерева принимает данные X и y и строит все дерево решений. Он делает это путем рекурсивного создания каждого узла, идеального разделения (с использованием find_split()) и назначения атрибутов узла: predicted_class, depth, samples, leftbranch и rightbranch.

Прогнозируемый метод

И, наконец, метод прогнозирования. После использования метода подгонки вы можете ввести новые данные с помощью этого метода и посмотреть, какими будут значения (я) целевого класса, предсказанные классификатором дерева решений. Метод просматривает каждую строку данных, которая получает ввод, и перемещается по дереву с помощью while node.left:. Нам нужно использовать только while node.left:, потому что в дереве классификации никогда не будет узла только с одним дочерним узлом. Если дочерних узлов нет (когда node.left == None), то мы находимся на правильном листе и можем добавить предсказанный класс в список предсказаний.

Сравнение моего классификатора дерева решений с scikit-learn

Чтобы сравнить мой класс Decision Tree Classifier с классом scikit-learn, я использовал scikit-learn, встроенный в набор данных Wine Toy. Я построил деревья со своим классом и с помощью scikit-learn, затем сравнил древовидные структуры и точность тестов.

Вот как выглядело мое дерево (вы можете увидеть мою функцию печати в моем репозитории кода):

Вот как выглядело дерево scikit-learn:

Они почти идентичны, за исключением реализации scikit-learn с дополнительным разделением с правой стороны. Вот прогнозы двух реализаций и соответствующие точности тестов:

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

Заключение

Несмотря на то, что деревья решений рекламировались как простой алгоритм, это была довольно сложная задача, для выполнения которой потребовалось много исследований. Я очень горжусь тем, какой оказалась моя реализация в отношении scikit-learn, хотя это был очень небольшой тест. Теперь я гораздо сильнее ценю все замечательные библиотеки анализа данных Python, а также гораздо лучше понимаю, как работают деревья решений. Спасибо за чтение, надеюсь, вам понравилось!

📝 Сохраните эту историю в Журнале.

👩‍💻 Просыпайтесь каждое воскресное утро и слышите самые интересные истории недели в области технологий, ожидающие в вашем почтовом ящике. Прочтите информационный бюллетень« Примечательно в технологиях .