Tree-Based Classifiers

Similar to Support Vector Machines (SVMs), trees are very good in multiclass classification. Essentially, however, the majority voting procedure to assign classes to terminal nodes implies that there is no need for techniques such as one-vs-one (OvO) or one-vs-all (OvA) strategies.

Description of data set

In contrast to the SVM tutorial, we use the bfi dataset to predict level of education by the Big-5 personality traits. We do not select a subset of observations that has balanced educational levels, because trees are much better in handling unbalanced data.

For simplicity, we treat education as a categorical variable here, although it is actually an ordinal variable (i.e., 1 < 2 < 3 < 4 < 5).

Type ?psych::bfi into your console for more information on the dataset. Note that the Big-5 triats agree, conscientious, extra, neuro, and open were created by averaging each participant’s targets to the five survey items per trait (e.g., A1-A5).

Tasks

  1. Read the data file modeul2-bfi-imbalanced.csv into R (assign it to a variable called “dat”).
dat <- read.csv('module2-bfi-imbalanced.csv', header = TRUE)
  1. Transform all discrete variables to factors for the tree algorithm to work as intended.
library(tidyverse)
── Attaching core tidyverse packages ─────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.0     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ───────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
dat <- dat %>% mutate_at(vars(education, gender), ~ factor(.))
  1. Build a tree model to predict the target “education” by all features except for the identifier “CASE”. (Hint: Set the seed to ensure reproducibility of your results, e.g., if your model has to randomly break ties)
library(mlr3verse)
Lade nötiges Paket: mlr3
set.seed(42)
tsk = as_task_classif(education ~ ., data = dat %>% select(-CASE))
mdl = lrn("classif.rpart", keep_model = TRUE)
mdl$train(tsk)
  1. Visualize your result from task 3 as a tree.
par(mfrow = c(1,2))
autoplot(mdl, type = "ggparty")

  1. Prune your tree from task 3 by means of 10-fold cross-validation. That is, choose the complexity penalty parameter cp (between 0 and 0.05 in steps of 0.01) to potentially remove unnecessary terminal nodes and reduce overfitting. Visualize your final result (i.e., best model) as a tree. Would your pruned tree be able to predict all available class labels. In other words, are there any educational levels for which no combination of features would result in the tree making a corresponding prediction? (Hint: Set the seed to ensure reproducibility of your results)
set.seed(42)

# Define set of complexity parameter values to be tested
cp_cv <- seq(0, 0.05, 0.01)

# Set up the conditions for the hyperparameter tuning
mdl_cv = auto_tuner(
  learner = lrn("classif.rpart", keep_model = TRUE, cp = to_tune(levels = cp_cv)),
  resampling = rsmp("cv", folds = 10),
  measure = msr("classif.ce"),
  tuner = tnr("grid_search"),
  terminator = trm("none")
)

# Actually tune the hyperparameter (i.e., cp) and fit the final model
invisible({capture.output({ #remove console output from html document
  mdl_cv$train(tsk)
})})

# Print the output of the tuning
mdl_cv$archive %>% 
  as.data.table() %>% 
  select(cp, classif.ce) %>% 
  arrange(as.numeric(cp))
mdl_cv$tuning_result

# Plot the final model
autoplot(mdl_cv$learner, type = "ggparty")

For this specific tree, multiple education levels would never be predicted. However, trees are rather unstable and even small changes in the data can yield a completely different result, such as for changing the seed to a different value. In my case, a different seed again produces a much more complex tree, but still not all education levels will be predicted:

set.seed(1)

mdl_cv2 = auto_tuner(
  learner = lrn("classif.rpart", keep_model = TRUE, cp = to_tune(levels = cp_cv)),
  resampling = rsmp("cv", folds = 10),
  measure = msr("classif.ce"),
  tuner = tnr("grid_search"),
  terminator = trm("none")
)

invisible({capture.output({ #remove console output from html document
  mdl_cv2$train(tsk)
})})

mdl_cv2$archive %>% 
  as.data.table() %>% 
  select(cp, classif.ce) %>% 
  arrange(as.numeric(cp))
mdl_cv2$tuning_result

