Vandamál í vélnámi: Flokkum gler!

Forvinnsla gagna fyrir líkanagerð

Vélnámi með viðgjafaraðferð1 má skipta í tvær tegundir vandamála. Annars vegar aðhvarfsvandamál2, og hins vegar flokkunarvandamál3. Í þessari færslu ætla ég að vinna mig í gegnum flokkunarvandamál, þar sem áherslan verður á að skoða forvinnsluaðgerðir4. Æfingin er fengin úr þriðja kafla bókarinnar Applied Predictive Modeling eftir Max Kuhn og Kjell Johnson. Nánar tiltekið er þetta verkefni 3.1.

Flokkum gler

Í þessu fyrsta vandamáli okkar í vélnámi notumst við við gagnasett úr The UC Irvine Machine Learning Repository sem snýr að greiningu mismunandi tegunda glers út frá ljósbrotsstuðli og samspili 8 mismunandi frumefna: Na, Mg Al, Si, K, Ca, Ba og Fe. Ljósbrotsstuðullinn og frumefnin eru skýribreyturnar og svarbreytan er tegund glersins. Sumsé, við viljum geta búið til líkan þannig að ef við höfum mælingar fyrir frumefnin og ljósbrotsstuðulinn getum við spáð fyrir um hvaða flokki þetta gler tilheyrir. Helst með þokkalegri nákvæmni.

Við nálgumst gögnin í mlbench pakkann, en auk hans hlöðum við inn öðrum pökkum sem við munum nota við úrvinnslu verkefnisins.

library(mlbench)
library(AppliedPredictiveModeling)
library(tidyverse)
library(tidymodels)
library(corrplot)
library(caret)
library(caretEnsemble)
library(viridis)
library(doParallel)
library(janitor)

Nú þegar við höfum hlaðið inn mlbench pakkanum getum við sótt Glass gögnin með data() aðgerðinni og skoðum strúktúr gagnanna með str() fallinu.

data(Glass)
str(Glass)
## 'data.frame':    214 obs. of  10 variables:
##  $ RI  : num  1.52 1.52 1.52 1.52 1.52 ...
##  $ Na  : num  13.6 13.9 13.5 13.2 13.3 ...
##  $ Mg  : num  4.49 3.6 3.55 3.69 3.62 3.61 3.6 3.61 3.58 3.6 ...
##  $ Al  : num  1.1 1.36 1.54 1.29 1.24 1.62 1.14 1.05 1.37 1.36 ...
##  $ Si  : num  71.8 72.7 73 72.6 73.1 ...
##  $ K   : num  0.06 0.48 0.39 0.57 0.55 0.64 0.58 0.57 0.56 0.57 ...
##  $ Ca  : num  8.75 7.83 7.78 8.22 8.07 8.07 8.17 8.24 8.3 8.4 ...
##  $ Ba  : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ Fe  : num  0 0 0 0 0 0.26 0 0 0 0.11 ...
##  $ Type: Factor w/ 6 levels "1","2","3","5",..: 1 1 1 1 1 1 1 1 1 1 ...

Við sjáum að Glass er gagnarammi með 214 athugunum á 10 breytum. Þar af eru níu talnabreytur og ein flokkabreyta. Flokkabreytan er svarbreytan okkar, þ.e. tegundir glersins, og eru 6 mismunandi tegundir.

Nú skulum við vinda okkur í verkefnin.

Leiðbeiningarnar eru eftirfarandi

    1. Using vizualisations, explore the predictor variables to understand their distributions as well as the relationships between predictors
    1. Do there appear to be any outliers in the data? Are any predictors skewed?
    1. Are there any relevant transformations of one or more predictors that might improve the classification model?

Eins og svo oft er ef til vill best hér að byrja með tíðniriti eða þéttniriti til að sjá dreifingu gilda innan hverrar skýribreytu. Við gerum eitt þéttnirit og eitt boxplott.

#Þéttnirit
Glass %>%
  gather(-Type, key = "efni", value = "gildi") %>%
  ggplot(mapping = aes(x = gildi)) +
    geom_density() +
    facet_wrap(~efni, scales = "free")

#Boxplott
Glass %>%
  gather(-Type, key = "efni", value = "gildi") %>%
  ggplot(mapping = aes(x = efni, y = gildi)) +
  geom_boxplot()

