Учебники

28) Деревья решений в R

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

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

Обучение и визуализация деревьев решений

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

Шаг 1) Импортируйте данные

Если вам интересно узнать о судьбе Титаника, вы можете посмотреть это видео на Youtube . Цель этого набора данных — предсказать, какие люди с большей вероятностью выживут после столкновения с айсбергом. Набор данных содержит 13 переменных и 1309 наблюдений. Набор данных упорядочен по переменной X.

set.seed(678)
path <- 'https://raw.githubusercontent.com/guru99-edu/R-Programming/master/titanic_data.csv'
titanic <-read.csv(path)
head(titanic)

Вывод:

##   X pclass survived                                            name    sex
## 1 1      1        1                   Allen, Miss. Elisabeth Walton female
## 2 2      1        1                  Allison, Master. Hudson Trevor   male
## 3 3      1        0                    Allison, Miss. Helen Loraine female
## 4 4      1        0            Allison, Mr. Hudson Joshua Creighton   male
## 5 5      1        0 Allison, Mrs. Hudson J C (Bessie Waldo Daniels) female
## 6 6      1        1                             Anderson, Mr. Harry   male
##       age sibsp parch ticket     fare   cabin embarked
## 1 29.0000     0     0  24160 211.3375      B5        S
## 2  0.9167     1     2 113781 151.5500 C22 C26        S
## 3  2.0000     1     2 113781 151.5500 C22 C26        S
## 4 30.0000     1     2 113781 151.5500 C22 C26        S
## 5 25.0000     1     2 113781 151.5500 C22 C26        S
## 6 48.0000     0     0  19952  26.5500     E12        S
##                         home.dest
## 1                    St Louis, MO
## 2 Montreal, PQ / Chesterville, ON
## 3 Montreal, PQ / Chesterville, ON
## 4 Montreal, PQ / Chesterville, ON
## 5 Montreal, PQ / Chesterville, ON
## 6                    New York, NY
tail(titanic)

Вывод:

##         X pclass survived                      name    sex  age sibsp
## 1304 1304      3        0     Yousseff, Mr. Gerious   male   NA     0
## 1305 1305      3        0      Zabour, Miss. Hileni female 14.5     1
## 1306 1306      3        0     Zabour, Miss. Thamine female   NA     1
## 1307 1307      3        0 Zakarian, Mr. Mapriededer   male 26.5     0
## 1308 1308      3        0       Zakarian, Mr. Ortin   male 27.0     0
## 1309 1309      3        0        Zimmerman, Mr. Leo   male 29.0     0
##      parch ticket    fare cabin embarked home.dest
## 1304     0   2627 14.4583              C          
## 1305     0   2665 14.4542              C          
## 1306     0   2665 14.4542              C          
## 1307     0   2656  7.2250              C          
## 1308     0   2670  7.2250              C          
## 1309     0 315082  7.8750              S

Из вывода головы и хвоста вы можете заметить, что данные не перемешаны. Это большая проблема! Когда вы разделите свои данные между набором поездов и тестовым набором, вы выберете только пассажира из классов 1 и 2 (ни один пассажир из класса 3 не входит в верхние 80 процентов наблюдений), что означает, что алгоритм никогда не увидит Особенности пассажира класса 3. Эта ошибка приведет к плохому прогнозу.

Чтобы преодолеть эту проблему, вы можете использовать функцию sample ().

shuffle_index <- sample(1:nrow(titanic))
head(shuffle_index)

Код Объяснение

  • sample (1: nrow (titanic)): создать случайный список индексов от 1 до 1309 (т. е. максимальное количество строк).

Вывод:

## [1]  288  874 1078  633  887  992	

Вы будете использовать этот индекс для перетасовки титанового набора данных.

titanic <- titanic[shuffle_index, ]
head(titanic)

Вывод:

##         X pclass survived
## 288   288      1        0
## 874   874      3        0
## 1078 1078      3        1
## 633   633      3        0
## 887   887      3        1
## 992   992      3        1
##                                                           name    sex age
## 288                                      Sutton, Mr. Frederick   male  61
## 874                   Humblen, Mr. Adolf Mathias Nicolai Olsen   male  42
## 1078                                 O'Driscoll, Miss. Bridget female  NA
## 633  Andersson, Mrs. Anders Johan (Alfrida Konstantia Brogren) female  39
## 887                                        Jermyn, Miss. Annie female  NA
## 992                                           Mamee, Mr. Hanna   male  NA
##      sibsp parch ticket    fare cabin embarked           home.dest## 288      0     0  36963 32.3208   D50        S     Haddenfield, NJ
## 874      0     0 348121  7.6500 F G63        S                    
## 1078     0     0  14311  7.7500              Q                    
## 633      1     5 347082 31.2750              S Sweden Winnipeg, MN
## 887      0     0  14313  7.7500              Q                    
## 992      0     0   2677  7.2292              C	

Шаг 2) Очистите набор данных

Структура данных показывает, что некоторые переменные имеют NA. Очистка данных должна быть сделана следующим образом

  • Сбросить переменные home.dest, каюта, имя, X и билет
  • Создайте факторные переменные для pclass и выжили
  • Брось АН
library(dplyr)
# Drop variables
clean_titanic <- titanic % > %
select(-c(home.dest, cabin, name, X, ticket)) % > % 
#Convert to factor level
	mutate(pclass = factor(pclass, levels = c(1, 2, 3), labels = c('Upper', 'Middle', 'Lower')),
	survived = factor(survived, levels = c(0, 1), labels = c('No', 'Yes'))) % > %
na.omit()
glimpse(clean_titanic)

Код Объяснение

  • выберите (-c (home.dest, каюта, имя, X, билет)): удалить ненужные переменные
  • pclass = factor (pclass, level = c (1,2,3), label = c («Upper», «Middle», «Lower»)): добавить метку в переменную pclass. 1 становится верхним, 2 становится простым, а 3 становится ниже
  • фактор (выжил, уровни = c (0,1), метки = c («нет», «да»)): добавить метку к выжившей переменной. 1 становится нет, а 2 становится да
  • na.omit (): удалить наблюдения NA

Вывод:

## Observations: 1,045
## Variables: 8
## $ pclass   <fctr> Upper, Lower, Lower, Upper, Middle, Upper, Middle, U...
## $ survived <fctr> No, No, No, Yes, No, Yes, Yes, No, No, No, No, No, Y...
## $ sex      <fctr> male, male, female, female, male, male, female, male...
## $ age      <dbl> 61.0, 42.0, 39.0, 49.0, 29.0, 37.0, 20.0, 54.0, 2.0, ...
## $ sibsp    <int> 0, 0, 1, 0, 0, 1, 0, 0, 4, 0, 0, 1, 1, 0, 0, 0, 1, 1,...
## $ parch    <int> 0, 0, 5, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 2, 0, 4, 0,...
## $ fare     <dbl> 32.3208, 7.6500, 31.2750, 25.9292, 10.5000, 52.5542, ...
## $ embarked <fctr> S, S, S, S, S, S, S, S, S, C, S, S, S, Q, C, S, S, C...		

Шаг 3) Создайте поезд / тестовый набор

Прежде чем тренировать свою модель, вам нужно выполнить два шага:

  • Создание поезда и набора тестов: вы тренируете модель на наборе поездов и тестируете прогноз на наборе тестов (то есть невидимых данных)
  • Установите rpart.plot из консоли

Обычной практикой является разделение данных на 80/20, 80 процентов данных служат для обучения модели, а 20 процентов — для прогнозирования. Вам необходимо создать два отдельных фрейма данных. Вы не хотите трогать тестовый набор, пока не закончите сборку модели. Вы можете создать имя функции create_train_test (), которое принимает три аргумента.