autoplot(mdl_cv2$learner, type = "ggparty")

  1. Because of the instability of a single tree, build an ensamble of trees using the random forest approach and default tuning parameter settings. To proceed later with task 7, you must set the importance argument of the learner equal to “permutation”. (Hint: Set the seed to ensure reproducibility of your results)
set.seed(42)
mdl = lrn("classif.ranger", importance = 'permutation')
mdl$train(tsk)
mdl$model
Ranger result

Call:
 ranger::ranger(dependent.variable.name = task$target_names, data = task$data(),      probability = self$predict_type == "prob", case.weights = task$weights$weight,      num.threads = 1L, importance = "permutation") 

Type:                             Classification 
Number of trees:                  500 
Sample size:                      100 
Number of independent variables:  7 
Mtry:                             2 
Target node size:                 1 
Variable importance mode:         permutation 
Splitrule:                        gini 
OOB prediction error:             61.00 % 
  1. Plot the feature importance of all features used in your random forest from task 8.
barplot(mdl$importance(), horiz = T, las = 2)

  1. Build a random forest and tune the hyperparameters num.trees from 500 to 1500 in steps of 500 and mtry from 2 to 5 in steps of 1. To proceed later with task 9, you must again set the importance argument of the learner equal to “permutation”. (Hint: Set the seed to ensure reproducibility of your results)
set.seed(42)

mtry_cv <- seq(2, 5)
num.trees_cv <- c(500, 1000, 1500)

mdl_cv = auto_tuner(
  learner = lrn("classif.ranger", importance = 'permutation',
                mtry = to_tune(levels = mtry_cv), 
                num.trees = to_tune(levels = num.trees_cv)),
  resampling = rsmp("cv", folds = 5),
  measure = msr("classif.ce"),
  tuner = tnr("grid_search"),
  terminator = trm("none")
)


invisible({capture.output({ #remove console output from html document
  mdl_cv$train(tsk)
})})

mdl_cv$archive %>% 
  as.data.table() %>% 
  select(mtry, num.trees, classif.ce) %>% 
  arrange(as.numeric(mtry), as.numeric(num.trees))

mdl_cv$tuning_result

mdl_cv$learner$model
Ranger result

Call:
 ranger::ranger(dependent.variable.name = task$target_names, data = task$data(),      probability = self$predict_type == "prob", case.weights = task$weights$weight,      num.threads = 1L, importance = "permutation", mtry = 2L,      num.trees = 1000L) 

Type:                             Classification 
Number of trees:                  1000 
Sample size:                      100 
Number of independent variables:  7 
Mtry:                             2 
Target node size:                 1 
Variable importance mode:         permutation 
Splitrule:                        gini 
OOB prediction error:             64.00 % 
  1. Plot the feature importance of the tuned random forest and compare the ranking to the feature importance plot of the random forest that was fit with default tuning parameter settings in task 6. Are there substantial differences between the two plots?
par(mfrow = c(1,2))
barplot(mdl$importance(), horiz = T, las = 2)
barplot(mdl_cv$importance(), horiz = T, las = 2)

There are rather substantial differences between the feature importance plots in terms of relative rankings of the features.

Note: Feature importance scores are typically calculated based on metrics like Gini impurity or mean decrease in node impurity. These scores provide a relative measure of the importance of each feature in the model. Comparing the absolute values of feature importance scores across different models is thus not very informative.

