Принятие решений на основе данных с помощью Python

Это первая статья из серии о деревьях решений. В этом посте я представлю деревья решений и опишу, как их растить с помощью данных. Пост завершается примером кода Python, показывающим, как создать и использовать дерево решений для составления медицинских прогнозов.

Ключевые моменты:

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

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

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

Они работают с серией вопросов «да-нет», которые используются для сужения возможных вариантов и достижения результата. Ниже показан простой пример дерева решений.

Как показано на рисунке выше, дерево решений состоит из узлов, соединенных направленными ребрами. Каждый узел в дереве решений соответствует условному оператору, основанному на переменной предиктора.

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

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

И наоборот, если это 16:00 или раньше, мы идем по правой ветке и оказываемся в так называемом разделяющем узле. Эти узлы дальше разбивают записи данных на основе условных операторов. Отсюда мы оцениваем, было ли количество часов сна прошлой ночи больше 6 часов. Если да, то снова идем с чаем, а если нет, то идем с кофе ☕️.

Использование деревьев решений

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

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

Графическое представление дерева решений

Другой способ представления деревьев решений – графический. (Это интуиция, которую я использую для деревьев решений.)

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

Интуитивно это все, что делает дерево решений. Разбиение пространства предикторов на разделы и присвоение метки (или вероятности) каждому разделу.

Как вырастить дерево решений?

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

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

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

В случае деревьев решений жадный поиск определяет выигрыш от каждого возможного варианта разбиения, а затем выбирает тот, который дает наибольший выигрыш [1,2]. Здесь «выигрыш» определяется критерием разделения, который может основываться на нескольких различных величинах, например Примесь Джини, прирост информации, среднеквадратическая ошибка (MSE) и другие. Этот процесс рекурсивно повторяется до тех пор, пока дерево решений не вырастет полностью.

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

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

Настройка гиперпараметров

Одним из способов борьбы с проблемой переобучения является настройка гиперпараметров. Гиперпараметры — это значения, ограничивающие рост дерева решений.

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

Альтернативные стратегии обучения

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

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

Хотя процесс обрезки может быть выполнен несколькими способами, он обычно удаляет узлы, которые не увеличивают значительно ошибку модели. Это альтернативный способ избежать переобучения вместо настройки гиперпараметров для ограничения роста дерева [3].

Максимальное правдоподобие. Мы можем обучить дерево решений, используя структуру максимального правдоподобия [4]. Хотя этот подход менее известен, он опирается на сильную теоретическую основу. Это позволяет нам использовать информационные критерии, такие как AIC и BIC, для объективной оптимизации количества параметров в дереве и его производительности, что помогает избежать необходимости обширной настройки гиперпараметров.

Пример кода: Прогноз выживания при сепсисе с использованием дерева решений

Теперь, когда у вас есть общее представление о деревьях решений и о том, как мы можем разработать их на основе данных, давайте углубимся в конкретный пример с использованием Python. Здесь мы будем использовать набор данных из репозитория машинного обучения UCI для обучения дерева решений, чтобы предсказать, выживет ли пациент, исходя из его возраста, пола и количества перенесенных эпизодов сепсиса [5,6].

Для обучения дерева решений мы будем использовать библиотеку Python sklearn [7]. Код этого примера находится в свободном доступе в репозитории GitHub.

Начнем с импорта некоторых полезных библиотек.

# import modules
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from sklearn.model_selection import train_test_split
from sklearn import tree
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from sklearn.metrics import precision_score, recall_score, f1_score
from imblearn.over_sampling import SMOTE

Затем мы загружаем наши данные из файла .csv и выполняем некоторую подготовку данных.

# read data from csv
df = pd.read_csv('raw/s41598-020-73558-3_sepsis_survival_primary_cohort.csv')

# look at data distributions
plt.rcParams.update({'font.size': 16})

# plot histograms
df.hist(figsize=(12,8))

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

Чтобы справиться с этой ситуацией, мы можем увеличить выборку миноритарного класса, чтобы сделать наши данные более сбалансированными. Один из способов сделать это — использовать метод под названием Техника искусственного меньшинства перед выборкой (SMOTE). Хотя я оставлю дополнительные подробности о SMOTE для будущей статьи, сейчас достаточно сказать, что это помогает нам сбалансировать наши данные и улучшить нашу модель дерева решений.

