Дърво на решенията в R: Класификационно дърво с пример

⚡ Умно обобщение

Дърветата на решенията в R разделят данните на клонове, използвайки прости правила „да“ или „не“, докато всеки лист съдържа един доминиращ клас. Това ръководство изгражда, нанася графики, оценява и настройва дърво за класификация на rpart върху набора от данни за оцеляване на „Титаник“.

  • 🌳 Основна дефиниция: Дърво рекурсивно разделя пространството на предсказващите устройства, избирайки във всеки възел разделянето, което най-много намалява примесите от класове.
  • 🔄 Подготовка на данните: Разбъркайте подредения файл Titanic с sample(), премахнете колоните с идентификатори, конвертирайте коефициенти и след това премахнете NA редовете.
  • 🛠️ Синтаксис на модела: Извикайте rpart(survived~., data = data_train, method = 'class') и рендирайте резултата с rpart.plot(fit, extra = 106).
  • ️ настройка: rpart.control() предоставя на minsplit, minbucket, maxdepth и cp, като точността на повдигане е 79.90 процента.
  • 📊 Оценка: Изградете матрица на объркване с table(), след което разделете диагонала на общия сбор за 78.47 процента точност на теста.

Дърво на решенията в R

Какво представляват дърветата на решенията?

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

Преди да се изгради такова дърво в код, е полезно да се знае как едно дърво решава къде да се раздели.

Как работи дървото на решенията?

Дървото на решенията превръща набора от данни в блок-схема с въпроси тип „да“ или „не“, изградена от три вида възли: корен провежда всяко наблюдение на обучението, вътрешен възел задава въпрос за един предиктор и разделя данните на две, и листо спира разделянето и връща класа на мажоритарството.

Растежът следва алчна процедура, наречена рекурсивно двоично разделяне:

  1. Оценете всяко разделяне на кандидатите. За всеки предиктор и точка на прекъсване, измерете колко нечисти биха били двете получени групи.
  2. Запазете най-добрия. Разделянето, което най-много намалява примесите, се превръща във въпрос, зададен в този възел.
  3. Повторете с всяко дете докато не бъде спряно от контролно правило: minsplit, minbucket, maxdepth или cp.
  4. Слива. След това cp отрязва клони, които не се изплащат сами, което спира дървото да запомни обучителния набор.

Тъй като всеки въпрос сравнява една променлива с праг, алгоритъмът никога не се нуждае от мащабиране или фиктивно кодиране.

Индекс на Джини срещу ентропия в дърветата на решенията

Тази примес може да се измери по два начина, а rpart ви позволява да избирате.

Критерии Индекс Джини Ентропия (информационно усилване)
Формула 1 – сума от квадратите на пропорциите на класовете -сума от p по log²(p)
Диапазон (два класа) 0 да 0.5 0 да 1
Изчислената По-бързо, без логаритъм По-бавно, използва логаритми
настройка на rpart По подразбиране parms = списък(разделяне = „информация“)
fit_entropy <- rpart(survived~., data = data_train, method = 'class',
    parms = list(split = "information"))

На практика и двата критерия избират едно и също разделяне през повечето време, така че индексът на Джини по подразбиране е безопасен избор.

Предимства и недостатъци на дърветата на решенията

Компромисите ви казват кога едно дърво е достатъчно и кога да преминете към ансамбъл.

Предимства

  • Напълно интерпретируемо: Подбраният модел е диаграма, която всеки заинтересован участник може да прочете.
  • Минимална предварителна обработка: Не се изисква мащабиране или нормализиране и факторите работят автоматично.
  • Справя се с двете задачи: method = 'class' съответства на класификатор, а method = 'anova' съответства на регресионно дърво.
  • Бързо за обучение: Големите набори от данни се побират за секунди, така че дърветата са полезна първа базова линия.

Недостатъци

  • Висока дисперсия: малка промяна в данните за обучение може да доведе до напълно различно дърво.
  • Склонни към пренареждане: Неограниченото дърво расте, докато всяко листо стане чисто, освен ако cp и maxdepth не го ограничат.
  • Само разделяне успоредно на осите: диагоналните граници се нуждаят от много разрези във формата на стълбище.

Лекарството за първите две слабости е да се осредни много дървета, което е и случайна гора прави.

Как да обучим и визуализираме дърво на решенията в R