Við sjáum að þónokkur skekking er til staðar fyrir efnin Ba, Ca, Fe og ljósbrotsstuðulinn RI. Auk þess eru K og Mg tvítoppa. Þá virðist vera nokkuð afgerandi útlagi í K.

Því næst skoðum við fylgnirit milli skýribreytanna og notum til þess corrplot pakkann sem við höfum nú þegar hlaðið inn. Röðun breytanna á plottinu er fengin með hclust klösunarreikniriti.

#Skilgreinum hvaða dálkar innihalda skýribreytur svo hægt sé að vísa til þeirra með nafni
skyribreytur <- names(Glass)[1:9]

#Til að laga corrplot er merkingum ása leyft að fara yfir spássíur með eftirfarandi breytingu
par(xpd = TRUE)
#Fylgniplott
fylgni <- cor(Glass[, skyribreytur])
corrplot(fylgni, method = "square", mar = c(1, 1, 1, 1), order = "hclust")

Við sjáum strax að það er eitthvað um fylgni milli skýribreyta. Sterkust er greinilega jákvæð fylgni milli ljósbrotsstuðulsins RI og svo Ca og þvínæst neikvæð fylgni milli ljósbrotsstuðulsins RI og Si. Marglínuleiki (fylgni milli skýribreytanna) getur valdið vandræðum í hinum ýmsu líkönum og þá skiptir ekki máli hvort fylgnin sé neikvæð eða jákvæð. Til frekari glöggvunar setjum við fram fylgnina á tvo aðra vegu, með hitakorti og súluriti.

fylgni_df <- as_tibble(fylgni) %>% 
  mutate(names = rownames(fylgni)) %>% 
  gather(-names, key = "key", value = "Fylgni") %>% 
  mutate(var_1 = pmin(names, key), # Setjum breytuna sem er á undan í stafrófinu í dálkinn var_1
         var_2 = pmax(names, key)) %>% # og breytuna sem er á eftir í stafrófinu í dálkinn var_2
  distinct(var_1, var_2, Fylgni) # og er hægt að ná í einstakar samsetningar með distinct

#Hitakort
fylgni_df %>%
  mutate(Fylgni = if_else(var_1 == var_2, NA_real_, Fylgni)) %>%
  ggplot(mapping = aes(x = var_1, y = var_2, fill = abs(Fylgni))) +
  geom_tile() +
  scale_fill_viridis()

fylgni_df_utd <- fylgni_df %>% 
  filter(var_1 != var_2) %>% # Fjarlægjum fylgni breytu við sjálfa sig 
  arrange(Fylgni) %>% 
  unite(var_1, var_2, sep = "/", col = "Efni")

nota_palette <- c("#bbbbbb", "#F8766D", "#00BFC4")
throskuldur <- 0.32

#Súlurit
fylgni_df_utd %>% 
  mutate(Skipting = case_when(Fylgni > throskuldur ~ "Tiltölulega sterk jákvæð fylgni",
                              Fylgni < -throskuldur ~ "Tiltölulega sterk neikvæð fylgni",
                              TRUE ~ "Minni fylgni")) %>% 
  ggplot(mapping = aes(x = as.numeric(rownames(.)), y = Fylgni, fill = Skipting)) +
  geom_bar(stat = "identity") +
  scale_x_continuous(breaks = NULL, labels = NULL, name = "Samsetningar") +
  scale_fill_manual(values = nota_palette) +
  geom_hline(yintercept = 0)

#Súlurit frh.
fylgni_df_utd %>% 
  filter(abs(Fylgni) > 0.32) %>% # Fjarlægjum fylgni sem er ekki meiri en +- 0.32
  mutate(Formerki = if_else(Fylgni >= 0, "Jákvætt", "Neikvætt")) %>% 
  ggplot(mapping = aes(x = as.numeric(rownames(.)), y = Fylgni, fill = Formerki)) +
  geom_bar(stat = "identity") +
  geom_text(mapping = aes(label = Efni, vjust = if_else(Fylgni > 0, -0.9, 1.6))) +
  scale_x_continuous(breaks = NULL, labels = NULL, name = "Samsetningar") +
  geom_hline(yintercept = 0) +
  coord_cartesian(ylim = c(-0.6, 0.9))

