Не заблудись в лесу (дерево решений)

машинное обучение GitHub алгоритм Kaggle
Источник: rddisorder Сборник: слабенький

Примечание редактора: Алан Марацци, специалист по данным в страховой отрасли, использует R, чтобы продемонстрировать мощь и простоту моделей на основе дерева решений.

Это краткое введение в модели на основе дерева решений, в котором максимально используются нетехнические термины, а также дана реализация модели на языке R. Поскольку этот пост достаточно длинный, мы опустили часть кода. Но не волнуйтесь, вы можете найти полный код в соответствующем репозитории GitHub: https://github.com/alanmarazzi/trees-forest.

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

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

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

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

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

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

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

Когда использовать модель на основе дерева решений

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

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

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

Конфигурация языка R

Прежде чем приступить к работе, вам может потребоваться сначала настроить среду R.

Установите следующие пакеты:

trees_packages <- c("FFTrees",    "evtree",    "party",    "randomForest",    "intubate",    "dplyr")install.packages(trees_packages)скопировать код

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

Пришло время посадки деревьев! Я решил использовать набор данных Titanic, один из самых известных наборов данных в сообществе машинного обучения. Вы можете получить этот набор данных из Kaggle (c/titanic) или GitHub (alanmarazzi/trees-forest). Я начну непосредственно с очистки данных и моделирования.Если вам нужна помощь с загрузкой данных, загрузкой или отсутствием подсказок, вы можете обратиться к моей предыдущей статье Data Science in Minutes или к полному коду в репозитории GitHub.

Предварительные данные

Во-первых, давайте посмотрим, как выглядят данные для обработки:

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

names(titanic) <- tolower(names(titanic))скопировать код

Затем преобразуйте переменные пола и посадки в факторы (категориальные переменные):

titanic$sex <- as.factor(titanic$sex)titanic$embarked <- as.factor(titanic$embarked)скопировать код

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

Существуют различные методы заполнения NA: заполнение среднего, медианы, моды или прогнозирование их значений с помощью модели. В нашем примере будет использоваться линейная регрессия для замены отсутствующих значений переменной age в наборе данных.

На первый взгляд идея кажется немного пугающей и немного странной, и вы можете подумать: «Что вы говорите, чтобы улучшить мою модель, я должен использовать другую модель?!» Но это не так сложно, как кажется, особенно если мы используем линейную регрессию.

Сначала давайте посмотрим, сколько NA содержится в переменной age:

mean(is.na(titanic$age))[1] 0.1986532скопировать код

Почти у 20% пассажиров нет записей о возрасте, а это означает, что если мы запустим модель непосредственно на наборе данных без замены отсутствующих значений, наши обучающие данные будут содержать только 714 элементов вместо 891 элемента.

Время для запуска линейной регрессии на данных:

age_prediction <- lm(age ~ survived + pclass + fare, data = titanic)summary(age_prediction)скопировать код

что мы сделали? Мы говорим R решить следующее линейное уравнение, чтобы найти соответствующие значения для α и βn.

age = α + β1∗survived + β2∗pclass + β3∗fareскопировать код

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

Call:lm(formula = age ~ survived + pclass + fare, data = titanic)Residuals:    Min      1Q  Median      3Q     Max -37.457  -8.523  -1.128   8.060  47.505 Coefficients:            Estimate Std. Error t value Pr(>|t|)    (Intercept) 54.14124    2.04430  26.484  < 2e-16 ***survived    -6.81709    1.06801  -6.383 3.14e-10 ***pclass      -9.12040    0.72469 -12.585  < 2e-16 ***fare        -0.03671    0.01112  -3.302  0.00101 ** ---Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1Residual standard error: 13.03 on 710 degrees of freedom  (177 observations deleted due to missingness)Multiple R-squared:  0.1993,    Adjusted R-squared:  0.1959 F-statistic:  58.9 on 3 and 710 DF,  p-value: < 2.2e-16скопировать код

Первая строка выше (Call) говорит нам, какая модель дала этот результат, вторая строка показывает остатки, за которыми следуют коэффициенты. Здесь мы можем посмотреть на оценки коэффициентов, их стандартные отклонения, t-значения и p-значения. После этого идут другие статистические данные. Мы видим, что R фактически удаляет данные, содержащие NA (177 наблюдений удалены из-за отсутствия).

Теперь мы можем использовать эту модель для заполнения NA. Мы используем функцию предсказания():

titanic$age[is.na(titanic$age)] <- predict(age_prediction,    newdata = titanic[is.na(titanic$age),])скопировать код

Тест логистической регрессии

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

Не волнуйтесь, логистическая регрессия в R довольно проста. Мы вводим библиотеки dplyr и intubate, а затем вызываемglm()Функция запускает логистическую регрессию.glm()принимает три параметра,predictorsявляются предикторами, такими как возраст, класс,responseэто результирующая переменная, здесь мы передаемsurvived,familyУкажите категорию возвращаемого результата, здесь мы передаемbinomial.