За да изградите първото си дърво на решенията в R, ще преминете през седем стъпки:

  • Стъпка 1: Импортирайте данните
  • Стъпка 2: Почистете набора от данни
  • Стъпка 3: Създайте комплект за обучение/тест
  • Стъпка 4: Изградете модела
  • Стъпка 5: Направете прогноза
  • Стъпка 6: Измерете ефективността
  • Стъпка 7: Настройте хиперпараметрите

Стъпка 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)

Дърво на решения R код Обяснение

  • 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, cabin, name, X и ticket
  • Създайте факторни променливи за pclass и survived
  • Пуснете NA
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)

Code Обяснение

  • select(-c(home.dest, cabin, name, X, ticket)): Премахнете ненужните променливи
  • pclass = factor(pclass, levels = c(1,2,3), labels= c('Горна', 'Средна', 'Долна')): Добавяне на етикет към променливата pclass. 1 става Горна, 2 става Средна и 3 става Долна
  • factor(survived, levels = c(0,1), labels = c('Не', 'Да')): Добавете етикети към променливата оцеляла. 0 става Не, а 1 става Да
  • 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, ])
    }
}

Code Обяснение

  • функция (данни, размер = 0.8, влак = TRUE): Добавяне на аргументите във функцията
  • n_row = nrow(данни): Преброяване на броя редове в набора от данни
  • total_row = size*n_row: Върнете n-тия ред, за да конструирате влака
  • train_sample <- 1:total_row: Изберете първия ред до n-тия ред
  • if (train ==TRUE){ } else { }: Ако условието е вярно, връща набора от влакове, в противен случай тестовия набор.

Можете да тествате функцията си и да проверите размерите.

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

Наборът от данни за влаковете има 836 реда и 8 колони, докато тестовият набор от данни има 209 реда и същите 8 колони.

Използвате функцията 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)

Code Обяснение

  • rpart(): Функция за пасване на модела. Аргументите са:
    • оцелял ~.: Формула на дърветата на решенията
    • данни = data_train: Набор от данни
    • method = 'class': Напасване на двоичен модел
  • rpart.plot(fit, extra= 106): Начертайте дървото. Аргументът extra е зададен на 106, което показва вероятността от втория клас плюс процента на наблюденията във всеки възел. Можете да се обърнете към винетка за повече информация относно другите възможности за избор.

Изход:

 Изградете модел на дървета на решения в R

Започвате от коренния възел, в горната част на графиката и на дълбочина 0 от 3:

  1. Най-отгоре това е общата вероятност за оцеляване. Той показва дела на пътниците, оцелели след катастрофата. 41% от пътниците са оцелели.
  2. Този възел пита дали полът на пътника е мъжки. Ако отговорът е „да“, тогава се слиза надолу към лявото дете на корена (дълбочина 1). 63 процента са мъже с вероятност за оцеляване от 21 процента.
  3. Във втория възел питате дали мъжкият пътник е на възраст над 3.5 години. Ако да, тогава шансът за оцеляване е 19 процента.
  4. Продължавате така, за да разберете кои характеристики влияят върху вероятността за оцеляване.

Имайте предвид, че едно от многото качества на дърветата на решенията е, че те изискват много малко подготовка на данни. По-специално, те не изискват мащабиране или центриране на функции.

По подразбиране функцията rpart() използва Джини мярка за примеси, за да се избере всяко разделяне. Колкото по-висока е стойността на Джини, толкова по-смесени са класовете в този възел, така че алгоритъмът винаги избира разделянето, което я понижава най-много.

Стъпка 5) Направете прогноза

Можете да предвидите набора от тестови данни. За да направите прогноза, можете да използвате функцията predict(). Основният синтаксис на предсказване за дърво на решения R е:

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')

Code Обяснение

  • predict(fit, data_test, type = 'class'): Предсказване на класа (0/1) на тестовия набор

Сега сравнете прогнозираните класове с реалните резултати.

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

Code Обяснение

  • table(data_test$survived, predict_unseen): Изграждане на таблица на контингентност на прогнозираните класове спрямо истинския резултат

Изход:

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

Редовете са действителните стойности, колоните са прогнозите. Моделът правилно идентифицира 106 неоцелели и 58 оцелели, но обозначи 15 неоцелели като оцелели и 30 оцелели като починали.

Стъпка 6) Измерете ефективността

Можете да изчислите мярка за точност за задача за класификация с матрица на объркване:

- матрица на объркване е по-добър избор за оценка на ефективността на класификацията. Общата идея е да се преброи колко пъти True екземплярите са класифицирани като False.

Измерете производителността на дърветата на решенията в R

