5  Bagging

I dette kapittelt skal vi bruke følgende pakker:

library(tidyverse)   # datahåndtering, grafikk og glimpse()
library(rsample)     # for å dele data i training og testing
library(rpart)       # for fitting decision trees
library(rpart.plot)  # for å plotte trær
library(ipred)       # for fitting bagged decision trees
library(caret)       # for general model fitting og confusionMatrix()
library(forcats)     # for rekoding av factor-variable

6 Introduksjon til bagging

Merk at (Berk 2016) gjør et poeng av at med bagging gjør vi et større steg vekk fra mer vanlige statistiske prosedyrer. Regresjon og klassifikasjonstrær (og det meste annet vi er mer vant med) gir ett sett av resultater. Med bagging får vi en hel haug av resultater. Prinsippet er rett og slett at man trekker et tilfeldig utvalg av dataene, bygger et klassifikasjonstre og gjør klassifikasjonen. Så gjentar man dette med et nytt tilfeldig utvalg fra det opprinnelige datasettet.1 Så gjentar man dette mange ganger.2

Hver observasjon i det opprinnelige datasettet har da blitt klassifisert mange ganger, men ikke nødvendigvis likt i hvert tre. (Enkle klassifikasjonstrær er nemlig litt ustabile greier). Hvis man har gjort prosedyren 100 ganger, så vil hver observasjon bli klassifisert 100 ganger - en gang i hvert tre. For prediksjon på trainingdata brukes rett og slett en opptelling over alle trærne for hver observasjon. Når (Berk 2016) snakker om “votes”, så er dette som menes: hvert tre har en stemme og så stemmes det over hva som blir utfallet for den enkelte.

Dette betyr at antall trær kan ha noe å si. Forvalget for funksjonen bagging() er 25. Det er en god start, men flere trær gir jo mer stabile resultater.

Husk at klassifikasjonstrær er litt ustabile greier, og små tilfeldige variasjoner kan påvirke en enkelt split. Ved å bruke bagging jevnes de tilfeldige feilene ut slik at man totalt sett skal få et mer stabilt resultat. Altså: at faren for overfitting reduseres. Hvor mange trær som trengs er litt verre å si noe om.

6.1 Empirisk eksempel: Syntetiske data

Vi bruker de syntetiske fødselskohort-dataene. Disse er generert for å etterligne en kohort født i 1996, der utfallsvariabelen felonies angir om personen har begått alvorlige lovbrudd (1) eller ikke (0). Vi fjerner variablene drugs og violence fordi disse er delkomponenter av utfallet. Vi demonstrerer også med Attrition-dataene.

load("data/synthetic_birthCohort96.RData")

synth <- synthetic_birthCohort %>%
  select(-drugs, -violence) %>%
  mutate(felonies = factor(felonies))

set.seed(426)
synth_split <- initial_split(synth)
training <- training(synth_split)
testing  <- testing(synth_split)

6.1.1 Klassifikasjonstre som utgangspunkt

Først lager vi et klassifikasjonstre for sammenligning:

klass_tre <- rpart(felonies ~ ., data = training, method = "class")
rpart.plot(klass_tre)

6.1.2 Bagging

Funksjonen bagging() har tilsvarende oppbygning som tidligere modeller. Her angir vi 100 trær med nbagg = 100 og bruker OOB med coob = TRUE:

set.seed(42)
bag <- bagging(felonies ~ ., data = training,
               nbagg = 100, coob = TRUE)

bag

Bagging classification trees with 100 bootstrap replications 

Call: bagging.data.frame(formula = felonies ~ ., data = training, nbagg = 100, 
    coob = TRUE)

Out-of-bag estimate of misclassification error:  0.0411 

Vi kan legge til justeringer for de enkelte trærne med control = rpart.control():

set.seed(42)
bag2 <- bagging(felonies ~ ., data = training,
                nbagg = 100, coob = TRUE,
                control = rpart.control(cp = 0.0000001, maxdepth = 20, minbucket = 3))

bag2

Bagging classification trees with 100 bootstrap replications 

Call: bagging.data.frame(formula = felonies ~ ., data = training, nbagg = 100, 
    coob = TRUE, control = rpart.control(cp = 1e-07, maxdepth = 20, 
        minbucket = 3))

Out-of-bag estimate of misclassification error:  0.0403 

Sammenlign med testing-data. Her lager vi prediksjoner fra både det enkle klassifikasjonstreet og bagging-modellen, slik at vi kan sammenligne dem direkte:

testing_pred <- testing %>%
  mutate(pred_tre = predict(klass_tre, newdata = testing, type = "class"),
         pred_bag = predict(bag, newdata = testing))

confusionMatrix(reference = testing_pred$felonies, testing_pred$pred_tre, positive = "1")
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 7193  307
         1    0    0
                                          
               Accuracy : 0.9591          
                 95% CI : (0.9543, 0.9634)
    No Information Rate : 0.9591          
    P-Value [Acc > NIR] : 0.5152          
                                          
                  Kappa : 0               
                                          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.00000         
            Specificity : 1.00000         
         Pos Pred Value :     NaN         
         Neg Pred Value : 0.95907         
             Prevalence : 0.04093         
         Detection Rate : 0.00000         
   Detection Prevalence : 0.00000         
      Balanced Accuracy : 0.50000         
                                          
       'Positive' Class : 1               
                                          
confusionMatrix(reference = testing_pred$felonies, testing_pred$pred_bag, positive = "1")
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 7183  302
         1   10    5
                                          
               Accuracy : 0.9584          
                 95% CI : (0.9536, 0.9628)
    No Information Rate : 0.9591          
    P-Value [Acc > NIR] : 0.6287          
                                          
                  Kappa : 0.0273          
                                          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.0162866       
            Specificity : 0.9986098       
         Pos Pred Value : 0.3333333       
         Neg Pred Value : 0.9596526       
             Prevalence : 0.0409333       
         Detection Rate : 0.0006667       
   Detection Prevalence : 0.0020000       
      Balanced Accuracy : 0.5074482       
                                          
       'Positive' Class : 1               
                                          

Sammenlign accuracy, sensitivity og specificity for de to modellene. Bagging gir typisk bedre og mer stabile resultater enn et enkelt tre fordi de tilfeldige feilene jevner seg ut over mange trær.

6.2 Eksempel med Attrition-data

Vi bruker også Attrition-dataene for å vise at bagging fungerer på tvers av datasett:

attrition <- readRDS("data/Attrition.rds") %>%
  select(-EmployeeNumber)

set.seed(426)
attrition_split <- initial_split(attrition)
attr_train <- training(attrition_split)
attr_test  <- testing(attrition_split)

Klassifikasjonstre, bagging og confusion matrix:

attr_tre <- rpart(Attrition ~ ., data = attr_train, cp = 0.00001, maxdepth = 5,
                  minbucket = 15, method = "class")
rpart.plot(attr_tre)

set.seed(42)
attr_bag <- bagging(Attrition ~ ., data = attr_train,
                    nbagg = 500, coob = TRUE)
attr_bag

Bagging classification trees with 500 bootstrap replications 

Call: bagging.data.frame(formula = Attrition ~ ., data = attr_train, 
    nbagg = 500, coob = TRUE)

Out-of-bag estimate of misclassification error:  0.1325 
attr_test_pred <- attr_test %>%
  mutate(pred_tre = predict(attr_tre, newdata = attr_test, type = "class"),
         pred_bag = predict(attr_bag, newdata = attr_test, type = "class"))

confusionMatrix(reference = attr_test_pred$Attrition, attr_test_pred$pred_tre, positive = "Yes")
Confusion Matrix and Statistics

          Reference
Prediction  No Yes
       No  288  57
       Yes  12  11
                                          
               Accuracy : 0.8125          
                 95% CI : (0.7688, 0.8511)
    No Information Rate : 0.8152          
    P-Value [Acc > NIR] : 0.5851          
                                          
                  Kappa : 0.1636          
                                          
 Mcnemar's Test P-Value : 1.177e-07       
                                          
            Sensitivity : 0.16176         
            Specificity : 0.96000         
         Pos Pred Value : 0.47826         
         Neg Pred Value : 0.83478         
             Prevalence : 0.18478         
         Detection Rate : 0.02989         
   Detection Prevalence : 0.06250         
      Balanced Accuracy : 0.56088         
                                          
       'Positive' Class : Yes             
                                          