library(dplyr) # 数据处理library(intubate) # 建模工作流# btbt_glb是 %>% 版本的glm函数logi <- titanic %>%     select(survived, pclass, sex, age, sibsp) %>%     ntbt_glm(survived ~ ., family = binomial)summary(logi)скопировать код

Давайте посмотрим на прогнозы, сделанные моделью логистической регрессии:

# 收集训练数据上的预测logi_pred <- predict(logi, type = "response")# 预测值在0和1之间,我们将其转换为`survived`或`not`survivors_logi <- rep(0, nrow(titanic))survivors_logi[logi_pred > .5] <- 1# 这将成为我们的基准table(model = survivors_logi, real = titanic$survived)скопировать код

Приведенная выше матрица путаницы дает результаты модели на обучающих данных: было предсказано, что 572 человека умрут (0), а 319 человек выжили (1). Диагональ матрицы показывает, что 480 и 250 человек были предсказаны правильно, в то время как 92 человека, которым было предсказано, что они умрут, на самом деле выжили, а 69 человек, которым было предсказано, что они выживут, не выжили.

Точность предсказания 82% — это неплохо для такой готовой модели. Но мы хотим протестировать ее на невидимых данных, поэтому давайте загрузим тестовый набор и посмотрим, как модель работает на тестовом наборе.

test <- read.csv(paste0("https://raw.githubusercontent.com/",    "alanmarazzi/trees-forest/master/data/test.csv"),    stringsAsFactors = FALSE,    na.strings = "")# 和训练集一样,清洗下数据names(test) <- tolower(names(test))test$sex <- as.factor(test$sex)скопировать код

Следующее предсказывает выживаемость на основе тестовых данных:

test_logi_pred <- predict(logi, test, type = "response")surv_test_logi <- data.frame(PassengerId = test$passengerid,     Survived = rep(0, nrow(test)))surv_test_logi$Survived[test_logi_pred > .5] <- 1write.csv(surv_test_logi, "results/logi.csv", row.names = FALSE)скопировать код

Мы сохраняем результаты в формате csv, потому что тестовые данные не имеют меток, и мы не знаем, верны ли прогнозы. Нам нужно загрузить результат в Kaggle, чтобы увидеть результат. Окончательная модель сделала 77,5% правильных прогнозов.

Быстрые и недорогие деревья решений

Наконец-то пришло время сажать деревья! Первая модель, которую мы попробуем, — это быстрое и недорогое дерево решений. Это, по сути, самая простая модель. Мы будем использовать RFFTreesСумка.

# 临时复制下数据集,因为FFTrees包里也包含titanic变量titanicc <- titaniclibrary(FFTrees)titanic <- titaniccrm(titanicc)скопировать код

Загружая пакет, мы просто применяем его к выбранной переменнойFFTrees.

fftitanic <- titanic %>%     select(age, pclass, sex, sibsp, fare, survived) %>%     ntbt(FFTrees, survived ~ .)скопировать код

Для запуска модели требуется некоторое время, потому что нужно обучить и протестировать более одного FFT-дерева. Конечным результатом является объект FFTree, содержащий все протестированные деревья FFT:

fftitanic[1] "An FFTrees object containing 8 trees using 4 predictors {sex,pclass,fare,age}"[1] "FFTrees AUC: (Train = 0.84, Test = --)"[1] "My favorite training tree is #5, here is how it performed:"                        trainn                      891.00p(Correct)               0.79Hit Rate (HR)            0.70False Alarm Rate (FAR)   0.16d-prime                  1.52скопировать код

Мы видим, что алгоритм протестировал 8 деревьев с 4 предикторами, и лучшим было дерево 5. Затем мы увидели некоторую статистику по этому дереву. Эти результаты полезны, но визуализация может помочь нам лучше понять, что происходит:

plot(fftitanic,      main = "Titanic", decision.names = c("Not Survived", "Survived"))скопировать код

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

Первый узел дерева решений рассматриваетsexПеременные: если женщина (sex != male), мы выйдем из дерева решений напрямую и предскажем выживание. Грубо, но достаточно эффективно. Если мужчина, пройдет через второй узелpclass. Здесь, если это 3-й класс, мы выходим из дерева решений и предсказываем смерть. Далее, если плата за лодку превышает 26,96 фунтов стерлингов (fare), обещают выжить. Последний рассматриваемый узелage(Возраст): предскажите смерть, если возраст больше 21,35 года.

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

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

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

ffpred <- ifelse(test$sex != "male", 1,                 ifelse(test$pclass > 2, 0,                        ifelse(test$fare < 26.96, 0,                               ifelse(test$age >= 21.36, 0, 1))))ffpred[is.na(ffpred)] <- 0скопировать код