Всеки ред в матрица на объркване представлява действителна цел, докато всяка колона представлява прогнозирана цел. Първият ред на тази матрица разглежда загиналите пътници (отрицателният клас): 106 са правилно класифицирани като загинали (Истински отрицателен), докато 15 бяха погрешно класифицирани като оцелели (Фалшиво позитивен). Вторият ред разглежда оцелелите: 58 са били правилно идентифицирани (Истинско положително), докато 30 бяха пропуснати (Фалшиво отрицателен).

Можете да изчислите тест за точност от матрицата на объркването:

Измерете производителността на дърветата на решенията в R

Това е съотношението на истинските положителни и истинските отрицателни спрямо сумата на матрицата. С R можете да кодирате както следва:

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

Code Обяснение

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

Можете да отпечатате точността на тестовия комплект:

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

Изход:

## [1] "Accuracy for test 0.784688995215311"

Точността на тестовия набор е 0.7847, което е 78.47 процента. Повторете упражнението на тренировъчния набор, за да видите доколко моделът е прекалено точен.

Стъпка 7) Настройте хиперпараметрите

Дървото на решенията в R има различни параметри, които контролират аспектите на съответствието. В библиотеката на дървото на решенията на 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 a terminal node, i.e. the leaf
-maxdepth: Set the maximum depth of any node of the final tree. The root node is treated as depth 0

Ще продължим както следва:

  • Конструктивна функция за връщане на точност
  • Настройте максималната дълбочина
  • Настройте минималния брой проби, които трябва да има възел, преди да може да се раздели
  • Настройте минималния брой проби, които листовият възел трябва да има

Можете да напишете функция за показване на точността. Просто обвивате кода, който сте използвали преди:

  1. прогнозиране: predict_unseen <- predict(fit, data_test, type = 'class')
  2. Създаване на таблица: table_mat <- table(data_test$survived, predict_unseen)
  3. Изчисляване на точност: 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
}

Сега настройте параметрите и вижте дали можете да подобрите модела по подразбиране. Като напомняне, трябва да надминете точността от 0.7847.

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)

Изход:

## [1] 0.7990431

Със следния параметър:

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

Точността се повишава от 0.7847 до 0.7990, така че настроеното дърво превъзхожда конфигурацията по подразбиране с около 1.4 процентни пункта.

Дървета на решенията в R: Бърз справочник на функциите

Таблицата по-долу изброява всяка функция, използвана в седемте стъпки по-горе, заедно с пакета, който я предоставя, и параметрите, които очаква. R.

Библиотека Цел функция клас параметри Детайли
rpart Дърво за класификация на влаковете в R rpart() клас формула, df, метод
rpart Обучете регресионно дърво rpart() анова формула, df, метод
rpart Начертайте дърветата rpart.plot() втален модел
база предскаже прогнозирам () клас втален модел, вид
база предскаже прогнозирам () проб втален модел, вид
база предскаже прогнозирам () вектор втален модел, вид
rpart Контролни параметри rpart.control() минсплит Задайте минималния брой наблюдения във възела, преди алгоритъмът да извърши разделяне
minbucket Задайте минималния брой наблюдения в терминален възел, т.е. листото
максимална дълбочина Задайте максималната дълбочина на всеки възел от крайното дърво. Коренният възел се третира като дълбочина 0
rpart Модел на влака с контролен параметър rpart() формула, df, метод, контрол

Забележка: Обучете модела на обучителни данни и тествайте ефективността на невидим набор от данни, т.е. тестов набор.

Въпроси и Отговори

Както класификацията на съответствие, така и регресионните дървета. rpart() имплементира CART с вградено кръстосано валидирано подрязване чрез cp и се сдвоява с rpart.plot за ясни диаграми, което го прави по-често срещан избор.

cp задава минималното подобрение, което едно разделяне трябва да осигури, за да се запази. По-големите стойности подрязват агресивно и водят до по-малки дървета. Използвайте printcp() и plotcp(), за да намерите стойността с най-ниската кръстосано валидирана грешка.

Да. rpart() използва сурогатни разделяния, за да насочи наблюденията с липсващи предиктори надолу по най-сходния клон. Този урок извиква na.omit() вместо това, единствено за да се опрости примерният набор от данни.

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

Да. Асистентите с изкуствен интелект могат да превеждат правилата за разделяне на разбираем език, да предлагат cp стойности за тестване и да маркират пренареждане в изхода на printcp(). Проверете всяко предложение спрямо вашите собствени кръстосано валидирани резултати, преди да действате по него.

Обобщете тази публикация с: