1 Assignment

Consider the the Breast Cancer Coimbra data set (available from UCI repository).

Compare three different ensemble approaches, using the ‘caret’ package:

Evaluation of derived models should follow a correct methodology, comparing different estimates of generalization error (i.e. holdout, cross-validation, bootstrap, …)

Submit a report (in PDF, generated from R) with the code and the resulting analysis.

2 Introduction

A quick review shows that Ensemble methods are not limited to the above referred. Nevertheless these are indeed the main ensemble learning methods.

Weak (or base) learners are the building block for more complex models. They normally do not perform well by themselves due to high variance or high bias. Ensemble methods therefore try to combine weak learners in order to create a strong learner.

Normally (in bagging and boosting methods), a single base learning algorithm is used to build what we call an “homogeneous” ensemble model. However, we can aggregate several base learning into an “heterogeneous” ensemble model. It is important to choose base models that are coherent to each other, i.e. one with high bias with anoter with high variance, so the ensemble will try to compensate each one.

These are the three main methods of ensemble:

3 Bagging

Here we will compare two bagging algorithms, treebag and random forest against the usual rpart model.

set.seed(2020)
control <- trainControl(
  method = "boot",
  number = 25,
  savePredictions = "final",
  classProbs = TRUE,
  index = createResample(training$Classification, 25),
  summaryFunction = twoClassSummary
)
metric <- "ROC"

set.seed(2020)
fit.rpart <- train(Classification ~ ., data = training, method = "rpart", metric = metric, trControl = control)

set.seed(2020)
fit.treebag <- train(Classification ~ .,
  data = training,
  method = "treebag", trControl = control, verbose = FALSE
)

set.seed(2020)
fit.rf <- train(Classification ~ .,
  data = training,
  method = "rf", trControl = control, verbose = FALSE
)

3.1 Comparing training metrics

results_bag <- resamples(list(rpart = fit.rpart, treebag = fit.treebag, rf = fit.rf))

# Compare models
dotplot(results_bag)

3.2 Comparing predictions

pred_bag <- list()
pred_bag$rpart <- predict(fit.rpart, newdata = testing, type = "prob")[, "Patient"]
pred_bag$treebag <- predict(fit.treebag, newdata = testing, type = "prob")[, "Patient"]
pred_bag$rf <- predict(fit.rf, newdata = testing, type = "prob")[, "Patient"]
pred_bag <- data.frame(pred_bag)

caTools::colAUC(pred_bag, testing$Classification, plotROC = TRUE)
                        rpart   treebag        rf
Patient vs. Control 0.5673077 0.7644231 0.8052885

4 Boosting

Here we will compare two boosting algorithms, gbm and C5.0 against the usual rpart model.

set.seed(2020)
control <- trainControl(
  method = "boot",
  number = 25,
  savePredictions = "final",
  classProbs = TRUE,
  index = createResample(training$Classification, 25),
  summaryFunction = twoClassSummary
)
metric <- "ROC"

set.seed(2020)
fit.rpart <- train(Classification ~ ., data = training, method = "rpart", metric = metric, trControl = control)

set.seed(2020)
fit.gbm <- train(Classification ~ .,
  data = training,
  method = "gbm", trControl = control, verbose = FALSE, distribution = "adaboost"
)

set.seed(2020)
fit.c50 <- train(Classification ~ .,
  data = training,
  method = "C5.0", trControl = control, verbose = FALSE
)

4.1 Comparing training metrics

results_boost <- resamples(list(rpart = fit.rpart, gbm = fit.gbm, c50 = fit.c50))

# Compare models
dotplot(results_boost)

4.2 Comparing predictions

pred_boost <- list()
pred_boost$rpart <- predict(fit.rpart, newdata = testing, type = "prob")[, "Patient"]
pred_boost$gbm <- predict(fit.gbm, newdata = testing, type = "prob")[, "Patient"]
pred_boost$c50 <- predict(fit.c50, newdata = testing, type = "prob")[, "Patient"]
pred_boost <- data.frame(pred_boost)

caTools::colAUC(pred_boost, testing$Classification, plotROC = TRUE)
                        rpart       gbm       c50
Patient vs. Control 0.5673077 0.7548077 0.7644231

5 Stacking

Here we will create a stacking model with rpart, svmLinear and naive bayes and compare with rpart alone.

# DO NOT use the trainControl object used to fit the training models to fit the ensemble.
set.seed(2020)
control <- trainControl(
  method = "boot",
  number = 25,
  savePredictions = "final",
  classProbs = TRUE,
  index = createResample(training$Classification, 25),
  summaryFunction = twoClassSummary
)
metric <- "ROC"

model_list <- caretList(
  Classification ~ .,
  data = training,
  trControl = control,
  methodList = c("rpart", "svmLinear", "nb")
)

set.seed(2020)
fit_control <- trainControl(
  method = "boot",
  number = 25,
  savePredictions = "final",
  classProbs = TRUE,
  index = createResample(training$Classification, 25),
  summaryFunction = twoClassSummary
)

set.seed(2020)
fit.rpart <- train(Classification ~ .,
  data = training, method = "rpart",
  metric = metric, trControl = fit_control
)

set.seed(2020)
fit.svm <- train(Classification ~ .,
  data = training, method = "svmLinear",
  metric = metric, trControl = fit_control
)

set.seed(2020)
fit.nb <- train(Classification ~ .,
  data = training, method = "nb",
  metric = metric, trControl = fit_control
)

set.seed(2020)
glm_ensemble <- caretStack(
  model_list,
  method = "glm",
  metric = metric,
  trControl = trainControl(
    method = "boot",
    number = 25,
    savePredictions = "final",
    classProbs = TRUE,
    summaryFunction = twoClassSummary
  )
)

5.1 Comparing training metrics

results_stack <- resamples(list(
  rpart = fit.rpart, svm = fit.svm, nb = fit.nb,
  stack = glm_ensemble$ens_model
))

# Compare models
dotplot(results_stack)

5.2 Comparing predictions

model_preds <- lapply(model_list, predict, newdata = testing, type = "prob")
model_preds <- lapply(model_preds, function(x) x[, "Patient"])
model_preds <- data.frame(model_preds)

model_preds$stack <- predict(glm_ensemble, newdata = testing, type = "prob")

caTools::colAUC(model_preds, testing$Classification, plotROC = TRUE)
                        rpart svmLinear        nb     stack
Patient vs. Control 0.5673077 0.7740385 0.7932692 0.7596154

6 Comparing all models

Just as a summary, let’s compare all models together.

6.1 Comparing training metrics

It is interesting to see that although SVM is ranked as the best model, we clearly see that the stack ensemble is much more robust, having the shortest confidence interval. In general we can also notice that ensemble models are always better than rpart alone.

results_all <- resamples(list(
  rpart = fit.rpart, svm = fit.svm, nb = fit.nb,
  stack = glm_ensemble$ens_model, gbm = fit.gbm, c50 = fit.c50, treebag = fit.treebag, rf = fit.rf
))

# Compare models
dotplot(results_all)

6.2 Comparing predictions

In the “final” test, the predictions in an independent dataset, rpart shows it’s weakness in generalizing the prediction, while rf do a good job.

all_preds <- data.frame(cbind(rpart = model_preds$rpart, svm = model_preds$svmLinear, nb = model_preds$nb, stack = model_preds$stack, gbm = pred_boost$gbm, c50 = pred_boost$c50, treebag = pred_bag$treebag, rf = pred_bag$rf))
caTools::colAUC(all_preds, testing$Classification)
                        rpart       svm        nb     stack       gbm       c50   treebag        rf
Patient vs. Control 0.5673077 0.7740385 0.7932692 0.7596154 0.7548077 0.7644231 0.7644231 0.8052885
LS0tCnRpdGxlOiAiRW5zZW1ibGUgTW9kZWxzIgphdXRob3I6Ci0gRnJhbmNpc2NvIEJpc2Nob2ZmCi0gTWFudWVsYSBNaWxuZQpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0aGVtZTogdW5pdGVkCiAgICBmaWdfd2lkdGg6IDEwCiAgICBmaWdfaGVpZ2h0OiAxMAogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICBwZGZfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwotLS0KCiMgQXNzaWdubWVudAoKQ29uc2lkZXIgdGhlIHRoZSBCcmVhc3QgQ2FuY2VyIENvaW1icmEgZGF0YSBzZXQgKGF2YWlsYWJsZSBmcm9tIFVDSSByZXBvc2l0b3J5KS4KCkNvbXBhcmUgdGhyZWUgZGlmZmVyZW50IGVuc2VtYmxlIGFwcHJvYWNoZXMsIHVzaW5nIHRoZSDigJhjYXJldOKAmSBwYWNrYWdlOgoKLSBCYWdnaW5nICh0cmVlYmFnIGFuZCByZikKLSBCb29zdGluZyAoQzUuMCBhbmQgZ2RtKQotIFN0YWNraW5nIChhdCBsZWFzdCB0aHJlZSBtb2RlbHMpCi0gVXNlZnVsIHBhY2thZ2VzOiBiYWcgYW5kIGNhcmV0RW5zZW1ibGUKCkV2YWx1YXRpb24gb2YgZGVyaXZlZCBtb2RlbHMgc2hvdWxkIGZvbGxvdyBhIGNvcnJlY3QgbWV0aG9kb2xvZ3ksIGNvbXBhcmluZyBkaWZmZXJlbnQgZXN0aW1hdGVzIG9mIGdlbmVyYWxpemF0aW9uIGVycm9yIChpLmUuIGhvbGRvdXQsIGNyb3NzLXZhbGlkYXRpb24sIGJvb3RzdHJhcCwgLi4uKQoKU3VibWl0IGEgcmVwb3J0IChpbiBQREYsIGdlbmVyYXRlZCBmcm9tIFIpIHdpdGggdGhlIGNvZGUgYW5kIHRoZSByZXN1bHRpbmcgYW5hbHlzaXMuCgpgYGB7ciBzZXR1cCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShyZWFkcikKbGlicmFyeShjYXJldCkKbGlicmFyeShnYm0pCmxpYnJhcnkoY2FyZXRFbnNlbWJsZSkKCmtuaXRyOjpvcHRzX2NodW5rJHNldCh0aWR5Lm9wdHM9bGlzdCh3aWR0aC5jdXRvZmY9NjApLAogICAgICAgICAgICAgICAgICAgICAgdGlkeT1UUlVFKQoKZGF0YXNldCA8LSByZWFkX2NzdigiZGF0YVIyLmNzdiIpCmRhdGFzZXQkQ2xhc3NpZmljYXRpb24gPC0gZmFjdG9yKGRhdGFzZXQkQ2xhc3NpZmljYXRpb24sIGxhYmVscyA9IGMoIlBhdGllbnQiLCAiQ29udHJvbCIpKQoKc2V0LnNlZWQoMjAyMCkKaWR4c190cmFpbiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHkgPSBkYXRhc2V0JENsYXNzaWZpY2F0aW9uLCBwID0gMC43NSwgbGlzdCA9IEZBTFNFKQp0cmFpbmluZyA8LSBkYXRhc2V0W2lkeHNfdHJhaW4sIF0KdGVzdGluZyA8LSBkYXRhc2V0Wy1pZHhzX3RyYWluLCBdCmBgYAoKIyBJbnRyb2R1Y3Rpb24KCkEgcXVpY2sgcmV2aWV3IHNob3dzIHRoYXQgRW5zZW1ibGUgbWV0aG9kcyBhcmUgbm90IGxpbWl0ZWQgdG8gdGhlIGFib3ZlIHJlZmVycmVkLiBOZXZlcnRoZWxlc3MgdGhlc2UgYXJlIGluZGVlZCB0aGUgbWFpbiBlbnNlbWJsZSBsZWFybmluZyBtZXRob2RzLiAKCldlYWsgKG9yIGJhc2UpIGxlYXJuZXJzIGFyZSB0aGUgYnVpbGRpbmcgYmxvY2sgZm9yIG1vcmUgY29tcGxleCBtb2RlbHMuIFRoZXkgbm9ybWFsbHkgZG8gbm90IHBlcmZvcm0gd2VsbCBieSB0aGVtc2VsdmVzIGR1ZSB0byBoaWdoIHZhcmlhbmNlIG9yIGhpZ2ggYmlhcy4gRW5zZW1ibGUgbWV0aG9kcyB0aGVyZWZvcmUgdHJ5IHRvIGNvbWJpbmUgd2VhayBsZWFybmVycyBpbiBvcmRlciB0byBjcmVhdGUgYSBzdHJvbmcgbGVhcm5lci4KCk5vcm1hbGx5IChpbiBiYWdnaW5nIGFuZCBib29zdGluZyBtZXRob2RzKSwgYSBzaW5nbGUgYmFzZSBsZWFybmluZyBhbGdvcml0aG0gaXMgdXNlZCB0byBidWlsZCB3aGF0IHdlIGNhbGwgYW4gImhvbW9nZW5lb3VzIiBlbnNlbWJsZSBtb2RlbC4gSG93ZXZlciwgd2UgY2FuIGFnZ3JlZ2F0ZSBzZXZlcmFsIGJhc2UgbGVhcm5pbmcgaW50byBhbiAiaGV0ZXJvZ2VuZW91cyIgZW5zZW1ibGUgbW9kZWwuIEl0IGlzIGltcG9ydGFudCB0byBjaG9vc2UgYmFzZSBtb2RlbHMgdGhhdCBhcmUgY29oZXJlbnQgdG8gZWFjaCBvdGhlciwgaS5lLiBvbmUgd2l0aCBoaWdoIGJpYXMgd2l0aCBhbm90ZXIgd2l0aCBoaWdoIHZhcmlhbmNlLCBzbyB0aGUgZW5zZW1ibGUgd2lsbCB0cnkgdG8gY29tcGVuc2F0ZSBlYWNoIG9uZS4KClRoZXNlIGFyZSB0aGUgdGhyZWUgbWFpbiBtZXRob2RzIG9mIGVuc2VtYmxlOgoKLSAqKkJhZ2dpbmcqKiAtIFVzZXMgdGhlIHNhbWUgbGVhcm5pbmcgYWxnb3JpdGhtIHNldmVyYWwgdGltZXMsIHdpdGggY2hhbmdlcyBpbiB0aGUgdHJhaW5pbmcgZGF0YXNldCBvciBwYXJhbWV0ZXJzIGFuZCBmaW5hbGx5IGNvbWJpbmUgdGhlIG1vZGVscyB3aXRoIHNvbWUga2luZCBvZiBkZXRlcm1pbmlzdGljIGF2ZXJhZ2luZyBwcm9jZXNzLiBUaGlzIG1ldGhvZCB1c3VhbGx5IHJlc3VsdHMgaW4gYSBtb2RlbCB3aXRoIGxlc3MgdmFyaWFuY2UgdGhhbiBiZWZvcmUuCgotICoqQm9vc3RpbmcqKiAtIFVzZXMgdGhlIHNhbWUgbGVhcm5pbmcgYWxnb3JpdGhtIHNldmVyYWwgdGltZXMsIGJ1dCBzZXF1ZW50aWFsbHkgKG9uZSBtb2RlbCBkZXBlbmRzIG9uIHRoZSBwcmV2aW91cyBvbmVzKSwgdHJ5aW5nIHRvIGNvbXBlbnNhdGUgdGhlIGVycm9yLCBhbmQgY29tYmluZXMgdGhlbSBmb2xsb3dpbmcgYSBkZXRlcm1pbmlzdGljIHN0cmF0ZWd5LiBUaGlzIG1ldGhvZCB1c3VhbGx5IHJlc3VsdHMgaW4gYSBtb2RlbCB3aXRoIGxlc3MgYmlhcyB0aGFuIGJlZm9yZSAodmFyaWFuY2UgY2FuIGFsc28gYmUgYWZmZWN0ZWQpLgoKLSAqKlN0YWNraW5nKiogLSBUaGlzIG9mdGVuIHVzZXMgZGlmZmVyZW50IGFsZ29yaXRobXMgYW5kIGNvbWJpbmVzIHRoZW0gdXNpbmcgYSBtZXRhLW1vZGVsIHRvIG91dHB1dCBhIGNvbWJpbmVkIHByZWRpY3Rpb24uIFRoaXMgbWV0aG9kIChhcyBib29zdGluZykgdXN1YWxseSByZXN1bHRzIGluIGEgbW9kZWwgd2l0aCBsZXNzIGJpYXMgdGhhbiBiZWZvcmUgKHZhcmlhbmNlIGNhbiBhbHNvIGJlIGFmZmVjdGVkKS4KCiMgQmFnZ2luZwoKSGVyZSB3ZSB3aWxsIGNvbXBhcmUgdHdvIGJhZ2dpbmcgYWxnb3JpdGhtcywgKnRyZWViYWcqIGFuZCAqcmFuZG9tIGZvcmVzdCogYWdhaW5zdCB0aGUgdXN1YWwgKnJwYXJ0KiBtb2RlbC4KCmBgYHtyIGJhZ2dpbmcsIHdhcm5pbmc9RkFMU0V9CnNldC5zZWVkKDIwMjApCmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJib290IiwKICBudW1iZXIgPSAyNSwKICBzYXZlUHJlZGljdGlvbnMgPSAiZmluYWwiLAogIGNsYXNzUHJvYnMgPSBUUlVFLAogIGluZGV4ID0gY3JlYXRlUmVzYW1wbGUodHJhaW5pbmckQ2xhc3NpZmljYXRpb24sIDI1KSwKICBzdW1tYXJ5RnVuY3Rpb24gPSB0d29DbGFzc1N1bW1hcnkKKQptZXRyaWMgPC0gIlJPQyIKCnNldC5zZWVkKDIwMjApCmZpdC5ycGFydCA8LSB0cmFpbihDbGFzc2lmaWNhdGlvbiB+IC4sIGRhdGEgPSB0cmFpbmluZywgbWV0aG9kID0gInJwYXJ0IiwgbWV0cmljID0gbWV0cmljLCB0ckNvbnRyb2wgPSBjb250cm9sKQoKc2V0LnNlZWQoMjAyMCkKZml0LnRyZWViYWcgPC0gdHJhaW4oQ2xhc3NpZmljYXRpb24gfiAuLAogIGRhdGEgPSB0cmFpbmluZywKICBtZXRob2QgPSAidHJlZWJhZyIsIHRyQ29udHJvbCA9IGNvbnRyb2wsIHZlcmJvc2UgPSBGQUxTRQopCgpzZXQuc2VlZCgyMDIwKQpmaXQucmYgPC0gdHJhaW4oQ2xhc3NpZmljYXRpb24gfiAuLAogIGRhdGEgPSB0cmFpbmluZywKICBtZXRob2QgPSAicmYiLCB0ckNvbnRyb2wgPSBjb250cm9sLCB2ZXJib3NlID0gRkFMU0UKKQpgYGAKCiMjIENvbXBhcmluZyB0cmFpbmluZyBtZXRyaWNzCgpgYGB7ciByZXNfYmFnZ2luZywgd2FybmluZz1GQUxTRX0KcmVzdWx0c19iYWcgPC0gcmVzYW1wbGVzKGxpc3QocnBhcnQgPSBmaXQucnBhcnQsIHRyZWViYWcgPSBmaXQudHJlZWJhZywgcmYgPSBmaXQucmYpKQoKIyBDb21wYXJlIG1vZGVscwpkb3RwbG90KHJlc3VsdHNfYmFnKQpgYGAKCiMjIENvbXBhcmluZyBwcmVkaWN0aW9ucwoKYGBge3IgcHJlZF9iYWdnaW5nLCB3YXJuaW5nPUZBTFNFfQpwcmVkX2JhZyA8LSBsaXN0KCkKcHJlZF9iYWckcnBhcnQgPC0gcHJlZGljdChmaXQucnBhcnQsIG5ld2RhdGEgPSB0ZXN0aW5nLCB0eXBlID0gInByb2IiKVssICJQYXRpZW50Il0KcHJlZF9iYWckdHJlZWJhZyA8LSBwcmVkaWN0KGZpdC50cmVlYmFnLCBuZXdkYXRhID0gdGVzdGluZywgdHlwZSA9ICJwcm9iIilbLCAiUGF0aWVudCJdCnByZWRfYmFnJHJmIDwtIHByZWRpY3QoZml0LnJmLCBuZXdkYXRhID0gdGVzdGluZywgdHlwZSA9ICJwcm9iIilbLCAiUGF0aWVudCJdCnByZWRfYmFnIDwtIGRhdGEuZnJhbWUocHJlZF9iYWcpCgpjYVRvb2xzOjpjb2xBVUMocHJlZF9iYWcsIHRlc3RpbmckQ2xhc3NpZmljYXRpb24sIHBsb3RST0MgPSBUUlVFKQpgYGAKCiMgQm9vc3RpbmcKCkhlcmUgd2Ugd2lsbCBjb21wYXJlIHR3byBib29zdGluZyBhbGdvcml0aG1zLCAqZ2JtKiBhbmQgKkM1LjAqIGFnYWluc3QgdGhlIHVzdWFsICpycGFydCogbW9kZWwuCgpgYGB7ciBib29zdGluZywgd2FybmluZz1GQUxTRX0Kc2V0LnNlZWQoMjAyMCkKY29udHJvbCA8LSB0cmFpbkNvbnRyb2woCiAgbWV0aG9kID0gImJvb3QiLAogIG51bWJlciA9IDI1LAogIHNhdmVQcmVkaWN0aW9ucyA9ICJmaW5hbCIsCiAgY2xhc3NQcm9icyA9IFRSVUUsCiAgaW5kZXggPSBjcmVhdGVSZXNhbXBsZSh0cmFpbmluZyRDbGFzc2lmaWNhdGlvbiwgMjUpLAogIHN1bW1hcnlGdW5jdGlvbiA9IHR3b0NsYXNzU3VtbWFyeQopCm1ldHJpYyA8LSAiUk9DIgoKc2V0LnNlZWQoMjAyMCkKZml0LnJwYXJ0IDwtIHRyYWluKENsYXNzaWZpY2F0aW9uIH4gLiwgZGF0YSA9IHRyYWluaW5nLCBtZXRob2QgPSAicnBhcnQiLCBtZXRyaWMgPSBtZXRyaWMsIHRyQ29udHJvbCA9IGNvbnRyb2wpCgpzZXQuc2VlZCgyMDIwKQpmaXQuZ2JtIDwtIHRyYWluKENsYXNzaWZpY2F0aW9uIH4gLiwKICBkYXRhID0gdHJhaW5pbmcsCiAgbWV0aG9kID0gImdibSIsIHRyQ29udHJvbCA9IGNvbnRyb2wsIHZlcmJvc2UgPSBGQUxTRSwgZGlzdHJpYnV0aW9uID0gImFkYWJvb3N0IgopCgpzZXQuc2VlZCgyMDIwKQpmaXQuYzUwIDwtIHRyYWluKENsYXNzaWZpY2F0aW9uIH4gLiwKICBkYXRhID0gdHJhaW5pbmcsCiAgbWV0aG9kID0gIkM1LjAiLCB0ckNvbnRyb2wgPSBjb250cm9sLCB2ZXJib3NlID0gRkFMU0UKKQpgYGAKCiMjIENvbXBhcmluZyB0cmFpbmluZyBtZXRyaWNzCgpgYGB7ciByZXNfYm9vc3QsIHdhcm5pbmc9RkFMU0V9CnJlc3VsdHNfYm9vc3QgPC0gcmVzYW1wbGVzKGxpc3QocnBhcnQgPSBmaXQucnBhcnQsIGdibSA9IGZpdC5nYm0sIGM1MCA9IGZpdC5jNTApKQoKIyBDb21wYXJlIG1vZGVscwpkb3RwbG90KHJlc3VsdHNfYm9vc3QpCmBgYAoKIyMgQ29tcGFyaW5nIHByZWRpY3Rpb25zCgpgYGB7ciBwcmVkX2Jvb3N0LCB3YXJuaW5nPUZBTFNFfQpwcmVkX2Jvb3N0IDwtIGxpc3QoKQpwcmVkX2Jvb3N0JHJwYXJ0IDwtIHByZWRpY3QoZml0LnJwYXJ0LCBuZXdkYXRhID0gdGVzdGluZywgdHlwZSA9ICJwcm9iIilbLCAiUGF0aWVudCJdCnByZWRfYm9vc3QkZ2JtIDwtIHByZWRpY3QoZml0LmdibSwgbmV3ZGF0YSA9IHRlc3RpbmcsIHR5cGUgPSAicHJvYiIpWywgIlBhdGllbnQiXQpwcmVkX2Jvb3N0JGM1MCA8LSBwcmVkaWN0KGZpdC5jNTAsIG5ld2RhdGEgPSB0ZXN0aW5nLCB0eXBlID0gInByb2IiKVssICJQYXRpZW50Il0KcHJlZF9ib29zdCA8LSBkYXRhLmZyYW1lKHByZWRfYm9vc3QpCgpjYVRvb2xzOjpjb2xBVUMocHJlZF9ib29zdCwgdGVzdGluZyRDbGFzc2lmaWNhdGlvbiwgcGxvdFJPQyA9IFRSVUUpCmBgYAoKIyBTdGFja2luZwoKSGVyZSB3ZSB3aWxsIGNyZWF0ZSBhIHN0YWNraW5nIG1vZGVsIHdpdGggKnJwYXJ0KiwgKnN2bUxpbmVhciogYW5kICpuYWl2ZSBiYXllcyogYW5kIGNvbXBhcmUgd2l0aCAqcnBhcnQqIGFsb25lLgoKYGBge3Igc3RhY2tpbmcsIHdhcm5pbmc9RkFMU0V9CiMgRE8gTk9UIHVzZSB0aGUgdHJhaW5Db250cm9sIG9iamVjdCB1c2VkIHRvIGZpdCB0aGUgdHJhaW5pbmcgbW9kZWxzIHRvIGZpdCB0aGUgZW5zZW1ibGUuCnNldC5zZWVkKDIwMjApCmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJib290IiwKICBudW1iZXIgPSAyNSwKICBzYXZlUHJlZGljdGlvbnMgPSAiZmluYWwiLAogIGNsYXNzUHJvYnMgPSBUUlVFLAogIGluZGV4ID0gY3JlYXRlUmVzYW1wbGUodHJhaW5pbmckQ2xhc3NpZmljYXRpb24sIDI1KSwKICBzdW1tYXJ5RnVuY3Rpb24gPSB0d29DbGFzc1N1bW1hcnkKKQptZXRyaWMgPC0gIlJPQyIKCm1vZGVsX2xpc3QgPC0gY2FyZXRMaXN0KAogIENsYXNzaWZpY2F0aW9uIH4gLiwKICBkYXRhID0gdHJhaW5pbmcsCiAgdHJDb250cm9sID0gY29udHJvbCwKICBtZXRob2RMaXN0ID0gYygicnBhcnQiLCAic3ZtTGluZWFyIiwgIm5iIikKKQoKc2V0LnNlZWQoMjAyMCkKZml0X2NvbnRyb2wgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJib290IiwKICBudW1iZXIgPSAyNSwKICBzYXZlUHJlZGljdGlvbnMgPSAiZmluYWwiLAogIGNsYXNzUHJvYnMgPSBUUlVFLAogIGluZGV4ID0gY3JlYXRlUmVzYW1wbGUodHJhaW5pbmckQ2xhc3NpZmljYXRpb24sIDI1KSwKICBzdW1tYXJ5RnVuY3Rpb24gPSB0d29DbGFzc1N1bW1hcnkKKQoKc2V0LnNlZWQoMjAyMCkKZml0LnJwYXJ0IDwtIHRyYWluKENsYXNzaWZpY2F0aW9uIH4gLiwKICBkYXRhID0gdHJhaW5pbmcsIG1ldGhvZCA9ICJycGFydCIsCiAgbWV0cmljID0gbWV0cmljLCB0ckNvbnRyb2wgPSBmaXRfY29udHJvbAopCgpzZXQuc2VlZCgyMDIwKQpmaXQuc3ZtIDwtIHRyYWluKENsYXNzaWZpY2F0aW9uIH4gLiwKICBkYXRhID0gdHJhaW5pbmcsIG1ldGhvZCA9ICJzdm1MaW5lYXIiLAogIG1ldHJpYyA9IG1ldHJpYywgdHJDb250cm9sID0gZml0X2NvbnRyb2wKKQoKc2V0LnNlZWQoMjAyMCkKZml0Lm5iIDwtIHRyYWluKENsYXNzaWZpY2F0aW9uIH4gLiwKICBkYXRhID0gdHJhaW5pbmcsIG1ldGhvZCA9ICJuYiIsCiAgbWV0cmljID0gbWV0cmljLCB0ckNvbnRyb2wgPSBmaXRfY29udHJvbAopCgpzZXQuc2VlZCgyMDIwKQpnbG1fZW5zZW1ibGUgPC0gY2FyZXRTdGFjaygKICBtb2RlbF9saXN0LAogIG1ldGhvZCA9ICJnbG0iLAogIG1ldHJpYyA9IG1ldHJpYywKICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2woCiAgICBtZXRob2QgPSAiYm9vdCIsCiAgICBudW1iZXIgPSAyNSwKICAgIHNhdmVQcmVkaWN0aW9ucyA9ICJmaW5hbCIsCiAgICBjbGFzc1Byb2JzID0gVFJVRSwKICAgIHN1bW1hcnlGdW5jdGlvbiA9IHR3b0NsYXNzU3VtbWFyeQogICkKKQpgYGAKCiMjIENvbXBhcmluZyB0cmFpbmluZyBtZXRyaWNzCgpgYGB7ciByZXNfc3RhY2tpbmcsIHdhcm5pbmc9RkFMU0V9CnJlc3VsdHNfc3RhY2sgPC0gcmVzYW1wbGVzKGxpc3QoCiAgcnBhcnQgPSBmaXQucnBhcnQsIHN2bSA9IGZpdC5zdm0sIG5iID0gZml0Lm5iLAogIHN0YWNrID0gZ2xtX2Vuc2VtYmxlJGVuc19tb2RlbAopKQoKIyBDb21wYXJlIG1vZGVscwpkb3RwbG90KHJlc3VsdHNfc3RhY2spCmBgYAoKIyMgQ29tcGFyaW5nIHByZWRpY3Rpb25zCgpgYGB7ciBwcmVkX3N0YWNraW5nLCB3YXJuaW5nPUZBTFNFfQptb2RlbF9wcmVkcyA8LSBsYXBwbHkobW9kZWxfbGlzdCwgcHJlZGljdCwgbmV3ZGF0YSA9IHRlc3RpbmcsIHR5cGUgPSAicHJvYiIpCm1vZGVsX3ByZWRzIDwtIGxhcHBseShtb2RlbF9wcmVkcywgZnVuY3Rpb24oeCkgeFssICJQYXRpZW50Il0pCm1vZGVsX3ByZWRzIDwtIGRhdGEuZnJhbWUobW9kZWxfcHJlZHMpCgptb2RlbF9wcmVkcyRzdGFjayA8LSBwcmVkaWN0KGdsbV9lbnNlbWJsZSwgbmV3ZGF0YSA9IHRlc3RpbmcsIHR5cGUgPSAicHJvYiIpCgpjYVRvb2xzOjpjb2xBVUMobW9kZWxfcHJlZHMsIHRlc3RpbmckQ2xhc3NpZmljYXRpb24sIHBsb3RST0MgPSBUUlVFKQpgYGAKCiMgQ29tcGFyaW5nIGFsbCBtb2RlbHMKCkp1c3QgYXMgYSBzdW1tYXJ5LCBsZXQncyBjb21wYXJlIGFsbCBtb2RlbHMgdG9nZXRoZXIuCgojIyBDb21wYXJpbmcgdHJhaW5pbmcgbWV0cmljcwoKSXQgaXMgaW50ZXJlc3RpbmcgdG8gc2VlIHRoYXQgYWx0aG91Z2ggYFNWTWAgaXMgcmFua2VkIGFzIHRoZSBiZXN0IG1vZGVsLCB3ZSBjbGVhcmx5IHNlZSB0aGF0CnRoZSBgc3RhY2tgIGVuc2VtYmxlIGlzIG11Y2ggbW9yZSByb2J1c3QsIGhhdmluZyB0aGUgc2hvcnRlc3QgY29uZmlkZW5jZSBpbnRlcnZhbC4gSW4gZ2VuZXJhbCB3ZSBjYW4gYWxzbwpub3RpY2UgdGhhdCBlbnNlbWJsZSBtb2RlbHMgYXJlIGFsd2F5cyBiZXR0ZXIgdGhhbiBgcnBhcnRgIGFsb25lLgoKYGBge3IgYWxsX3JvY3N9CnJlc3VsdHNfYWxsIDwtIHJlc2FtcGxlcyhsaXN0KAogIHJwYXJ0ID0gZml0LnJwYXJ0LCBzdm0gPSBmaXQuc3ZtLCBuYiA9IGZpdC5uYiwKICBzdGFjayA9IGdsbV9lbnNlbWJsZSRlbnNfbW9kZWwsIGdibSA9IGZpdC5nYm0sIGM1MCA9IGZpdC5jNTAsIHRyZWViYWcgPSBmaXQudHJlZWJhZywgcmYgPSBmaXQucmYKKSkKCiMgQ29tcGFyZSBtb2RlbHMKZG90cGxvdChyZXN1bHRzX2FsbCkKYGBgCgojIyBDb21wYXJpbmcgcHJlZGljdGlvbnMKCkluIHRoZSAiZmluYWwiIHRlc3QsIHRoZSBwcmVkaWN0aW9ucyBpbiBhbiBpbmRlcGVuZGVudCBkYXRhc2V0LCBgcnBhcnRgIHNob3dzIGl0J3Mgd2Vha25lc3MgaW4KZ2VuZXJhbGl6aW5nIHRoZSBwcmVkaWN0aW9uLCB3aGlsZSBgcmZgIGRvIGEgZ29vZCBqb2IuCgpgYGB7ciBhbGxfcHJlZHN9CmFsbF9wcmVkcyA8LSBkYXRhLmZyYW1lKGNiaW5kKHJwYXJ0ID0gbW9kZWxfcHJlZHMkcnBhcnQsIHN2bSA9IG1vZGVsX3ByZWRzJHN2bUxpbmVhciwgbmIgPSBtb2RlbF9wcmVkcyRuYiwgc3RhY2sgPSBtb2RlbF9wcmVkcyRzdGFjaywgZ2JtID0gcHJlZF9ib29zdCRnYm0sIGM1MCA9IHByZWRfYm9vc3QkYzUwLCB0cmVlYmFnID0gcHJlZF9iYWckdHJlZWJhZywgcmYgPSBwcmVkX2JhZyRyZikpCmNhVG9vbHM6OmNvbEFVQyhhbGxfcHJlZHMsIHRlc3RpbmckQ2xhc3NpZmljYXRpb24pCmBgYAo=