confusionMatrix(reference = attr_test_pred$Attrition, attr_test_pred$pred_bag, positive = "Yes")
Confusion Matrix and Statistics

          Reference
Prediction  No Yes
       No  296  55
       Yes   4  13
                                          
               Accuracy : 0.8397          
                 95% CI : (0.7981, 0.8757)
    No Information Rate : 0.8152          
    P-Value [Acc > NIR] : 0.1258          
                                          
                  Kappa : 0.2505          
                                          
 Mcnemar's Test P-Value : 7.543e-11       
                                          
            Sensitivity : 0.19118         
            Specificity : 0.98667         
         Pos Pred Value : 0.76471         
         Neg Pred Value : 0.84330         
             Prevalence : 0.18478         
         Detection Rate : 0.03533         
   Detection Prevalence : 0.04620         
      Balanced Accuracy : 0.58892         
                                          
       'Positive' Class : Yes             
                                          

Igjen ser vi at bagging typisk gir et resultat som er noe mer stabilt enn et enkelt tre. Merk at forbedringen ikke alltid er like dramatisk – det kommer an på dataene og det underliggende mønsteret.

6.3 Out-of-bag data (OOB)

Bagging kan brukes på training data og testing data som vi har gjort før. Men en ulempe med dette er jo at man får et litt mindre datasett å tilpasse modellene med. Merk at bagging i utgangspunktet baserer seg på å trekke et tilfeldig utvalg fra de opprinnelige dataene. Så for hvert tre er det en del observasjoner som ikke blir brukt til å bygge treet. Altså har vi et testing-datasett umiddelbart tilgjengelig!

Altså: hvis man bruker 70% av dataene til å bygge hvert enkelt tre, så har man også 30% testingdata tilgjengelig for å gjøre klassifiseringen på det enkelte treet. Dette testing-datasettet kalles out-of-bag (OOB) data, men er altså i prinsippet det samme som et testingdatasett.

Forvalget i bagging() er imidlertid å ikke bruke OOB. For å gjøre dette legges det til argumentet coob = TRUE og man får utregnet klassifikasjonsfeilen i output.

6.4 Mer tuning

Man kan si at bagging består av to deler: 1) en grunnleggende klassifikasjonsteknikk (noen ganger omtalt som “base learner”), og 2) en bootstrapping med gjentatte estimeringer på utvalg av data.

Forvalget i bagging() er å bruke klassifikasjonstrær med rpart(). Det betyr at tuning som kan gjøres med bruk av rpart() også kan gjøres med bagging(). Dette gjøres ved å legge til argumentet control = rpart.control():

set.seed(42)
bag_tuned <- bagging(felonies ~ ., data = training,
                     nbagg = 500, coob = TRUE,
                     control = rpart.control(maxdepth = 6, cp = 0.0001))
bag_tuned

Bagging classification trees with 500 bootstrap replications 

Call: bagging.data.frame(formula = felonies ~ ., data = training, nbagg = 500, 
    coob = TRUE, control = rpart.control(maxdepth = 6, cp = 1e-04))

Out-of-bag estimate of misclassification error:  0.0403 

Det viktige poenget akkurat nå er egentlig ikke all tuning som er mulig å gjøre i bagging, men at det er bygget på en annen underliggende algoritme. Vi bruker i praksis ikke bagging noe særlig alene, men derimot er random forest en variant av bagging med litt ekstra saker. Slik sett er bagging å regne som en byggesten til random forest sammen med klassifikasjonstrær. Så derfor skal vi ikke bruke så mye tid på bagging, men gå raskt videre til random forest.


  1. Dette er altså det vi ofte kaller tilfeldig utvalg med tilbakelegging.↩︎

  2. Bagging ligner altså veldig på det vi i vanlig statistikk kaller for bootstrapping. Men i bootstrapping er ofte formålet å korrigere standardfeilene eller noe slikt og ikke så mye punktestimatet. Skjønt, det går an å bruke bagging på regresjonsmodeller også, og da tror jeg forskjellen mot bootstrapping er mest semantisk.↩︎