Всего с помощью 4 вложенных операторов ifelse мы можем классифицировать весь набор данных. У нас было только 2 NA, поэтому я решил классифицировать их как «не выжившие». Затем мы просто загружаем результаты в формате csv в Kaggle и смотрим, как работает модель.

Наши 4 оператора if-else работают всего на 1% хуже, чем в бенчмарке, что, учитывая простоту модели, очень хороший результат.

время веселиться

Пакет party использует условные деревья вывода, более сложные деревья решений, чем FFT-деревья. Проще говоря, дерево условного вывода учитывает не только важность, но и распределение данных при принятии решения о разделении узлов. Хотя дерево условного вывода более сложное, его просто использовать, после загрузки пакета просто используйтеctreeФункция для создания дерева решений.

library(party)partyTitanic <- titanic %>%     select(age, pclass, sex, sibsp, fare, survived) %>%     ntbt(ctree, as.factor(survived) ~ .)скопировать код

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

plot(partyTitanic, main = "Titanic prediction", type = "simple",     inner_panel = node_inner(partyTitanic,                               pval = FALSE),     terminal_panel = node_terminal(partyTitanic,                                    abbreviate = TRUE,                                    digits = 1,                                    fill = "white"))скопировать код

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

Дополнительная сложность снижает ошибку обучения на 15%. Это улучшение по сравнению с FFTree выше.

train_party <- Predict(partyTitanic)table(tree = train_party, real = titanic$survived)скопировать код

К сожалению, дальше мы усвоим один из самых важных уроков машинного обучения. На самом деле точность классификации на тестовом наборе составляет всего 73,7%!

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

ансамблевая модель

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

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

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

Мы рассмотрим три разных алгоритма ансамбля: бэггинг, случайный лес, бустинг.

Bagging

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

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

x <- rnorm(100) # 生成随机向量# 定义固定取样函数boot_x <- function(x, size) {    sample(x, size, replace = TRUE)}# 循环取样,直到取满需要的样本bootstrapping <- function(x, reps, size) {    y <- list()    for (i in seq_len(reps)) {        y[[i]] <- boot_x(x, size)    }    y}# 结果是一个列表z <- bootstrapping(x, 500, 20)скопировать код

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

library(randomForest)# 如果你希望重现结果,别忘了设置一样的随机数种子set.seed(123)# 创建bagging模型titanic_bag <- titanic %>%     select(survived, age, pclass, sex, sibsp, fare, parch) %>%     ntbt_randomForest(as.factor(survived) ~ ., mtry = 6)скопировать код

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

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

Есть проблема с приведенным выше кодом, он пропускает NA напрямую и не делает прогнозов. Чтобы генерировать результаты, совместимые с Kaggle, без дальнейшей разработки функций, я решил заменить NA в тестовом наборе медианами. К сожалению, эта проблема ограничивала предсказательную силу, в результате чего вероятность правильного предсказания составила 66,5%.

случайный лес

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

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

Чтобы ограничить такое поведение, мы используем случайные леса.mtryПараметр ограничивает предиктор. Мы используем перекрестную проверку, чтобы определить «лучшие» значения параметров, или пробуем некоторые эмпирические правила, такие какncol(data)/3иsqrt(ncol(data)), но в этом примере я будуmtryЗначение параметра установлено на 3.

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

set.seed(456)titanic_rf <- titanic %>%     select(survived, age, pclass, sex, sibsp, fare, parch) %>%     ntbt_randomForest(as.factor(survived) ~ ., mtry = 3, n.trees = 5000)скопировать код

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

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

set.seed(415)titanic_rf_party <- titanic %>%     select(survived, age, pclass, sex, sibsp, fare, parch) %>%     ntbt(cforest, as.factor(survived) ~ .,             controls = cforest_unbiased(ntree = 5000, mtry = 3))скопировать код

Как видите, код тот же, что и раньше, но разве результат не тот же?

Этот результат почти ничья с логистической регрессией.

Boosting

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

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

library(gbm)set.seed(999)titanic_boost <- titanic %>%     select(survived, age, pclass, sex, sibsp, fare, parch) %>%     ntbt(gbm, survived ~ .,         distribution = "bernoulli",         n.trees = 5000,         interaction.depth = 3)скопировать код

Мы используемgbmОдноименная функция в пакете (ntbt), и указатьdistributionПараметрыbernoulli(распределение Бернулли), сообщая функции, что это проблема классификации.n.treesПараметр указывает количество создаваемых деревьев решений,interaction.depthЗадает максимальную глубину дерева.

76%, аналогично результатам логистической регрессии, случайных лесов и БПФ-деревьев.

мы выучили

  • сложная модель > простая модель == ложь. Логистическую регрессию и БПФ-деревья трудно превзойти, и мы можем еще больше повысить производительность простых моделей, просто немного разработав функции.

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

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

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

Оригинальный адрес: https://www.rdisorder.eu/2016/12/21/dont-get-lost-in-a-forest/