Þá sjáum við að ef að við notum ljósbrotsstuðul RI sem skýribreytu fyrir tegund glersins, þá er efnið Ca að fara að veita okkur minnstar viðbótarupplýsingar, og öfugt. Eins og ég nefndi hér að ofan getur marglínuleiki valdið okkur vandræðum. Þannig væri líklega okkar fyrsta verk að fjarlægja ljósbrotsstuðulinn RI, þar sem sterk fylgni er milli hans og efnisins Ca, og jafnframt tiltölulega sterk neikvæð fylgni milli hans og efnisins Si. Það væri þá til að koma í veg fyrir marglínuleika fyrir þau líkön sem slíkt hefur áhrif á, en ef til vill væri það líka eftirsóknarvert til einföldunar þó að við værum með til dæmis slembiskógalíkan, sem marglínuleiki hefur ekki áhrif á. Það að fækka skýribreytum einfaldar líkanið og styttir reiknitíma. Að því sögðu þá er fókusinn í þessu dæmi á forvinnsluaðgerðir til að leiðrétta fyrir skekkingu í dreifingu5 - þannig við látum það ógert að henda út ljósbrotsstuðlinum að svo stöddu.

Forvinnsluaðgerðir

Til að leiðrétta fyrir skekkingu í dreifingu

Til að leiðrétta fyrir skekkingu í dreifingu skýribreytanna væri ákjósanlegt að geta beitt fyrir sig Box-Cox umbreytingu. Til þess þurfa öll gildi að vera stærri en núll. Því þurfum við að athuga hvort einhver núllgildi eða neikvæð gildi leynist í einhverjum skýribreytanna. Til þess að auðvelda okkur það erum við sniðug og notumst við map_dbl() fallið úr purrr pakkanum. Við höfum þegar hlaðið inn purrr hér að ofan um leið og við hlóðum inn tidyverse pakkasamstæðunni.

Glass[, skyribreytur] %>% 
  map_dbl(~sum(. <= 0))
##  RI  Na  Mg  Al  Si   K  Ca  Ba  Fe 
##   0   0  42   0   0  30   0 176 144

Einungis RI, Na, Al, Si og Ca innihalda bara jákvæð gildi, en hin ekki. Við sjáum því strax að Box-Cox umbreyting er ekki valkostur fyrir allar skýribreyturnar, né þá heldur log-umbreyting. Þá prófum við Yeo-Johnson umbreytingu, en hún er ekki alls ósvipuð Box-Cox nema fyrir það að gildi mega vera neikvæð eða núll. Raunar er Yeo-Johnson umbreytingin í reynd Box-Cox umbreyting í þeim tilfellum þar sem öll gildi breytu eru jákvæð. Til þess að kalla fram Yeo-Johnson umbreytingu getum við notað preProcess() fallið úr caret pakkanum, sem skrifaður er af Max Kuhn, öðrum höfundi Applied Predictive Modeling sem verkefnið er úr.

Við ætlum einnig að prófa að miðja og skala gögnin.

pp_object_Glass_both <- preProcess(Glass, method = c("center", "scale", "YeoJohnson"))

glass_both <- predict(pp_object_Glass_both, Glass)

pp_object_Glass_yj <- preProcess(Glass, method = c("YeoJohnson"))

glass_yj <- predict(pp_object_Glass_yj, Glass)

pp_object_Glass_cs <- preProcess(Glass, method = c("center", "scale"))

glass_cs <- predict(pp_object_Glass_cs, Glass)

Prófum sambærilegt þéttnirit og áður. Berum saman tvær mismunandi uppskriftir að forvinnslu. Annars vegar miðjun og skölun, og hins vegar miðjun, skölun og Yeo-Johnson umbreytingu. Hér er búið að kommenta út þrjú önnur þéttnirit, svo þau birtist ekki.

# glass_both %>% 
#   gather(-Type, key = "efni", value = "gildi") %>%
#   ggplot(mapping = aes(x = gildi)) +
#     geom_density() +
#     facet_wrap(~efni, scales = "free")
# 
# 
# glass_yj %>% 
#   gather(-Type, key = "efni", value = "gildi") %>%
#   ggplot(mapping = aes(x = gildi)) +
#     geom_density() +
#     facet_wrap(~efni, scales = "free")
# 
# glass_cs %>% 
#   gather(-Type, key = "efni", value = "gildi") %>%
#   ggplot(mapping = aes(x = gildi)) +
#     geom_density() +
#     facet_wrap(~efni, scales = "free")

glass_both_tidy <- glass_both %>% 
  gather(-Type, key = "efni", value = "gildi")

glass_cs_tidy <- glass_cs %>% 
    gather(-Type, key = "efni", value = "gildi")

glass_saman <- bind_rows("Fyrir" = glass_cs_tidy, "Eftir" = glass_both_tidy, .id = "Y-J umbreyting")

glass_saman %>% 
  ggplot(mapping = aes(x = gildi, color = `Y-J umbreyting`, fill = `Y-J umbreyting`)) +
    geom_density(alpha = 0.2) +
    facet_wrap(~efni, scales = "free")

Eru forvinnsluaðgerðirnar líklegar til að bæta spár um flokkun glersins?

Til þess að svara því liggur beinast við að forvinna gögnin eftir mismunandi uppskriftum og spá því næst fyrir um tegundir glers út frá skýribreytunum. Við búum til þjálfunarsett og prófunarsett til að vinna með. training_set_raw og test_set_raw eru grunngögnin án nokkurar forvinnslu. Í training_set_cs og test_set_cs eru gögnin miðjuð og sköluð6. Í training_set_yj og test_set_yj er notast er við Yeo-Johnson umbreytingu. Í training_set_both og test_set_both er notast við miðjun og skölun, og Yeo-Johnson umbreytingu.

Við tökum 85% af gögnunum og notum þau sem þjálfunarsett og restin verður prófunarsett.

train_ind <- caret::createDataPartition(y = glass_cs$Type, p = 0.85, list = FALSE)
pred_vars <- 1:9 # Dálkanúmer skýribreytanna

training_set_raw  <- Glass[train_ind, ]
training_set_both <- glass_both[train_ind, ]
training_set_cs   <- glass_cs[train_ind, ]
training_set_yj   <- glass_yj[train_ind, ]

test_set_raw      <- Glass[-train_ind, ]
test_set_both     <- glass_both[-train_ind, ]
test_set_cs       <- glass_cs[-train_ind, ]
test_set_yj       <- glass_yj[-train_ind, ]

Þegar við höfum skilgreint þjálfunarsett og prófunarsett hefjumst við handa við að þjálfa líkanið á hráu, óunnu gögnunum. Við notumst við caret pakkann, en hann er samansafn tóla til spálíkanagerðar. Við notumst einnig við caretEnsemble pakkann, sem er allajafna notaður til að búa til samleik spálíkana. Með samleik er átt við það að búa til eitt líkan úr mörgum líkönum til að bæta spánákvæmni. Hér ætlum við hinsvegar einungis að nota hann til að halda utan um mismunandi reiknirit sem við ætlum að beita á gögnin. knn er næsta-nágranna reiknirit7, treebag er bagged trees reiknirit, rf er slembiskógur8, rpart er einfalt ákvarðanatré9, svmRadial er stuðningsvigra reiknirit með kúlulaga ákvörðunarskilum10. xgbTree aðferðin kallar til xgboost reikniritið sem framkvæmir ofurmögnun stiguls11.

Við notumst við 3 sinnum 10 brota kross prófun12 í öllum tilvikum til að reyna að tryggja að við séum ekki að ofsníða13 að þjálfunargögnunum.

styring_reiknirita <- trainControl(method="repeatedcv", number=10, repeats=3, index = createMultiFolds(training_set_raw$Type, k = 10, times = 3),
                                   savePredictions="final", classProbs=TRUE)

listi_reiknirita <- c('knn', 'treebag', 'rf', 'rpart', 'svmRadial', 'xgbTree') 

Hér nota ég 7 kjarna til að skipta útreikningunum niður á. Ef þú hyggst keyra þessa útreikninga á eigin vélbúnaði þarf að taka mið af fjölda kjarna í örgjörva vélbúnaðarins sem útreikningarnir fara fram á. Að því sögðu er vert að nefna að formúluviðmótið hægir talsvert á útreikningum á xgboost, þannig í raun væri vond hugmynd að kópera þennan kóða beint. En hey, ég er búinn að þjálfa líkönin þegar þessi orð eru skrifuð, þannig það skiptir ekki öllu máli.

training_set_raw$Type <- as.factor(training_set_raw$Type)
levels(training_set_raw$Type) <- c("One", "Two", "Three", "Five", "Six", "Seven")