create_train_test(df, size = 0.8, train = TRUE)
arguments:
-df: Dataset used to train the model.
-size: Size of the split. By default, 0.8. Numerical value
-train: If set to `TRUE`, the function creates the train set, otherwise the test set. Default value sets to `TRUE`. Boolean value.You need to add a Boolean parameter because R does not allow to return two data frames simultaneously.
create_train_test <- function(data, size = 0.8, train = TRUE) {
    n_row = nrow(data)
    total_row = size * n_row
    train_sample < - 1: total_row
    if (train == TRUE) {
        return (data[train_sample, ])
    } else {
        return (data[-train_sample, ])
    }
}

Код Объяснение

  • функция (данные, размер = 0,8, поезд = ИСТИНА): добавить аргументы в функцию
  • n_row = nrow (data): подсчитать количество строк в наборе данных
  • total_row = size * n_row: вернуть n-ую строку для построения набора поездов
  • train_sample <- 1: total_row: выберите от первой строки до n-й строки
  • if (train == TRUE) {} else {}: если условие имеет значение true, вернуть набор поездов, иначе набор тестов.

Вы можете проверить свою функцию и проверить размерность.

data_train <- create_train_test(clean_titanic, 0.8, train = TRUE)
data_test <- create_train_test(clean_titanic, 0.8, train = FALSE)
dim(data_train)

Вывод:

## [1] 836   8
dim(data_test)

Вывод:

## [1] 209   8	

Набор данных поезда имеет 1046 строк, а набор тестовых данных — 262 строки.

Вы используете функцию prop.table () в сочетании с table () для проверки правильности процесса рандомизации.

prop.table(table(data_train$survived))

Вывод:

##
##        No       Yes 
## 0.5944976 0.4055024
prop.table(table(data_test$survived))

Вывод:

## 
##        No       Yes 
## 0.5789474 0.4210526

В обоих наборах данных число выживших одинаково — около 40 процентов.

Установите rpart.plot

rpart.plot недоступен в библиотеках conda. Вы можете установить его из консоли:

install.packages("rpart.plot")	

Шаг 4) Постройте модель

Вы готовы построить модель. Синтаксис для функции Rpart ():

rpart(formula, data=, method='')
arguments:			
- formula: The function to predict
- data: Specifies the data frame- method: 			
- "class" for a classification tree 			
- "anova" for a regression tree	

Вы используете метод класса, потому что вы предсказываете класс.

library(rpart)
library(rpart.plot)
fit <- rpart(survived~., data = data_train, method = 'class')
rpart.plot(fit, extra = 106

Код Объяснение

Вывод:

Вы начинаете с корневого узла (глубина от 0 до 3, верхняя часть графика):

  1. Вверху это общая вероятность выживания. Он показывает долю пассажиров, переживших аварию. 41 процент пассажиров выжил.
  2. Этот узел спрашивает, является ли пол пассажира мужским. Если да, то вы переходите к левому дочернему узлу корня (глубина 2). 63 процента составляют мужчины с вероятностью выживания 21 процент.
  3. Во втором узле вы спрашиваете, является ли пассажир мужского пола старше 3,5 лет. Если да, то шанс на выживание составляет 19 процентов.
  4. Вы продолжаете в том же духе, чтобы понять, какие особенности влияют на вероятность выживания.

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

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

Шаг 5) Сделайте прогноз

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

predict(fitted_model, df, type = 'class')
arguments:
- fitted_model: This is the object stored after model estimation. 
- df: Data frame used to make the prediction
- type: Type of prediction			
    - 'class': for classification			
    - 'prob': to compute the probability of each class			
    - 'vector': Predict the mean response at the node level	

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

predict_unseen <-predict(fit, data_test, type = 'class')

Код Объяснение

  • предикат (fit, data_test, type = ‘class’): прогнозирование класса (0/1) тестового набора

Тестирование пассажира, который не сделал это и тех, кто сделал.

table_mat <- table(data_test$survived, predict_unseen)
table_mat