# Balance data using SMOTE

# define predictor and target variable names
X_var_names = df.columns[:3]
y_var_name = df.columns[3]

# create predictor and target arrays
X = df[X_var_names]
y = df[y_var_name]

# oversample minority class using smote
X_resampled, y_resampled = SMOTE().fit_resample(X, y)

# plot resulting outcome histogram
y_resampled.hist(figsize=(6,4))
plt.title('hospital_outcome_1alive_0dead \n (balanced)')

Последним шагом в подготовке наших данных является разделение наших данных передискретизации на наборы данных для обучения и тестирования. данные обучения будут использоваться для выращивания дерева решений, а данные тестирования будут использоваться для оценки его производительности. . Здесь мы используем разделение поезд-тест 80–20.

# create train and test datasets
X_train, X_test, y_train, y_test = \
      train_test_split(X_resampled, y_resampled, test_size=0.2, random_state=0)

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

# Training
clf = tree.DecisionTreeClassifier(random_state=0)
clf = clf.fit(X_train, y_train)

Давайте посмотрим на результат.

# Display decision tree
plt.figure(figsize=(24,16))

tree.plot_tree(clf)
plt.savefig('visuals/fully_grown_decision_tree.png',facecolor='white',bbox_inches="tight")
plt.show()

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

Для оценки производительности мы используем матрицу путаницы, которая отображает количество истинно положительных (TP), истинно отрицательных (TN), ложноположительных (FP) и ложноотрицательных (FN).

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

Мы можем взять числа из наших матриц путаницы и вычислить три разных показателя производительности: точность, отзыв и показатель f1. Вкратце, точность = TP / (TP + FP) , отзыв = TP / (TP + FN), а f1-оценка – это среднее гармоническое точности и отзыва.

Код для генерации этих результатов приведен ниже.

# Function to plot confusion matrix and print precision, recall, and f1-score
def evaluateModel(clf, X, y):

    # confusion matrix
    y_pred = clf.predict(X)
    cm = confusion_matrix(y, y_pred)
    cm_disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=['dead', 'alive'])
    cm_disp.plot()

    # print metrics
    print("Precision = " + str(np.round(precision_score(y, y_pred),3)))
    print("Recall = " + str(np.round(recall_score(y, y_pred),3)))
    print("F1 = " + str(np.round(f1_score(y, y_pred),3)))

Настройка гиперпараметров

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

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

Здесь мы попробуем установить max_depth = 3.

# train model with max_depth set to 3
clf_tuned = tree.DecisionTreeClassifier(random_state=0, max_depth=3)
clf_tuned = clf_tuned.fit(X_train, y_train)

Теперь давайте посмотрим на получившееся дерево решений.

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

Мы снова оцениваем производительность модели, используя матрицы путаницы и те же три метрики производительности, что и раньше.

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

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

Эту гипотезу можно проверить, применив каждую модель к двум другим наборам данных, доступным в репозитории GitHub.



Ансамбли деревьев решений

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

Популярным решением этой проблемы является использование ансамбля деревьев, а не одного дерева решений для прогнозирования. Они называются ансамблями деревьев решений и станут темой следующей статьи этой серии.





Ресурсы

Подробнее о деревьях решений: Ансамбли деревьев | 2 варианта использования

Подключить: Мой сайт | Заказать звонок | "Спрашивай о чем угодно"

Социальные сети: YouTube 🎥 | ЛинкедИн | Твиттер

Поддержка: Купи мне кофе ☕️ | Стать участником⭐️

[1] Деревья классификации и регрессии, Брейман и др.

[2] Деревья решений: недавний обзор Коциантиса С.Б.

[3] Сравнительный анализ методов обрезки деревьев решений Эспозито и др.

[4] Деревья регрессии максимального правдоподобия Су и др.

[5] Дуа, Д. и Графф, К. (2019). Репозиторий машинного обучения UCI [http://archive.ics.uci.edu/ml]. Ирвин, Калифорния: Калифорнийский университет, Школа информационных и компьютерных наук. (CC BY 4.0)

[6] Прогноз выживаемости пациентов с сепсисом только по возрасту, полу и количеству септических эпизодов по Chicco & Jurman

[7] Scikit-learn: Machine Learning in Python, Pedregosa et al., JMLR 12, стр. 2825–2830, 2011.