LS0tDQp0aXRsZTogIk1vZHVsZSAyOiBUdXRvcmlhbDogVHJlZXMgYW5kIEZvcmVzdHMiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCmVkaXRvcl9vcHRpb25zOiANCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0KLS0tDQoNCiMgVHJlZS1CYXNlZCBDbGFzc2lmaWVycw0KDQpTaW1pbGFyIHRvIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIChTVk1zKSwgdHJlZXMgYXJlIHZlcnkgZ29vZCBpbiBtdWx0aWNsYXNzIGNsYXNzaWZpY2F0aW9uLiBFc3NlbnRpYWxseSwgaG93ZXZlciwgdGhlIG1ham9yaXR5IHZvdGluZyBwcm9jZWR1cmUgdG8gYXNzaWduIGNsYXNzZXMgdG8gdGVybWluYWwgbm9kZXMgaW1wbGllcyB0aGF0IHRoZXJlIGlzIG5vIG5lZWQgZm9yIHRlY2huaXF1ZXMgc3VjaCBhcyBvbmUtdnMtb25lIChPdk8pIG9yIG9uZS12cy1hbGwgKE92QSkgc3RyYXRlZ2llcy4NCg0KIyMgRGVzY3JpcHRpb24gb2YgZGF0YSBzZXQNCg0KSW4gY29udHJhc3QgdG8gdGhlIFNWTSB0dXRvcmlhbCwgd2UgdXNlIHRoZSBgYmZpYCBkYXRhc2V0IHRvIHByZWRpY3QgbGV2ZWwgb2YgZWR1Y2F0aW9uIGJ5IHRoZSBCaWctNSBwZXJzb25hbGl0eSB0cmFpdHMuIFdlIGRvIG5vdCBzZWxlY3QgYSBzdWJzZXQgb2Ygb2JzZXJ2YXRpb25zIHRoYXQgaGFzIGJhbGFuY2VkIGVkdWNhdGlvbmFsIGxldmVscywgYmVjYXVzZSB0cmVlcyBhcmUgbXVjaCBiZXR0ZXIgaW4gaGFuZGxpbmcgdW5iYWxhbmNlZCBkYXRhLiANCg0KRm9yIHNpbXBsaWNpdHksIHdlIHRyZWF0IGBlZHVjYXRpb25gIGFzIGEgY2F0ZWdvcmljYWwgdmFyaWFibGUgaGVyZSwgYWx0aG91Z2ggaXQgaXMgYWN0dWFsbHkgYW4gb3JkaW5hbCB2YXJpYWJsZSAoaS5lLiwgMSBcPCAyIFw8IDMgXDwgNCBcPCA1KS4NCg0KVHlwZSA/cHN5Y2g6OmJmaSBpbnRvIHlvdXIgY29uc29sZSBmb3IgbW9yZSBpbmZvcm1hdGlvbiBvbiB0aGUgZGF0YXNldC4gTm90ZSB0aGF0IHRoZSBCaWctNSB0cmlhdHMgYGFncmVlYCwgYGNvbnNjaWVudGlvdXNgLCBgZXh0cmFgLCBgbmV1cm9gLCBhbmQgYG9wZW5gIHdlcmUgY3JlYXRlZCBieSBhdmVyYWdpbmcgZWFjaCBwYXJ0aWNpcGFudCdzIHRhcmdldHMgdG8gdGhlIGZpdmUgc3VydmV5IGl0ZW1zIHBlciB0cmFpdCAoZS5nLiwgYEExYC1gQTVgKS4NCg0KIyMgVGFza3MNCg0KMS4gIFJlYWQgdGhlIGRhdGEgZmlsZSBtb2RldWwyLWJmaS1pbWJhbGFuY2VkLmNzdiBpbnRvIFIgKGFzc2lnbiBpdCB0byBhIHZhcmlhYmxlIGNhbGxlZCAiZGF0IikuDQoNCmBgYHtyfQ0KZGF0IDwtIHJlYWQuY3N2KCdtb2R1bGUyLWJmaS1pbWJhbGFuY2VkLmNzdicsIGhlYWRlciA9IFRSVUUpDQpgYGANCg0KMi4gIFRyYW5zZm9ybSBhbGwgZGlzY3JldGUgdmFyaWFibGVzIHRvIGZhY3RvcnMgZm9yIHRoZSB0cmVlIGFsZ29yaXRobSB0byB3b3JrIGFzIGludGVuZGVkLg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KZGF0IDwtIGRhdCAlPiUgbXV0YXRlX2F0KHZhcnMoZWR1Y2F0aW9uLCBnZW5kZXIpLCB+IGZhY3RvciguKSkNCmBgYA0KDQozLiAgQnVpbGQgYSB0cmVlIG1vZGVsIHRvIHByZWRpY3QgdGhlIHRhcmdldCAiZWR1Y2F0aW9uIiBieSBhbGwgZmVhdHVyZXMgZXhjZXB0IGZvciB0aGUgaWRlbnRpZmllciAiQ0FTRSIuIChIaW50OiBTZXQgdGhlIHNlZWQgdG8gZW5zdXJlIHJlcHJvZHVjaWJpbGl0eSBvZiB5b3VyIHJlc3VsdHMsIGUuZy4sIGlmIHlvdXIgbW9kZWwgaGFzIHRvIHJhbmRvbWx5IGJyZWFrIHRpZXMpDQoNCmBgYHtyfQ0KbGlicmFyeShtbHIzdmVyc2UpDQoNCnNldC5zZWVkKDQyKQ0KdHNrID0gYXNfdGFza19jbGFzc2lmKGVkdWNhdGlvbiB+IC4sIGRhdGEgPSBkYXQgJT4lIHNlbGVjdCgtQ0FTRSkpDQptZGwgPSBscm4oImNsYXNzaWYucnBhcnQiLCBrZWVwX21vZGVsID0gVFJVRSkNCm1kbCR0cmFpbih0c2spDQpgYGANCg0KNC4gIFZpc3VhbGl6ZSB5b3VyIHJlc3VsdCBmcm9tIHRhc2sgMyBhcyBhIHRyZWUuDQoNCmBgYHtyfQ0KYXV0b3Bsb3QobWRsLCB0eXBlID0gImdncGFydHkiKQ0KYGBgDQoNCjUuICBQcnVuZSB5b3VyIHRyZWUgZnJvbSB0YXNrIDMgYnkgbWVhbnMgb2YgMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uLiBUaGF0IGlzLCBjaG9vc2UgdGhlIGNvbXBsZXhpdHkgcGVuYWx0eSBwYXJhbWV0ZXIgYGNwYCAoYmV0d2VlbiAwIGFuZCAwLjA1IGluIHN0ZXBzIG9mIDAuMDEpIHRvIHBvdGVudGlhbGx5IHJlbW92ZSB1bm5lY2Vzc2FyeSB0ZXJtaW5hbCBub2RlcyBhbmQgcmVkdWNlIG92ZXJmaXR0aW5nLiBWaXN1YWxpemUgeW91ciBmaW5hbCByZXN1bHQgKGkuZS4sIGJlc3QgbW9kZWwpIGFzIGEgdHJlZS4gV291bGQgeW91ciBwcnVuZWQgdHJlZSBiZSBhYmxlIHRvIHByZWRpY3QgYWxsIGF2YWlsYWJsZSBjbGFzcyBsYWJlbHMuIEluIG90aGVyIHdvcmRzLCBhcmUgdGhlcmUgYW55IGVkdWNhdGlvbmFsIGxldmVscyBmb3Igd2hpY2ggbm8gY29tYmluYXRpb24gb2YgZmVhdHVyZXMgd291bGQgcmVzdWx0IGluIHRoZSB0cmVlIG1ha2luZyBhIGNvcnJlc3BvbmRpbmcgcHJlZGljdGlvbj8gKEhpbnQ6IFNldCB0aGUgc2VlZCB0byBlbnN1cmUgcmVwcm9kdWNpYmlsaXR5IG9mIHlvdXIgcmVzdWx0cykNCg0KYGBge3J9DQpzZXQuc2VlZCg0MikNCg0KIyBEZWZpbmUgc2V0IG9mIGNvbXBsZXhpdHkgcGFyYW1ldGVyIHZhbHVlcyB0byBiZSB0ZXN0ZWQNCmNwX2N2IDwtIHNlcSgwLCAwLjA1LCAwLjAxKQ0KDQojIFNldCB1cCB0aGUgY29uZGl0aW9ucyBmb3IgdGhlIGh5cGVycGFyYW1ldGVyIHR1bmluZw0KbWRsX2N2ID0gYXV0b190dW5lcigNCiAgbGVhcm5lciA9IGxybigiY2xhc3NpZi5ycGFydCIsIGtlZXBfbW9kZWwgPSBUUlVFLCBjcCA9IHRvX3R1bmUobGV2ZWxzID0gY3BfY3YpKSwNCiAgcmVzYW1wbGluZyA9IHJzbXAoImN2IiwgZm9sZHMgPSAxMCksDQogIG1lYXN1cmUgPSBtc3IoImNsYXNzaWYuY2UiKSwNCiAgdHVuZXIgPSB0bnIoImdyaWRfc2VhcmNoIiksDQogIHRlcm1pbmF0b3IgPSB0cm0oIm5vbmUiKQ0KKQ0KDQojIEFjdHVhbGx5IHR1bmUgdGhlIGh5cGVycGFyYW1ldGVyIChpLmUuLCBjcCkgYW5kIGZpdCB0aGUgZmluYWwgbW9kZWwNCmludmlzaWJsZSh7Y2FwdHVyZS5vdXRwdXQoeyAjcmVtb3ZlIGNvbnNvbGUgb3V0cHV0IGZyb20gaHRtbCBkb2N1bWVudA0KICBtZGxfY3YkdHJhaW4odHNrKQ0KfSl9KQ0KDQojIFByaW50IHRoZSBvdXRwdXQgb2YgdGhlIHR1bmluZw0KbWRsX2N2JGFyY2hpdmUgJT4lIA0KICBhcy5kYXRhLnRhYmxlKCkgJT4lIA0KICBzZWxlY3QoY3AsIGNsYXNzaWYuY2UpICU+JSANCiAgYXJyYW5nZShhcy5udW1lcmljKGNwKSkNCm1kbF9jdiR0dW5pbmdfcmVzdWx0DQoNCiMgUGxvdCB0aGUgZmluYWwgbW9kZWwNCmF1dG9wbG90KG1kbF9jdiRsZWFybmVyLCB0eXBlID0gImdncGFydHkiKQ0KYGBgDQoNCkZvciB0aGlzIHNwZWNpZmljIHRyZWUsIG11bHRpcGxlIGVkdWNhdGlvbiBsZXZlbHMgd291bGQgbmV2ZXIgYmUgcHJlZGljdGVkLiBIb3dldmVyLCB0cmVlcyBhcmUgcmF0aGVyIHVuc3RhYmxlIGFuZCBldmVuIHNtYWxsIGNoYW5nZXMgaW4gdGhlIGRhdGEgY2FuIHlpZWxkIGEgY29tcGxldGVseSBkaWZmZXJlbnQgcmVzdWx0LCBzdWNoIGFzIGZvciBjaGFuZ2luZyB0aGUgc2VlZCB0byBhIGRpZmZlcmVudCB2YWx1ZS4gSW4gbXkgY2FzZSwgYSBkaWZmZXJlbnQgc2VlZCBhZ2FpbiBwcm9kdWNlcyBhIG11Y2ggbW9yZSBjb21wbGV4IHRyZWUsIGJ1dCBzdGlsbCBub3QgYWxsIGVkdWNhdGlvbiBsZXZlbHMgd2lsbCBiZSBwcmVkaWN0ZWQ6DQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCg0KbWRsX2N2MiA9IGF1dG9fdHVuZXIoDQogIGxlYXJuZXIgPSBscm4oImNsYXNzaWYucnBhcnQiLCBrZWVwX21vZGVsID0gVFJVRSwgY3AgPSB0b190dW5lKGxldmVscyA9IGNwX2N2KSksDQogIHJlc2FtcGxpbmcgPSByc21wKCJjdiIsIGZvbGRzID0gMTApLA0KICBtZWFzdXJlID0gbXNyKCJjbGFzc2lmLmNlIiksDQogIHR1bmVyID0gdG5yKCJncmlkX3NlYXJjaCIpLA0KICB0ZXJtaW5hdG9yID0gdHJtKCJub25lIikNCikNCg0KaW52aXNpYmxlKHtjYXB0dXJlLm91dHB1dCh7ICNyZW1vdmUgY29uc29sZSBvdXRwdXQgZnJvbSBodG1sIGRvY3VtZW50DQogIG1kbF9jdjIkdHJhaW4odHNrKQ0KfSl9KQ0KDQptZGxfY3YyJGFyY2hpdmUgJT4lIA0KICBhcy5kYXRhLnRhYmxlKCkgJT4lIA0KICBzZWxlY3QoY3AsIGNsYXNzaWYuY2UpICU+JSANCiAgYXJyYW5nZShhcy5udW1lcmljKGNwKSkNCm1kbF9jdjIkdHVuaW5nX3Jlc3VsdA0KDQphdXRvcGxvdChtZGxfY3YyJGxlYXJuZXIsIHR5cGUgPSAiZ2dwYXJ0eSIpDQpgYGANCg0KNi4gIEJlY2F1c2Ugb2YgdGhlIGluc3RhYmlsaXR5IG9mIGEgc2luZ2xlIHRyZWUsIGJ1aWxkIGFuIGVuc2FtYmxlIG9mIHRyZWVzIHVzaW5nIHRoZSByYW5kb20gZm9yZXN0IGFwcHJvYWNoIGFuZCBkZWZhdWx0IHR1bmluZyBwYXJhbWV0ZXIgc2V0dGluZ3MuIFRvIHByb2NlZWQgbGF0ZXIgd2l0aCB0YXNrIDcsIHlvdSBtdXN0IHNldCB0aGUgYGltcG9ydGFuY2VgIGFyZ3VtZW50IG9mIHRoZSBsZWFybmVyIGVxdWFsIHRvICJwZXJtdXRhdGlvbiIuIChIaW50OiBTZXQgdGhlIHNlZWQgdG8gZW5zdXJlIHJlcHJvZHVjaWJpbGl0eSBvZiB5b3VyIHJlc3VsdHMpDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoNDIpDQptZGwgPSBscm4oImNsYXNzaWYucmFuZ2VyIiwgaW1wb3J0YW5jZSA9ICdwZXJtdXRhdGlvbicpDQptZGwkdHJhaW4odHNrKQ0KbWRsJG1vZGVsDQpgYGANCg0KNy4gIFBsb3QgdGhlIGZlYXR1cmUgaW1wb3J0YW5jZSBvZiBhbGwgZmVhdHVyZXMgdXNlZCBpbiB5b3VyIHJhbmRvbSBmb3Jlc3QgZnJvbSB0YXNrIDguDQoNCmBgYHtyfQ0KYmFycGxvdChtZGwkaW1wb3J0YW5jZSgpLCBob3JpeiA9IFQsIGxhcyA9IDIpDQpgYGANCjguICBCdWlsZCBhIHJhbmRvbSBmb3Jlc3QgYW5kIHR1bmUgdGhlIGh5cGVycGFyYW1ldGVycyBgbnVtLnRyZWVzYCBmcm9tIDUwMCB0byAxNTAwIGluIHN0ZXBzIG9mIDUwMCBhbmQgYG10cnlgIGZyb20gMiB0byA1IGluIHN0ZXBzIG9mIDEuIFRvIHByb2NlZWQgbGF0ZXIgd2l0aCB0YXNrIDksIHlvdSBtdXN0IGFnYWluIHNldCB0aGUgYGltcG9ydGFuY2VgIGFyZ3VtZW50IG9mIHRoZSBsZWFybmVyIGVxdWFsIHRvICJwZXJtdXRhdGlvbiIuIChIaW50OiBTZXQgdGhlIHNlZWQgdG8gZW5zdXJlIHJlcHJvZHVjaWJpbGl0eSBvZiB5b3VyIHJlc3VsdHMpDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoNDIpDQoNCm10cnlfY3YgPC0gc2VxKDIsIDUpDQpudW0udHJlZXNfY3YgPC0gYyg1MDAsIDEwMDAsIDE1MDApDQoNCm1kbF9jdiA9IGF1dG9fdHVuZXIoDQogIGxlYXJuZXIgPSBscm4oImNsYXNzaWYucmFuZ2VyIiwgaW1wb3J0YW5jZSA9ICdwZXJtdXRhdGlvbicsDQogICAgICAgICAgICAgICAgbXRyeSA9IHRvX3R1bmUobGV2ZWxzID0gbXRyeV9jdiksIA0KICAgICAgICAgICAgICAgIG51bS50cmVlcyA9IHRvX3R1bmUobGV2ZWxzID0gbnVtLnRyZWVzX2N2KSksDQogIHJlc2FtcGxpbmcgPSByc21wKCJjdiIsIGZvbGRzID0gNSksDQogIG1lYXN1cmUgPSBtc3IoImNsYXNzaWYuY2UiKSwNCiAgdHVuZXIgPSB0bnIoImdyaWRfc2VhcmNoIiksDQogIHRlcm1pbmF0b3IgPSB0cm0oIm5vbmUiKQ0KKQ0KDQoNCmludmlzaWJsZSh7Y2FwdHVyZS5vdXRwdXQoeyAjcmVtb3ZlIGNvbnNvbGUgb3V0cHV0IGZyb20gaHRtbCBkb2N1bWVudA0KICBtZGxfY3YkdHJhaW4odHNrKQ0KfSl9KQ0KDQptZGxfY3YkYXJjaGl2ZSAlPiUgDQogIGFzLmRhdGEudGFibGUoKSAlPiUgDQogIHNlbGVjdChtdHJ5LCBudW0udHJlZXMsIGNsYXNzaWYuY2UpICU+JSANCiAgYXJyYW5nZShhcy5udW1lcmljKG10cnkpLCBhcy5udW1lcmljKG51bS50cmVlcykpDQoNCm1kbF9jdiR0dW5pbmdfcmVzdWx0DQoNCm1kbF9jdiRsZWFybmVyJG1vZGVsDQpgYGANCg0KOS4gIFBsb3QgdGhlIGZlYXR1cmUgaW1wb3J0YW5jZSBvZiB0aGUgdHVuZWQgcmFuZG9tIGZvcmVzdCBhbmQgY29tcGFyZSB0aGUgcmFua2luZyB0byB0aGUgZmVhdHVyZSBpbXBvcnRhbmNlIHBsb3Qgb2YgdGhlIHJhbmRvbSBmb3Jlc3QgdGhhdCB3YXMgZml0IHdpdGggZGVmYXVsdCB0dW5pbmcgcGFyYW1ldGVyIHNldHRpbmdzIGluIHRhc2sgNi4gQXJlIHRoZXJlIHN1YnN0YW50aWFsIGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlIHR3byBwbG90cz8NCg0KYGBge3J9DQpwYXIobWZyb3cgPSBjKDEsMikpDQpiYXJwbG90KG1kbCRpbXBvcnRhbmNlKCksIGhvcml6ID0gVCwgbGFzID0gMikNCmJhcnBsb3QobWRsX2N2JGltcG9ydGFuY2UoKSwgaG9yaXogPSBULCBsYXMgPSAyKQ0KYGBgDQoNClRoZXJlIGFyZSByYXRoZXIgc3Vic3RhbnRpYWwgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgZmVhdHVyZSBpbXBvcnRhbmNlIHBsb3RzIGluIHRlcm1zIG9mIHJlbGF0aXZlIHJhbmtpbmdzIG9mIHRoZSBmZWF0dXJlcy4NCg0KTm90ZTogRmVhdHVyZSBpbXBvcnRhbmNlIHNjb3JlcyBhcmUgdHlwaWNhbGx5IGNhbGN1bGF0ZWQgYmFzZWQgb24gbWV0cmljcyBsaWtlIEdpbmkgaW1wdXJpdHkgb3IgbWVhbiBkZWNyZWFzZSBpbiBub2RlIGltcHVyaXR5LiBUaGVzZSBzY29yZXMgcHJvdmlkZSBhIHJlbGF0aXZlIG1lYXN1cmUgb2YgdGhlIGltcG9ydGFuY2Ugb2YgZWFjaCBmZWF0dXJlIGluIHRoZSBtb2RlbC4gQ29tcGFyaW5nIHRoZSBhYnNvbHV0ZSB2YWx1ZXMgb2YgZmVhdHVyZSBpbXBvcnRhbmNlIHNjb3JlcyBhY3Jvc3MgZGlmZmVyZW50IG1vZGVscyBpcyB0aHVzIG5vdCB2ZXJ5IGluZm9ybWF0aXZlLg0K