Код Объяснение

  • таблица (data_test $ выжил, предикат_unseen): создайте таблицу, чтобы подсчитать, сколько пассажиров классифицировано как выжившие и скончались, сравнить с правильной классификацией

Вывод:

##      predict_unseen
##        No Yes
##   No  106  15
##   Yes  30  58

Модель правильно предсказала 106 погибших пассажиров, но 15 оставшихся в живых погибших. По аналогии, модель неправильно классифицировала 30 пассажиров как выживших, в то время как они оказались мертвыми.

Шаг 6) Измерьте производительность

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

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

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

Вы можете вычислить тест точности из матрицы путаницы:

Это доля истинно положительного и истинно отрицательного значения в сумме матрицы. С помощью R вы можете кодировать следующим образом:

accuracy_Test <- sum(diag(table_mat)) / sum(table_mat)

Код Объяснение

  • сумма (diag (table_mat)): сумма диагонали
  • sum (table_mat): сумма матрицы.

Вы можете распечатать точность тестового набора:

print(paste('Accuracy for test', accuracy_Test))

Вывод:

## [1] "Accuracy for test 0.784688995215311"	

У вас есть 78 процентов за тестовый набор. Вы можете повторить то же упражнение с набором тренировочных данных.

Шаг 7) Настройте гиперпараметры

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

rpart.control(minsplit = 20, minbucket = round(minsplit/3), maxdepth = 30)
Arguments:
-minsplit: Set the minimum number of observations in the node before the algorithm perform a split
-minbucket:  Set the minimum number of observations in the final note i.e. the leaf
-maxdepth: Set the maximum depth of any node of the final tree. The root node is treated a depth 0

We will proceed as follow:

  • Construct function to return accuracy
  • Tune the maximum depth
  • Tune the minimum number of sample a node must have before it can split
  • Tune the minimum number of sample a leaf node must have

You can write a function to display the accuracy. You simply wrap the code you used before:

  1. predict: predict_unseen <- predict(fit, data_test, type = ‘class’)
  2. Produce table: table_mat <- table(data_test$survived, predict_unseen)
  3. Compute accuracy: accuracy_Test <- sum(diag(table_mat))/sum(table_mat)
accuracy_tune <- function(fit) {
    predict_unseen <- predict(fit, data_test, type = 'class')
    table_mat <- table(data_test$survived, predict_unseen)
    accuracy_Test <- sum(diag(table_mat)) / sum(table_mat)
    accuracy_Test
}

You can try to tune the parameters and see if you can improve the model over the default value. As a reminder, you need to get an accuracy higher than 0.78

control <- rpart.control(minsplit = 4,
    minbucket = round(5 / 3),
    maxdepth = 3,
    cp = 0)
tune_fit <- rpart(survived~., data = data_train, method = 'class', control = control)
accuracy_tune(tune_fit)

Output:

## [1] 0.7990431	

With the following parameter:

minsplit = 4
minbucket= round(5/3)
maxdepth = 3cp=0	

You get a higher performance than the previous model. Congratulation!

Summary

We can summarize the functions to train a decision trees algorithm.

Library

Objective

function

class

parameters

details

rpart

Train classification trees

rpart()

class

formula, df, method

rpart

Train regression tree

rpart()

anova

formula, df, method

rpart

Plot the trees

rpart.plot()

fitted model

base

predict

predict()

class

fitted model, type

base

predict

predict()

prob

fitted model, type

base

predict

predict()

vector

fitted model, type

rpart

Control parameters

rpart.control()

minsplit

Set the minimum number of observations in the node before the algorithm perform a split

minbucket

Set the minimum number of observations in the final note i.e. the leaf

maxdepth

Set the maximum depth of any node of the final tree. The root node is treated a depth 0

rpart

Train model with control parameter

rpart()

formula, df, method, control

Note : Train the model on a training data and test the performance on an unseen dataset, i.e. test set.