cl <- makeCluster(7)
registerDoParallel(cl)

models_raw <- caretList(Type ~ ., data = training_set_raw, 
                        trControl = styring_reiknirita, 
                        methodList = listi_reiknirita)
results_raw <- resamples(models_raw)

stopCluster(cl)

Því næst gerum við hið nákvæmlega sama fyrir gagnasettið sem hefur verið skalað og miðjað.

training_set_cs$Type <- as.factor(training_set_cs$Type)
levels(training_set_cs$Type) <- c("One", "Two", "Three", "Five", "Six", "Seven")

cl <- makeCluster(7)
registerDoParallel(cl)

models_cs <- caretList(Type~., data=training_set_cs,
                       trControl = styring_reiknirita,
                       methodList = listi_reiknirita)

results_cs <- resamples(models_cs)

stopCluster(cl)

Og því næst gagnasettið með Yeo-Johnson umbreytingu.

training_set_yj$Type <- as.factor(training_set_yj$Type)
levels(training_set_yj$Type) <- c("One", "Two", "Three", "Five", "Six", "Seven")

cl <- makeCluster(7)
registerDoParallel(cl)

models_yj <- caretList(Type~., data=training_set_yj,
                       trControl = styring_reiknirita,
                       methodList = listi_reiknirita)

results_yj <- resamples(models_yj)

stopCluster(cl)

Og svo fyrir gögnin með miðjun og skölun, og Yeo-Johnson umbreytingu.

training_set_both$Type <- as.factor(training_set_both$Type)
levels(training_set_both$Type) <- c("One", "Two", "Three", "Five", "Six", "Seven")

cl <- makeCluster(7)
registerDoParallel(cl)

models_both <- caretList(Type~., data=training_set_both,
                         trControl = styring_reiknirita,
                         methodList = listi_reiknirita)

results_both <- resamples(models_both)

stopCluster(cl)

Þegar við höfum þjálfað áðurnefnd líkön á gögnum með mismunandi ‘forvinnslu-uppskriftum’ skoðum við áhrif forvinnslu á spánákvæmni. Í stað þess að nota bara nákæmni14 (hlutfall þeirra athugana sem líkan flokkar rétt) heldur skoðum við einnig Cohen’s Kappa, sem er mælikvarði á nákvæmni líkansins umfram vænta nákvæmni. Þá er átt við að í tilvikum þar sem eru einungis tveir flokkar má vænta að slembiflokkari myndi flokka athugun í réttan flokk í helmingi tilvika.

Cohen’s kappa er reiknað á eftirfarandi hátt.

\[ Kappa = \frac{Nákvæmni_{reiknuð} - Nákvæmni_{vænt}}{1 - Nákvæmni_{vænt}} \]

nidurstodur <- list("hrátt" = results_raw$values,
     "cs" = results_cs$values,
     "yj" = results_yj$values,
     "bæði" = results_both$values) %>% 
  map(~as_tibble(.)) %>%
  map(~set_names(., ~str_replace(., "~", "_"))) %>% 
  bind_rows(.id = "pre_process") %>% 
  group_by(pre_process) %>% 
  summarize_at(.vars = vars(-1),
               .funs = mean) %>% 
  pivot_longer(-pre_process, names_to = "model_metric", values_to = "value") %>%
  arrange(model_metric, pre_process) %>% 
  separate(col = model_metric, into = c("model", "metric"), sep = "_") %>% 
  mutate(pre_process = fct_relevel(pre_process, "hrátt", "cs", "yj", "bæði"))

nidurstodur %>% 
  filter(metric == "Kappa") %>% 
  ggplot(aes(x = pre_process, y = value, fill = pre_process)) +
  geom_col() +
  facet_wrap(~model) +
  labs(title = "Cohen's Kappa eftir forvinnsluaðferð")

nidurstodur %>% 
  filter(metric == "Accuracy") %>% 
  ggplot(aes(x = pre_process, y = value, fill = pre_process)) +
  geom_col() +
  facet_wrap(~model) +
  labs(title = "Nákvæmni eftir forvinnsluaðferð")

nidurstodur %>% 
  filter(metric == "Accuracy") %>% 
  group_by(model) %>% 
  mutate(centered = value - mean(value)) %>% 
  ggplot(aes(x = pre_process, y = centered, fill = pre_process)) +
  geom_hline(yintercept = 0) +
  geom_col() +
  facet_wrap(~model) +
  labs(title = "Frávik frá meðalnákvæmni hvers reiknirits")

