Создание своего случайного леса с нуля на Python и интерпретация математики, лежащей в основе «черного ящика»

Мотивация: ансамбли случайных лесов широко используются для решения реальных задач машинного обучения, классификации и регрессии. Их популярность можно объяснить тем фактом, что специалисты-практики часто получают оптимальные результаты, используя алгоритм случайного леса с минимальной очисткой данных и без масштабирования функций. Чтобы лучше понять лежащие в основе принципы, я решил написать случайный лес с нуля и выполнить несколько визуализаций различных функций в данных и их роли в принятии окончательного результата. Алгоритм, который широко считается черным ящиком, покажет, что он на самом деле вполне интерпретируемый, не говоря уже о том, что это мощный метод, использующий «силу большинства голосов».

Введение

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

Случайный лесной ансамбль

В основе концепции случайного леса лежит усреднение результатов ряда деревьев решений. Деревья решений часто являются удобными инструментами для объяснения интуиции, лежащей в основе прогнозов, людям, незнакомым с машинным обучением. Но объяснить, как случайный лес пришел к прогнозу и использовать какие функции или независимые переменные, может оказаться непростой задачей. Случайные леса часто ошибочно воспринимаются как «черные ящики» или трудны для понимания.

Кодирование случайного леса на Python

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

Классы случайного леса и дерева решений

Используя подход объектно-ориентированного программирования в Python, определяется класс «RandomForest», состоящий из объектов «DecisionTree». Случайный лес - это, по сути, сборщик и вычислитель средних значений для каждой из основных функций, выполняемых его деревьями решений.

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

Логический процесс для обучения модели

Метод fit () в классе Random Forest вызывается для обучения модели, которая вызывает функцию var_split () в классе Decision Tree, которая вызывает find_better_split () в цикле. Это создает каждое из деревьев решений в лесу и подбирает обучающие данные для каждого дерева.

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

  • Первый цикл проходит через все функции дерева, чтобы определить, какая из них лучше всего подходит для разделения на определенном уровне. Функция var_split () вызывает find_better_split () для каждой функции, чтобы определить функцию, дающую наиболее чистые листовые узлы.
  • Во внутреннем цикле для каждого объекта вызывается функция find_better_split (), чтобы вычислить оптимальное значение признака, при котором дерево должно быть разделено.
  • Это делается путем сортировки значений этой функции, перебора всех отдельных значений функции и отслеживания соответствующих значений зависимой переменной.
  • Вычисляется оценка, которая представляет собой получение информации, полученной путем разбиения дерева для каждой характеристики по каждому отдельному значению. Эта оценка представляет собой дисперсию поддеревьев, сформированных после разделения, которую необходимо минимизировать.
  • Для разделения выбирается комбинация признака и значения признака, приводящая к наименьшей дисперсии в поддеревьях или максимальной однородности.
  • После нахождения лучшей функции и оптимального значения разделения дерево разделяется на левое и правое поддеревья, и заполняются члены класса leftTree и rightTree.
  • Функция var_split () вызывается рекурсивно для членов leftTree и rightTree, чтобы найти лучшую функцию для разделения и лучшее значение этой функции. Это продолжается до тех пор, пока не будет выполнено одно из условий листового узла, то есть максимальная глубина дерева или минимальное количество выборок данных.
  • Значение, присвоенное этому листу, является средним значением обучающих выборок, содержащихся в нем, или соответствующим значением, которое в совокупности представляет данные в листе.

Прогнозы для набора данных тестирования / проверки

Код для прогнозирования результатов с использованием модели довольно понятен.

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

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

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

Интерпретация модели случайного леса

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

Интуиция, лежащая в основе важности функций

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

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

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

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

Графики частичной зависимости для случайного леса

Графики частичной зависимости особенно полезны для понимания того, как результат или целевая переменная изменяется в зависимости от конкретной функции, и каковы фактические отношения между целью и этой функцией. Монотонно увеличивается / уменьшается или нерегулярно?

Одномерный график против графика частичной зависимости

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

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

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

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

Реализация графика частичной зависимости

Моя реализация графика частичной зависимости выглядит следующим образом:

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

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

Интерпретаторы деревьев для случайного леса

Очень полезный метод объяснения потока предсказаний случайного леса - использование интерпретаторов деревьев для каждого дерева решений.

· Значение в корне дерева (которое представляет собой просто среднее значение всех целей в обучающих данных) называется «смещением».

· Функция predic () внутри класса Tree Interpreter вызывает функцию pred_row_for_ti () внутри дерева решений, которая предсказывает результат для «строки» или отдельной выборки данных.

· При каждом разбиении вклад функции, используемой в разбиении, рассчитывается с использованием количества, на которое будет изменяться среднее значение выборок после разбиения на поддеревья.

· Добавленные функции хранятся в массиве до тех пор, пока не будет достигнут лист, а затем результаты возвращаются интерпретатору дерева.

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

Диаграмма водопада на основе Tree Interpreter

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

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

Будущие улучшения

Вот несколько областей, в которых необходимо дальнейшее пополнение библиотеки Random Forest:

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

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

Примечание. Стандартные библиотеки машинного обучения в Python оптимизированы для обеспечения скорости и эффективности, поэтому любой код, который вы пишете с нуля, может потребоваться выполнить в Cython (базовая реализация C используется Python) для достижения более быстрых результатов. Для этого просто добавьте «%% cython» в начало вашего кода.

Заключительные мысли и основные выводы

Это хорошая отправная точка для написания алгоритмов машинного обучения с нуля, а также понимания нескольких суперполезных библиотек на Python, включая Numpy для быстрых математических вычислений и Matplotlib для визуализации данных.

Полную реализацию можно найти в моем блокноте Jupyter по адресу https://github.com/SonaliDasgupta/MLandAIAlgorithmsFromScratch, где я попытался поиграть с идеями и визуализациями, используя набор данных. Код требует доработки и оптимизации, и я над этим работаю. Любые предложения или вклады приветствуются.

Это было очень приятное упражнение для меня, и я надеюсь, что помог вам глубже математически и визуально погрузиться в реализацию Random Forest и понять, что это не так сложно или «черный ящик», как кажется в конце концов.

Ссылки:

  1. Http://blog.citizennet.com/blog/2012/11/10/random-forests-ensembles-and-performance-metrics
  2. Https://course.fast.ai/ml.html