Þannig sjáum við að slembiskógareikniritið rf skilar besta líkaninu fyrir okkur. Við sjáum jafnframt að sú forvinnsluaðferð sem skilar besta slembiskógalíkaninu er Yeo-Johnson umbreyting án miðjunar og skölunar.

nidurstodur %>% 
  filter(value == max(value))
## # A tibble: 1 x 4
##   pre_process model metric   value
##   <fct>       <chr> <chr>    <dbl>
## 1 yj          rf    Accuracy 0.799

Nákvæmni slembiskógalíkansins með Yeo-Johnson umbreytingu er 79.9% á þjálfunargögnunum. Það eru séð gögn.

models_yj$rf
## Random Forest 
## 
## 185 samples
##   9 predictor
##   6 classes: 'One', 'Two', 'Three', 'Five', 'Six', 'Seven' 
## 
## No pre-processing
## Resampling: Cross-Validated (10 fold, repeated 3 times) 
## Summary of sample sizes: 168, 168, 167, 168, 166, 166, ... 
## Resampling results across tuning parameters:
## 
##   mtry  Accuracy   Kappa    
##   2     0.7990341  0.7234367
##   5     0.7684922  0.6854291
##   9     0.7491762  0.6602136
## 
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was mtry = 2.

Við sjáum að bakvið tjöldin valdi caret gildið 2 fyrir stillibreytuna15 mtry, sem stjórnar fjölda þeirra breyta sem eru mögulegar í slembivali sem kandidatar fyrir “splitt” í ákvarðanatrjánum sem liggja að baki slembiskógalíkaninu. Við sjáum einnig að caret hefur skoðað þrjú möguleg gildi fyrir mtry.

Framhaldið

Við erum búin að komast að því að slembiskógur hentar vel fyrir þessi gögn, og þessi æfing gefur til kynna að það sé best að beita bara Yeo-Johnson umbreytingu á gögnin. Í raun er þó ótrúlega lítill munur á nákvæmninni milli forvinnsluaðgerða, þannig það er strangt til tekið ekkert útséð í þeim málum.

Ef þetta væri raunverulegt verkefni myndum við nú halda áfram og einblína á þau líkön sem koma best út. Til dæmis að einblína á slembiskógalíkön, en gefa caret leiðbeiningar um að skoða fleiri gildi fyrir stillibreytuna mtry og sjá hvort það geti ekki haft þónokkur áhrif.

Þegar við værum orðin sátt við nákvæmnina á séðum gögnum út frá tiltekinni nálgun myndum við því næst reikna nákvæmni og Kappa fyrir óséð gögn. Það eru gögn sem voru ekki notuð til að þjálfa líkanið. Til þess erum við með prófunarsett. Við myndum mæla nákvæmni og Kappa á prófunarsettinu og niðurstaðan úr því væri sú nákvæmni sem við gætum búist við af líkaninu.

Að lokum er vert að koma eftirfarandi fyrirvara að: Í þessu vandamáli hefur nálgunin alfarið verið reiknidrifin16 og að engu leyti reynsludrifin17. Ég hef enga sérfræðiþekkingu eða reynslu sem snýr að flokkun mismunandi gerða glers sem hefði getað nýst mér í þessu verkefni. Í raunveruleikanum þarf maður að prjóna saman sérfræðiþekkingu við tölfræðilega útreikninga til þess að ná sem bestum árangri. En það er efni í annað blogg.

Takk í dag!


  1. (e. supervised machine learning)

  2. (e. regression problem)

  3. (e. classification problem)

  4. (e. pre-processing)

  5. (lesist: ég sjúklega latur og þetta er bara sýnidæmi)

  6. (e. centering and scaling)

  7. (e. nearest neighbour)

  8. (e. random forest)

  9. (e. decision tree)

  10. (e. support vector machines using a radial decision boundary)

  11. (e. extreme gradient boosting)

  12. (e. cross-validation)

  13. (e. over-fit)

  14. (e. accuracy)

  15. (e. tuning parameter)

  16. (e. empirically driven modeling)

  17. (e. experience-driven modeling)

Avatar
Hlynur Hallgrímsson
Senior Data Scientist

My research interests include the intersection of machine learning and microsimulation.